diff --git a/embassy-macros/src/chip/rp.rs b/embassy-macros/src/chip/rp.rs index 04211f8f..d40a4496 100644 --- a/embassy-macros/src/chip/rp.rs +++ b/embassy-macros/src/chip/rp.rs @@ -3,10 +3,16 @@ use proc_macro2::TokenStream; use quote::quote; pub fn generate(embassy_prefix: &ModulePrefix, config: syn::Expr) -> TokenStream { + let embassy_path = embassy_prefix.append("embassy").path(); let embassy_rp_path = embassy_prefix.append("embassy_rp").path(); quote!( use #embassy_rp_path::{interrupt, peripherals}; let p = #embassy_rp_path::init(#config); + + let alarm = unsafe { <#embassy_rp_path::peripherals::TIMER_ALARM0 as #embassy_path::util::Steal>::steal() }; + let mut alarm = #embassy_rp_path::timer::Alarm::new(alarm); + let alarm = unsafe { make_static(&mut alarm) }; + executor.set_alarm(alarm); ) } diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml index 503d2c70..ba9984a8 100644 --- a/embassy-rp/Cargo.toml +++ b/embassy-rp/Cargo.toml @@ -22,6 +22,6 @@ cortex-m-rt = "0.6.13" cortex-m = "0.7.1" critical-section = "0.2.1" -rp2040-pac2 = { git = "https://github.com/embassy-rs/rp2040-pac2", rev="91fa122b4923fdc02462a39ec109b161aedb29b4", features = ["rt"] } -#rp2040-pac2 = { path = "../../rp/rp2040-pac2" } +rp2040-pac2 = { git = "https://github.com/embassy-rs/rp2040-pac2", rev="2ce29ba58ad904d3995ce65bb46807e853f1fbf9", features = ["rt"] } +#rp2040-pac2 = { path = "../../rp/rp2040-pac2", features = ["rt"] } embedded-hal = { version = "0.2.4", features = [ "unproven" ] } diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index 6835ecad..3082cd0d 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -4,6 +4,7 @@ use crate::{pac, reset}; const XOSC_MHZ: u32 = 12; +/// safety: must be called exactly once at bootup pub unsafe fn init() { // Reset everything except: // - QSPI (we're using it to run this code!) diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index 1e6417c9..aefc86c0 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -11,10 +11,12 @@ pub use rp2040_pac2 as pac; pub(crate) mod fmt; pub mod interrupt; +pub use embassy_macros::interrupt; pub mod dma; pub mod gpio; pub mod spi; +pub mod timer; pub mod uart; mod clocks; @@ -64,6 +66,11 @@ embassy_extras::peripherals! { SPI0, SPI1, + TIMER_ALARM0, + TIMER_ALARM1, + TIMER_ALARM2, + TIMER_ALARM3, + DMA_CH0, DMA_CH1, DMA_CH2, @@ -100,6 +107,7 @@ pub fn init(_config: config::Config) -> Peripherals { unsafe { clocks::init(); + timer::init(); } peripherals diff --git a/embassy-rp/src/spi.rs b/embassy-rp/src/spi.rs index 6c3aabfb..ec2437e9 100644 --- a/embassy-rp/src/spi.rs +++ b/embassy-rp/src/spi.rs @@ -33,6 +33,33 @@ pub struct Spi<'d, T: Instance> { phantom: PhantomData<&'d mut T>, } +fn div_roundup(a: u32, b: u32) -> u32 { + (a + b - 1) / b +} + +fn calc_prescs(freq: u32) -> (u8, u8) { + let clk_peri = crate::clocks::clk_peri_freq(); + + // final SPI frequency: spi_freq = clk_peri / presc / postdiv + // presc must be in 2..=254, and must be even + // postdiv must be in 1..=256 + + // divide extra by 2, so we get rid of the "presc must be even" requirement + let ratio = div_roundup(clk_peri, freq * 2); + if ratio > 127 * 256 { + panic!("Requested too low SPI frequency"); + } + + let presc = div_roundup(ratio, 256); + let postdiv = if presc == 1 { + ratio + } else { + div_roundup(ratio, presc) + }; + + ((presc * 2) as u8, (postdiv - 1) as u8) +} + impl<'d, T: Instance> Spi<'d, T> { pub fn new( inner: impl Unborrow, @@ -46,45 +73,14 @@ impl<'d, T: Instance> Spi<'d, T> { unsafe { let p = inner.regs(); + let (presc, postdiv) = calc_prescs(config.frequency); - let clk_peri = crate::clocks::clk_peri_freq(); - assert!(config.frequency <= clk_peri); - - // final SPI frequency: spi_freq = clk_peri / presc / postdiv - // presc must be in 2..=254, and must be even - // postdiv must be in 1..=256 - - fn div_roundup(a: u32, b: u32) -> u32 { - (a + b - 1) / b - } - - // divide extra by 2, so we get rid of the "presc must be even" requirement - let ratio = div_roundup(clk_peri, config.frequency * 2); - if ratio > 127 * 256 { - panic!("Requested too low SPI frequency"); - } - - let presc = div_roundup(ratio, 256); - let postdiv = if presc == 1 { - ratio - } else { - div_roundup(ratio, presc) - }; - - debug!( - "SPI: requested freq {=u32}, actual freq {=u32}, presc {=u32}, postdiv {=u32}", - config.frequency, - clk_peri / (presc * 2 * postdiv), - presc * 2, - postdiv - ); - - p.cpsr().write(|w| w.set_cpsdvsr((presc * 2) as _)); + p.cpsr().write(|w| w.set_cpsdvsr(presc)); p.cr0().write(|w| { w.set_dss(0b0111); // 8bit w.set_spo(config.polarity == ehnb::Polarity::IdleHigh); w.set_sph(config.phase == ehnb::Phase::CaptureOnSecondTransition); - w.set_scr((postdiv - 1) as u8); + w.set_scr(postdiv); }); p.cr1().write(|w| { w.set_sse(true); // enable @@ -139,6 +135,17 @@ impl<'d, T: Instance> Spi<'d, T> { while p.sr().read().bsy() {} } } + + pub fn set_frequency(&mut self, freq: u32) { + let (presc, postdiv) = calc_prescs(freq); + let p = self.inner.regs(); + unsafe { + p.cpsr().write(|w| w.set_cpsdvsr(presc)); + p.cr0().modify(|w| { + w.set_scr(postdiv); + }); + } + } } impl<'d, T: Instance> eh::Write for Spi<'d, T> { diff --git a/embassy-rp/src/timer.rs b/embassy-rp/src/timer.rs new file mode 100644 index 00000000..5baa0260 --- /dev/null +++ b/embassy-rp/src/timer.rs @@ -0,0 +1,187 @@ +use core::cell::Cell; +use critical_section::CriticalSection; +use embassy::interrupt::{Interrupt, InterruptExt}; +use embassy::util::CriticalSectionMutex as Mutex; + +use crate::{interrupt, pac}; + +struct AlarmState { + timestamp: Cell, + callback: Cell>, +} +unsafe impl Send for AlarmState {} + +const ALARM_COUNT: usize = 4; +const DUMMY_ALARM: AlarmState = AlarmState { + timestamp: Cell::new(0), + callback: Cell::new(None), +}; + +static ALARMS: Mutex<[AlarmState; ALARM_COUNT]> = Mutex::new([DUMMY_ALARM; ALARM_COUNT]); + +fn now() -> u64 { + loop { + unsafe { + let hi = pac::TIMER.timerawh().read(); + let lo = pac::TIMER.timerawl().read(); + let hi2 = pac::TIMER.timerawh().read(); + if hi == hi2 { + return (hi as u64) << 32 | (lo as u64); + } + } + } +} + +struct Timer; +impl embassy::time::Clock for Timer { + fn now(&self) -> u64 { + now() + } +} + +pub trait AlarmInstance { + fn alarm_num(&self) -> usize; +} + +impl AlarmInstance for crate::peripherals::TIMER_ALARM0 { + fn alarm_num(&self) -> usize { + 0 + } +} +impl AlarmInstance for crate::peripherals::TIMER_ALARM1 { + fn alarm_num(&self) -> usize { + 1 + } +} +impl AlarmInstance for crate::peripherals::TIMER_ALARM2 { + fn alarm_num(&self) -> usize { + 2 + } +} +impl AlarmInstance for crate::peripherals::TIMER_ALARM3 { + fn alarm_num(&self) -> usize { + 3 + } +} + +pub struct Alarm { + inner: T, +} + +impl Alarm { + pub fn new(inner: T) -> Self { + Self { inner } + } +} + +impl embassy::time::Alarm for Alarm { + fn set_callback(&self, callback: fn(*mut ()), ctx: *mut ()) { + let n = self.inner.alarm_num(); + critical_section::with(|cs| { + let alarm = &ALARMS.borrow(cs)[n]; + alarm.callback.set(Some((callback, ctx))); + }) + } + + fn set(&self, timestamp: u64) { + let n = self.inner.alarm_num(); + + critical_section::with(|cs| { + let alarm = &ALARMS.borrow(cs)[n]; + alarm.timestamp.set(timestamp); + + // Arm it. + // Note that we're not checking the high bits at all. This means the irq may fire early + // if the alarm is more than 72 minutes (2^32 us) in the future. This is OK, since on irq fire + // it is checked if the alarm time has passed. + unsafe { pac::TIMER.alarm(n).write_value(timestamp as u32) }; + + let now = now(); + + // If alarm timestamp has passed, trigger it instantly. + // This disarms it. + if timestamp <= now { + trigger_alarm(n, cs); + } + }) + } + + fn clear(&self) { + self.set(u64::MAX); + } +} + +fn check_alarm(n: usize) { + critical_section::with(|cs| { + let alarm = &ALARMS.borrow(cs)[n]; + let timestamp = alarm.timestamp.get(); + if timestamp <= now() { + trigger_alarm(n, cs) + } else { + // Not elapsed, arm it again. + // This can happen if it was set more than 2^32 us in the future. + unsafe { pac::TIMER.alarm(n).write_value(timestamp as u32) }; + } + }); + + // clear the irq + unsafe { pac::TIMER.intr().write(|w| w.set_alarm(n, true)) } +} + +fn trigger_alarm(n: usize, cs: CriticalSection) { + // disarm + unsafe { pac::TIMER.armed().write(|w| w.set_armed(1 << n)) } + + let alarm = &ALARMS.borrow(cs)[n]; + alarm.timestamp.set(u64::MAX); + + // Call after clearing alarm, so the callback can set another alarm. + if let Some((f, ctx)) = alarm.callback.get() { + f(ctx); + } +} + +/// safety: must be called exactly once at bootup +pub unsafe fn init() { + // init alarms + critical_section::with(|cs| { + let alarms = ALARMS.borrow(cs); + for a in alarms { + a.timestamp.set(u64::MAX); + } + }); + + // enable all irqs + pac::TIMER.inte().write(|w| { + w.set_alarm(0, true); + w.set_alarm(1, true); + w.set_alarm(2, true); + w.set_alarm(3, true); + }); + interrupt::TIMER_IRQ_0::steal().enable(); + interrupt::TIMER_IRQ_1::steal().enable(); + interrupt::TIMER_IRQ_2::steal().enable(); + interrupt::TIMER_IRQ_3::steal().enable(); + + embassy::time::set_clock(&Timer); +} + +#[interrupt] +unsafe fn TIMER_IRQ_0() { + check_alarm(0) +} + +#[interrupt] +unsafe fn TIMER_IRQ_1() { + check_alarm(1) +} + +#[interrupt] +unsafe fn TIMER_IRQ_2() { + check_alarm(2) +} + +#[interrupt] +unsafe fn TIMER_IRQ_3() { + check_alarm(3) +} diff --git a/examples/rp/src/bin/blinky.rs b/examples/rp/src/bin/blinky.rs index a162ed2f..9c476745 100644 --- a/examples/rp/src/bin/blinky.rs +++ b/examples/rp/src/bin/blinky.rs @@ -11,6 +11,7 @@ mod example_common; use defmt::*; use embassy::executor::Spawner; +use embassy::time::{Duration, Timer}; use embassy_rp::{gpio, Peripherals}; use gpio::{Level, Output}; @@ -21,10 +22,10 @@ async fn main(_spawner: Spawner, p: Peripherals) { loop { info!("led on!"); led.set_high(); - cortex_m::asm::delay(1_000_000); + Timer::after(Duration::from_secs(1)).await; info!("led off!"); led.set_low(); - cortex_m::asm::delay(1_000_000); + Timer::after(Duration::from_secs(1)).await; } }