diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a43de59..768a018 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,9 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - - run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --features va41630 + - run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p vorago-peb1 + - run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va416xx-hal --features va41630 + - run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va416xx clippy: name: Clippy diff --git a/Cargo.toml b/Cargo.toml index c04e850..1947a61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,14 @@ [workspace] resolver = "2" members = [ + "va416xx", + "va416xx-hal", + "vorago-peb1", "bootloader", "flashloader", "examples/simple", - "va416xx", - "va416xx-hal", - "vorago-peb1" + "examples/embassy", + "examples/rtic", ] exclude = [ "flashloader/slot-a-blinky", diff --git a/README.md b/README.md index c9b6d7e..39e06b3 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,9 @@ It also contains the following helper crates: crate contains a sample flashloader which is able to update the redundant images in the NVM which is compatible to the provided bootloader as well. - The `examples` folder contains various example applications crates for the HAL and the PAC. + This folder also contains dedicated example applications using the + [`RTIC`](https://rtic.rs/2/book/en/) and [`embassy`](https://github.com/embassy-rs/embassy) + native Rust RTOSes. ## Using the `.cargo/config.toml` file diff --git a/automation/Jenkinsfile b/automation/Jenkinsfile index 18b0646..7fe8c1e 100644 --- a/automation/Jenkinsfile +++ b/automation/Jenkinsfile @@ -25,7 +25,9 @@ pipeline { stage('Docs') { steps { sh """ - RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --all-features + RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p vorago-peb1 + RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va416xx-hal --features va41630 + RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va416xx """ } } diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..99fcfb1 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,25 @@ +VA416xx Example Applications +======== + +This folder contains various examples +Consult the main README first for setup of the repository. + +## Simple examples + +```rs +cargo run --example blinky +``` + +You can have a look at the `simple/examples` folder to see all available simple examples + +## RTIC example + +```rs +cargo run --bin rtic-example +``` + +## Embassy example + +```rs +cargo run --bin embassy-example +``` diff --git a/examples/embassy/Cargo.toml b/examples/embassy/Cargo.toml new file mode 100644 index 0000000..49eafe5 --- /dev/null +++ b/examples/embassy/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "embassy-example" +version = "0.1.0" +edition = "2021" + +[dependencies] +cortex-m = { version = "0.7", features = ["critical-section-single-core"] } +cortex-m-rt = "0.7" +embedded-hal = "1" + +rtt-target = { version = "0.5" } +panic-rtt-target = { version = "0.1" } +critical-section = "1" + +embassy-sync = { version = "0.6.0" } +embassy-time = { version = "0.3.2" } +embassy-time-driver = { version = "0.1" } + +[dependencies.once_cell] +version = "1" +default-features = false +features = ["critical-section"] + +[dependencies.embassy-executor] +version = "0.6.0" +features = [ + "arch-cortex-m", + "executor-thread", + "executor-interrupt", + "integrated-timers", +] + +[dependencies.va416xx-hal] +path = "../../va416xx-hal" +features = ["va41630"] + +[features] +default = ["ticks-hz-1_000"] +ticks-hz-1_000 = ["embassy-time/tick-hz-1_000"] +ticks-hz-32_768 = ["embassy-time/tick-hz-32_768"] diff --git a/examples/embassy/src/lib.rs b/examples/embassy/src/lib.rs new file mode 100644 index 0000000..b934a40 --- /dev/null +++ b/examples/embassy/src/lib.rs @@ -0,0 +1,4 @@ +#![no_std] +pub mod time_driver; + +pub use time_driver::init; diff --git a/examples/embassy/src/main.rs b/examples/embassy/src/main.rs new file mode 100644 index 0000000..6f75702 --- /dev/null +++ b/examples/embassy/src/main.rs @@ -0,0 +1,46 @@ +#![no_std] +#![no_main] +use embassy_executor::Spawner; +use embassy_time::{Duration, Instant, Ticker}; +use embedded_hal::digital::StatefulOutputPin; +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; +use va416xx_hal::{gpio::PinsG, pac, prelude::*, time::Hertz}; + +const EXTCLK_FREQ: u32 = 40_000_000; + +// main is itself an async function. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + rtt_init_print!(); + rprintln!("VA416xx Embassy Demo"); + + let mut dp = pac::Peripherals::take().unwrap(); + + // Initialize the systick interrupt & obtain the token to prove that we did + // Use the external clock connected to XTAL_N. + let clocks = dp + .clkgen + .constrain() + .xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ)) + .freeze(&mut dp.sysconfig) + .unwrap(); + // Safety: Only called once here. + unsafe { + embassy_example::init( + &mut dp.sysconfig, + &dp.irq_router, + dp.tim15, + dp.tim14, + &clocks, + ) + }; + let portg = PinsG::new(&mut dp.sysconfig, dp.portg); + let mut led = portg.pg5.into_readable_push_pull_output(); + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + ticker.next().await; + rprintln!("Current time: {}", Instant::now().as_secs()); + led.toggle().ok(); + } +} diff --git a/examples/embassy/src/time_driver.rs b/examples/embassy/src/time_driver.rs new file mode 100644 index 0000000..bf88171 --- /dev/null +++ b/examples/embassy/src/time_driver.rs @@ -0,0 +1,323 @@ +//! This is a sample time driver implementation for the VA416xx family of devices, supporting +//! one alarm and requiring/reserving 2 TIM peripherals. You could adapt this implementation to +//! support more alarms. +use core::{ + cell::Cell, + mem, ptr, + sync::atomic::{AtomicU32, AtomicU8, Ordering}, +}; +use critical_section::CriticalSection; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::blocking_mutex::Mutex; + +use embassy_time_driver::{time_driver_impl, AlarmHandle, Driver, TICK_HZ}; +use once_cell::sync::OnceCell; +use va416xx_hal::{ + clock::Clocks, + enable_interrupt, + irq_router::enable_and_init_irq_router, + pac::{self, interrupt}, + pwm::{assert_tim_reset_for_two_cycles, enable_tim_clk, ValidTim}, +}; + +pub type TimekeeperClk = pac::Tim15; +pub type AlarmClk0 = pac::Tim14; +pub type AlarmClk1 = pac::Tim13; +pub type AlarmClk2 = pac::Tim12; + +/// Initialization method for embassy +/// +/// # Safety +/// This has to be called once at initialization time to initiate the time driver for +/// embassy. +pub unsafe fn init( + syscfg: &mut pac::Sysconfig, + irq_router: &pac::IrqRouter, + timekeeper: TimekeeperClk, + alarm: AlarmClk0, + clocks: &Clocks, +) { + enable_and_init_irq_router(syscfg, irq_router); + DRIVER.init(syscfg, timekeeper, alarm, clocks) +} + +const fn alarm_tim(idx: usize) -> &'static pac::tim0::RegisterBlock { + // Safety: This is a static memory-mapped peripheral. + match idx { + 0 => unsafe { &*AlarmClk0::ptr() }, + 1 => unsafe { &*AlarmClk1::ptr() }, + 2 => unsafe { &*AlarmClk2::ptr() }, + _ => { + panic!("invalid alarm timer index") + } + } +} + +const fn timekeeping_tim() -> &'static pac::tim0::RegisterBlock { + // Safety: This is a memory-mapped peripheral. + unsafe { &*TimekeeperClk::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 ()>, +} + +impl AlarmState { + const fn new() -> Self { + Self { + timestamp: Cell::new(u64::MAX), + callback: Cell::new(ptr::null()), + ctx: Cell::new(ptr::null_mut()), + } + } +} + +unsafe impl Send for AlarmState {} + +const ALARM_COUNT: usize = 1; + +static SCALE: OnceCell = OnceCell::new(); + +pub struct TimerDriverEmbassy { + periods: AtomicU32, + alarm_count: AtomicU8, + /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. + alarms: Mutex, +} + +impl TimerDriverEmbassy { + fn init( + &self, + syscfg: &mut pac::Sysconfig, + timekeeper: TimekeeperClk, + alarm_tim: AlarmClk0, + clocks: &Clocks, + ) { + enable_tim_clk(syscfg, TimekeeperClk::TIM_ID); + assert_tim_reset_for_two_cycles(syscfg, TimekeeperClk::TIM_ID); + + // Initiate scale value here. This is required to convert timer ticks back to a timestamp. + SCALE + .set((TimekeeperClk::clock(clocks).raw() / TICK_HZ as u32) as u64) + .unwrap(); + timekeeper + .rst_value() + .write(|w| unsafe { w.bits(u32::MAX) }); + // Decrementing counter. + timekeeper + .cnt_value() + .write(|w| unsafe { w.bits(u32::MAX) }); + // Switch on. Timekeeping should always be done. + unsafe { + enable_interrupt(TimekeeperClk::IRQ); + } + timekeeper.ctrl().modify(|_, w| w.irq_enb().set_bit()); + timekeeper.enable().write(|w| unsafe { w.bits(1) }); + + enable_tim_clk(syscfg, AlarmClk0::TIM_ID); + assert_tim_reset_for_two_cycles(syscfg, AlarmClk0::TIM_ID); + + // Explicitely disable alarm timer until needed. + alarm_tim.ctrl().modify(|_, w| { + w.irq_enb().clear_bit(); + w.enable().clear_bit() + }); + // Enable general interrupts. The IRQ enable of the peripheral remains cleared. + unsafe { + enable_interrupt(AlarmClk0::IRQ); + } + } + + // Should be called inside the IRQ of the timekeeper timer. + fn on_interrupt_timekeeping(&self) { + self.next_period(); + } + + // Should be called inside the IRQ of the alarm timer. + fn on_interrupt_alarm(&self, idx: usize) { + critical_section::with(|cs| { + if self.alarms.borrow(cs)[idx].timestamp.get() <= self.now() { + self.trigger_alarm(idx, cs) + } + }) + } + + fn next_period(&self) { + let period = self.periods.fetch_add(1, Ordering::AcqRel) + 1; + let t = (period as u64) << 32; + critical_section::with(|cs| { + for i in 0..ALARM_COUNT { + let alarm = &self.alarms.borrow(cs)[i]; + let at = alarm.timestamp.get(); + let alarm_tim = alarm_tim(0); + if at < t { + self.trigger_alarm(i, cs); + } else { + let remaining_ticks = (at - t) * *SCALE.get().unwrap(); + if remaining_ticks <= u32::MAX as u64 { + alarm_tim.enable().write(|w| unsafe { w.bits(0) }); + alarm_tim + .cnt_value() + .write(|w| unsafe { w.bits(remaining_ticks as u32) }); + alarm_tim.ctrl().modify(|_, w| w.irq_enb().set_bit()); + alarm_tim.enable().write(|w| unsafe { w.bits(1) }) + } + } + } + }) + } + + 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) { + alarm_tim(n).ctrl().modify(|_, w| { + w.irq_enb().clear_bit(); + w.enable().clear_bit() + }); + + let alarm = &self.alarms.borrow(cs)[n]; + // Setting the maximum value disables the alarm. + alarm.timestamp.set(u64::MAX); + + // Call after clearing alarm, so the callback can set another alarm. + + // safety: + // - we can ignore the possiblity 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()); + } +} + +impl Driver for TimerDriverEmbassy { + fn now(&self) -> u64 { + if SCALE.get().is_none() { + return 0; + } + let mut period1: u32; + let mut period2: u32; + let mut counter_val: u32; + + loop { + // Acquire ensures that we get the latest value of `periods` and + // no instructions can be reordered before the load. + period1 = self.periods.load(Ordering::Acquire); + + counter_val = u32::MAX - timekeeping_tim().cnt_value().read().bits(); + + // Double read to protect against race conditions when the counter is overflowing. + period2 = self.periods.load(Ordering::Relaxed); + if period1 == period2 { + let now = (((period1 as u64) << 32) | counter_val as u64) / *SCALE.get().unwrap(); + return now; + } + } + } + + unsafe fn allocate_alarm(&self) -> Option { + let id = self + .alarm_count + .fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| { + if x < ALARM_COUNT as u8 { + Some(x + 1) + } else { + None + } + }); + + match id { + Ok(id) => Some(AlarmHandle::new(id)), + Err(_) => None, + } + } + + fn set_alarm_callback( + &self, + alarm: embassy_time_driver::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: embassy_time_driver::AlarmHandle, timestamp: u64) -> bool { + if SCALE.get().is_none() { + return false; + } + critical_section::with(|cs| { + let n = alarm.id(); + let alarm_tim = alarm_tim(n.into()); + alarm_tim.ctrl().modify(|_, w| { + w.irq_enb().clear_bit(); + w.enable().clear_bit() + }); + + let alarm = self.get_alarm(cs, alarm); + alarm.timestamp.set(timestamp); + + let t = self.now(); + if timestamp <= t { + alarm.timestamp.set(u64::MAX); + return false; + } + + // If it hasn't triggered yet, setup the relevant reset value, regardless of whether + // the interrupts are enabled or not. When they are enabled at a later point, the + // right value is already set. + + // If the timestamp is in the next few ticks, add a bit of buffer to be sure the alarm + // is not missed. + // + // This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed + // by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time, + // and we don't do that here. + let safe_timestamp = timestamp.max(t + 3); + let timer_ticks = (safe_timestamp - t) * *SCALE.get().unwrap(); + alarm_tim.rst_value().write(|w| unsafe { w.bits(u32::MAX) }); + if timer_ticks <= u32::MAX as u64 { + alarm_tim + .cnt_value() + .write(|w| unsafe { w.bits(timer_ticks as u32) }); + alarm_tim.ctrl().modify(|_, w| w.irq_enb().set_bit()); + alarm_tim.enable().write(|w| unsafe { w.bits(1) }); + } + // If it's too far in the future, don't enable timer yet. + // It will be enabled later by `next_period`. + + true + }) + } +} + +time_driver_impl!( + static DRIVER: TimerDriverEmbassy = TimerDriverEmbassy { + periods: AtomicU32::new(0), + alarm_count: AtomicU8::new(0), + alarms: Mutex::const_new(CriticalSectionRawMutex::new(), [AlarmState::new(); ALARM_COUNT]) +}); + +#[interrupt] +#[allow(non_snake_case)] +fn TIM15() { + DRIVER.on_interrupt_timekeeping() +} + +#[interrupt] +#[allow(non_snake_case)] +fn TIM14() { + DRIVER.on_interrupt_alarm(0) +} diff --git a/examples/rtic/Cargo.toml b/examples/rtic/Cargo.toml new file mode 100644 index 0000000..604a1e9 --- /dev/null +++ b/examples/rtic/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "rtic-example" +version = "0.1.0" +edition = "2021" + +[dependencies] +cortex-m = { version = "0.7", features = ["critical-section-single-core"] } +cortex-m-rt = "0.7" +embedded-hal = "1" +rtt-target = { version = "0.5" } +rtic-sync = { version = "1.3", features = ["defmt-03"] } +panic-rtt-target = { version = "0.1.3" } + +[dependencies.va416xx-hal] +path = "../../va416xx-hal" +features = ["va41630"] + +[dependencies.rtic] +version = "2" +features = ["thumbv7-backend"] + +[dependencies.rtic-monotonics] +version = "2" +features = ["cortex-m-systick"] diff --git a/examples/simple/examples/rtic-empty.rs b/examples/rtic/src/bin/rtic-empty.rs similarity index 100% rename from examples/simple/examples/rtic-empty.rs rename to examples/rtic/src/bin/rtic-empty.rs diff --git a/examples/rtic/src/main.rs b/examples/rtic/src/main.rs new file mode 100644 index 0000000..f412cc3 --- /dev/null +++ b/examples/rtic/src/main.rs @@ -0,0 +1,57 @@ +//! RTIC minimal blinky +#![no_main] +#![no_std] + +#[rtic::app(device = pac, dispatchers = [U1, U2, U3])] +mod app { + use cortex_m::asm; + use embedded_hal::digital::StatefulOutputPin; + use panic_rtt_target as _; + use rtic_monotonics::systick::prelude::*; + use rtic_monotonics::Monotonic; + use rtt_target::{rprintln, rtt_init_default}; + use va416xx_hal::{ + gpio::{OutputReadablePushPull, Pin, PinsG, PG5}, + pac, + }; + + #[local] + struct Local { + led: Pin, + } + + #[shared] + struct Shared {} + + rtic_monotonics::systick_monotonic!(Mono, 10_000); + + #[init] + fn init(_ctx: init::Context) -> (Shared, Local) { + rtt_init_default!(); + rprintln!("-- Vorago RTIC template --"); + let mut dp = pac::Peripherals::take().unwrap(); + let portg = PinsG::new(&mut dp.sysconfig, dp.portg); + let led = portg.pg5.into_readable_push_pull_output(); + blinky::spawn().ok(); + (Shared {}, Local { led }) + } + + // `shared` cannot be accessed from this context + #[idle] + fn idle(_cx: idle::Context) -> ! { + loop { + asm::nop(); + } + } + + #[task( + priority = 3, + local=[led], + )] + async fn blinky(cx: blinky::Context) { + loop { + cx.local.led.toggle().ok(); + Mono::delay(200.millis()).await; + } + } +} diff --git a/examples/simple/Cargo.toml b/examples/simple/Cargo.toml index 039ea64..526e605 100644 --- a/examples/simple/Cargo.toml +++ b/examples/simple/Cargo.toml @@ -4,11 +4,11 @@ version = "0.1.0" edition = "2021" [dependencies] +cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m-rt = "0.7" +critical-section = "1" panic-rtt-target = { version = "0.1.3" } rtt-target = { version = "0.5" } -cortex-m = { version = "0.7", features = ["critical-section-single-core"] } -rtic-sync = { version = "1.3", features = ["defmt-03"] } embedded-hal = "1" embedded-hal-nb = "1" nb = "1" diff --git a/examples/simple/examples/dma.rs b/examples/simple/examples/dma.rs index 54b78f7..baf933e 100644 --- a/examples/simple/examples/dma.rs +++ b/examples/simple/examples/dma.rs @@ -4,13 +4,14 @@ use core::cell::Cell; -use cortex_m::interrupt::Mutex; use cortex_m_rt::entry; +use critical_section::Mutex; use embedded_hal::delay::DelayNs; use panic_rtt_target as _; use rtt_target::{rprintln, rtt_init_print}; use simple_examples::peb1; use va416xx_hal::dma::{Dma, DmaCfg, DmaChannel, DmaCtrlBlock}; +use va416xx_hal::irq_router::enable_and_init_irq_router; use va416xx_hal::pwm::CountdownTimer; use va416xx_hal::{ pac::{self, interrupt}, @@ -45,6 +46,7 @@ fn main() -> ! { .xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ) .freeze(&mut dp.sysconfig) .unwrap(); + enable_and_init_irq_router(&mut dp.sysconfig, &dp.irq_router); // Safety: The DMA control block has an alignment rule of 128 and we constructed it directly // statically. let dma = Dma::new(&mut dp.sysconfig, dp.dma, DmaCfg::default(), unsafe { @@ -88,10 +90,10 @@ fn transfer_example_8_bit( (0..64).for_each(|i| { src_buf[i] = i as u8; }); - cortex_m::interrupt::free(|cs| { + critical_section::with(|cs| { DMA_DONE_FLAG.borrow(cs).set(false); }); - cortex_m::interrupt::free(|cs| { + critical_section::with(|cs| { DMA_ACTIVE_FLAG.borrow(cs).set(false); }); // Safety: The source and destination buffer are valid for the duration of the DMA transfer. @@ -112,7 +114,7 @@ fn transfer_example_8_bit( // Use polling for completion status. loop { let mut dma_done = false; - cortex_m::interrupt::free(|cs| { + critical_section::with(|cs| { if DMA_ACTIVE_FLAG.borrow(cs).get() { rprintln!("DMA0 is active with 8 bit transfer"); DMA_ACTIVE_FLAG.borrow(cs).set(false); @@ -143,10 +145,10 @@ fn transfer_example_16_bit(dma0: &mut DmaChannel, delay_ms: &mut CountdownTimer< DMA_SRC_BUF[i] = (i as u32 * u16::MAX as u32 / (dest_buf_ref.len() as u32 - 1)) as u16; }); } - cortex_m::interrupt::free(|cs| { + critical_section::with(|cs| { DMA_DONE_FLAG.borrow(cs).set(false); }); - cortex_m::interrupt::free(|cs| { + critical_section::with(|cs| { DMA_ACTIVE_FLAG.borrow(cs).set(false); }); // Safety: The source and destination buffer are valid for the duration of the DMA transfer. @@ -170,7 +172,7 @@ fn transfer_example_16_bit(dma0: &mut DmaChannel, delay_ms: &mut CountdownTimer< // Use polling for completion status. loop { let mut dma_done = false; - cortex_m::interrupt::free(|cs| { + critical_section::with(|cs| { if DMA_ACTIVE_FLAG.borrow(cs).get() { rprintln!("DMA0 is active with 16-bit transfer"); DMA_ACTIVE_FLAG.borrow(cs).set(false); @@ -206,10 +208,10 @@ fn transfer_example_32_bit( (0..16).for_each(|i| { src_buf[i] = (i as u64 * u32::MAX as u64 / (src_buf.len() - 1) as u64) as u32; }); - cortex_m::interrupt::free(|cs| { + critical_section::with(|cs| { DMA_DONE_FLAG.borrow(cs).set(false); }); - cortex_m::interrupt::free(|cs| { + critical_section::with(|cs| { DMA_ACTIVE_FLAG.borrow(cs).set(false); }); // Safety: The source and destination buffer are valid for the duration of the DMA transfer. @@ -230,7 +232,7 @@ fn transfer_example_32_bit( // Use polling for completion status. loop { let mut dma_done = false; - cortex_m::interrupt::free(|cs| { + critical_section::with(|cs| { if DMA_ACTIVE_FLAG.borrow(cs).get() { rprintln!("DMA0 is active with 32-bit transfer"); DMA_ACTIVE_FLAG.borrow(cs).set(false); @@ -260,7 +262,7 @@ fn transfer_example_32_bit( #[allow(non_snake_case)] fn DMA_DONE0() { // Notify the main loop that the DMA transfer is finished. - cortex_m::interrupt::free(|cs| { + critical_section::with(|cs| { DMA_DONE_FLAG.borrow(cs).set(true); }); } @@ -269,7 +271,7 @@ fn DMA_DONE0() { #[allow(non_snake_case)] fn DMA_ACTIVE0() { // Notify the main loop that the DMA 0 is active now. - cortex_m::interrupt::free(|cs| { + critical_section::with(|cs| { DMA_ACTIVE_FLAG.borrow(cs).set(true); }); } diff --git a/examples/simple/examples/timer-ticks.rs b/examples/simple/examples/timer-ticks.rs index 4eb47e8..b603a46 100644 --- a/examples/simple/examples/timer-ticks.rs +++ b/examples/simple/examples/timer-ticks.rs @@ -3,12 +3,14 @@ #![no_std] use core::cell::Cell; -use cortex_m::interrupt::Mutex; +use cortex_m::asm; use cortex_m_rt::entry; +use critical_section::Mutex; use panic_rtt_target as _; use rtt_target::{rprintln, rtt_init_print}; use simple_examples::peb1; use va416xx_hal::{ + irq_router::enable_and_init_irq_router, pac::{self, interrupt}, prelude::*, timer::{default_ms_irq_handler, set_up_ms_tick, CountdownTimer, MS_COUNTER}, @@ -35,19 +37,21 @@ fn main() -> ! { .xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ) .freeze(&mut dp.sysconfig) .unwrap(); + enable_and_init_irq_router(&mut dp.sysconfig, &dp.irq_router); let _ = set_up_ms_tick(&mut dp.sysconfig, dp.tim0, &clocks); let mut second_timer = CountdownTimer::new(&mut dp.sysconfig, dp.tim1, &clocks); - second_timer.start(1.Hz()); second_timer.listen(); + second_timer.start(1.Hz()); loop { - let current_ms = cortex_m::interrupt::free(|cs| MS_COUNTER.borrow(cs).get()); - if current_ms - last_ms >= 1000 { - last_ms = current_ms; + let current_ms = critical_section::with(|cs| MS_COUNTER.borrow(cs).get()); + if current_ms >= last_ms + 1000 { + // To prevent drift. + last_ms += 1000; rprintln!("MS counter: {}", current_ms); - let second = cortex_m::interrupt::free(|cs| SEC_COUNTER.borrow(cs).get()); + let second = critical_section::with(|cs| SEC_COUNTER.borrow(cs).get()); rprintln!("Second counter: {}", second); } - cortex_m::asm::delay(10000); + asm::delay(1000); } } @@ -60,7 +64,7 @@ fn TIM0() { #[interrupt] #[allow(non_snake_case)] fn TIM1() { - cortex_m::interrupt::free(|cs| { + critical_section::with(|cs| { let mut sec = SEC_COUNTER.borrow(cs).get(); sec += 1; SEC_COUNTER.borrow(cs).set(sec); diff --git a/examples/simple/examples/wdt.rs b/examples/simple/examples/wdt.rs index 75c6e43..cf7b0c7 100644 --- a/examples/simple/examples/wdt.rs +++ b/examples/simple/examples/wdt.rs @@ -3,11 +3,12 @@ #![no_std] use core::cell::Cell; -use cortex_m::interrupt::Mutex; use cortex_m_rt::entry; +use critical_section::Mutex; use panic_rtt_target as _; use rtt_target::{rprintln, rtt_init_print}; use simple_examples::peb1; +use va416xx_hal::irq_router::enable_and_init_irq_router; use va416xx_hal::pac::{self, interrupt}; use va416xx_hal::prelude::*; use va416xx_hal::wdt::Wdt; @@ -40,6 +41,7 @@ fn main() -> ! { .xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ) .freeze(&mut dp.sysconfig) .unwrap(); + enable_and_init_irq_router(&mut dp.sysconfig, &dp.irq_router); let mut delay_sysclk = cortex_m::delay::Delay::new(cp.SYST, clocks.apb0().raw()); let mut last_interrupt_counter = 0; @@ -49,7 +51,7 @@ fn main() -> ! { if TEST_MODE != TestMode::AllowReset { wdt_ctrl.feed(); } - let interrupt_counter = cortex_m::interrupt::free(|cs| WDT_INTRPT_COUNT.borrow(cs).get()); + let interrupt_counter = critical_section::with(|cs| WDT_INTRPT_COUNT.borrow(cs).get()); if interrupt_counter > last_interrupt_counter { rprintln!("interrupt counter has increased to {}", interrupt_counter); last_interrupt_counter = interrupt_counter; @@ -65,7 +67,7 @@ fn main() -> ! { #[interrupt] #[allow(non_snake_case)] fn WATCHDOG() { - cortex_m::interrupt::free(|cs| { + critical_section::with(|cs| { WDT_INTRPT_COUNT .borrow(cs) .set(WDT_INTRPT_COUNT.borrow(cs).get() + 1); diff --git a/flashloader/src/main.rs b/flashloader/src/main.rs index 0cde1ba..98edfca 100644 --- a/flashloader/src/main.rs +++ b/flashloader/src/main.rs @@ -108,6 +108,7 @@ mod app { use spacepackets::ecss::{ tc::PusTcReader, tm::PusTmCreator, EcssEnumU8, PusPacket, WritablePusPacket, }; + use va416xx_hal::irq_router::enable_and_init_irq_router; use va416xx_hal::{ clock::ClkgenExt, edac, @@ -163,6 +164,7 @@ mod app { .xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ)) .freeze(&mut cx.device.sysconfig) .unwrap(); + enable_and_init_irq_router(&mut cx.device.sysconfig, &cx.device.irq_router); setup_edac(&mut cx.device.sysconfig); let gpiob = PinsG::new(&mut cx.device.sysconfig, cx.device.portg); @@ -488,7 +490,7 @@ mod app { .unwrap(); Mono::delay(2.millis()).await; } - Mono::delay(30.millis()).await; + Mono::delay(50.millis()).await; } } diff --git a/va416xx-hal/CHANGELOG.md b/va416xx-hal/CHANGELOG.md index 14790d4..82e2e2a 100644 --- a/va416xx-hal/CHANGELOG.md +++ b/va416xx-hal/CHANGELOG.md @@ -25,6 +25,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Fixes for the SPI implementation where the clock divider values were not calculated correctly - Fixes for UART IRQ handler implementation +- Add new IRQ router initialization method `irq_router::enable_and_init_irq_router`. This method + also sets the initial values of some registers to 0 where the datasheet and the actual reset + value are inconsistent, which can lead to weird bugs like IRQs not being triggered properly. ## Added diff --git a/va416xx-hal/Cargo.toml b/va416xx-hal/Cargo.toml index 038b44e..fac1f63 100644 --- a/va416xx-hal/Cargo.toml +++ b/va416xx-hal/Cargo.toml @@ -12,6 +12,7 @@ categories = ["embedded", "no-std", "hardware-support"] [dependencies] cortex-m = { version = "0.7", features = ["critical-section-single-core"] } +critical-section = "1" nb = "1" paste = "1" embedded-hal-nb = "1" diff --git a/va416xx-hal/src/clock.rs b/va416xx-hal/src/clock.rs index 37ec541..4685674 100644 --- a/va416xx-hal/src/clock.rs +++ b/va416xx-hal/src/clock.rs @@ -494,7 +494,7 @@ pub struct Clocks { impl Clocks { /// Returns the frequency of the HBO clock - pub fn hbo(&self) -> Hertz { + pub const fn hbo(&self) -> Hertz { HBO_FREQ } @@ -504,23 +504,23 @@ impl Clocks { } /// Returns system clock divied by 2. - pub fn apb1(&self) -> Hertz { + pub const fn apb1(&self) -> Hertz { self.apb1 } /// Returns system clock divied by 4. - pub fn apb2(&self) -> Hertz { + pub const fn apb2(&self) -> Hertz { self.apb2 } /// Returns the system (core) frequency - pub fn sysclk(&self) -> Hertz { + pub const fn sysclk(&self) -> Hertz { self.sysclk } /// Returns the ADC clock frequency which has a separate divider. #[cfg(not(feature = "va41628"))] - pub fn adc_clk(&self) -> Hertz { + pub const fn adc_clk(&self) -> Hertz { self.adc_clk } } diff --git a/va416xx-hal/src/irq_router.rs b/va416xx-hal/src/irq_router.rs new file mode 100644 index 0000000..4c7dbb5 --- /dev/null +++ b/va416xx-hal/src/irq_router.rs @@ -0,0 +1,18 @@ +use crate::{ + clock::{PeripheralSelect, SyscfgExt}, + pac, +}; + +pub fn enable_and_init_irq_router(sysconfig: &mut pac::Sysconfig, irq_router: &pac::IrqRouter) { + sysconfig.enable_peripheral_clock(PeripheralSelect::IrqRouter); + sysconfig.assert_periph_reset_for_two_cycles(PeripheralSelect::IrqRouter); + unsafe { + irq_router.dmasel0().write_with_zero(|w| w); + irq_router.dmasel1().write_with_zero(|w| w); + irq_router.dmasel2().write_with_zero(|w| w); + irq_router.dmasel3().write_with_zero(|w| w); + irq_router.adcsel().write_with_zero(|w| w); + irq_router.dacsel0().write_with_zero(|w| w); + irq_router.dacsel1().write_with_zero(|w| w); + } +} diff --git a/va416xx-hal/src/lib.rs b/va416xx-hal/src/lib.rs index 8132766..e174c75 100644 --- a/va416xx-hal/src/lib.rs +++ b/va416xx-hal/src/lib.rs @@ -1,3 +1,26 @@ +//! This is the **H**ardware **A**bstraction **L**ayer (HAL) for the VA416xx MCU family. +//! +//! It is an additional hardware abstraction on top of the [peripheral access API](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/va416xx). + +//! It is the result of reading the datasheet for the device and encoding a type-safe layer over the +//! raw PAC. This crate also implements traits specified by the +//! [embedded-hal](https://github.com/rust-embedded/embedded-hal) project, making it compatible with +//! various drivers in the embedded rust ecosystem. + +//! You have to enable one of the following device features to use this crate depending on +//! which chip you are using: + +//! - `va41630` +//! - `va41629` +//! - `va41628` +//! - `va41620` +//! +//! When using this HAL and writing applications for the VA416xx family in general, it is strongly +//! recommended that you set up the clock properly, because the default internal HBO clock +//! is not very accurate. You can use the [crate::clock] module for this. If you are working +//! with interrupts, it is strongly recommended to set up the IRQ router with the +//! [crate::irq_router] module at the very least because that peripheral has confusing and/or +//! faulty register reset values which might leads to weird bugs and glitches. #![no_std] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #[cfg(test)] @@ -22,6 +45,7 @@ pub mod dma; pub mod edac; pub mod gpio; pub mod i2c; +pub mod irq_router; pub mod nvm; pub mod pwm; pub mod spi; diff --git a/va416xx-hal/src/timer.rs b/va416xx-hal/src/timer.rs index d794efe..7f53162 100644 --- a/va416xx-hal/src/timer.rs +++ b/va416xx-hal/src/timer.rs @@ -5,7 +5,8 @@ //! - [Timer MS and Second Tick Example](https://github.com/us-irs/va416xx-rs/blob/main/examples/simple/examples/timer-ticks.rs) use core::cell::Cell; -use cortex_m::interrupt::Mutex; +use cortex_m::asm; +use critical_section::Mutex; use crate::clock::Clocks; use crate::gpio::{ @@ -169,6 +170,14 @@ macro_rules! tim_markers { }; } +pub const fn const_clock(_: &Tim, clocks: &Clocks) -> Hertz { + if Tim::TIM_ID <= 15 { + clocks.apb1() + } else { + clocks.apb2() + } +} + tim_markers!( (pac::Tim0, 0, pac::Interrupt::TIM0), (pac::Tim1, 1, pac::Interrupt::TIM1), @@ -328,19 +337,27 @@ valid_pin_and_tims!( /// /// Only the bit related to the corresponding TIM peripheral is modified #[inline] -fn assert_tim_reset(syscfg: &mut pac::Sysconfig, tim_id: u8) { +pub fn assert_tim_reset(syscfg: &mut pac::Sysconfig, tim_id: u8) { syscfg .tim_reset() .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << tim_id as u32)) }) } #[inline] -fn deassert_tim_reset(syscfg: &mut pac::Sysconfig, tim_id: u8) { +pub fn deassert_tim_reset(syscfg: &mut pac::Sysconfig, tim_id: u8) { syscfg .tim_reset() .modify(|r, w| unsafe { w.bits(r.bits() | (1 << tim_id as u32)) }) } +#[inline] +pub fn assert_tim_reset_for_two_cycles(syscfg: &mut pac::Sysconfig, tim_id: u8) { + assert_tim_reset(syscfg, tim_id); + asm::nop(); + asm::nop(); + deassert_tim_reset(syscfg, tim_id); +} + pub type TimRegBlock = pac::tim0::RegisterBlock; /// Register interface. @@ -481,7 +498,7 @@ pub struct CountdownTimer { } #[inline] -fn enable_tim_clk(syscfg: &mut pac::Sysconfig, idx: u8) { +pub fn enable_tim_clk(syscfg: &mut pac::Sysconfig, idx: u8) { syscfg .tim_clk_enable() .modify(|r, w| unsafe { w.bits(r.bits() | (1 << idx)) }); @@ -579,9 +596,10 @@ impl CountdownTimer { pub fn load(&mut self, timeout: impl Into) { self.tim.reg().ctrl().modify(|_, w| w.enable().clear_bit()); self.curr_freq = timeout.into(); - self.rst_val = self.clock.raw() / self.curr_freq.raw(); + self.rst_val = (self.clock.raw() / self.curr_freq.raw()) - 1; self.set_reload(self.rst_val); - self.set_count(0); + // Decrementing counter, to set the reset value. + self.set_count(self.rst_val); } #[inline(always)] @@ -601,7 +619,7 @@ impl CountdownTimer { #[inline(always)] pub fn enable(&mut self) { - self.tim.reg().ctrl().modify(|_, w| w.enable().set_bit()); + self.tim.reg().enable().write(|w| unsafe { w.bits(1) }); } #[inline(always)] @@ -778,7 +796,7 @@ pub fn set_up_ms_tick( /// This function can be called in a specified interrupt handler to increment /// the MS counter pub fn default_ms_irq_handler() { - cortex_m::interrupt::free(|cs| { + critical_section::with(|cs| { let mut ms = MS_COUNTER.borrow(cs).get(); ms += 1; MS_COUNTER.borrow(cs).set(ms); @@ -787,7 +805,7 @@ pub fn default_ms_irq_handler() { /// Get the current MS tick count pub fn get_ms_ticks() -> u32 { - cortex_m::interrupt::free(|cs| MS_COUNTER.borrow(cs).get()) + critical_section::with(|cs| MS_COUNTER.borrow(cs).get()) } pub struct DelayMs(CountdownTimer); diff --git a/vscode/launch.json b/vscode/launch.json index f583000..0a41f77 100644 --- a/vscode/launch.json +++ b/vscode/launch.json @@ -410,5 +410,65 @@ ] } }, + { + "type": "cortex-debug", + "request": "launch", + "name": "Embassy Example", + "servertype": "jlink", + "jlinkscript": "${workspaceFolder}/jlink/JLinkSettings.JLinkScript", + "cwd": "${workspaceRoot}", + "device": "Cortex-M4", + "svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched", + "preLaunchTask": "embassy-example", + "overrideLaunchCommands": [ + "monitor halt", + "monitor reset", + "load", + ], + "executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/embassy-example", + "interface": "swd", + "runToEntryPoint": "main", + "rttConfig": { + "enabled": true, + "address": "auto", + "decoders": [ + { + "port": 0, + "timestamp": true, + "type": "console" + } + ] + } + }, + { + "type": "cortex-debug", + "request": "launch", + "name": "RTIC Example", + "servertype": "jlink", + "jlinkscript": "${workspaceFolder}/jlink/JLinkSettings.JLinkScript", + "cwd": "${workspaceRoot}", + "device": "Cortex-M4", + "svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched", + "preLaunchTask": "embassy-example", + "overrideLaunchCommands": [ + "monitor halt", + "monitor reset", + "load", + ], + "executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/rtic-example", + "interface": "swd", + "runToEntryPoint": "main", + "rttConfig": { + "enabled": true, + "address": "auto", + "decoders": [ + { + "port": 0, + "timestamp": true, + "type": "console" + } + ] + } + }, ] } diff --git a/vscode/tasks.json b/vscode/tasks.json index cf2752f..4bc6c05 100644 --- a/vscode/tasks.json +++ b/vscode/tasks.json @@ -186,5 +186,18 @@ "kind": "build", } }, + { + "label": "rtic-example", + "type": "shell", + "command": "~/.cargo/bin/cargo", // note: full path to the cargo + "args": [ + "build", + "--bin", + "rtic-example" + ], + "group": { + "kind": "build", + } + }, ] -} \ No newline at end of file +}