first commit
This commit is contained in:
commit
57a257d47b
21
.cargo/config.toml
Normal file
21
.cargo/config.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[target.thumbv6m-none-eabi]
|
||||
runner = 'probe-rs run --chip STM32L010RBTx'
|
||||
|
||||
rustflags = [
|
||||
"-C", "link-arg=--nmagic",
|
||||
"-C", "link-arg=-Tlink.x",
|
||||
"-C", "link-arg=-Tdefmt.x",
|
||||
"-C", "link-arg=--no-rosegment"
|
||||
]
|
||||
|
||||
[build]
|
||||
target = "thumbv6m-none-eabi"
|
||||
|
||||
[env]
|
||||
DEFMT_LOG = "trace"
|
||||
|
||||
[alias]
|
||||
main = "embed --release --bin main"
|
||||
rb = "embed --release --bin"
|
||||
s = "size --bin main -- -B -x"
|
||||
sr = "size --release --bin main -- -B -x"
|
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
72
Cargo.toml
Normal file
72
Cargo.toml
Normal file
@ -0,0 +1,72 @@
|
||||
[package]
|
||||
name = "stm32_app"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["wieerwill <jeutter@wieerwill.de>"]
|
||||
resolver = "2"
|
||||
|
||||
[dependencies]
|
||||
embassy-stm32 = { version = "0.1.0", features = [
|
||||
"defmt",
|
||||
"memory-x",
|
||||
"stm32l010rb",
|
||||
"time-driver-any",
|
||||
"exti",
|
||||
"unstable-pac",
|
||||
"exti",
|
||||
"time",
|
||||
] }
|
||||
cortex-m = { version = "0.7.6", features = [
|
||||
"inline-asm",
|
||||
"critical-section-single-core",
|
||||
] }
|
||||
cortex-m-rt = "0.7.0"
|
||||
defmt = "0.3"
|
||||
defmt-rtt = "0.4"
|
||||
panic-probe = { version = "0.3", features = ["print-defmt"] }
|
||||
embassy-sync = { version = "0.6.0", features = [
|
||||
"defmt",
|
||||
] }
|
||||
embassy-executor = { version = "0.6.0", features = [
|
||||
"arch-cortex-m",
|
||||
"executor-thread",
|
||||
"executor-interrupt",
|
||||
"defmt",
|
||||
"integrated-timers",
|
||||
] }
|
||||
embassy-time = { version = "0.3.1", features = [
|
||||
"defmt",
|
||||
"defmt-timestamp-uptime",
|
||||
"tick-hz-32_768",
|
||||
] }
|
||||
embassy-futures = { version = "0.1.1", features = [
|
||||
"defmt",
|
||||
] }
|
||||
static_cell = "2"
|
||||
portable-atomic = { version = "1.5", features = ["unsafe-assume-single-core"] }
|
||||
heapless = "0.8.0"
|
||||
|
||||
# cargo build/run
|
||||
[profile.dev]
|
||||
codegen-units = 1
|
||||
debug = 2
|
||||
debug-assertions = true
|
||||
incremental = false
|
||||
opt-level = 0
|
||||
overflow-checks = true
|
||||
|
||||
# cargo build/run --release
|
||||
[profile.release]
|
||||
codegen-units = 1 # reduce Code Generation Units
|
||||
debug = true # symbols are nice and they don't increase the size on Flash
|
||||
debug-assertions = false
|
||||
incremental = false
|
||||
lto = true # Link Time Optimization
|
||||
opt-level = "z" # Optimize for size "s" or "z"
|
||||
overflow-checks = true
|
||||
# strip = "symbols" # or true, removes all output from rtt
|
||||
# panic = "abort" # abort immediately rather than unwind
|
||||
# location-detail="none" # Remove Location Details
|
||||
|
||||
[target.stm32l010rb]
|
||||
runner = 'probe-rs run --chip stm32l010rbtx'
|
103
Embed.toml
Normal file
103
Embed.toml
Normal file
@ -0,0 +1,103 @@
|
||||
[default.probe]
|
||||
# USB vendor ID
|
||||
# usb_vid = "1337"
|
||||
# USB product ID
|
||||
# usb_pid = "1337"
|
||||
# Serial number
|
||||
# serial = "12345678"
|
||||
# The protocol to be used for communicating with the target.
|
||||
protocol = "Swd"
|
||||
# The speed in kHz of the data link to the target.
|
||||
# speed = 1337
|
||||
|
||||
[default.flashing]
|
||||
# Whether or not the target should be flashed.
|
||||
enabled = true
|
||||
# Whether or not bytes erased but not rewritten with data from the ELF
|
||||
# should be restored with their contents before erasing.
|
||||
restore_unwritten_bytes = false
|
||||
# The path where an SVG of the assembled flash layout should be written to.
|
||||
# flash_layout_output_path = "out.svg"
|
||||
# Triggers a full chip erase instead of a page by page erase.
|
||||
do_chip_erase = false
|
||||
# Whether to disable double buffering
|
||||
disable_double_buffering = false
|
||||
# Whether to verify flash contents after downloading
|
||||
verify = true
|
||||
|
||||
[default.reset]
|
||||
# Whether or not the target should be reset.
|
||||
# When flashing is enabled as well, the target will be reset after flashing regardless of this setting.
|
||||
enabled = true
|
||||
# Whether or not the target should be halted after reset.
|
||||
halt_afterwards = false
|
||||
|
||||
[default.general]
|
||||
# The chip name of the chip to be debugged.
|
||||
chip = "STM32L010RBTx"
|
||||
# A list of chip descriptions to be loaded during runtime.
|
||||
chip_descriptions = []
|
||||
# The default log level to be used. Possible values are one of:
|
||||
# "OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE"
|
||||
# If not set, the `RUST_LOG` environment variable will be used.
|
||||
log_level = "WARN"
|
||||
# Use this flag to assert the nreset & ntrst pins during attaching the probe to the chip.
|
||||
connect_under_reset = false
|
||||
|
||||
[default.rtt]
|
||||
# Whether or not an RTTUI should be opened after flashing.
|
||||
enabled = true
|
||||
# The duration in ms for which the logger should retry to attach to RTT.
|
||||
timeout = 10000
|
||||
# Whether to save rtt history buffer on exit.
|
||||
log_enabled = true
|
||||
# Where to save rtt history buffer relative to manifest path.
|
||||
log_path = "./target/logs"
|
||||
# A list of up (target -> host) channel settings associations to be displayed. If left empty, all channels are displayed.
|
||||
# object key - RTT channel identifier number
|
||||
# mode (Optional) - RTT operation mode. Describes how the target handles RTT outputs that won't
|
||||
# fit in the buffer. If left unset, the firmware will determine the default
|
||||
# for each RTT up channel.
|
||||
# * NoBlockSkip - Skip writing the data completely if it doesn't fit in its
|
||||
# entirety.
|
||||
# * NoBlockTrim - Write as much as possible of the data and ignore the rest.
|
||||
# * BlockIfFull - Spin until the host reads data. Can result in app freezing.
|
||||
# format (Optional) - How to interpret data from target firmware. One of:
|
||||
# * String - Directly show output from the target (default)
|
||||
# * Defmt - Format output on the host, see https://defmt.ferrous-systems.com/
|
||||
# * BinaryLE - Display as raw hex
|
||||
# show_location (Optional) - Whether to show the location of defmt messages in the UI.
|
||||
# show_timestamps (Optional) - Whether to show the timestamps of String and Defmt messages in the UI, if available.
|
||||
# socket (Optional) - Server socket address (for optional external frontend or endpoint).
|
||||
# log_format (Optional) - Control the output format for `format = Defmt`.
|
||||
up_channels = [
|
||||
# { channel = 0, mode = "BlockIfFull", format = "Defmt", show_location = true },
|
||||
# { channel = 1, mode = "BlockIfFull", format = "String", show_timestamps = false, socket = "127.0.0.1:12345" },
|
||||
]
|
||||
|
||||
# A list of down (host -> target) channel settings. You can select a down channel for each UI tab,
|
||||
# which will be used to send data to the target.
|
||||
# object key - RTT channel identifier number
|
||||
# mode (Optional) - RTT operation mode. Describes how the target handles RTT outputs that won't
|
||||
# fit in the buffer. If left unset, the firmware will determine the default
|
||||
# for each RTT down channel.
|
||||
down_channels = [
|
||||
# { channel = 0, mode = "BlockIfFull" }
|
||||
]
|
||||
|
||||
# UI tab settings. All up channels are displayed, except when hidden here. You can specify how each
|
||||
# tab is displayed and whether they allow sending data to the target.
|
||||
# up_channel - The channel_number of the RTT up channel to display
|
||||
# hide (Optional) - Whether to hide the tab. Defaults to false.
|
||||
# down_channel (Optional) - The channel_number of the RTT down channel to use for this tab.
|
||||
# name (Optional) - String to be displayed in the RTTUI tab. Defaults to the channel name.
|
||||
tabs = [
|
||||
# { up_channel = 0, down_channel = 0, name = "My channel" },
|
||||
# { up_channel = 1, hide = true },
|
||||
]
|
||||
|
||||
[default.gdb]
|
||||
# Whether or not a GDB server should be opened after flashing.
|
||||
enabled = false
|
||||
# The connection string in host:port format wher the GDB server will open a socket.
|
||||
gdb_connection_string = "127.0.0.1:1337"
|
19
README.md
Normal file
19
README.md
Normal file
@ -0,0 +1,19 @@
|
||||
# STM32 Firmware with Rust and Embassy
|
||||
Used Chip in this Project was a STM32L0, the low power edition. Other targets are easily exchangeable.
|
||||
|
||||
# Dev Tools
|
||||
- rustup 1.27.1
|
||||
- rustc 1.81.0
|
||||
- Cargo 1.81.0
|
||||
- Probe-RS
|
||||
|
||||
# Binaries
|
||||
All binaries are compiled and then transfered to the connected board (if exist). Then the RTT Console will be opened to read and debug.
|
||||
|
||||
The ready binary is a .elf file in the `target` folder.
|
||||
|
||||
```sh
|
||||
cargo rb blinky # blinky example
|
||||
cargo main # main program
|
||||
./elf-to-hex.sh # convert main program to Hex and versionize
|
||||
```
|
15
elf-to-hex.sh
Executable file
15
elf-to-hex.sh
Executable file
@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
objcopy -O ihex target/thumbv6m-none-eabi/release/main target/main.hex -S
|
||||
|
||||
# read version number
|
||||
version=$(awk -F ' = ' '$1 ~ /version/ { gsub(/[\"]/, "", $2); printf("%s",$2) }' Cargo.toml)
|
||||
# check if version was found
|
||||
if [[ -z "$version" ]]; then
|
||||
echo "Error: Version not found in Cargo.toml"
|
||||
exit 1
|
||||
fi
|
||||
echo "Firmware Version $version"
|
||||
|
||||
# write version into filename
|
||||
mv target/main.hex target/FW-$version-main.hex
|
||||
echo "target/FW-$version-main.hex"
|
6
rust-toolchain.toml
Normal file
6
rust-toolchain.toml
Normal file
@ -0,0 +1,6 @@
|
||||
# Before upgrading check that everything is available on all tier1 targets here:
|
||||
# https://rust-lang.github.io/rustup-components-history
|
||||
[toolchain]
|
||||
channel = "stable"
|
||||
components = [ "rustfmt" ]
|
||||
targets = [ "thumbv6m-none-eabi" ]
|
37
src/bin/adc.rs
Normal file
37
src/bin/adc.rs
Normal file
@ -0,0 +1,37 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use defmt::*;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_stm32::adc::{Adc, SampleTime};
|
||||
use embassy_stm32::peripherals::ADC1;
|
||||
use embassy_stm32::{adc, bind_interrupts};
|
||||
use embassy_time::Timer;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
bind_interrupts!(struct AdcIrqs {
|
||||
ADC1 => adc::InterruptHandler<ADC1>;
|
||||
});
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(_spawner: Spawner) {
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
|
||||
let mut adc = Adc::new(p.ADC1, AdcIrqs);
|
||||
adc.set_sample_time(SampleTime::CYCLES79_5);
|
||||
let mut pin = p.PA1;
|
||||
|
||||
let mut vrefint = adc.enable_vref();
|
||||
let vrefint_sample = adc.read(&mut vrefint).await;
|
||||
let convert_to_millivolts = |sample| {
|
||||
// Embedded reference voltage
|
||||
const VREFINT_MV: u32 = 1224; // mV
|
||||
(u32::from(sample) * VREFINT_MV / u32::from(vrefint_sample)) as u16
|
||||
};
|
||||
|
||||
loop {
|
||||
let v = adc.read(&mut pin).await;
|
||||
info!("--> {} - {} mV", v, convert_to_millivolts(v));
|
||||
Timer::after_millis(100).await;
|
||||
}
|
||||
}
|
25
src/bin/blinky.rs
Normal file
25
src/bin/blinky.rs
Normal file
@ -0,0 +1,25 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use defmt::*;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_stm32::gpio::{Level, Output, Speed};
|
||||
use embassy_time::Timer;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(_spawner: Spawner) {
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
|
||||
let mut led = Output::new(p.PA9, Level::High, Speed::Low);
|
||||
|
||||
loop {
|
||||
info!("high");
|
||||
led.set_high();
|
||||
Timer::after_millis(300).await;
|
||||
|
||||
info!("low");
|
||||
led.set_low();
|
||||
Timer::after_millis(300).await;
|
||||
}
|
||||
}
|
25
src/bin/button.rs
Normal file
25
src/bin/button.rs
Normal file
@ -0,0 +1,25 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use defmt::*;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_stm32::exti::ExtiInput;
|
||||
use embassy_stm32::gpio::Pull;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(_spawner: Spawner) {
|
||||
// Initialize and create handle for devicer peripherals
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
|
||||
// Configure the button pin and obtain handler.
|
||||
let mut button = ExtiInput::new(p.PC2, p.EXTI2, Pull::Up);
|
||||
|
||||
info!("Press the button...");
|
||||
loop {
|
||||
button.wait_for_falling_edge().await;
|
||||
info!("Pressed!");
|
||||
button.wait_for_rising_edge().await;
|
||||
info!("Released!");
|
||||
}
|
||||
}
|
16
src/bin/hello.rs
Normal file
16
src/bin/hello.rs
Normal file
@ -0,0 +1,16 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use defmt::info;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::Timer;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(_spawner: Spawner) -> ! {
|
||||
let _p = embassy_stm32::init(Default::default());
|
||||
loop {
|
||||
Timer::after_secs(1).await;
|
||||
info!("Hello World");
|
||||
}
|
||||
}
|
20
src/bin/main.rs
Normal file
20
src/bin/main.rs
Normal file
@ -0,0 +1,20 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use defmt::{info, debug};
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::Timer;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(_spawner: Spawner) -> ! {
|
||||
let _p = embassy_stm32::init(Default::default());
|
||||
|
||||
info!("Hello World");
|
||||
debug!("Start your application here");
|
||||
|
||||
loop {
|
||||
Timer::after_secs(1).await;
|
||||
info!(".");
|
||||
}
|
||||
}
|
46
src/bin/pwm.rs
Normal file
46
src/bin/pwm.rs
Normal file
@ -0,0 +1,46 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use defmt::*;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_stm32::gpio::OutputType::PushPull;
|
||||
use embassy_stm32::time::hz;
|
||||
use embassy_stm32::timer::low_level::CountingMode::EdgeAlignedUp;
|
||||
use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
|
||||
use embassy_stm32::timer::Channel;
|
||||
use embassy_time::Timer;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(_spawner: Spawner) -> ! {
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
|
||||
let buzz_pin = PwmPin::new_ch1(p.PB13, PushPull);
|
||||
let mut pwm = SimplePwm::new(
|
||||
p.TIM21,
|
||||
Some(buzz_pin),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
hz(2000),
|
||||
EdgeAlignedUp,
|
||||
);
|
||||
|
||||
let max_duty = pwm.get_max_duty();
|
||||
// Configure the Duty Cycle to 50%
|
||||
pwm.set_duty(Channel::Ch2, max_duty / 2);
|
||||
|
||||
pwm.enable(Channel::Ch2);
|
||||
|
||||
let mut frequence = 300;
|
||||
loop {
|
||||
info!("on, freq {}hz", frequence);
|
||||
pwm.set_frequency(hz(frequence));
|
||||
pwm.enable(Channel::Ch2);
|
||||
Timer::after_millis(1000).await;
|
||||
info!("off");
|
||||
pwm.disable(Channel::Ch2);
|
||||
Timer::after_millis(500).await;
|
||||
frequence += 20;
|
||||
}
|
||||
}
|
28
src/bin/spawner.rs
Normal file
28
src/bin/spawner.rs
Normal file
@ -0,0 +1,28 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use defmt::info;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::Timer;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn run() {
|
||||
loop {
|
||||
info!("Tick");
|
||||
Timer::after_secs(1).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(spawner: Spawner) -> ! {
|
||||
let _p = embassy_stm32::init(Default::default());
|
||||
|
||||
spawner.spawn(run()).unwrap();
|
||||
Timer::after_secs(1).await;
|
||||
spawner.spawn(run()).unwrap();
|
||||
loop {
|
||||
Timer::after_secs(2).await;
|
||||
info!("Tack");
|
||||
}
|
||||
}
|
31
src/bin/spi.rs
Normal file
31
src/bin/spi.rs
Normal file
@ -0,0 +1,31 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use defmt::*;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_stm32::gpio::{Level, Output, Speed};
|
||||
use embassy_stm32::spi::{Config, Spi};
|
||||
use embassy_stm32::time::Hertz;
|
||||
use embassy_time::Timer;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(_spawner: Spawner) {
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
|
||||
let mut spi_config = Config::default();
|
||||
spi_config.frequency = Hertz(1_000_000);
|
||||
|
||||
let mut spi = Spi::new_blocking(p.SPI1, p.PB3, p.PA7, p.PA6, spi_config);
|
||||
|
||||
let mut cs = Output::new(p.PA15, Level::High, Speed::VeryHigh);
|
||||
|
||||
loop {
|
||||
let mut buf = [0x0Au8; 4];
|
||||
cs.set_low();
|
||||
unwrap!(spi.blocking_transfer_in_place(&mut buf));
|
||||
cs.set_high();
|
||||
info!("xfer {=[u8]:x}", buf);
|
||||
Timer::after_millis(300).await;
|
||||
}
|
||||
}
|
42
src/bin/uart.rs
Normal file
42
src/bin/uart.rs
Normal file
@ -0,0 +1,42 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use defmt::*;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_stm32::usart::{Config, InterruptHandler, Uart};
|
||||
use embassy_stm32::{bind_interrupts, peripherals};
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
bind_interrupts!(struct Irqs{
|
||||
LPUART1 => InterruptHandler<peripherals::LPUART1>;
|
||||
});
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(_spawner: Spawner) {
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
|
||||
info!("Starting system");
|
||||
|
||||
let mut config = Config::default();
|
||||
config.baudrate = 9600;
|
||||
|
||||
let mut usart = Uart::new(
|
||||
p.LPUART1, p.PC0, p.PC1, Irqs, p.DMA1_CH2, p.DMA1_CH3, config,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
unwrap!(usart.write(b"Hello Embassy World!\r\n").await);
|
||||
|
||||
let mut buf = [0u8; 300];
|
||||
loop {
|
||||
let result = usart.read_until_idle(&mut buf).await;
|
||||
match result {
|
||||
Ok(size) => {
|
||||
info!("result {}, size {}", result, size);
|
||||
}
|
||||
Err(_err) => {
|
||||
info!("error...");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
src/bin/watchdog.rs
Normal file
25
src/bin/watchdog.rs
Normal file
@ -0,0 +1,25 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use defmt::*;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_stm32::wdg::IndependentWatchdog;
|
||||
use embassy_time::Timer;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(_spawner: Spawner) {
|
||||
// Initialize and create handle for devicer peripherals
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
|
||||
// Configure the independent watchdog timer
|
||||
let mut wdg = IndependentWatchdog::new(p.IWDG, 2_000_000);
|
||||
|
||||
info!("Watchdog start");
|
||||
wdg.unleash();
|
||||
|
||||
loop {
|
||||
Timer::after_secs(1).await;
|
||||
wdg.pet();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user