From 261b79f04601c3b246044a01eff8221370359ab1 Mon Sep 17 00:00:00 2001 From: Serhii Kovalchuk Date: Mon, 26 Aug 2024 21:35:17 +0300 Subject: [PATCH 1/4] Add basic Embassy support --- .cargo/{config => config.toml} | 0 Cargo.toml | 24 ++ examples/embassy.rs | 38 ++++ src/embassy/mod.rs | 9 + src/embassy/time_driver.rs | 401 +++++++++++++++++++++++++++++++++ src/lib.rs | 3 + 6 files changed, 475 insertions(+) rename .cargo/{config => config.toml} (100%) create mode 100644 examples/embassy.rs create mode 100644 src/embassy/mod.rs create mode 100644 src/embassy/time_driver.rs diff --git a/.cargo/config b/.cargo/config.toml similarity index 100% rename from .cargo/config rename to .cargo/config.toml diff --git a/Cargo.toml b/Cargo.toml index dd552da..4c684a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ default-target = "x86_64-unknown-linux-gnu" [dependencies] cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.3" +critical-section = { version = "1.1.3", optional = true } embedded-dma = "0.2.0" embedded-hal = "1.0.0" embedded-hal-02 = { package = "embedded-hal", version = "0.2.7", features = [ @@ -26,6 +27,14 @@ embedded-io = "0.6.1" gd32f1 = { version = "0.9.1", features = ["critical-section"] } nb = "1.1.0" void = { version = "1.0.2", default-features = false, optional = true } +embassy-executor = { version = "0.6", features = [ + "arch-cortex-m", + "executor-thread", + "integrated-timers", +], optional = true } +embassy-time = { version = "0.3.2", optional = true } +embassy-time-driver = { version = "0.1.0", optional = true } +embassy-sync = { version = "0.6.0", optional = true } [dev-dependencies] cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] } @@ -56,6 +65,17 @@ gd32f190 = ["gd32f1/gd32f190", "device-selected"] gd32f190x4 = ["gd32f190"] gd32f190x6 = ["gd32f190"] gd32f190x8 = ["gd32f190"] +embassy = [ + "rt", + "dep:critical-section", + "dep:embassy-executor", + "dep:embassy-sync", + "dep:embassy-time", + "dep:embassy-time-driver", +] +time-driver-tim1=["embassy"] +time-driver-tim2=["embassy"] +time-driver-tim14=["embassy"] [profile.dev] incremental = false @@ -109,3 +129,7 @@ required-features = ["rt"] #[[example]] #name = "can-rtic" #required-features = ["has-can", "rt"] + +[[example]] +name = "embassy" +required-features = ["time-driver-tim1"] diff --git a/examples/embassy.rs b/examples/embassy.rs new file mode 100644 index 0000000..5d1076d --- /dev/null +++ b/examples/embassy.rs @@ -0,0 +1,38 @@ +#![deny(unsafe_code)] +#![no_std] +#![no_main] + +use panic_halt as _; + +use embassy_executor::{self, Spawner}; +use embassy_time::Timer; +use embedded_hal::digital::OutputPin; +use gd32f1x0_hal::{embassy, pac, prelude::*}; + +#[embassy_executor::task] +async fn blink_task(mut led: impl OutputPin + 'static) { + loop { + Timer::after_millis(1_000).await; + led.set_high().unwrap(); + Timer::after_millis(1_000).await; + led.set_low().unwrap(); + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = pac::Peripherals::take().unwrap(); + let mut rcu = p.rcu.constrain(); + let mut flash = p.fmc.constrain(); + let clocks = rcu.cfgr.freeze(&mut flash.ws); + + embassy::init(p.timer1, &clocks, &mut rcu.apb1); + + let mut gpioc = p.gpioc.split(&mut rcu.ahb); + let led = gpioc + .pc13 + .into_push_pull_output(&mut gpioc.config) + .downgrade(); + + spawner.must_spawn(blink_task(led)); +} diff --git a/src/embassy/mod.rs b/src/embassy/mod.rs new file mode 100644 index 0000000..425b950 --- /dev/null +++ b/src/embassy/mod.rs @@ -0,0 +1,9 @@ +use time_driver::{Bus, EmbassyTimeDriver, Timer}; + +use crate::rcu::Clocks; + +mod time_driver; + +pub fn init(timer: Timer, clocks: &Clocks, apb: &mut Bus) { + EmbassyTimeDriver::init(timer, clocks, apb) +} diff --git a/src/embassy/time_driver.rs b/src/embassy/time_driver.rs new file mode 100644 index 0000000..c048d59 --- /dev/null +++ b/src/embassy/time_driver.rs @@ -0,0 +1,401 @@ +use core::cell::Cell; +use core::convert::TryInto; +use core::sync::atomic::{compiler_fence, AtomicU32, AtomicU8, Ordering}; +use core::{mem, ptr, u16}; +use critical_section::CriticalSection; +use embassy_sync::blocking_mutex::{raw::CriticalSectionRawMutex, Mutex}; +use embassy_time_driver::{AlarmHandle, Driver, TICK_HZ}; + +use crate::pac::{interrupt, Interrupt}; +use crate::rcu::{sealed::RcuBus, Clocks, Enable, GetBusFreq, Reset}; + +#[cfg(not(any( + feature = "time-driver-tim1", + feature = "time-driver-tim2", + feature = "time-driver-tim14", +)))] +compile_error!("Embassy feature enabled however a time driver was not specified. A `--features time-driver-tim<1, 2, 14>` is required."); + +#[cfg(any( + all(feature = "time-driver-tim1", feature = "time-driver-tim2"), + all(feature = "time-driver-tim1", feature = "time-driver-tim14"), + all(feature = "time-driver-tim2", feature = "time-driver-tim14"), +))] +compile_error!( + "Multiple Embassy time drivers are specified. Only a single `--features time-driver-tim<1, 2, 14>` can be specified." +); + +#[cfg(feature = "time-driver-tim1")] +pub(super) type Timer = crate::pac::Timer1; +#[cfg(feature = "time-driver-tim2")] +pub(super) type Timer = crate::pac::Timer2; +#[cfg(feature = "time-driver-tim14")] +pub(super) type Timer = crate::pac::Timer14; + +#[cfg(any(feature = "time-driver-tim1", feature = "time-driver-tim2"))] +pub(super) type Bus = crate::rcu::APB1; +#[cfg(feature = "time-driver-tim14")] +pub(super) type Bus = crate::rcu::APB2; + +#[cfg(any(feature = "time-driver-tim1", feature = "time-driver-tim2"))] +type RegisterBlock = crate::pac::timer1::RegisterBlock; +#[cfg(feature = "time-driver-tim14")] +type RegisterBlock = crate::pac::timer14::RegisterBlock; + +// NOTE regarding the ALARM_COUNT: +// +// This driver is implemented using CC0 as the halfway rollover interrupt, and any additional CC +// capabilities to provide timer alarms to embassy-time. +// Hence, only general-purpose timer instances with two or more channels are allowed: +// - TIMER1, TIMER2 - 4 channels +// - TIMER14 - 2 channels + +#[cfg(any(feature = "time-driver-tim1", feature = "time-driver-tim2"))] +const ALARM_COUNT: usize = 3; + +#[cfg(feature = "time-driver-tim14")] +const ALARM_COUNT: usize = 1; + +fn timer() -> &'static RegisterBlock { + unsafe { &*Timer::PTR } +} + +struct AlarmState { + timestamp: Cell, + + // This is really a Option<(fn(*mut ()), *mut ())> + // but fn pointers aren't allowed in const yet + callback: Cell<*const ()>, + ctx: Cell<*mut ()>, +} + +unsafe impl Send for AlarmState {} + +impl AlarmState { + const fn new() -> Self { + Self { + timestamp: Cell::new(u64::MAX), + callback: Cell::new(ptr::null()), + ctx: Cell::new(ptr::null_mut()), + } + } +} + +// NOTE: this definitely could be improved in the PAC crate +// An alternative would be to use a pointer-based access to the registers +trait ChannelsAccess { + type Counter; + + fn set_chn_cc_value(&self, channel: usize, value: Self::Counter); + fn set_chn_ie(&self, channel: usize, value: bool); +} + +#[cfg(any(feature = "time-driver-tim1", feature = "time-driver-tim2"))] +impl ChannelsAccess for crate::pac::timer1::RegisterBlock { + type Counter = u32; + + fn set_chn_cc_value(&self, channel: usize, value: Self::Counter) { + match channel { + 0 => self.ch0cv().write(|w| w.ch0val().bits(value)), + 1 => self.ch1cv().write(|w| w.ch1val().bits(value)), + 2 => self.ch2cv().write(|w| w.ch2val().bits(value)), + 3 => self.ch3cv().write(|w| w.ch3val().bits(value)), + _ => return, + } + } + + fn set_chn_ie(&self, channel: usize, value: bool) { + match channel { + 0 => self.dmainten().modify(|_, w| w.ch0ie().bit(value)), + 1 => self.dmainten().modify(|_, w| w.ch1ie().bit(value)), + 2 => self.dmainten().modify(|_, w| w.ch2ie().bit(value)), + 3 => self.dmainten().modify(|_, w| w.ch3ie().bit(value)), + _ => return, + }; + } +} + +#[cfg(feature = "time-driver-tim14")] +impl ChannelsAccess for crate::pac::timer14::RegisterBlock { + type Counter = u16; + + fn set_chn_cc_value(&self, channel: usize, value: Self::Counter) { + match channel { + 0 => self.ch0cv().write(|w| w.ch0val().bits(value)), + 1 => self.ch1cv().write(|w| w.ch1val().bits(value)), + _ => return, + } + } + + fn set_chn_ie(&self, channel: usize, value: bool) { + match channel { + 0 => self.dmainten().modify(|_, w| w.ch0ie().bit(value)), + 1 => self.dmainten().modify(|_, w| w.ch1ie().bit(value)), + _ => return, + }; + } +} + +// Clock timekeeping works with something we call "periods", which are time intervals +// of 2^15 ticks. The Clock counter value is 16 bits, so one "overflow cycle" is 2 periods. +// Timer1 has 32 bits counter, however for a sake of consistency, only 16 bits are used. +// +// A `period` count is maintained in parallel to the Timer hardware `counter`, like this: +// - `period` and `counter` start at 0 +// - `period` is incremented on overflow (at counter value 0) +// - `period` is incremented "midway" between overflows (at counter value 0x8000) +// +// Therefore, when `period` is even, counter is in 0..0x7FFF. When odd, counter is in 0x8000..0xFFFF +// This allows for now() to return the correct value even if it races an overflow. +// +// To get `now()`, `period` is read first, then `counter` is read. If the counter value matches +// the expected range for the `period` parity, we're done. If it doesn't, this means that +// a new period start has raced us between reading `period` and `counter`, so we assume the `counter` value +// corresponds to the next period. +// +// `period` is a 32bit unsigned integer, so It overflows on 2^32 * 2^15 / 32768 seconds of uptime, which is 136 years. +fn calc_now(period: u32, counter: u32) -> u64 { + ((period as u64) << 15) + ((counter ^ ((period & 1) << 15)) as u64) +} + +const ALARM_STATE_NEW: AlarmState = AlarmState::new(); + +pub(super) struct EmbassyTimeDriver { + /// Number of 2^31 periods elapsed since boot. + period: AtomicU32, + alarm_count: AtomicU8, + /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. + alarms: Mutex, +} + +impl EmbassyTimeDriver { + pub(super) fn init(timer: Timer, clocks: &Clocks, apb: &mut Bus) { + Timer::enable(apb); + Timer::reset(apb); + + timer.ctl0().modify(|_, w| w.cen().disabled()); + + timer.cnt().write(|w| w.cnt().bits(0)); + + let timer_freq = ::Bus::get_timer_frequency(clocks); + // NOTE: using the default 1MHz tick rate + let psc = (timer_freq.0 as u64) / TICK_HZ - 1; + let psc: u16 = match psc.try_into() { + Err(_) => panic!("psc division overflow: {}", psc), + Ok(n) => n, + }; + + timer.psc().write(|w| w.psc().bits(psc)); + timer.car().write(|w| { + w.car() + .bits(u16::MAX as ::Counter) + }); + + timer.ctl0().modify(|_, w| w.ups().counter_only()); + timer.swevg().write(|w| w.upg().update()); + timer.ctl0().modify(|_, w| w.ups().any_event()); + + // Half of the u16::MAX value + timer.ch0cv().write(|w| w.ch0val().bits(0x8000)); + + timer + .dmainten() + .modify(|_, w| w.upie().enabled().ch0ie().enabled()); + + #[cfg(feature = "time-driver-tim1")] + { + unsafe { + cortex_m::peripheral::NVIC::unmask(Interrupt::TIMER1); + } + } + #[cfg(feature = "time-driver-tim2")] + { + unsafe { + cortex_m::peripheral::NVIC::unmask(Interrupt::TIMER2); + } + } + #[cfg(feature = "time-driver-tim14")] + { + unsafe { + cortex_m::peripheral::NVIC::unmask(Interrupt::TIMER14); + } + } + + timer.ctl0().modify(|_, w| w.cen().enabled()); + } + + fn get_alarm<'a>(&'a self, cs: CriticalSection<'a>, alarm: AlarmHandle) -> &'a AlarmState { + // safety: we're allowed to assume the AlarmState is created by us, and + // we never create one that's out of bounds. + unsafe { self.alarms.borrow(cs).get_unchecked(alarm.id() as usize) } + } + + fn trigger_alarm(&self, n: usize, cs: CriticalSection) { + let alarm = &self.alarms.borrow(cs)[n]; + alarm.timestamp.set(u64::MAX); + // Call after clearing alarm, so the callback can set another alarm. + + // Safety: + // - we can ignore the possibility of `f` being unset (null) because of the safety contract of `allocate_alarm`. + // - other than that we only store valid function pointers into alarm.callback + let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) }; + f(alarm.ctx.get()); + } + + fn on_interrupt(&self) { + let r = timer(); + + critical_section::with(|cs| { + let status = r.intf().read(); + let enabled = r.dmainten().read(); + + // Clear all interrupt flags. Bits in SR are "write 0 to clear", so write the bitwise NOT. + // Other approaches such as writing all zeros, or RMWing won't work, they can + // miss interrupts. + r.intf().write(|w| unsafe { w.bits(!status.bits()) }); + + if status.upif().bit() { + self.next_period() + } + + if status.ch0if().bit() { + self.next_period() + } + + for n in 0..ALARM_COUNT { + // Bit 0 indicates Update CC interrupt, bit 1 corresponds to reserved Channel 0 + let channel = n + 2; + if ((enabled.bits() >> channel) & 1 != 0) && ((status.bits() >> channel) & 1 != 0) { + self.trigger_alarm(n, cs); + } + } + }) + } + + fn next_period(&self) { + let r = timer(); + + // We only modify the period from the timer interrupt, so we know this can't race. + let period = self.period.load(Ordering::Relaxed) + 1; + self.period.store(period, Ordering::Relaxed); + let t = (period as u64) << 15; + + critical_section::with(move |cs| { + for n in 0..ALARM_COUNT { + let alarm = &self.alarms.borrow(cs)[n]; + let at = alarm.timestamp.get(); + + if at < t + 0xc000 { + // just enable it. `set_alarm` has already set the correct CCR val. + r.set_chn_ie(n + 1, true); + } + } + }) + } +} + +impl Driver for EmbassyTimeDriver { + fn now(&self) -> u64 { + let r = timer(); + let period = self.period.load(Ordering::Relaxed); + compiler_fence(Ordering::Acquire); + // Timer1 has a 32-bit counter, while other timers resolution is limited to 16 bits + let counter = r.cnt().read().cnt().bits() as u32; + + calc_now(period, counter) + } + + unsafe fn allocate_alarm(&self) -> Option { + critical_section::with(|_| { + let id = self.alarm_count.load(Ordering::Relaxed); + if id < ALARM_COUNT as u8 { + self.alarm_count.store(id + 1, Ordering::Relaxed); + Some(AlarmHandle::new(id)) + } else { + None + } + }) + } + + fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { + critical_section::with(|cs| { + let alarm = self.get_alarm(cs, alarm); + + alarm.callback.set(callback as *const ()); + alarm.ctx.set(ctx); + }) + } + + fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { + critical_section::with(|cs| { + let r = timer(); + + let n = alarm.id() as usize; + let alarm = self.get_alarm(cs, alarm); + alarm.timestamp.set(timestamp); + + let t = self.now(); + if timestamp <= t { + // If alarm timestamp has passed the alarm will not fire. + // Disarm the alarm and return `false` to indicate that. + r.set_chn_ie(n + 1, false); + + alarm.timestamp.set(u64::MAX); + + return false; + } + + // Write the CCR value regardless of whether we're going to enable it now or not. + // This way, when we enable it later, the right value is already set. + r.set_chn_cc_value( + n + 1, + timestamp as ::Counter, + ); + + // Enable it if it'll happen soon. Otherwise, `next_period` will enable it. + let diff = timestamp - t; + r.set_chn_ie(n + 1, diff < 0xc000); + + // Reevaluate if the alarm timestamp is still in the future + let t = self.now(); + if timestamp <= t { + // If alarm timestamp has passed since we set it, we have a race condition and + // the alarm may or may not have fired. + // Disarm the alarm and return `false` to indicate that. + // It is the caller's responsibility to handle this ambiguity. + r.set_chn_ie(n + 1, false); + + alarm.timestamp.set(u64::MAX); + + return false; + } + + // We're confident the alarm will ring in the future. + true + }) + } +} + +embassy_time_driver::time_driver_impl!(static DRIVER: EmbassyTimeDriver = EmbassyTimeDriver{ + period: AtomicU32::new(0), + alarm_count: AtomicU8::new(0), + alarms: Mutex::const_new(CriticalSectionRawMutex::new(), [ALARM_STATE_NEW; ALARM_COUNT]), +}); + +#[cfg(feature = "time-driver-tim1")] +#[interrupt] +fn TIMER1() { + DRIVER.on_interrupt() +} + +#[cfg(feature = "time-driver-tim2")] +#[interrupt] +fn TIMER2() { + DRIVER.on_interrupt() +} + +#[cfg(feature = "time-driver-tim14")] +#[interrupt] +fn TIMER14() { + DRIVER.on_interrupt() +} diff --git a/src/lib.rs b/src/lib.rs index b1c64c2..8e0a1ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -173,3 +173,6 @@ pub mod timer; pub mod usb;*/ #[cfg(feature = "device-selected")] pub mod watchdog; + +#[cfg(feature = "embassy")] +pub mod embassy; From b692fd280ca08ee474fa946aca6f4fa1465c2413 Mon Sep 17 00:00:00 2001 From: Andrew Walbran Date: Mon, 9 Sep 2024 17:41:54 +0100 Subject: [PATCH 2/4] Avoid casting function pointer. --- src/embassy/time_driver.rs | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/embassy/time_driver.rs b/src/embassy/time_driver.rs index c048d59..f960a07 100644 --- a/src/embassy/time_driver.rs +++ b/src/embassy/time_driver.rs @@ -1,7 +1,7 @@ use core::cell::Cell; use core::convert::TryInto; use core::sync::atomic::{compiler_fence, AtomicU32, AtomicU8, Ordering}; -use core::{mem, ptr, u16}; +use core::u16; use critical_section::CriticalSection; use embassy_sync::blocking_mutex::{raw::CriticalSectionRawMutex, Mutex}; use embassy_time_driver::{AlarmHandle, Driver, TICK_HZ}; @@ -62,11 +62,7 @@ fn timer() -> &'static RegisterBlock { struct AlarmState { timestamp: Cell, - - // This is really a Option<(fn(*mut ()), *mut ())> - // but fn pointers aren't allowed in const yet - callback: Cell<*const ()>, - ctx: Cell<*mut ()>, + callback: Cell>, } unsafe impl Send for AlarmState {} @@ -75,8 +71,7 @@ impl AlarmState { const fn new() -> Self { Self { timestamp: Cell::new(u64::MAX), - callback: Cell::new(ptr::null()), - ctx: Cell::new(ptr::null_mut()), + callback: Cell::new(None), } } } @@ -235,11 +230,10 @@ impl EmbassyTimeDriver { alarm.timestamp.set(u64::MAX); // Call after clearing alarm, so the callback can set another alarm. - // Safety: - // - we can ignore the possibility of `f` being unset (null) because of the safety contract of `allocate_alarm`. - // - other than that we only store valid function pointers into alarm.callback - let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) }; - f(alarm.ctx.get()); + // The callback must have been set by `allocate_alarm` so this shouldn't panic. + let callback = alarm.callback.get().unwrap(); + let f: fn(*mut ()) = callback.0; + f(callback.1); } fn on_interrupt(&self) { @@ -321,8 +315,7 @@ impl Driver for EmbassyTimeDriver { critical_section::with(|cs| { let alarm = self.get_alarm(cs, alarm); - alarm.callback.set(callback as *const ()); - alarm.ctx.set(ctx); + alarm.callback.set(Some((callback, ctx))); }) } From bdd0402727d7df281c0d45385b2f9a8037d3ac86 Mon Sep 17 00:00:00 2001 From: Serhii Kovalchuk Date: Sat, 21 Sep 2024 20:08:26 +0300 Subject: [PATCH 3/4] Move example-specific crates to dev dependencies. --- Cargo.toml | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4c684a1..749ea5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,8 @@ default-target = "x86_64-unknown-linux-gnu" cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.3" critical-section = { version = "1.1.3", optional = true } +embassy-time-driver = { version = "0.1.0", optional = true } +embassy-sync = { version = "0.6.0", optional = true } embedded-dma = "0.2.0" embedded-hal = "1.0.0" embedded-hal-02 = { package = "embedded-hal", version = "0.2.7", features = [ @@ -27,19 +29,17 @@ embedded-io = "0.6.1" gd32f1 = { version = "0.9.1", features = ["critical-section"] } nb = "1.1.0" void = { version = "1.0.2", default-features = false, optional = true } -embassy-executor = { version = "0.6", features = [ - "arch-cortex-m", - "executor-thread", - "integrated-timers", -], optional = true } -embassy-time = { version = "0.3.2", optional = true } -embassy-time-driver = { version = "0.1.0", optional = true } -embassy-sync = { version = "0.6.0", optional = true } [dev-dependencies] cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] } cortex-m-rtic = "1.1.4" cortex-m-semihosting = "0.5.0" +embassy-executor = { version = "0.6", features = [ + "arch-cortex-m", + "executor-thread", + "integrated-timers", +] } +embassy-time = { version = "0.3.2" } panic-halt = "0.2.0" panic-itm = "0.4.2" panic-semihosting = "0.6.0" @@ -68,14 +68,12 @@ gd32f190x8 = ["gd32f190"] embassy = [ "rt", "dep:critical-section", - "dep:embassy-executor", "dep:embassy-sync", - "dep:embassy-time", "dep:embassy-time-driver", ] -time-driver-tim1=["embassy"] -time-driver-tim2=["embassy"] -time-driver-tim14=["embassy"] +time-driver-tim1 = ["embassy"] +time-driver-tim2 = ["embassy"] +time-driver-tim14 = ["embassy"] [profile.dev] incremental = false From e083ac94644daaec4dd84d03c000342bc4b043af Mon Sep 17 00:00:00 2001 From: Serhii Kovalchuk Date: Sat, 21 Sep 2024 20:28:17 +0300 Subject: [PATCH 4/4] Add watchdog timer to the Embassy example. --- examples/embassy.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/embassy.rs b/examples/embassy.rs index 5d1076d..d31f72c 100644 --- a/examples/embassy.rs +++ b/examples/embassy.rs @@ -7,7 +7,7 @@ use panic_halt as _; use embassy_executor::{self, Spawner}; use embassy_time::Timer; use embedded_hal::digital::OutputPin; -use gd32f1x0_hal::{embassy, pac, prelude::*}; +use gd32f1x0_hal::{embassy, pac, prelude::*, time::MilliSeconds, watchdog::FreeWatchdog}; #[embassy_executor::task] async fn blink_task(mut led: impl OutputPin + 'static) { @@ -34,5 +34,13 @@ async fn main(spawner: Spawner) { .into_push_pull_output(&mut gpioc.config) .downgrade(); + // This task will run in parallel with the loop below time-sharing MCU resources spawner.must_spawn(blink_task(led)); + + let mut watchdog = FreeWatchdog::new(p.fwdgt); + watchdog.start(MilliSeconds(100)); + loop { + Timer::after_micros(500).await; + watchdog.feed(); + } }