From 4ce3bdb3703e5120c7936b5e3762744ae4461e75 Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Sat, 26 Apr 2025 21:54:40 +0200 Subject: Add core voltage scaling options and PLL parameter finder for RP2040 --- embassy-rp/src/clocks.rs | 242 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 228 insertions(+), 14 deletions(-) diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index 67aa5e540..ba7b139a6 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -69,6 +69,48 @@ pub enum PeriClkSrc { // Gpin1 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN1 as _ , } +/// Core voltage scaling options for RP2040. +/// See RP2040 Datasheet, Table 18. +#[cfg(feature = "rp2040")] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum VoltageScale { + /// 0.85V + V0_85 = 0b1000, + /// 0.90V + V0_90 = 0b1001, + /// 0.95V + V0_95 = 0b1010, + /// 1.00V + V1_00 = 0b1011, + /// 1.05V + V1_05 = 0b1100, + /// 1.10V (Default) + V1_10 = 0b1101, + /// 1.15V + V1_15 = 0b1110, + /// 1.20V + V1_20 = 0b1111, +} + +#[cfg(feature = "rp2040")] +impl VoltageScale { + /// Get the recommended Brown-Out Detection (BOD) setting for this voltage. + /// See RP2040 Datasheet, Table 19. + fn recommended_bod(self) -> u8 { + match self { + VoltageScale::V0_85 => 0b1000, // BOD recommends VSEL + 1 + VoltageScale::V0_90 => 0b1001, + VoltageScale::V0_95 => 0b1010, + VoltageScale::V1_00 => 0b1011, + VoltageScale::V1_05 => 0b1100, + VoltageScale::V1_10 => 0b1101, // Default + VoltageScale::V1_15 => 0b1110, + VoltageScale::V1_20 => 0b1111, + } + } +} + /// CLock configuration. #[non_exhaustive] pub struct ClockConfig { @@ -89,6 +131,9 @@ pub struct ClockConfig { /// RTC clock configuration. #[cfg(feature = "rp2040")] pub rtc_clk: Option, + /// Core voltage scaling (RP2040 only). Defaults to 1.10V if None. + #[cfg(feature = "rp2040")] + pub voltage_scale: Option, // gpin0: Option<(u32, Gpin<'static, AnyPin>)>, // gpin1: Option<(u32, Gpin<'static, AnyPin>)>, } @@ -152,6 +197,93 @@ impl ClockConfig { div_frac: 0, phase: 0, }), + #[cfg(feature = "rp2040")] + voltage_scale: None, // Use hardware default (1.10V) + // gpin0: None, + // gpin1: None, + } + } + + /// Clock configuration derived from external crystal, targeting a specific SYS clock frequency for RP2040. + /// + /// This function calculates the required PLL settings and core voltage based on the target frequency. + /// + /// # Arguments + /// + /// * `crystal_hz`: The frequency of the external crystal (e.g., 12_000_000 for 12MHz). + /// * `sys_freq_hz`: The target system clock frequency. + /// + /// # Panics + /// + /// Panics if the requested frequency is impossible to achieve with the given crystal, + /// or if the required voltage for the frequency is not supported or known. + /// + /// # Safety Notes (RP2040) + /// + /// * Frequencies > 133MHz require increased core voltage. + /// * This function automatically selects `VoltageScale::V1_15` for frequencies > 133MHz and <= 200MHz. + /// * Frequencies > 200MHz might require `VoltageScale::V1_20` or higher and are considered overclocking beyond datasheet recommendations. + /// This function will select `VoltageScale::V1_20` for frequencies > 200MHz, use with caution. + /// * Ensure your hardware supports the selected voltage and frequency. + #[cfg(feature = "rp2040")] + pub fn crystal_freq(crystal_hz: u32, sys_freq_hz: u32) -> Self { + // Determine required voltage based on target frequency + let voltage_scale = match sys_freq_hz { + 0..=133_000_000 => VoltageScale::V1_10, // Default voltage is sufficient + 133_000_001..=200_000_000 => VoltageScale::V1_15, // Requires 1.15V + _ => VoltageScale::V1_20, // Frequencies > 200MHz require at least 1.20V (Overclocking) + }; + + // Find suitable PLL parameters + let pll_params = find_pll_params(crystal_hz, sys_freq_hz) + .expect("Could not find valid PLL parameters for the requested frequency"); + + Self { + rosc: Some(RoscConfig { + hz: 6_500_000, + range: RoscRange::Medium, + drive_strength: [0; 8], + div: 16, + }), + xosc: Some(XoscConfig { + hz: crystal_hz, + sys_pll: Some(pll_params), + // Keep USB PLL at 48MHz for compatibility + usb_pll: Some(PllConfig { + refdiv: 1, + fbdiv: 120, + post_div1: 6, + post_div2: 5, + }), + delay_multiplier: 128, + }), + ref_clk: RefClkConfig { + src: RefClkSrc::Xosc, + div: 1, + }, + sys_clk: SysClkConfig { + src: SysClkSrc::PllSys, + div_int: 1, + div_frac: 0, + }, + peri_clk_src: Some(PeriClkSrc::Sys), + usb_clk: Some(UsbClkConfig { + src: UsbClkSrc::PllUsb, + div: 1, + phase: 0, + }), + adc_clk: Some(AdcClkConfig { + src: AdcClkSrc::PllUsb, + div: 1, + phase: 0, + }), + rtc_clk: Some(RtcClkConfig { + src: RtcClkSrc::PllUsb, + div_int: 1024, + div_frac: 0, + phase: 0, + }), + voltage_scale: Some(voltage_scale), // gpin0: None, // gpin1: None, } @@ -192,8 +324,10 @@ impl ClockConfig { div_frac: 171, phase: 0, }), - // gpin0: None, - // gpin1: None, + #[cfg(feature = "rp2040")] + voltage_scale: None, // Use hardware default (1.10V) + // gpin0: None, + // gpin1: None, } } @@ -405,6 +539,72 @@ pub struct RtcClkConfig { pub phase: u8, } +/// Find valid PLL parameters (refdiv, fbdiv, post_div1, post_div2) for a target output frequency +/// based on the input frequency. +/// +/// See RP2040 Datasheet section 2.16.3. Reference Clock (ref) and 2.18.3. PLL +#[cfg(feature = "rp2040")] +fn find_pll_params(input_hz: u32, target_hz: u32) -> Option { + // Constraints from datasheet: + // REFDIV: 1..=63 + // FBDIV: 16..=320 + // POSTDIV1: 1..=7 + // POSTDIV2: 1..=7 (must be <= POSTDIV1) + // VCO frequency (input_hz / refdiv * fbdiv): 400MHz ..= 1600MHz + + for refdiv in 1..=63 { + let ref_clk = input_hz / refdiv; + // Reference clock must be >= 5MHz (implied by VCO min / FBDIV max) + if ref_clk < 5_000_000 { + continue; + } + + for fbdiv in (16..=320).rev() { + // Iterate high fbdiv first for better VCO stability + let vco_freq = ref_clk * fbdiv; + if !(400_000_000..=1_600_000_000).contains(&vco_freq) { + continue; + } + + // We want vco_freq / (post_div1 * post_div2) = target_hz + // So, post_div1 * post_div2 = vco_freq / target_hz + let target_post_div_product = vco_freq as f64 / target_hz as f64; + if target_post_div_product < 1.0 || target_post_div_product > 49.0 { + // 7*7 = 49 + continue; + } + // Manual rounding: floor(x + 0.5) + let target_post_div_product_int = (target_post_div_product + 0.5) as u32; + if target_post_div_product_int == 0 { + continue; + } + + // Check if the rounded product gives the target frequency + if vco_freq / target_post_div_product_int != target_hz { + continue; + } + + for post_div1 in (1..=7).rev() { + // Iterate high post_div1 first + if target_post_div_product_int % post_div1 == 0 { + let post_div2 = target_post_div_product_int / post_div1; + if (1..=7).contains(&post_div2) && post_div2 <= post_div1 { + // Found a valid combination + return Some(PllConfig { + refdiv: refdiv as u8, // Cast u32 to u8 (safe: 1..=63) + fbdiv: fbdiv as u16, // Cast u32 to u16 (safe: 16..=320) + post_div1: post_div1 as u8, + post_div2: post_div2 as u8, + }); + } + } + } + } + } + + None // No valid parameters found +} + /// safety: must be called exactly once at bootup pub(crate) unsafe fn init(config: ClockConfig) { // Reset everything except: @@ -447,23 +647,14 @@ pub(crate) unsafe fn init(config: ClockConfig) { reset::reset(peris); reset::unreset_wait(peris); - // let gpin0_freq = config.gpin0.map_or(0, |p| { - // core::mem::forget(p.1); - // p.0 - // }); - // CLOCKS.gpin0.store(gpin0_freq, Ordering::Relaxed); - // let gpin1_freq = config.gpin1.map_or(0, |p| { - // core::mem::forget(p.1); - // p.0 - // }); - // CLOCKS.gpin1.store(gpin1_freq, Ordering::Relaxed); - + // Configure ROSC first if present let rosc_freq = match config.rosc { Some(config) => configure_rosc(config), None => 0, }; CLOCKS.rosc.store(rosc_freq, Ordering::Relaxed); + // Configure XOSC and PLLs if present let (xosc_freq, pll_sys_freq, pll_usb_freq) = match config.xosc { Some(config) => { // start XOSC @@ -488,6 +679,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { CLOCKS.pll_sys.store(pll_sys_freq, Ordering::Relaxed); CLOCKS.pll_usb.store(pll_usb_freq, Ordering::Relaxed); + // Configure REF clock source and divider let (ref_src, ref_aux, clk_ref_freq) = { use {ClkRefCtrlAuxsrc as Aux, ClkRefCtrlSrc as Src}; let div = config.ref_clk.div as u32; @@ -514,7 +706,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { w.set_int(config.ref_clk.div); }); - // Configure tick generation on the 2040. + // Configure tick generation using REF clock #[cfg(feature = "rp2040")] pac::WATCHDOG.tick().write(|w| { w.set_cycles((clk_ref_freq / 1_000_000) as u16); @@ -532,6 +724,28 @@ pub(crate) unsafe fn init(config: ClockConfig) { pac::TICKS.watchdog_ctrl().write(|w| w.set_enable(true)); } + // Set Core Voltage (RP2040 only) BEFORE switching SYS clock to high speed PLL + #[cfg(feature = "rp2040")] + if let Some(voltage) = config.voltage_scale { + let vreg = pac::VREG_AND_CHIP_RESET; + let current_vsel = vreg.vreg().read().vsel(); + let target_vsel = voltage as u8; + + if target_vsel != current_vsel { + // Set voltage and recommended BOD level + vreg.bod().write(|w| w.set_vsel(voltage.recommended_bod())); + vreg.vreg().write(|w| w.set_vsel(target_vsel)); + + // Wait 10us for regulator to settle. Delay calculation uses REF clock + // as it's guaranteed to be stable here, before SYS potentially switches. + // 10 us = 1/100_000 s. cycles = freq * time. + let delay_cycles = clk_ref_freq / 100_000; + // delay(N) waits N+1 cycles. + cortex_m::asm::delay(delay_cycles.saturating_sub(1)); + } + } + + // Configure SYS clock source and divider let (sys_src, sys_aux, clk_sys_freq) = { use {ClkSysCtrlAuxsrc as Aux, ClkSysCtrlSrc as Src}; let (src, aux, freq) = match config.sys_clk.src { -- cgit From 713d6291d569cf44ce3a53bc93ddd7d569fb93ed Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Sat, 26 Apr 2025 21:54:48 +0200 Subject: Scale clock dividers in HD44780, rotary encoder, and stepper driver based on system clock frequency --- embassy-rp/src/pio_programs/hd44780.rs | 15 +++++++++++++-- embassy-rp/src/pio_programs/rotary_encoder.rs | 8 +++++++- embassy-rp/src/pio_programs/stepper.rs | 5 +++-- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/embassy-rp/src/pio_programs/hd44780.rs b/embassy-rp/src/pio_programs/hd44780.rs index 5846a8027..3aa54495f 100644 --- a/embassy-rp/src/pio_programs/hd44780.rs +++ b/embassy-rp/src/pio_programs/hd44780.rs @@ -1,5 +1,6 @@ //! [HD44780 display driver](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) +use crate::clocks::clk_sys_freq; use crate::dma::{AnyChannel, Channel}; use crate::pio::{ Common, Config, Direction, FifoJoin, Instance, Irq, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, @@ -134,7 +135,12 @@ impl<'l, P: Instance, const S: usize> PioHD44780<'l, P, S> { let mut cfg = Config::default(); cfg.use_program(&word_prg.prg, &[&e]); - cfg.clock_divider = 125u8.into(); + + // Scale the divider based on system clock frequency + // Original: 125 at 125 MHz (1 MHz PIO clock) + let word_divider = (clk_sys_freq() / 1_000_000) as u8; // Target 1 MHz PIO clock + cfg.clock_divider = word_divider.into(); + cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); cfg.shift_out = ShiftConfig { auto_fill: true, @@ -160,7 +166,12 @@ impl<'l, P: Instance, const S: usize> PioHD44780<'l, P, S> { let mut cfg = Config::default(); cfg.use_program(&seq_prg.prg, &[&e]); - cfg.clock_divider = 8u8.into(); // ~64ns/insn + + // Original: 8 at 125 MHz (~15.6 MHz PIO clock) + // Comment says ~64ns/insn which is 1/(15.6 MHz) = ~64ns + let seq_divider = (clk_sys_freq() / 15_600_000) as u8; // Target ~15.6 MHz PIO clock (~64ns/insn) + cfg.clock_divider = seq_divider.into(); + cfg.set_jmp_pin(&db7); cfg.set_set_pins(&[&rs, &rw]); cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); diff --git a/embassy-rp/src/pio_programs/rotary_encoder.rs b/embassy-rp/src/pio_programs/rotary_encoder.rs index e520da8a3..7bd463bb8 100644 --- a/embassy-rp/src/pio_programs/rotary_encoder.rs +++ b/embassy-rp/src/pio_programs/rotary_encoder.rs @@ -2,6 +2,7 @@ use fixed::traits::ToFixed; +use crate::clocks::clk_sys_freq; use crate::gpio::Pull; use crate::pio::{ Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine, @@ -48,7 +49,12 @@ impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> { cfg.set_in_pins(&[&pin_a, &pin_b]); cfg.fifo_join = FifoJoin::RxOnly; cfg.shift_in.direction = ShiftDirection::Left; - cfg.clock_divider = 10_000.to_fixed(); + + // Original: 10_000 at 125 MHz (12.5 KHz PIO clock) + // Scale divider to maintain same PIO clock frequency at different system clocks + let divider = (clk_sys_freq() as f32 / 12_500.0).to_fixed(); + cfg.clock_divider = divider; + cfg.use_program(&program.prg, &[]); sm.set_config(&cfg); sm.set_enable(true); diff --git a/embassy-rp/src/pio_programs/stepper.rs b/embassy-rp/src/pio_programs/stepper.rs index 495191659..6878c32f5 100644 --- a/embassy-rp/src/pio_programs/stepper.rs +++ b/embassy-rp/src/pio_programs/stepper.rs @@ -6,6 +6,7 @@ use fixed::traits::ToFixed; use fixed::types::extra::U8; use fixed::FixedU32; +use crate::clocks::clk_sys_freq; use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, PioPin, StateMachine}; use crate::Peri; @@ -64,7 +65,7 @@ impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> { sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]); let mut cfg = Config::default(); cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]); - cfg.clock_divider = (125_000_000 / (100 * 136)).to_fixed(); + cfg.clock_divider = (clk_sys_freq() / (100 * 136)).to_fixed(); cfg.use_program(&program.prg, &[]); sm.set_config(&cfg); sm.set_enable(true); @@ -73,7 +74,7 @@ impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> { /// Set pulse frequency pub fn set_frequency(&mut self, freq: u32) { - let clock_divider: FixedU32 = (125_000_000 / (freq * 136)).to_fixed(); + let clock_divider: FixedU32 = (clk_sys_freq() / (freq * 136)).to_fixed(); assert!(clock_divider <= 65536, "clkdiv must be <= 65536"); assert!(clock_divider >= 1, "clkdiv must be >= 1"); self.sm.set_clock_divider(clock_divider); -- cgit From 45b7127d614ddc65181249e70d94422500865ecd Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Sat, 26 Apr 2025 21:55:16 +0200 Subject: fmt --- embassy-rp/src/pio_programs/rotary_encoder.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-rp/src/pio_programs/rotary_encoder.rs b/embassy-rp/src/pio_programs/rotary_encoder.rs index 7bd463bb8..71567a602 100644 --- a/embassy-rp/src/pio_programs/rotary_encoder.rs +++ b/embassy-rp/src/pio_programs/rotary_encoder.rs @@ -49,12 +49,12 @@ impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> { cfg.set_in_pins(&[&pin_a, &pin_b]); cfg.fifo_join = FifoJoin::RxOnly; cfg.shift_in.direction = ShiftDirection::Left; - + // Original: 10_000 at 125 MHz (12.5 KHz PIO clock) // Scale divider to maintain same PIO clock frequency at different system clocks let divider = (clk_sys_freq() as f32 / 12_500.0).to_fixed(); cfg.clock_divider = divider; - + cfg.use_program(&program.prg, &[]); sm.set_config(&cfg); sm.set_enable(true); -- cgit From b0594d16f238f803a0192810833ae2b0c3941ec3 Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Sat, 26 Apr 2025 22:55:24 +0200 Subject: Add overclock example for RP2040 with 200 MHz clock configuration --- examples/rp/src/bin/overclock.rs | 60 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 examples/rp/src/bin/overclock.rs diff --git a/examples/rp/src/bin/overclock.rs b/examples/rp/src/bin/overclock.rs new file mode 100644 index 000000000..429fff1ac --- /dev/null +++ b/examples/rp/src/bin/overclock.rs @@ -0,0 +1,60 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::clocks::{clk_sys_freq, ClockConfig}; +use embassy_rp::config::Config; +use embassy_rp::gpio::{Level, Output}; +use embassy_time::{Duration, Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +const COUNT_TO: i32 = 1_000_000; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + // Set up for clock frequency of 200 MHz + // We will need a clock config in the HAL config that supports this frequency + // The RP2040 can run at 200 MHz with a 12 MHz crystal + let config = Config::new(ClockConfig::crystal_freq(12_000_000, 200_000_000)); + + // Initialize the peripherals + let p = embassy_rp::init(config); + + // Show CPU frequency for verification + let sys_freq = clk_sys_freq(); + info!("System clock frequency: {} Hz", sys_freq); + + // LED to indicate the system is running + let mut led = Output::new(p.PIN_25, Level::Low); + + loop { + // Reset the counter at the start of measurement period + let mut counter = 0; + + // Turn LED on while counting + led.set_high(); + + let start = Instant::now(); + + // Count to COUNT_TO + // This is a busy loop that will take some time to complete + while counter < COUNT_TO { + counter += 1; + } + + let elapsed = start - Instant::now(); + + // Report the elapsed time + led.set_low(); + info!( + "At {}Mhz: Elapsed time to count to {}: {}ms", + sys_freq / 1_000_000, + COUNT_TO, + elapsed.as_millis() + ); + + // Wait 2 seconds before starting the next measurement + Timer::after(Duration::from_secs(2)).await; + } +} -- cgit From 3a6dc910ffc66d4a30b89f299432b383271a719f Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Mon, 28 Apr 2025 22:54:15 +0200 Subject: first working draft --- embassy-rp/src/clocks.rs | 658 +++++++++++++++++++++++++++++---------- examples/rp/src/bin/overclock.rs | 26 +- 2 files changed, 505 insertions(+), 179 deletions(-) diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index ba7b139a6..b74e90f5e 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -70,43 +70,51 @@ pub enum PeriClkSrc { } /// Core voltage scaling options for RP2040. -/// See RP2040 Datasheet, Table 18. +/// See RP2040 Datasheet, Table 189, VREG Register. #[cfg(feature = "rp2040")] #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(u8)] pub enum VoltageScale { /// 0.85V - V0_85 = 0b1000, + V0_85 = 0b0110, /// 0.90V - V0_90 = 0b1001, + V0_90 = 0b0111, /// 0.95V - V0_95 = 0b1010, + V0_95 = 0b1000, /// 1.00V - V1_00 = 0b1011, + V1_00 = 0b1001, /// 1.05V - V1_05 = 0b1100, + V1_05 = 0b1010, /// 1.10V (Default) - V1_10 = 0b1101, + V1_10 = 0b1011, /// 1.15V - V1_15 = 0b1110, + V1_15 = 0b1100, /// 1.20V - V1_20 = 0b1111, + V1_20 = 0b1101, + /// 1.25V + V1_25 = 0b1110, + /// 1.30V + V1_30 = 0b1111, } #[cfg(feature = "rp2040")] impl VoltageScale { /// Get the recommended Brown-Out Detection (BOD) setting for this voltage. - /// See RP2040 Datasheet, Table 19. + /// Sets the BOD threshold to approximately 90% of the core voltage. + /// See RP2040 Datasheet, Table 190, BOD Register fn recommended_bod(self) -> u8 { match self { - VoltageScale::V0_85 => 0b1000, // BOD recommends VSEL + 1 - VoltageScale::V0_90 => 0b1001, - VoltageScale::V0_95 => 0b1010, - VoltageScale::V1_00 => 0b1011, - VoltageScale::V1_05 => 0b1100, - VoltageScale::V1_10 => 0b1101, // Default - VoltageScale::V1_15 => 0b1110, - VoltageScale::V1_20 => 0b1111, + // ~90% of voltage setting based on Table 190 values + VoltageScale::V0_85 => 0b0111, // 0.774V (~91% of 0.85V) + VoltageScale::V0_90 => 0b1000, // 0.817V (~91% of 0.90V) + VoltageScale::V0_95 => 0b1001, // 0.860V (~91% of 0.95V) + VoltageScale::V1_00 => 0b1010, // 0.903V (~90% of 1.00V) + VoltageScale::V1_05 => 0b1011, // 0.946V (~90% of 1.05V) + VoltageScale::V1_10 => 0b1100, // 0.989V (~90% of 1.10V) + VoltageScale::V1_15 => 0b1101, // 1.032V (~90% of 1.15V) + VoltageScale::V1_20 => 0b1110, // 1.075V (~90% of 1.20V) + VoltageScale::V1_25 => 0b1111, // 1.118V (~89% of 1.25V) + VoltageScale::V1_30 => 0b1111, // 1.118V (~86% of 1.30V) - using max available threshold } } } @@ -134,6 +142,9 @@ pub struct ClockConfig { /// Core voltage scaling (RP2040 only). Defaults to 1.10V if None. #[cfg(feature = "rp2040")] pub voltage_scale: Option, + /// Voltage stabilization delay in microseconds. + #[cfg(feature = "rp2040")] + pub voltage_stabilization_delay_us: Option, // gpin0: Option<(u32, Gpin<'static, AnyPin>)>, // gpin1: Option<(u32, Gpin<'static, AnyPin>)>, } @@ -199,8 +210,10 @@ impl ClockConfig { }), #[cfg(feature = "rp2040")] voltage_scale: None, // Use hardware default (1.10V) - // gpin0: None, - // gpin1: None, + #[cfg(feature = "rp2040")] + voltage_stabilization_delay_us: None, + // gpin0: None, + // gpin1: None, } } @@ -235,8 +248,16 @@ impl ClockConfig { }; // Find suitable PLL parameters - let pll_params = find_pll_params(crystal_hz, sys_freq_hz) - .expect("Could not find valid PLL parameters for the requested frequency"); + let pll_params = match find_pll_params(crystal_hz, sys_freq_hz) { + Some(params) => params, + None => { + // If we can't find valid parameters for the requested frequency, + // fall back to safe defaults (125 MHz for RP2040) + let safe_freq = 125_000_000; // Safe default frequency + find_pll_params(crystal_hz, safe_freq) + .expect("Could not find valid PLL parameters even for safe default frequency") + } + }; Self { rosc: Some(RoscConfig { @@ -284,6 +305,8 @@ impl ClockConfig { phase: 0, }), voltage_scale: Some(voltage_scale), + #[cfg(feature = "rp2040")] + voltage_stabilization_delay_us: None, // gpin0: None, // gpin1: None, } @@ -326,11 +349,128 @@ impl ClockConfig { }), #[cfg(feature = "rp2040")] voltage_scale: None, // Use hardware default (1.10V) - // gpin0: None, - // gpin1: None, + #[cfg(feature = "rp2040")] + voltage_stabilization_delay_us: None, + // gpin0: None, + // gpin1: None, } } + /// Configure the system clock to a specific frequency in MHz. + /// + /// This is a more user-friendly way to configure the system clock, similar to + /// the Pico SDK's approach. It automatically handles voltage scaling based on the + /// requested frequency and uses the standard 12MHz crystal found on most RP2040 boards. + /// + /// # Arguments + /// + /// * `sys_freq_mhz` - The target system clock frequency in MHz + /// + /// # Safety Notes + /// + /// * Frequencies > 133MHz require increased core voltage and are considered overclocking. + /// * Frequencies > 250MHz are extreme overclocking and may not be stable on all chips. + /// + /// # Example + /// + /// ``` + /// // Configure for standard 125MHz clock + /// let config = ClockConfig::with_speed_mhz(125); + /// + /// // Overclock to 200MHz (requires higher voltage) + /// let config = ClockConfig::with_speed_mhz(200); + /// ``` + #[cfg(feature = "rp2040")] + pub fn with_speed_mhz(sys_freq_mhz: u32) -> Self { + // For 125MHz, use exactly the same config as the default to avoid any differences + if sys_freq_mhz == 125 { + return Self::crystal(12_000_000); + } + + // For other frequencies, provide appropriate voltage scaling and PLL configuration + // Standard crystal on Raspberry Pi Pico boards is 12MHz + const DEFAULT_CRYSTAL_HZ: u32 = 12_000_000; + + let sys_freq_hz = sys_freq_mhz * 1_000_000; + let mut config = Self::crystal_freq(DEFAULT_CRYSTAL_HZ, sys_freq_hz); + + // For frequencies above 200MHz, ensure we're using the highest voltage + if sys_freq_mhz > 200 { + config.voltage_scale = Some(VoltageScale::V1_20); + } + + config + } + + /// Configure the system clock to a specific frequency in Hz, using a custom crystal frequency. + /// + /// This more flexible version allows specifying both the crystal frequency and target + /// system frequency for boards with non-standard crystals. + /// + /// # Arguments + /// + /// * `crystal_hz` - The frequency of the external crystal in Hz + /// * `sys_freq_hz` - The target system clock frequency in Hz + /// + /// # Safety Notes + /// + /// * Frequencies > 133MHz require increased core voltage and are considered overclocking. + /// * Frequencies > 250MHz are extreme overclocking and may not be stable on all chips. + /// + /// # Example + /// + /// ``` + /// // Use a non-standard 16MHz crystal to achieve 250MHz + /// let config = ClockConfig::with_custom_crystal(16_000_000, 250_000_000); + /// ``` + #[cfg(feature = "rp2040")] + pub fn with_custom_crystal(crystal_hz: u32, sys_freq_hz: u32) -> Self { + Self::crystal_freq(crystal_hz, sys_freq_hz) + } + + #[cfg(feature = "rp2040")] + pub fn with_speed_mhz_test_voltage(sys_freq_mhz: u32, voltage: Option) -> Self { + // Create a config with the requested frequency + let sys_freq_hz = sys_freq_mhz * 1_000_000; + let mut config = Self::crystal_freq(12_000_000, sys_freq_hz); + + // Override the voltage setting + config.voltage_scale = voltage; + + // For debugging + // println!("Debug: Setting freq to {}MHz with voltage {:?}", sys_freq_mhz, voltage); + + config + } + + /// Similar to `with_speed_mhz_test_voltage` but with an extended voltage stabilization delay. + /// + /// This function is useful for testing voltage stability issues where the default delay + /// may not be sufficient for the voltage regulator to fully stabilize. + /// + /// # Arguments + /// + /// * `sys_freq_mhz` - The target system clock frequency in MHz + /// * `voltage` - The desired voltage scale setting + /// * `stabilization_delay_us` - Voltage stabilization delay in microseconds (default: 500μs) + #[cfg(feature = "rp2040")] + pub fn with_speed_mhz_test_voltage_extended_delay( + sys_freq_mhz: u32, + voltage: Option, + stabilization_delay_us: Option, + ) -> Self { + // Create a config with the requested frequency + let sys_freq_hz = sys_freq_mhz * 1_000_000; + let mut config = Self::crystal_freq(12_000_000, sys_freq_hz); + + // Override the voltage setting + config.voltage_scale = voltage; + + // Add a custom voltage stabilization delay + config.voltage_stabilization_delay_us = stabilization_delay_us; + + config + } // pub fn bind_gpin(&mut self, gpin: Gpin<'static, P>, hz: u32) { // match P::NR { // 0 => self.gpin0 = Some((hz, gpin.into())), @@ -385,6 +525,7 @@ pub struct XoscConfig { } /// PLL configuration. +#[derive(Clone, Copy, Debug)] pub struct PllConfig { /// Reference divisor. pub refdiv: u8, @@ -542,67 +683,124 @@ pub struct RtcClkConfig { /// Find valid PLL parameters (refdiv, fbdiv, post_div1, post_div2) for a target output frequency /// based on the input frequency. /// +/// Similar to the Pico SDK's parameter selection approach, prioritizing stability. /// See RP2040 Datasheet section 2.16.3. Reference Clock (ref) and 2.18.3. PLL #[cfg(feature = "rp2040")] fn find_pll_params(input_hz: u32, target_hz: u32) -> Option { + // Fixed reference divider for system PLL + const PLL_SYS_REFDIV: u8 = 1; + // Constraints from datasheet: // REFDIV: 1..=63 // FBDIV: 16..=320 // POSTDIV1: 1..=7 // POSTDIV2: 1..=7 (must be <= POSTDIV1) - // VCO frequency (input_hz / refdiv * fbdiv): 400MHz ..= 1600MHz + // VCO frequency (input_hz / refdiv * fbdiv): 750MHz ..= 1800MHz - for refdiv in 1..=63 { - let ref_clk = input_hz / refdiv; - // Reference clock must be >= 5MHz (implied by VCO min / FBDIV max) - if ref_clk < 5_000_000 { + // Calculate reference frequency + let reference_freq = input_hz / PLL_SYS_REFDIV as u32; + + // Start from highest fbdiv for better stability (like SDK does) + for fbdiv in (16..=320).rev() { + let vco_freq = reference_freq * fbdiv; + + // Check VCO frequency is within valid range + if vco_freq < 750_000_000 || vco_freq > 1_800_000_000 { continue; } - for fbdiv in (16..=320).rev() { - // Iterate high fbdiv first for better VCO stability - let vco_freq = ref_clk * fbdiv; - if !(400_000_000..=1_600_000_000).contains(&vco_freq) { - continue; + // Try all possible postdiv combinations starting from larger values + // (more conservative/stable approach) + for post_div1 in (1..=7).rev() { + for post_div2 in (1..=post_div1).rev() { + let out_freq = vco_freq / (post_div1 * post_div2) as u32; + + // Check if we get the exact target frequency without remainder + if out_freq == target_hz && (vco_freq % (post_div1 * post_div2) as u32 == 0) { + return Some(PllConfig { + refdiv: PLL_SYS_REFDIV, + fbdiv: fbdiv as u16, + post_div1: post_div1 as u8, + post_div2: post_div2 as u8, + }); + } } + } + } - // We want vco_freq / (post_div1 * post_div2) = target_hz - // So, post_div1 * post_div2 = vco_freq / target_hz - let target_post_div_product = vco_freq as f64 / target_hz as f64; - if target_post_div_product < 1.0 || target_post_div_product > 49.0 { - // 7*7 = 49 - continue; - } - // Manual rounding: floor(x + 0.5) - let target_post_div_product_int = (target_post_div_product + 0.5) as u32; - if target_post_div_product_int == 0 { - continue; - } + // If we couldn't find an exact match, find the closest match + let mut best_config = None; + let mut min_diff = u32::MAX; - // Check if the rounded product gives the target frequency - if vco_freq / target_post_div_product_int != target_hz { - continue; - } + for fbdiv in (16..=320).rev() { + let vco_freq = reference_freq * fbdiv; - for post_div1 in (1..=7).rev() { - // Iterate high post_div1 first - if target_post_div_product_int % post_div1 == 0 { - let post_div2 = target_post_div_product_int / post_div1; - if (1..=7).contains(&post_div2) && post_div2 <= post_div1 { - // Found a valid combination - return Some(PllConfig { - refdiv: refdiv as u8, // Cast u32 to u8 (safe: 1..=63) - fbdiv: fbdiv as u16, // Cast u32 to u16 (safe: 16..=320) - post_div1: post_div1 as u8, - post_div2: post_div2 as u8, - }); - } + if vco_freq < 750_000_000 || vco_freq > 1_800_000_000 { + continue; + } + + for post_div1 in (1..=7).rev() { + for post_div2 in (1..=post_div1).rev() { + let out_freq = vco_freq / (post_div1 * post_div2) as u32; + let diff = if out_freq > target_hz { + out_freq - target_hz + } else { + target_hz - out_freq + }; + + // If this is closer to the target, save it + if diff < min_diff { + min_diff = diff; + best_config = Some(PllConfig { + refdiv: PLL_SYS_REFDIV, + fbdiv: fbdiv as u16, + post_div1: post_div1 as u8, + post_div2: post_div2 as u8, + }); } } } } - None // No valid parameters found + // Return the closest match if we found one + best_config +} + +#[cfg(feature = "rp2040")] +pub fn compare_pll_params() { + // Parameters from default configuration + let default_params = PllConfig { + refdiv: 1, + fbdiv: 125, + post_div1: 6, + post_div2: 2, + }; + + // Calculate parameters using our find_pll_params function + let crystal_hz = 12_000_000; + let target_hz = 125_000_000; + let calculated_params = find_pll_params(crystal_hz, target_hz).expect("Failed to find PLL parameters"); + + // Check if they're identical + let params_match = default_params.refdiv == calculated_params.refdiv + && default_params.fbdiv == calculated_params.fbdiv + && default_params.post_div1 == calculated_params.post_div1 + && default_params.post_div2 == calculated_params.post_div2; + + // Here we'd normally print results, but without a console we'll just + // use this for debugging in our IDE + let _default_output_freq = crystal_hz / default_params.refdiv as u32 * default_params.fbdiv as u32 + / (default_params.post_div1 * default_params.post_div2) as u32; + + let _calculated_output_freq = crystal_hz / calculated_params.refdiv as u32 * calculated_params.fbdiv as u32 + / (calculated_params.post_div1 * calculated_params.post_div2) as u32; + + // Parameters: default vs calculated + // refdiv: 1 vs {calculated_params.refdiv} + // fbdiv: 125 vs {calculated_params.fbdiv} + // post_div1: 6 vs {calculated_params.post_div1} + // post_div2: 2 vs {calculated_params.post_div2} + // params_match: {params_match} } /// safety: must be called exactly once at bootup @@ -640,12 +838,42 @@ pub(crate) unsafe fn init(config: ClockConfig) { #[cfg(feature = "_rp235x")] while c.clk_ref_selected().read() != pac::clocks::regs::ClkRefSelected(1) {} - // Reset the PLLs - let mut peris = reset::Peripherals(0); - peris.set_pll_sys(true); - peris.set_pll_usb(true); - reset::reset(peris); - reset::unreset_wait(peris); + // Set Core Voltage (RP2040 only) BEFORE doing anything with PLLs + // This is critical for overclocking - must be done before PLL setup + #[cfg(feature = "rp2040")] + if let Some(voltage) = config.voltage_scale { + let vreg = pac::VREG_AND_CHIP_RESET; + let current_vsel = vreg.vreg().read().vsel(); + let target_vsel = voltage as u8; + + if target_vsel != current_vsel { + // IMPORTANT: Use modify() instead of write() to preserve the HIZ and EN bits + // This is critical - otherwise we might disable the regulator when changing voltage + vreg.vreg().modify(|w| w.set_vsel(target_vsel)); + + // For higher voltage settings (overclocking), we need a longer stabilization time + // Default to 1000 µs (1ms) like the SDK, but allow user override + let settling_time_us = config.voltage_stabilization_delay_us.unwrap_or_else(|| { + match voltage { + VoltageScale::V1_15 => 1000, // 1ms for 1.15V (matches SDK default) + VoltageScale::V1_20 | VoltageScale::V1_25 | VoltageScale::V1_30 => 2000, // 2ms for higher voltages + _ => 500, // 500 µs for standard voltages + } + }); + + // We need a clock that's guaranteed to be running at this point + // Use ROSC which should be configured by now + let rosc_freq_rough = 6_000_000; // Rough ROSC frequency estimate + let cycles_per_us = rosc_freq_rough / 1_000_000; + let delay_cycles = settling_time_us * cycles_per_us; + + // Wait for voltage to stabilize + cortex_m::asm::delay(delay_cycles); + + // Only NOW set the BOD level after voltage has stabilized + vreg.bod().write(|w| w.set_vsel(voltage.recommended_bod())); + } + } // Configure ROSC first if present let rosc_freq = match config.rosc { @@ -654,59 +882,91 @@ pub(crate) unsafe fn init(config: ClockConfig) { }; CLOCKS.rosc.store(rosc_freq, Ordering::Relaxed); - // Configure XOSC and PLLs if present - let (xosc_freq, pll_sys_freq, pll_usb_freq) = match config.xosc { + // Configure XOSC - we'll need this for our temporary stable clock + let xosc_freq = match &config.xosc { Some(config) => { - // start XOSC - // datasheet mentions support for clock inputs into XIN, but doesn't go into - // how this is achieved. pico-sdk doesn't support this at all. start_xosc(config.hz, config.delay_multiplier); - - let pll_sys_freq = match config.sys_pll { - Some(sys_pll_config) => configure_pll(pac::PLL_SYS, config.hz, sys_pll_config), - None => 0, - }; - let pll_usb_freq = match config.usb_pll { - Some(usb_pll_config) => configure_pll(pac::PLL_USB, config.hz, usb_pll_config), - None => 0, - }; - - (config.hz, pll_sys_freq, pll_usb_freq) + config.hz } - None => (0, 0, 0), + None => 0, }; CLOCKS.xosc.store(xosc_freq, Ordering::Relaxed); - CLOCKS.pll_sys.store(pll_sys_freq, Ordering::Relaxed); - CLOCKS.pll_usb.store(pll_usb_freq, Ordering::Relaxed); - - // Configure REF clock source and divider - let (ref_src, ref_aux, clk_ref_freq) = { - use {ClkRefCtrlAuxsrc as Aux, ClkRefCtrlSrc as Src}; - let div = config.ref_clk.div as u32; - assert!(div >= 1 && div <= 4); - match config.ref_clk.src { - RefClkSrc::Xosc => (Src::XOSC_CLKSRC, Aux::CLKSRC_PLL_USB, xosc_freq / div), - RefClkSrc::Rosc => (Src::ROSC_CLKSRC_PH, Aux::CLKSRC_PLL_USB, rosc_freq / div), - RefClkSrc::PllUsb => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq / div), - // RefClkSrc::Gpin0 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN0, gpin0_freq / div), - // RefClkSrc::Gpin1 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN1, gpin1_freq / div), - } + + // SETUP TEMPORARY STABLE CLOCKS FIRST + // Configure USB PLL for our stable temporary clock + // This follows the SDK's approach of using USB PLL as a stable intermediate clock + let usb_pll_freq = match &config.xosc { + Some(config) => match &config.usb_pll { + Some(usb_pll_config) => { + // Reset USB PLL + let mut peris = reset::Peripherals(0); + peris.set_pll_usb(true); + reset::reset(peris); + reset::unreset_wait(peris); + + // Configure the USB PLL - this should give us 48MHz + let usb_pll_freq = configure_pll(pac::PLL_USB, xosc_freq, *usb_pll_config); + CLOCKS.pll_usb.store(usb_pll_freq, Ordering::Relaxed); + usb_pll_freq + } + None => 0, + }, + None => 0, }; - assert!(clk_ref_freq != 0); - CLOCKS.reference.store(clk_ref_freq, Ordering::Relaxed); + + // Configure REF clock to use XOSC c.clk_ref_ctrl().write(|w| { - w.set_src(ref_src); - w.set_auxsrc(ref_aux); + w.set_src(ClkRefCtrlSrc::XOSC_CLKSRC); }); #[cfg(feature = "rp2040")] - while c.clk_ref_selected().read() != (1 << ref_src as u32) {} + while c.clk_ref_selected().read() != (1 << ClkRefCtrlSrc::XOSC_CLKSRC as u32) {} #[cfg(feature = "_rp235x")] - while c.clk_ref_selected().read() != pac::clocks::regs::ClkRefSelected(1 << ref_src as u32) {} - c.clk_ref_div().write(|w| { - w.set_int(config.ref_clk.div); + while c.clk_ref_selected().read() != pac::clocks::regs::ClkRefSelected(1 << ClkRefCtrlSrc::XOSC_CLKSRC as u32) {} + + // First switch the system clock to a stable source (USB PLL at 48MHz) + // This follows the Pico SDK's approach to ensure stability during reconfiguration + c.clk_sys_ctrl().write(|w| { + w.set_auxsrc(ClkSysCtrlAuxsrc::CLKSRC_PLL_USB); + w.set_src(ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX); }); + #[cfg(feature = "rp2040")] + while c.clk_sys_selected().read() != (1 << ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX as u32) {} + #[cfg(feature = "_rp235x")] + while c.clk_sys_selected().read() + != pac::clocks::regs::ClkSysSelected(1 << ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX as u32) + {} + + // Short delay after switching to USB PLL to ensure stability + cortex_m::asm::delay(100); + + // NOW CONFIGURE THE SYSTEM PLL (safely, since we're running from the USB PLL) + let pll_sys_freq = match &config.xosc { + Some(config) => match &config.sys_pll { + Some(sys_pll_config) => { + // Reset SYS PLL + let mut peris = reset::Peripherals(0); + peris.set_pll_sys(true); + reset::reset(peris); + reset::unreset_wait(peris); + + // Configure the SYS PLL + let pll_sys_freq = configure_pll(pac::PLL_SYS, xosc_freq, *sys_pll_config); + + // Ensure PLL is locked and stable + cortex_m::asm::delay(100); + + CLOCKS.pll_sys.store(pll_sys_freq, Ordering::Relaxed); + pll_sys_freq + } + None => 0, + }, + None => 0, + }; + // Configure tick generation using REF clock + let clk_ref_freq = xosc_freq; // REF clock is now XOSC + CLOCKS.reference.store(clk_ref_freq, Ordering::Relaxed); #[cfg(feature = "rp2040")] pac::WATCHDOG.tick().write(|w| { w.set_cycles((clk_ref_freq / 1_000_000) as u16); @@ -724,34 +984,14 @@ pub(crate) unsafe fn init(config: ClockConfig) { pac::TICKS.watchdog_ctrl().write(|w| w.set_enable(true)); } - // Set Core Voltage (RP2040 only) BEFORE switching SYS clock to high speed PLL - #[cfg(feature = "rp2040")] - if let Some(voltage) = config.voltage_scale { - let vreg = pac::VREG_AND_CHIP_RESET; - let current_vsel = vreg.vreg().read().vsel(); - let target_vsel = voltage as u8; - - if target_vsel != current_vsel { - // Set voltage and recommended BOD level - vreg.bod().write(|w| w.set_vsel(voltage.recommended_bod())); - vreg.vreg().write(|w| w.set_vsel(target_vsel)); - - // Wait 10us for regulator to settle. Delay calculation uses REF clock - // as it's guaranteed to be stable here, before SYS potentially switches. - // 10 us = 1/100_000 s. cycles = freq * time. - let delay_cycles = clk_ref_freq / 100_000; - // delay(N) waits N+1 cycles. - cortex_m::asm::delay(delay_cycles.saturating_sub(1)); - } - } - - // Configure SYS clock source and divider + // NOW SWITCH THE SYSTEM CLOCK TO THE CONFIGURED SOURCE + // The SYS PLL is now stable and we can safely switch to it let (sys_src, sys_aux, clk_sys_freq) = { use {ClkSysCtrlAuxsrc as Aux, ClkSysCtrlSrc as Src}; let (src, aux, freq) = match config.sys_clk.src { SysClkSrc::Ref => (Src::CLK_REF, Aux::CLKSRC_PLL_SYS, clk_ref_freq), SysClkSrc::PllSys => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_SYS, pll_sys_freq), - SysClkSrc::PllUsb => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq), + SysClkSrc::PllUsb => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_USB, usb_pll_freq), SysClkSrc::Rosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::ROSC_CLKSRC, rosc_freq), SysClkSrc::Xosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::XOSC_CLKSRC, xosc_freq), // SysClkSrc::Gpin0 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN0, gpin0_freq), @@ -762,28 +1002,48 @@ pub(crate) unsafe fn init(config: ClockConfig) { }; assert!(clk_sys_freq != 0); CLOCKS.sys.store(clk_sys_freq, Ordering::Relaxed); - if sys_src != ClkSysCtrlSrc::CLK_REF { - c.clk_sys_ctrl().write(|w| w.set_src(ClkSysCtrlSrc::CLK_REF)); - #[cfg(feature = "rp2040")] - while c.clk_sys_selected().read() != (1 << ClkSysCtrlSrc::CLK_REF as u32) {} - #[cfg(feature = "_rp235x")] - while c.clk_sys_selected().read() != pac::clocks::regs::ClkSysSelected(1 << ClkSysCtrlSrc::CLK_REF as u32) {} + + // Set the divider before changing the source if it's increasing + if config.sys_clk.div_int > 1 || config.sys_clk.div_frac > 0 { + c.clk_sys_div().write(|w| { + w.set_int(config.sys_clk.div_int); + w.set_frac(config.sys_clk.div_frac); + }); + } + + // Configure aux source first if needed + if sys_src == ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX { + c.clk_sys_ctrl().modify(|w| { + w.set_auxsrc(sys_aux); + }); } + + // Now set the source c.clk_sys_ctrl().write(|w| { - w.set_auxsrc(sys_aux); + if sys_src == ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX { + w.set_auxsrc(sys_aux); + } w.set_src(sys_src); }); + // Wait for the clock to be selected #[cfg(feature = "rp2040")] while c.clk_sys_selected().read() != (1 << sys_src as u32) {} #[cfg(feature = "_rp235x")] while c.clk_sys_selected().read() != pac::clocks::regs::ClkSysSelected(1 << sys_src as u32) {} - c.clk_sys_div().write(|w| { - w.set_int(config.sys_clk.div_int); - w.set_frac(config.sys_clk.div_frac); - }); + // Short delay after final clock switch to ensure stability + cortex_m::asm::delay(100); + // Set the divider after changing the source if it's decreasing + if config.sys_clk.div_int == 1 && config.sys_clk.div_frac == 0 { + c.clk_sys_div().write(|w| { + w.set_int(config.sys_clk.div_int); + w.set_frac(config.sys_clk.div_frac); + }); + } + + // CONFIGURE PERIPHERAL CLOCK let mut peris = reset::ALL_PERIPHERALS; if let Some(src) = config.peri_clk_src { @@ -794,7 +1054,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { let peri_freq = match src { PeriClkSrc::Sys => clk_sys_freq, PeriClkSrc::PllSys => pll_sys_freq, - PeriClkSrc::PllUsb => pll_usb_freq, + PeriClkSrc::PllUsb => usb_pll_freq, PeriClkSrc::Rosc => rosc_freq, PeriClkSrc::Xosc => xosc_freq, // PeriClkSrc::Gpin0 => gpin0_freq, @@ -810,6 +1070,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { CLOCKS.peri.store(0, Ordering::Relaxed); } + // CONFIGURE USB CLOCK if let Some(conf) = config.usb_clk { c.clk_usb_div().write(|w| w.set_int(conf.div)); c.clk_usb_ctrl().write(|w| { @@ -818,7 +1079,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { w.set_auxsrc(ClkUsbCtrlAuxsrc::from_bits(conf.src as _)); }); let usb_freq = match conf.src { - UsbClkSrc::PllUsb => pll_usb_freq, + UsbClkSrc::PllUsb => usb_pll_freq, UsbClkSrc::PllSys => pll_sys_freq, UsbClkSrc::Rosc => rosc_freq, UsbClkSrc::Xosc => xosc_freq, @@ -833,6 +1094,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { CLOCKS.usb.store(0, Ordering::Relaxed); } + // CONFIGURE ADC CLOCK if let Some(conf) = config.adc_clk { c.clk_adc_div().write(|w| w.set_int(conf.div)); c.clk_adc_ctrl().write(|w| { @@ -841,7 +1103,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { w.set_auxsrc(ClkAdcCtrlAuxsrc::from_bits(conf.src as _)); }); let adc_in_freq = match conf.src { - AdcClkSrc::PllUsb => pll_usb_freq, + AdcClkSrc::PllUsb => usb_pll_freq, AdcClkSrc::PllSys => pll_sys_freq, AdcClkSrc::Rosc => rosc_freq, AdcClkSrc::Xosc => xosc_freq, @@ -856,7 +1118,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { CLOCKS.adc.store(0, Ordering::Relaxed); } - // rp2040 specific clocks + // CONFIGURE RTC CLOCK #[cfg(feature = "rp2040")] if let Some(conf) = config.rtc_clk { c.clk_rtc_div().write(|w| { @@ -869,7 +1131,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { w.set_auxsrc(ClkRtcCtrlAuxsrc::from_bits(conf.src as _)); }); let rtc_in_freq = match conf.src { - RtcClkSrc::PllUsb => pll_usb_freq, + RtcClkSrc::PllUsb => usb_pll_freq, RtcClkSrc::PllSys => pll_sys_freq, RtcClkSrc::Rosc => rosc_freq, RtcClkSrc::Xosc => xosc_freq, @@ -999,43 +1261,101 @@ fn start_xosc(crystal_hz: u32, delay_multiplier: u32) { #[inline(always)] fn configure_pll(p: pac::pll::Pll, input_freq: u32, config: PllConfig) -> u32 { + // Calculate reference frequency let ref_freq = input_freq / config.refdiv as u32; - assert!(config.fbdiv >= 16 && config.fbdiv <= 320); - assert!(config.post_div1 >= 1 && config.post_div1 <= 7); - assert!(config.post_div2 >= 1 && config.post_div2 <= 7); - assert!(config.refdiv >= 1 && config.refdiv <= 63); - assert!(ref_freq >= 5_000_000 && ref_freq <= 800_000_000); + + // Validate PLL parameters + assert!( + config.fbdiv >= 16 && config.fbdiv <= 320, + "fbdiv must be between 16 and 320" + ); + assert!( + config.post_div1 >= 1 && config.post_div1 <= 7, + "post_div1 must be between 1 and 7" + ); + assert!( + config.post_div2 >= 1 && config.post_div2 <= 7, + "post_div2 must be between 1 and 7" + ); + assert!(config.post_div2 <= config.post_div1, "post_div2 must be <= post_div1"); + assert!( + config.refdiv >= 1 && config.refdiv <= 63, + "refdiv must be between 1 and 63" + ); + assert!( + ref_freq >= 5_000_000 && ref_freq <= 800_000_000, + "ref_freq must be between 5MHz and 800MHz" + ); + + // Calculate VCO frequency let vco_freq = ref_freq.saturating_mul(config.fbdiv as u32); - assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); + assert!( + vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000, + "VCO frequency must be between 750MHz and 1800MHz" + ); + + // We follow the SDK's approach to PLL configuration which is: + // 1. Power down PLL + // 2. Configure the reference divider + // 3. Configure the feedback divider + // 4. Power up PLL and VCO + // 5. Wait for PLL to lock + // 6. Configure post-dividers + // 7. Enable post-divider output + + // 1. Power down PLL before configuration + p.pwr().write(|w| { + w.set_pd(true); // Power down the PLL + w.set_vcopd(true); // Power down the VCO + w.set_postdivpd(true); // Power down the post divider + w.set_dsmpd(true); // Disable fractional mode + *w + }); + + // Short delay after powering down + cortex_m::asm::delay(10); - // Load VCO-related dividers before starting VCO + // 2. Configure reference divider first p.cs().write(|w| w.set_refdiv(config.refdiv as _)); + + // 3. Configure feedback divider p.fbdiv_int().write(|w| w.set_fbdiv_int(config.fbdiv)); - // Turn on PLL - let pwr = p.pwr().write(|w| { - w.set_dsmpd(true); // "nothing is achieved by setting this low" - w.set_pd(false); - w.set_vcopd(false); - w.set_postdivpd(true); + // 4. Power up PLL and VCO, but keep post divider powered down during initial lock + p.pwr().write(|w| { + w.set_pd(false); // Power up the PLL + w.set_vcopd(false); // Power up the VCO + w.set_postdivpd(true); // Keep post divider powered down during initial lock + w.set_dsmpd(true); // Disable fractional mode (simpler configuration) *w }); - // Wait for PLL to lock - while !p.cs().read().lock() {} + // 5. Wait for PLL to lock with a timeout + let mut timeout = 1_000_000; // Reasonable timeout value + while !p.cs().read().lock() { + timeout -= 1; + if timeout == 0 { + // PLL failed to lock, return 0 to indicate failure + return 0; + } + } - // Set post-dividers + // 6. Configure post dividers after PLL is locked p.prim().write(|w| { w.set_postdiv1(config.post_div1); w.set_postdiv2(config.post_div2); }); - // Turn on post divider - p.pwr().write(|w| { - *w = pwr; - w.set_postdivpd(false); + // 7. Enable the post divider output + p.pwr().modify(|w| { + w.set_postdivpd(false); // Power up post divider + *w }); + // Final delay to ensure everything is stable + cortex_m::asm::delay(100); + + // Calculate and return actual output frequency vco_freq / ((config.post_div1 * config.post_div2) as u32) } diff --git a/examples/rp/src/bin/overclock.rs b/examples/rp/src/bin/overclock.rs index 429fff1ac..db6c8f448 100644 --- a/examples/rp/src/bin/overclock.rs +++ b/examples/rp/src/bin/overclock.rs @@ -3,22 +3,18 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_rp::clocks::{clk_sys_freq, ClockConfig}; +use embassy_rp::clocks::{clk_sys_freq, ClockConfig, VoltageScale}; use embassy_rp::config::Config; use embassy_rp::gpio::{Level, Output}; use embassy_time::{Duration, Instant, Timer}; use {defmt_rtt as _, panic_probe as _}; -const COUNT_TO: i32 = 1_000_000; +const COUNT_TO: i64 = 10_000_000; #[embassy_executor::main] async fn main(_spawner: Spawner) -> ! { // Set up for clock frequency of 200 MHz - // We will need a clock config in the HAL config that supports this frequency - // The RP2040 can run at 200 MHz with a 12 MHz crystal - let config = Config::new(ClockConfig::crystal_freq(12_000_000, 200_000_000)); - - // Initialize the peripherals + let config = Config::new(ClockConfig::with_speed_mhz(200)); let p = embassy_rp::init(config); // Show CPU frequency for verification @@ -37,20 +33,19 @@ async fn main(_spawner: Spawner) -> ! { let start = Instant::now(); - // Count to COUNT_TO // This is a busy loop that will take some time to complete while counter < COUNT_TO { counter += 1; } - let elapsed = start - Instant::now(); + let elapsed = Instant::now() - start; // Report the elapsed time led.set_low(); info!( "At {}Mhz: Elapsed time to count to {}: {}ms", sys_freq / 1_000_000, - COUNT_TO, + counter, elapsed.as_millis() ); @@ -58,3 +53,14 @@ async fn main(_spawner: Spawner) -> ! { Timer::after(Duration::from_secs(2)).await; } } + +// let config = Config::new(ClockConfig::with_speed_mhz_test_voltage(125, Some(VoltageScale::V1_10))); +// let config = Config::default(); +// let config = Config::new(ClockConfig::with_speed_mhz_test_voltage_extended_delay( +// 200, // Standard 125MHz clock +// Some(VoltageScale::V1_15), // 1.15V voltage +// Some(1000), // 1000μs (1ms) stabilization delay - significantly longer than default +// )); +// Initialize the peripherals + +// let p = embassy_rp::init(Default::default()); //testing the bog standard -- cgit From 77e8bc9b28d6988b2703029679f290b351fc54a0 Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Tue, 29 Apr 2025 22:49:05 +0200 Subject: refactoring to have higher and lower level api --- embassy-rp/sdk examples | 1 + embassy-rp/src/clocks.rs | 546 ++++++++++++++++++++++++--------------- examples/rp/src/bin/overclock.rs | 13 +- 3 files changed, 352 insertions(+), 208 deletions(-) create mode 160000 embassy-rp/sdk examples diff --git a/embassy-rp/sdk examples b/embassy-rp/sdk examples new file mode 160000 index 000000000..ee68c78d0 --- /dev/null +++ b/embassy-rp/sdk examples @@ -0,0 +1 @@ +Subproject commit ee68c78d0afae2b69c03ae1a72bf5cc267a2d94c diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index b74e90f5e..1f5c27df1 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -1,4 +1,78 @@ -//! Clock configuration for the RP2040 +//! # Clock configuration for the RP2040 and RP235x microcontrollers. +//! +//! # Clock Configuration API +//! +//! This module provides both high-level convenience functions and low-level manual +//! configuration options for the RP2040 clock system. +//! +//! ## High-Level Convenience Functions +//! +//! For most users, these functions provide an easy way to configure clocks: +//! +//! - `ClockConfig::crystal(12_000_000)` - Default configuration with 12MHz crystal giving 125MHz system clock +//! - `ClockConfig::at_sys_frequency_mhz(200)` - Set system clock to a specific frequency with automatic voltage scaling +//! - `ClockConfig::with_external_crystal(16_000_000)` - Configure with a non-standard crystal frequency +//! +//! ## Manual Configuration +//! +//! For advanced users who need precise control: +//! +//! ```rust,ignore +//! // Start with default configuration and customize it +//! let mut config = ClockConfig::default(); +//! +//! // Set custom PLL parameters +//! config.xosc = Some(XoscConfig { +//! hz: 12_000_000, +//! sys_pll: Some(PllConfig { +//! refdiv: 1, +//! fbdiv: 200, +//! post_div1: 6, +//! post_div2: 2, +//! }), +//! // ... other fields +//! }); +//! +//! // Set voltage for overclocking +//! config.voltage_scale = Some(VoltageScale::V1_15); +//! ``` +//! +//! ## Voltage Scaling for Overclocking (RP2040 only) +//! +//! When overclocking beyond 133MHz, higher core voltages are needed: +//! +//! - Up to 133MHz: `VoltageScale::V1_10` (default) +//! - 133-200MHz: `VoltageScale::V1_15` +//! - Above 200MHz: `VoltageScale::V1_20` or higher +//! +//! The `at_sys_frequency_mhz()` function automatically sets appropriate voltages. +//! +//! ## Examples +//! +//! ### Standard 125MHz configuration +//! ```rust,ignore +//! let config = ClockConfig::crystal(12_000_000); +//! ``` +//! +//! Or using the default configuration: +//! ```rust,ignore +//! let config = ClockConfig::default(); +//! ``` +//! +//! ### Overclock to 200MHz +//! ```rust,ignore +//! let config = ClockConfig::at_sys_frequency_mhz(200); +//! ``` +//! +//! ### Manual configuration for advanced scenarios +//! ```rust,ignore +//! use embassy_rp::clocks::{ClockConfig, XoscConfig, PllConfig, VoltageScale}; +//! +//! // Start with defaults and customize +//! let mut config = ClockConfig::default(); +//! config.voltage_scale = Some(VoltageScale::V1_15); +//! // Set other parameters as needed... +//! ``` #[cfg(feature = "rp2040")] use core::arch::asm; @@ -18,6 +92,7 @@ use crate::{pac, reset, Peri}; // gpin is not usually safe to use during the boot init() call, so it won't // be very useful until we have runtime clock reconfiguration. once this // happens we can resurrect the commented-out gpin bits. + struct Clocks { xosc: AtomicU32, sys: AtomicU32, @@ -70,30 +145,58 @@ pub enum PeriClkSrc { } /// Core voltage scaling options for RP2040. -/// See RP2040 Datasheet, Table 189, VREG Register. +/// +/// The RP2040 voltage regulator can be configured for different output voltages. +/// Higher voltages allow for higher clock frequencies but increase power consumption. +/// +/// # Typical Use Cases +/// +/// - `V0_85` to `V1_05`: Power saving for lower frequencies (below 100MHz) +/// - `V1_10`: Default voltage, safe for standard 125MHz operation +/// - `V1_15`: Required for frequencies above 133MHz (e.g., 200MHz overclocking) +/// - `V1_20`: For more extreme overclocking (200MHz+) +/// - `V1_25` and `V1_30`: Highest voltage settings, use with caution +/// +/// # Overclocking Notes +/// +/// When overclocking: +/// - Frequencies up to 133MHz are typically stable at default voltage (`V1_10`) +/// - Frequencies from 133MHz to 200MHz generally require `V1_15` +/// - Frequencies above 200MHz typically require `V1_20` or higher +/// +/// # Power Consumption +/// +/// Higher voltages increase power consumption and heat generation. In battery-powered +/// applications, consider using lower voltages when maximum performance is not required. +/// +/// # Safety +/// +/// Increased voltage can reduce the lifespan of the chip if used for extended periods, +/// especially at `V1_25` and `V1_30`. These higher voltages should be used with +/// consideration of thermal management. #[cfg(feature = "rp2040")] #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(u8)] pub enum VoltageScale { - /// 0.85V + /// 0.85V - Lowest power consumption, suitable for low frequencies V0_85 = 0b0110, - /// 0.90V + /// 0.90V - Low power consumption V0_90 = 0b0111, - /// 0.95V + /// 0.95V - Low power consumption V0_95 = 0b1000, - /// 1.00V + /// 1.00V - Medium power consumption V1_00 = 0b1001, - /// 1.05V + /// 1.05V - Medium power consumption V1_05 = 0b1010, - /// 1.10V (Default) + /// 1.10V (Default) - Standard voltage for 125MHz operation V1_10 = 0b1011, - /// 1.15V + /// 1.15V - Required for frequencies above 133MHz V1_15 = 0b1100, - /// 1.20V + /// 1.20V - For higher overclocking (200MHz+) V1_20 = 0b1101, - /// 1.25V + /// 1.25V - High voltage, use with caution V1_25 = 0b1110, - /// 1.30V + /// 1.30V - Maximum voltage, use with extreme caution V1_30 = 0b1111, } @@ -143,122 +246,58 @@ pub struct ClockConfig { #[cfg(feature = "rp2040")] pub voltage_scale: Option, /// Voltage stabilization delay in microseconds. + /// If not set, appropriate defaults will be used based on voltage level. #[cfg(feature = "rp2040")] pub voltage_stabilization_delay_us: Option, // gpin0: Option<(u32, Gpin<'static, AnyPin>)>, // gpin1: Option<(u32, Gpin<'static, AnyPin>)>, } -impl ClockConfig { - /// Clock configuration derived from external crystal. - pub fn crystal(crystal_hz: u32) -> Self { +impl Default for ClockConfig { + /// Creates a minimal default configuration with safe values. + /// + /// This configuration uses the ring oscillator (ROSC) as the clock source + /// and sets minimal defaults that guarantee a working system. It's intended + /// as a starting point for manual configuration. + /// + /// Most users should use one of the more specific configuration functions: + /// - `ClockConfig::crystal()` - Standard configuration with external crystal + /// - `ClockConfig::rosc()` - Configuration using only the internal oscillator + /// - `ClockConfig::at_sys_frequency_mhz()` - Configuration for a specific system frequency + fn default() -> Self { Self { - rosc: Some(RoscConfig { - hz: 6_500_000, - range: RoscRange::Medium, - drive_strength: [0; 8], - div: 16, - }), - xosc: Some(XoscConfig { - hz: crystal_hz, - sys_pll: Some(PllConfig { - refdiv: 1, - fbdiv: 125, - #[cfg(feature = "rp2040")] - post_div1: 6, - #[cfg(feature = "_rp235x")] - post_div1: 5, - post_div2: 2, - }), - usb_pll: Some(PllConfig { - refdiv: 1, - fbdiv: 120, - post_div1: 6, - post_div2: 5, - }), - delay_multiplier: 128, - }), + rosc: None, + xosc: None, ref_clk: RefClkConfig { - src: RefClkSrc::Xosc, + src: RefClkSrc::Rosc, div: 1, }, sys_clk: SysClkConfig { - src: SysClkSrc::PllSys, + src: SysClkSrc::Rosc, div_int: 1, div_frac: 0, }, - peri_clk_src: Some(PeriClkSrc::Sys), - // CLK USB = PLL USB (48MHz) / 1 = 48MHz - usb_clk: Some(UsbClkConfig { - src: UsbClkSrc::PllUsb, - div: 1, - phase: 0, - }), - // CLK ADC = PLL USB (48MHZ) / 1 = 48MHz - adc_clk: Some(AdcClkConfig { - src: AdcClkSrc::PllUsb, - div: 1, - phase: 0, - }), - // CLK RTC = PLL USB (48MHz) / 1024 = 46875Hz + peri_clk_src: None, + usb_clk: None, + adc_clk: None, #[cfg(feature = "rp2040")] - rtc_clk: Some(RtcClkConfig { - src: RtcClkSrc::PllUsb, - div_int: 1024, - div_frac: 0, - phase: 0, - }), + rtc_clk: None, #[cfg(feature = "rp2040")] - voltage_scale: None, // Use hardware default (1.10V) + voltage_scale: None, #[cfg(feature = "rp2040")] voltage_stabilization_delay_us: None, // gpin0: None, // gpin1: None, } } +} - /// Clock configuration derived from external crystal, targeting a specific SYS clock frequency for RP2040. - /// - /// This function calculates the required PLL settings and core voltage based on the target frequency. - /// - /// # Arguments - /// - /// * `crystal_hz`: The frequency of the external crystal (e.g., 12_000_000 for 12MHz). - /// * `sys_freq_hz`: The target system clock frequency. - /// - /// # Panics - /// - /// Panics if the requested frequency is impossible to achieve with the given crystal, - /// or if the required voltage for the frequency is not supported or known. - /// - /// # Safety Notes (RP2040) +impl ClockConfig { + /// Clock configuration derived from external crystal. /// - /// * Frequencies > 133MHz require increased core voltage. - /// * This function automatically selects `VoltageScale::V1_15` for frequencies > 133MHz and <= 200MHz. - /// * Frequencies > 200MHz might require `VoltageScale::V1_20` or higher and are considered overclocking beyond datasheet recommendations. - /// This function will select `VoltageScale::V1_20` for frequencies > 200MHz, use with caution. - /// * Ensure your hardware supports the selected voltage and frequency. - #[cfg(feature = "rp2040")] - pub fn crystal_freq(crystal_hz: u32, sys_freq_hz: u32) -> Self { - // Determine required voltage based on target frequency - let voltage_scale = match sys_freq_hz { - 0..=133_000_000 => VoltageScale::V1_10, // Default voltage is sufficient - 133_000_001..=200_000_000 => VoltageScale::V1_15, // Requires 1.15V - _ => VoltageScale::V1_20, // Frequencies > 200MHz require at least 1.20V (Overclocking) - }; - - // Find suitable PLL parameters - let pll_params = match find_pll_params(crystal_hz, sys_freq_hz) { - Some(params) => params, - None => { - // If we can't find valid parameters for the requested frequency, - // fall back to safe defaults (125 MHz for RP2040) - let safe_freq = 125_000_000; // Safe default frequency - find_pll_params(crystal_hz, safe_freq) - .expect("Could not find valid PLL parameters even for safe default frequency") - } - }; - + /// This uses default settings for most parameters, suitable for typical use cases. + /// For manual control of PLL parameters, use `new_manual()` or modify the struct fields directly. + pub fn crystal(crystal_hz: u32) -> Self { Self { rosc: Some(RoscConfig { hz: 6_500_000, @@ -268,8 +307,15 @@ impl ClockConfig { }), xosc: Some(XoscConfig { hz: crystal_hz, - sys_pll: Some(pll_params), - // Keep USB PLL at 48MHz for compatibility + sys_pll: Some(PllConfig { + refdiv: 1, + fbdiv: 125, + #[cfg(feature = "rp2040")] + post_div1: 6, + #[cfg(feature = "_rp235x")] + post_div1: 5, + post_div2: 2, + }), usb_pll: Some(PllConfig { refdiv: 1, fbdiv: 120, @@ -288,23 +334,28 @@ impl ClockConfig { div_frac: 0, }, peri_clk_src: Some(PeriClkSrc::Sys), + // CLK USB = PLL USB (48MHz) / 1 = 48MHz usb_clk: Some(UsbClkConfig { src: UsbClkSrc::PllUsb, div: 1, phase: 0, }), + // CLK ADC = PLL USB (48MHZ) / 1 = 48MHz adc_clk: Some(AdcClkConfig { src: AdcClkSrc::PllUsb, div: 1, phase: 0, }), + // CLK RTC = PLL USB (48MHz) / 1024 = 46875Hz + #[cfg(feature = "rp2040")] rtc_clk: Some(RtcClkConfig { src: RtcClkSrc::PllUsb, div_int: 1024, div_frac: 0, phase: 0, }), - voltage_scale: Some(voltage_scale), + #[cfg(feature = "rp2040")] + voltage_scale: None, // Use hardware default (1.10V) #[cfg(feature = "rp2040")] voltage_stabilization_delay_us: None, // gpin0: None, @@ -366,22 +417,17 @@ impl ClockConfig { /// /// * `sys_freq_mhz` - The target system clock frequency in MHz /// - /// # Safety Notes - /// - /// * Frequencies > 133MHz require increased core voltage and are considered overclocking. - /// * Frequencies > 250MHz are extreme overclocking and may not be stable on all chips. - /// /// # Example /// /// ``` /// // Configure for standard 125MHz clock - /// let config = ClockConfig::with_speed_mhz(125); + /// let config = ClockConfig::at_sys_frequency_mhz(125); /// - /// // Overclock to 200MHz (requires higher voltage) - /// let config = ClockConfig::with_speed_mhz(200); + /// // Overclock to 200MHz + /// let config = ClockConfig::at_sys_frequency_mhz(200); /// ``` #[cfg(feature = "rp2040")] - pub fn with_speed_mhz(sys_freq_mhz: u32) -> Self { + pub fn at_sys_frequency_mhz(sys_freq_mhz: u32) -> Self { // For 125MHz, use exactly the same config as the default to avoid any differences if sys_freq_mhz == 125 { return Self::crystal(12_000_000); @@ -392,12 +438,7 @@ impl ClockConfig { const DEFAULT_CRYSTAL_HZ: u32 = 12_000_000; let sys_freq_hz = sys_freq_mhz * 1_000_000; - let mut config = Self::crystal_freq(DEFAULT_CRYSTAL_HZ, sys_freq_hz); - - // For frequencies above 200MHz, ensure we're using the highest voltage - if sys_freq_mhz > 200 { - config.voltage_scale = Some(VoltageScale::V1_20); - } + let config = Self::crystal_freq(DEFAULT_CRYSTAL_HZ, sys_freq_hz); config } @@ -412,11 +453,6 @@ impl ClockConfig { /// * `crystal_hz` - The frequency of the external crystal in Hz /// * `sys_freq_hz` - The target system clock frequency in Hz /// - /// # Safety Notes - /// - /// * Frequencies > 133MHz require increased core voltage and are considered overclocking. - /// * Frequencies > 250MHz are extreme overclocking and may not be stable on all chips. - /// /// # Example /// /// ``` @@ -428,58 +464,93 @@ impl ClockConfig { Self::crystal_freq(crystal_hz, sys_freq_hz) } - #[cfg(feature = "rp2040")] - pub fn with_speed_mhz_test_voltage(sys_freq_mhz: u32, voltage: Option) -> Self { - // Create a config with the requested frequency - let sys_freq_hz = sys_freq_mhz * 1_000_000; - let mut config = Self::crystal_freq(12_000_000, sys_freq_hz); - - // Override the voltage setting - config.voltage_scale = voltage; - - // For debugging - // println!("Debug: Setting freq to {}MHz with voltage {:?}", sys_freq_mhz, voltage); - - config - } - - /// Similar to `with_speed_mhz_test_voltage` but with an extended voltage stabilization delay. + /// Configure clocks derived from an external crystal with specific system frequency. /// - /// This function is useful for testing voltage stability issues where the default delay - /// may not be sufficient for the voltage regulator to fully stabilize. + /// This function calculates optimal PLL parameters to achieve the requested system + /// frequency from the given crystal frequency. It's used internally by higher-level + /// configuration functions. /// /// # Arguments /// - /// * `sys_freq_mhz` - The target system clock frequency in MHz - /// * `voltage` - The desired voltage scale setting - /// * `stabilization_delay_us` - Voltage stabilization delay in microseconds (default: 500μs) + /// * `crystal_hz` - The frequency of the external crystal in Hz + /// * `sys_freq_hz` - The desired system clock frequency in Hz + /// + /// # Returns + /// + /// A ClockConfig configured to achieve the requested system frequency using the + /// specified crystal, or panic if no valid parameters can be found. #[cfg(feature = "rp2040")] - pub fn with_speed_mhz_test_voltage_extended_delay( - sys_freq_mhz: u32, - voltage: Option, - stabilization_delay_us: Option, - ) -> Self { - // Create a config with the requested frequency - let sys_freq_hz = sys_freq_mhz * 1_000_000; - let mut config = Self::crystal_freq(12_000_000, sys_freq_hz); + fn crystal_freq(crystal_hz: u32, sys_freq_hz: u32) -> Self { + // Find optimal PLL parameters for the requested frequency + let sys_pll_params = find_pll_params(crystal_hz, sys_freq_hz) + .unwrap_or_else(|| panic!("Could not find valid PLL parameters for system clock")); - // Override the voltage setting - config.voltage_scale = voltage; + // Set the voltage scale based on the target frequency + // Higher frequencies require higher voltage + let voltage_scale = match sys_freq_hz { + freq if freq > 200_000_000 => Some(VoltageScale::V1_20), + freq if freq > 133_000_000 => Some(VoltageScale::V1_15), + _ => None, // Use default voltage (V1_10) + }; - // Add a custom voltage stabilization delay - config.voltage_stabilization_delay_us = stabilization_delay_us; + // For USB PLL, we always want 48MHz for USB + let usb_pll_params = if crystal_hz == 12_000_000 { + // For standard 12MHz crystal, use the default parameters + PllConfig { + refdiv: 1, + fbdiv: 120, + post_div1: 6, + post_div2: 5, + } + } else { + // For other crystals, calculate parameters to get 48MHz + find_pll_params(crystal_hz, 48_000_000) + .unwrap_or_else(|| panic!("Could not find valid PLL parameters for USB clock")) + }; - config + Self { + rosc: Some(RoscConfig { + hz: 6_500_000, + range: RoscRange::Medium, + drive_strength: [0; 8], + div: 16, + }), + xosc: Some(XoscConfig { + hz: crystal_hz, + sys_pll: Some(sys_pll_params), + usb_pll: Some(usb_pll_params), + delay_multiplier: 128, + }), + ref_clk: RefClkConfig { + src: RefClkSrc::Xosc, + div: 1, + }, + sys_clk: SysClkConfig { + src: SysClkSrc::PllSys, + div_int: 1, + div_frac: 0, + }, + peri_clk_src: Some(PeriClkSrc::Sys), + usb_clk: Some(UsbClkConfig { + src: UsbClkSrc::PllUsb, + div: 1, + phase: 0, + }), + adc_clk: Some(AdcClkConfig { + src: AdcClkSrc::PllUsb, + div: 1, + phase: 0, + }), + rtc_clk: Some(RtcClkConfig { + src: RtcClkSrc::PllUsb, + div_int: 1024, + div_frac: 0, + phase: 0, + }), + voltage_scale, + voltage_stabilization_delay_us: None, + } } - // pub fn bind_gpin(&mut self, gpin: Gpin<'static, P>, hz: u32) { - // match P::NR { - // 0 => self.gpin0 = Some((hz, gpin.into())), - // 1 => self.gpin1 = Some((hz, gpin.into())), - // _ => unreachable!(), - // } - // // pin is now provisionally bound. if the config is applied it must be forgotten, - // // or Gpin::drop will deconfigure the clock input. - // } } /// ROSC freq range. @@ -525,6 +596,30 @@ pub struct XoscConfig { } /// PLL configuration. +/// +/// This struct defines the parameters used to configure the Phase-Locked Loop (PLL) +/// in the RP2040. The parameters follow the definitions from the RP2040 datasheet, +/// section 2.18.3. +/// +/// # Parameters +/// +/// * `refdiv` - Reference divider (1-63) +/// * `fbdiv` - VCO feedback divider (16-320) +/// * `post_div1` - First post divider (1-7) +/// * `post_div2` - Second post divider (1-7) - must be less than or equal to post_div1 +/// +/// # Constraints +/// +/// * VCO frequency (input_hz / refdiv * fbdiv) must be between 750MHz and 1800MHz +/// * post_div2 must be less than or equal to post_div1 +/// +/// # Calculation +/// +/// The output frequency of the PLL is calculated as: +/// +/// `output_hz = (input_hz / refdiv * fbdiv) / (post_div1 * post_div2)` +/// +/// Where input_hz is typically the crystal frequency (e.g., 12MHz). #[derive(Clone, Copy, Debug)] pub struct PllConfig { /// Reference divisor. @@ -537,6 +632,50 @@ pub struct PllConfig { pub post_div2: u8, } +impl PllConfig { + /// Calculate the output frequency for this PLL configuration + /// given an input frequency. + pub fn output_frequency(&self, input_hz: u32) -> u32 { + let ref_freq = input_hz / self.refdiv as u32; + let vco_freq = ref_freq * self.fbdiv as u32; + vco_freq / ((self.post_div1 * self.post_div2) as u32) + } + + /// Check if this PLL configuration is valid for the given input frequency. + pub fn is_valid(&self, input_hz: u32) -> bool { + // Check divisor constraints + if self.refdiv < 1 || self.refdiv > 63 { + return false; + } + if self.fbdiv < 16 || self.fbdiv > 320 { + return false; + } + if self.post_div1 < 1 || self.post_div1 > 7 { + return false; + } + if self.post_div2 < 1 || self.post_div2 > 7 { + return false; + } + if self.post_div2 > self.post_div1 { + return false; + } + + // Calculate reference frequency + let ref_freq = input_hz / self.refdiv as u32; + + // Check reference frequency range + if ref_freq < 5_000_000 || ref_freq > 800_000_000 { + return false; + } + + // Calculate VCO frequency + let vco_freq = ref_freq * self.fbdiv as u32; + + // Check VCO frequency range + vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000 + } +} + /// Reference clock config. pub struct RefClkConfig { /// Reference clock source. @@ -683,6 +822,35 @@ pub struct RtcClkConfig { /// Find valid PLL parameters (refdiv, fbdiv, post_div1, post_div2) for a target output frequency /// based on the input frequency. /// +/// This function searches for the best PLL configuration to achieve the requested target frequency +/// while staying within the VCO frequency range of 750MHz to 1800MHz. It prioritizes stability +/// over exact frequency matching by using larger divisors where possible. +/// +/// # Parameters +/// +/// * `input_hz`: The input frequency in Hz (typically the crystal frequency, e.g. 12MHz) +/// * `target_hz`: The desired output frequency in Hz (e.g. 125MHz for standard RP2040 operation) +/// +/// # Returns +/// +/// * `Some(PllConfig)` if valid parameters were found +/// * `None` if no valid parameters could be found for the requested combination +/// +/// # Algorithm +/// +/// 1. Set reference divider to 1 (fixed for simplicity) +/// 2. Try different feedback divisors (fbdiv) starting from highest to lowest +/// 3. For each fbdiv value, check if the resulting VCO frequency is valid (750-1800MHz) +/// 4. Find post-divider combinations that give the exact requested frequency +/// 5. If no exact match, return the closest approximation +/// +/// # Example +/// +/// ``` +/// // Find parameters for 133MHz system clock from 12MHz crystal +/// let pll_params = find_pll_params(12_000_000, 133_000_000).unwrap(); +/// ``` +/// /// Similar to the Pico SDK's parameter selection approach, prioritizing stability. /// See RP2040 Datasheet section 2.16.3. Reference Clock (ref) and 2.18.3. PLL #[cfg(feature = "rp2040")] @@ -766,43 +934,6 @@ fn find_pll_params(input_hz: u32, target_hz: u32) -> Option { best_config } -#[cfg(feature = "rp2040")] -pub fn compare_pll_params() { - // Parameters from default configuration - let default_params = PllConfig { - refdiv: 1, - fbdiv: 125, - post_div1: 6, - post_div2: 2, - }; - - // Calculate parameters using our find_pll_params function - let crystal_hz = 12_000_000; - let target_hz = 125_000_000; - let calculated_params = find_pll_params(crystal_hz, target_hz).expect("Failed to find PLL parameters"); - - // Check if they're identical - let params_match = default_params.refdiv == calculated_params.refdiv - && default_params.fbdiv == calculated_params.fbdiv - && default_params.post_div1 == calculated_params.post_div1 - && default_params.post_div2 == calculated_params.post_div2; - - // Here we'd normally print results, but without a console we'll just - // use this for debugging in our IDE - let _default_output_freq = crystal_hz / default_params.refdiv as u32 * default_params.fbdiv as u32 - / (default_params.post_div1 * default_params.post_div2) as u32; - - let _calculated_output_freq = crystal_hz / calculated_params.refdiv as u32 * calculated_params.fbdiv as u32 - / (calculated_params.post_div1 * calculated_params.post_div2) as u32; - - // Parameters: default vs calculated - // refdiv: 1 vs {calculated_params.refdiv} - // fbdiv: 125 vs {calculated_params.fbdiv} - // post_div1: 6 vs {calculated_params.post_div1} - // post_div2: 2 vs {calculated_params.post_div2} - // params_match: {params_match} -} - /// safety: must be called exactly once at bootup pub(crate) unsafe fn init(config: ClockConfig) { // Reset everything except: @@ -1603,6 +1734,7 @@ impl rand_core::RngCore for RoscRng { dest.fill_with(Self::next_u8) } } + /// Enter the `DORMANT` sleep state. This will stop *all* internal clocks /// and can only be exited through resets, dormant-wake GPIO interrupts, /// and RTC interrupts. If RTC is clocked from an internal clock source diff --git a/examples/rp/src/bin/overclock.rs b/examples/rp/src/bin/overclock.rs index db6c8f448..9027f1516 100644 --- a/examples/rp/src/bin/overclock.rs +++ b/examples/rp/src/bin/overclock.rs @@ -14,7 +14,18 @@ const COUNT_TO: i64 = 10_000_000; #[embassy_executor::main] async fn main(_spawner: Spawner) -> ! { // Set up for clock frequency of 200 MHz - let config = Config::new(ClockConfig::with_speed_mhz(200)); + // This will set all the necessary defaults including slightly raised voltage + // See embassy_rp::clocks::ClockConfig for more options, including full manual control + let config = Config::new(ClockConfig::at_sys_frequency_mhz(200)); + + // Show the voltage scale and brownout-detection for verification + info!("System core voltage: {}", Debug2Format(&config.clocks.voltage_scale)); + // info!( + // "Brownout detection: {}", + // Debug2Format(&config.clocks.brownout_detection) + // ); + + // Initialize the peripherals let p = embassy_rp::init(config); // Show CPU frequency for verification -- cgit From d44b94523576d3f8c1f586811f600eb3ba223606 Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Tue, 29 Apr 2025 22:49:53 +0200 Subject: Remove unused embassy-rp/sdk examples subproject --- embassy-rp/sdk examples | 1 - 1 file changed, 1 deletion(-) delete mode 160000 embassy-rp/sdk examples diff --git a/embassy-rp/sdk examples b/embassy-rp/sdk examples deleted file mode 160000 index ee68c78d0..000000000 --- a/embassy-rp/sdk examples +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ee68c78d0afae2b69c03ae1a72bf5cc267a2d94c -- cgit From 22b5f73811a7cc0dbca920e02b5d001d252d344c Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Thu, 1 May 2025 00:11:56 +0200 Subject: add manual overclock example, finalize API, cleanup --- embassy-rp/src/clocks.rs | 268 +++++++++++++++++--------------- examples/rp/src/bin/overclock.rs | 24 +-- examples/rp/src/bin/overclock_manual.rs | 81 ++++++++++ 3 files changed, 227 insertions(+), 146 deletions(-) create mode 100644 examples/rp/src/bin/overclock_manual.rs diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index 1f5c27df1..86c172879 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -147,56 +147,30 @@ pub enum PeriClkSrc { /// Core voltage scaling options for RP2040. /// /// The RP2040 voltage regulator can be configured for different output voltages. -/// Higher voltages allow for higher clock frequencies but increase power consumption. -/// -/// # Typical Use Cases -/// -/// - `V0_85` to `V1_05`: Power saving for lower frequencies (below 100MHz) -/// - `V1_10`: Default voltage, safe for standard 125MHz operation -/// - `V1_15`: Required for frequencies above 133MHz (e.g., 200MHz overclocking) -/// - `V1_20`: For more extreme overclocking (200MHz+) -/// - `V1_25` and `V1_30`: Highest voltage settings, use with caution -/// -/// # Overclocking Notes -/// -/// When overclocking: -/// - Frequencies up to 133MHz are typically stable at default voltage (`V1_10`) -/// - Frequencies from 133MHz to 200MHz generally require `V1_15` -/// - Frequencies above 200MHz typically require `V1_20` or higher -/// -/// # Power Consumption -/// -/// Higher voltages increase power consumption and heat generation. In battery-powered -/// applications, consider using lower voltages when maximum performance is not required. -/// -/// # Safety -/// -/// Increased voltage can reduce the lifespan of the chip if used for extended periods, -/// especially at `V1_25` and `V1_30`. These higher voltages should be used with -/// consideration of thermal management. +/// Higher voltages allow for higher clock frequencies but increase power consumption and heat. #[cfg(feature = "rp2040")] #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(u8)] pub enum VoltageScale { - /// 0.85V - Lowest power consumption, suitable for low frequencies + /// 0.85V V0_85 = 0b0110, - /// 0.90V - Low power consumption + /// 0.90V V0_90 = 0b0111, - /// 0.95V - Low power consumption + /// 0.95V V0_95 = 0b1000, - /// 1.00V - Medium power consumption + /// 1.00V V1_00 = 0b1001, - /// 1.05V - Medium power consumption + /// 1.05V V1_05 = 0b1010, - /// 1.10V (Default) - Standard voltage for 125MHz operation + /// 1.10V V1_10 = 0b1011, - /// 1.15V - Required for frequencies above 133MHz + /// 1.15V V1_15 = 0b1100, - /// 1.20V - For higher overclocking (200MHz+) + /// 1.20V V1_20 = 0b1101, - /// 1.25V - High voltage, use with caution + /// 1.25V V1_25 = 0b1110, - /// 1.30V - Maximum voltage, use with extreme caution + /// 1.30V V1_30 = 0b1111, } @@ -204,10 +178,8 @@ pub enum VoltageScale { impl VoltageScale { /// Get the recommended Brown-Out Detection (BOD) setting for this voltage. /// Sets the BOD threshold to approximately 90% of the core voltage. - /// See RP2040 Datasheet, Table 190, BOD Register fn recommended_bod(self) -> u8 { match self { - // ~90% of voltage setting based on Table 190 values VoltageScale::V0_85 => 0b0111, // 0.774V (~91% of 0.85V) VoltageScale::V0_90 => 0b1000, // 0.817V (~91% of 0.90V) VoltageScale::V0_95 => 0b1001, // 0.860V (~91% of 0.95V) @@ -246,7 +218,7 @@ pub struct ClockConfig { #[cfg(feature = "rp2040")] pub voltage_scale: Option, /// Voltage stabilization delay in microseconds. - /// If not set, appropriate defaults will be used based on voltage level. + /// If not set, defaults will be used based on voltage level. #[cfg(feature = "rp2040")] pub voltage_stabilization_delay_us: Option, // gpin0: Option<(u32, Gpin<'static, AnyPin>)>, @@ -409,7 +381,7 @@ impl ClockConfig { /// Configure the system clock to a specific frequency in MHz. /// - /// This is a more user-friendly way to configure the system clock, similar to + /// This is a user-friendly way to configure the system clock, similar to /// the Pico SDK's approach. It automatically handles voltage scaling based on the /// requested frequency and uses the standard 12MHz crystal found on most RP2040 boards. /// @@ -420,9 +392,6 @@ impl ClockConfig { /// # Example /// /// ``` - /// // Configure for standard 125MHz clock - /// let config = ClockConfig::at_sys_frequency_mhz(125); - /// /// // Overclock to 200MHz /// let config = ClockConfig::at_sys_frequency_mhz(200); /// ``` @@ -551,6 +520,98 @@ impl ClockConfig { voltage_stabilization_delay_us: None, } } + + /// Configure with manual PLL settings for full control over system clock + /// + /// This method provides a simple way to configure the system with custom PLL parameters + /// without needing to understand the full nested configuration structure. + /// + /// # Arguments + /// + /// * `xosc_hz` - The frequency of the external crystal in Hz + /// * `pll_config` - The PLL configuration parameters to achieve desired frequency + /// * `voltage_scale` - Optional voltage scaling for overclocking (required for >133MHz) + /// + /// # Returns + /// + /// A ClockConfig configured with the specified PLL parameters + /// + /// # Example + /// + /// ```rust,ignore + /// // Configure for 200MHz operation + /// let config = Config::default(); + /// config.clocks = ClockConfig::manual_pll( + /// 12_000_000, + /// PllConfig { + /// refdiv: 1, // Reference divider (12 MHz / 1 = 12 MHz) + /// fbdiv: 100, // Feedback divider (12 MHz * 100 = 1200 MHz VCO) + /// post_div1: 3, // First post divider (1200 MHz / 3 = 400 MHz) + /// post_div2: 2, // Second post divider (400 MHz / 2 = 200 MHz) + /// }, + /// Some(VoltageScale::V1_15) + /// ); + /// ``` + #[cfg(feature = "rp2040")] + pub fn manual_pll(xosc_hz: u32, pll_config: PllConfig, voltage_scale: Option) -> Self { + // Calculate the actual output frequency for documentation + // let ref_freq = xosc_hz / pll_config.refdiv as u32; + // let vco_freq = ref_freq * pll_config.fbdiv as u32; + // let sys_freq = vco_freq / ((pll_config.post_div1 * pll_config.post_div2) as u32); + + // Validate PLL parameters + assert!(pll_config.is_valid(xosc_hz), "Invalid PLL parameters"); + + let mut config = Self::default(); + + config.xosc = Some(XoscConfig { + hz: xosc_hz, + sys_pll: Some(pll_config), + usb_pll: Some(PllConfig { + refdiv: 1, + fbdiv: 120, + post_div1: 6, + post_div2: 5, + }), + delay_multiplier: 128, + }); + + config.ref_clk = RefClkConfig { + src: RefClkSrc::Xosc, + div: 1, + }; + + config.sys_clk = SysClkConfig { + src: SysClkSrc::PllSys, + div_int: 1, + div_frac: 0, + }; + + config.voltage_scale = voltage_scale; + config.peri_clk_src = Some(PeriClkSrc::Sys); + + // Set reasonable defaults for other clocks + config.usb_clk = Some(UsbClkConfig { + src: UsbClkSrc::PllUsb, + div: 1, + phase: 0, + }); + + config.adc_clk = Some(AdcClkConfig { + src: AdcClkSrc::PllUsb, + div: 1, + phase: 0, + }); + + config.rtc_clk = Some(RtcClkConfig { + src: RtcClkSrc::PllUsb, + div_int: 1024, + div_frac: 0, + phase: 0, + }); + + config + } } /// ROSC freq range. @@ -596,30 +657,6 @@ pub struct XoscConfig { } /// PLL configuration. -/// -/// This struct defines the parameters used to configure the Phase-Locked Loop (PLL) -/// in the RP2040. The parameters follow the definitions from the RP2040 datasheet, -/// section 2.18.3. -/// -/// # Parameters -/// -/// * `refdiv` - Reference divider (1-63) -/// * `fbdiv` - VCO feedback divider (16-320) -/// * `post_div1` - First post divider (1-7) -/// * `post_div2` - Second post divider (1-7) - must be less than or equal to post_div1 -/// -/// # Constraints -/// -/// * VCO frequency (input_hz / refdiv * fbdiv) must be between 750MHz and 1800MHz -/// * post_div2 must be less than or equal to post_div1 -/// -/// # Calculation -/// -/// The output frequency of the PLL is calculated as: -/// -/// `output_hz = (input_hz / refdiv * fbdiv) / (post_div1 * post_div2)` -/// -/// Where input_hz is typically the crystal frequency (e.g., 12MHz). #[derive(Clone, Copy, Debug)] pub struct PllConfig { /// Reference divisor. @@ -836,35 +873,17 @@ pub struct RtcClkConfig { /// * `Some(PllConfig)` if valid parameters were found /// * `None` if no valid parameters could be found for the requested combination /// -/// # Algorithm -/// -/// 1. Set reference divider to 1 (fixed for simplicity) -/// 2. Try different feedback divisors (fbdiv) starting from highest to lowest -/// 3. For each fbdiv value, check if the resulting VCO frequency is valid (750-1800MHz) -/// 4. Find post-divider combinations that give the exact requested frequency -/// 5. If no exact match, return the closest approximation -/// /// # Example /// /// ``` /// // Find parameters for 133MHz system clock from 12MHz crystal /// let pll_params = find_pll_params(12_000_000, 133_000_000).unwrap(); /// ``` -/// -/// Similar to the Pico SDK's parameter selection approach, prioritizing stability. -/// See RP2040 Datasheet section 2.16.3. Reference Clock (ref) and 2.18.3. PLL #[cfg(feature = "rp2040")] fn find_pll_params(input_hz: u32, target_hz: u32) -> Option { // Fixed reference divider for system PLL const PLL_SYS_REFDIV: u8 = 1; - // Constraints from datasheet: - // REFDIV: 1..=63 - // FBDIV: 16..=320 - // POSTDIV1: 1..=7 - // POSTDIV2: 1..=7 (must be <= POSTDIV1) - // VCO frequency (input_hz / refdiv * fbdiv): 750MHz ..= 1800MHz - // Calculate reference frequency let reference_freq = input_hz / PLL_SYS_REFDIV as u32; @@ -969,8 +988,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { #[cfg(feature = "_rp235x")] while c.clk_ref_selected().read() != pac::clocks::regs::ClkRefSelected(1) {} - // Set Core Voltage (RP2040 only) BEFORE doing anything with PLLs - // This is critical for overclocking - must be done before PLL setup + // Set Core Voltage (RP2040 only), if we have config for it and we're not using the default #[cfg(feature = "rp2040")] if let Some(voltage) = config.voltage_scale { let vreg = pac::VREG_AND_CHIP_RESET; @@ -978,17 +996,15 @@ pub(crate) unsafe fn init(config: ClockConfig) { let target_vsel = voltage as u8; if target_vsel != current_vsel { - // IMPORTANT: Use modify() instead of write() to preserve the HIZ and EN bits - // This is critical - otherwise we might disable the regulator when changing voltage + // Use modify() instead of write() to preserve the HIZ and EN bits - otherwise we will disable the regulator when changing voltage vreg.vreg().modify(|w| w.set_vsel(target_vsel)); - // For higher voltage settings (overclocking), we need a longer stabilization time - // Default to 1000 µs (1ms) like the SDK, but allow user override + // Wait for the voltage to stabilize. Use the provided delay or default based on voltage let settling_time_us = config.voltage_stabilization_delay_us.unwrap_or_else(|| { match voltage { - VoltageScale::V1_15 => 1000, // 1ms for 1.15V (matches SDK default) + VoltageScale::V1_15 => 1000, // 1ms for 1.15V VoltageScale::V1_20 | VoltageScale::V1_25 | VoltageScale::V1_30 => 2000, // 2ms for higher voltages - _ => 500, // 500 µs for standard voltages + _ => 0, // no delay for all others } }); @@ -1001,7 +1017,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { // Wait for voltage to stabilize cortex_m::asm::delay(delay_cycles); - // Only NOW set the BOD level after voltage has stabilized + // Only now set the BOD level after voltage has stabilized vreg.bod().write(|w| w.set_vsel(voltage.recommended_bod())); } } @@ -1026,9 +1042,9 @@ pub(crate) unsafe fn init(config: ClockConfig) { // SETUP TEMPORARY STABLE CLOCKS FIRST // Configure USB PLL for our stable temporary clock // This follows the SDK's approach of using USB PLL as a stable intermediate clock - let usb_pll_freq = match &config.xosc { + let pll_usb_freq = match &config.xosc { Some(config) => match &config.usb_pll { - Some(usb_pll_config) => { + Some(pll_usb_config) => { // Reset USB PLL let mut peris = reset::Peripherals(0); peris.set_pll_usb(true); @@ -1036,7 +1052,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { reset::unreset_wait(peris); // Configure the USB PLL - this should give us 48MHz - let usb_pll_freq = configure_pll(pac::PLL_USB, xosc_freq, *usb_pll_config); + let usb_pll_freq = configure_pll(pac::PLL_USB, xosc_freq, *pll_usb_config); CLOCKS.pll_usb.store(usb_pll_freq, Ordering::Relaxed); usb_pll_freq } @@ -1122,7 +1138,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { let (src, aux, freq) = match config.sys_clk.src { SysClkSrc::Ref => (Src::CLK_REF, Aux::CLKSRC_PLL_SYS, clk_ref_freq), SysClkSrc::PllSys => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_SYS, pll_sys_freq), - SysClkSrc::PllUsb => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_USB, usb_pll_freq), + SysClkSrc::PllUsb => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq), SysClkSrc::Rosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::ROSC_CLKSRC, rosc_freq), SysClkSrc::Xosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::XOSC_CLKSRC, xosc_freq), // SysClkSrc::Gpin0 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN0, gpin0_freq), @@ -1185,7 +1201,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { let peri_freq = match src { PeriClkSrc::Sys => clk_sys_freq, PeriClkSrc::PllSys => pll_sys_freq, - PeriClkSrc::PllUsb => usb_pll_freq, + PeriClkSrc::PllUsb => pll_usb_freq, PeriClkSrc::Rosc => rosc_freq, PeriClkSrc::Xosc => xosc_freq, // PeriClkSrc::Gpin0 => gpin0_freq, @@ -1210,7 +1226,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { w.set_auxsrc(ClkUsbCtrlAuxsrc::from_bits(conf.src as _)); }); let usb_freq = match conf.src { - UsbClkSrc::PllUsb => usb_pll_freq, + UsbClkSrc::PllUsb => pll_usb_freq, UsbClkSrc::PllSys => pll_sys_freq, UsbClkSrc::Rosc => rosc_freq, UsbClkSrc::Xosc => xosc_freq, @@ -1234,7 +1250,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { w.set_auxsrc(ClkAdcCtrlAuxsrc::from_bits(conf.src as _)); }); let adc_in_freq = match conf.src { - AdcClkSrc::PllUsb => usb_pll_freq, + AdcClkSrc::PllUsb => pll_usb_freq, AdcClkSrc::PllSys => pll_sys_freq, AdcClkSrc::Rosc => rosc_freq, AdcClkSrc::Xosc => xosc_freq, @@ -1262,7 +1278,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { w.set_auxsrc(ClkRtcCtrlAuxsrc::from_bits(conf.src as _)); }); let rtc_in_freq = match conf.src { - RtcClkSrc::PllUsb => usb_pll_freq, + RtcClkSrc::PllUsb => pll_usb_freq, RtcClkSrc::PllSys => pll_sys_freq, RtcClkSrc::Rosc => rosc_freq, RtcClkSrc::Xosc => xosc_freq, @@ -1390,40 +1406,36 @@ fn start_xosc(crystal_hz: u32, delay_multiplier: u32) { while !pac::XOSC.status().read().stable() {} } +/// PLL (Phase-Locked Loop) configuration #[inline(always)] fn configure_pll(p: pac::pll::Pll, input_freq: u32, config: PllConfig) -> u32 { // Calculate reference frequency let ref_freq = input_freq / config.refdiv as u32; // Validate PLL parameters - assert!( - config.fbdiv >= 16 && config.fbdiv <= 320, - "fbdiv must be between 16 and 320" - ); - assert!( - config.post_div1 >= 1 && config.post_div1 <= 7, - "post_div1 must be between 1 and 7" - ); - assert!( - config.post_div2 >= 1 && config.post_div2 <= 7, - "post_div2 must be between 1 and 7" - ); - assert!(config.post_div2 <= config.post_div1, "post_div2 must be <= post_div1"); - assert!( - config.refdiv >= 1 && config.refdiv <= 63, - "refdiv must be between 1 and 63" - ); - assert!( - ref_freq >= 5_000_000 && ref_freq <= 800_000_000, - "ref_freq must be between 5MHz and 800MHz" - ); + // Feedback divider (FBDIV) must be between 16 and 320 + assert!(config.fbdiv >= 16 && config.fbdiv <= 320); + + // Post divider 1 (POSTDIV1) must be between 1 and 7 + assert!(config.post_div1 >= 1 && config.post_div1 <= 7); + + // Post divider 2 (POSTDIV2) must be between 1 and 7 + assert!(config.post_div2 >= 1 && config.post_div2 <= 7); + + // Post divider 2 (POSTDIV2) must be less than or equal to post divider 1 (POSTDIV1) + assert!(config.post_div2 <= config.post_div1); + + // Reference divider (REFDIV) must be between 1 and 63 + assert!(config.refdiv >= 1 && config.refdiv <= 63); + + // Reference frequency (REF_FREQ) must be between 5MHz and 800MHz + assert!(ref_freq >= 5_000_000 && ref_freq <= 800_000_000); // Calculate VCO frequency let vco_freq = ref_freq.saturating_mul(config.fbdiv as u32); - assert!( - vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000, - "VCO frequency must be between 750MHz and 1800MHz" - ); + + // VCO (Voltage Controlled Oscillator) frequency must be between 750MHz and 1800MHz + assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); // We follow the SDK's approach to PLL configuration which is: // 1. Power down PLL diff --git a/examples/rp/src/bin/overclock.rs b/examples/rp/src/bin/overclock.rs index 9027f1516..e3ac77340 100644 --- a/examples/rp/src/bin/overclock.rs +++ b/examples/rp/src/bin/overclock.rs @@ -1,9 +1,13 @@ +//! # Overclocking the RP2040 to 200 MHz +//! +//! This example demonstrates how to configure the RP2040 to run at 200 MHz using a higher level API. + #![no_std] #![no_main] use defmt::*; use embassy_executor::Spawner; -use embassy_rp::clocks::{clk_sys_freq, ClockConfig, VoltageScale}; +use embassy_rp::clocks::{clk_sys_freq, ClockConfig}; use embassy_rp::config::Config; use embassy_rp::gpio::{Level, Output}; use embassy_time::{Duration, Instant, Timer}; @@ -15,15 +19,10 @@ const COUNT_TO: i64 = 10_000_000; async fn main(_spawner: Spawner) -> ! { // Set up for clock frequency of 200 MHz // This will set all the necessary defaults including slightly raised voltage - // See embassy_rp::clocks::ClockConfig for more options, including full manual control let config = Config::new(ClockConfig::at_sys_frequency_mhz(200)); - // Show the voltage scale and brownout-detection for verification + // Show the voltage scale for verification info!("System core voltage: {}", Debug2Format(&config.clocks.voltage_scale)); - // info!( - // "Brownout detection: {}", - // Debug2Format(&config.clocks.brownout_detection) - // ); // Initialize the peripherals let p = embassy_rp::init(config); @@ -64,14 +63,3 @@ async fn main(_spawner: Spawner) -> ! { Timer::after(Duration::from_secs(2)).await; } } - -// let config = Config::new(ClockConfig::with_speed_mhz_test_voltage(125, Some(VoltageScale::V1_10))); -// let config = Config::default(); -// let config = Config::new(ClockConfig::with_speed_mhz_test_voltage_extended_delay( -// 200, // Standard 125MHz clock -// Some(VoltageScale::V1_15), // 1.15V voltage -// Some(1000), // 1000μs (1ms) stabilization delay - significantly longer than default -// )); -// Initialize the peripherals - -// let p = embassy_rp::init(Default::default()); //testing the bog standard diff --git a/examples/rp/src/bin/overclock_manual.rs b/examples/rp/src/bin/overclock_manual.rs new file mode 100644 index 000000000..ad6abf0e7 --- /dev/null +++ b/examples/rp/src/bin/overclock_manual.rs @@ -0,0 +1,81 @@ +//! # Overclocking the RP2040 to 200 MHz manually +//! +//! This example demonstrates how to manually configure the RP2040 to run at 200 MHz. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::clocks; +use embassy_rp::clocks::{ClockConfig, PllConfig, VoltageScale}; +use embassy_rp::config::Config; +use embassy_rp::gpio::{Level, Output}; +use embassy_time::{Duration, Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +const COUNT_TO: i64 = 10_000_000; + +/// Configure the RP2040 for 200 MHz operation by manually specifying +/// all the required parameters instead of using higher-level APIs. +fn configure_manual_overclock() -> Config { + // Set the PLL configuration manually, starting from default values + let mut config = Config::default(); + + // Set the system clock to 200 MHz using a PLL with a reference frequency of 12 MHz + config.clocks = ClockConfig::manual_pll( + 12_000_000, + PllConfig { + refdiv: 1, + fbdiv: 100, + post_div1: 3, + post_div2: 2, + }, + // For 200 MHz, we need a voltage scale of 1.15V + Some(VoltageScale::V1_15), + ); + + config +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + // Initialize with our manual overclock configuration + let p = embassy_rp::init(configure_manual_overclock()); + + // Verify the actual system clock frequency + let sys_freq = clocks::clk_sys_freq(); + info!("System clock frequency: {} MHz", sys_freq / 1_000_000); + + // LED to indicate the system is running + let mut led = Output::new(p.PIN_25, Level::Low); + + loop { + // Reset the counter at the start of measurement period + let mut counter = 0; + + // Turn LED on while counting + led.set_high(); + + let start = Instant::now(); + + // This is a busy loop that will take some time to complete + while counter < COUNT_TO { + counter += 1; + } + + let elapsed = Instant::now() - start; + + // Report the elapsed time + led.set_low(); + info!( + "At {}Mhz: Elapsed time to count to {}: {}ms", + sys_freq / 1_000_000, + counter, + elapsed.as_millis() + ); + + // Wait 2 seconds before starting the next measurement + Timer::after(Duration::from_secs(2)).await; + } +} -- cgit From c01776a3d779caf5f43fda03e5506f569f1bf9ac Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Thu, 1 May 2025 17:07:48 +0200 Subject: - two more doc examples test ignored - added tests for the new calculations and fixed an overflow issue these tests surfaced. - Activate brownout detection. --- embassy-rp/src/clocks.rs | 222 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 214 insertions(+), 8 deletions(-) diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index 86c172879..005564b8b 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -391,7 +391,7 @@ impl ClockConfig { /// /// # Example /// - /// ``` + /// ```rust,ignore /// // Overclock to 200MHz /// let config = ClockConfig::at_sys_frequency_mhz(200); /// ``` @@ -424,7 +424,7 @@ impl ClockConfig { /// /// # Example /// - /// ``` + /// ```rust,ignore /// // Use a non-standard 16MHz crystal to achieve 250MHz /// let config = ClockConfig::with_custom_crystal(16_000_000, 250_000_000); /// ``` @@ -875,7 +875,7 @@ pub struct RtcClkConfig { /// /// # Example /// -/// ``` +/// ```rust,ignore /// // Find parameters for 133MHz system clock from 12MHz crystal /// let pll_params = find_pll_params(12_000_000, 133_000_000).unwrap(); /// ``` @@ -885,7 +885,7 @@ fn find_pll_params(input_hz: u32, target_hz: u32) -> Option { const PLL_SYS_REFDIV: u8 = 1; // Calculate reference frequency - let reference_freq = input_hz / PLL_SYS_REFDIV as u32; + let reference_freq = input_hz as u64 / PLL_SYS_REFDIV as u64; // Start from highest fbdiv for better stability (like SDK does) for fbdiv in (16..=320).rev() { @@ -900,10 +900,10 @@ fn find_pll_params(input_hz: u32, target_hz: u32) -> Option { // (more conservative/stable approach) for post_div1 in (1..=7).rev() { for post_div2 in (1..=post_div1).rev() { - let out_freq = vco_freq / (post_div1 * post_div2) as u32; + let out_freq = vco_freq / (post_div1 * post_div2); // Check if we get the exact target frequency without remainder - if out_freq == target_hz && (vco_freq % (post_div1 * post_div2) as u32 == 0) { + if out_freq == target_hz as u64 && (vco_freq % (post_div1 * post_div2) == 0) { return Some(PllConfig { refdiv: PLL_SYS_REFDIV, fbdiv: fbdiv as u16, @@ -928,7 +928,7 @@ fn find_pll_params(input_hz: u32, target_hz: u32) -> Option { for post_div1 in (1..=7).rev() { for post_div2 in (1..=post_div1).rev() { - let out_freq = vco_freq / (post_div1 * post_div2) as u32; + let out_freq = (vco_freq / (post_div1 * post_div2) as u64) as u32; let diff = if out_freq > target_hz { out_freq - target_hz } else { @@ -1018,7 +1018,10 @@ pub(crate) unsafe fn init(config: ClockConfig) { cortex_m::asm::delay(delay_cycles); // Only now set the BOD level after voltage has stabilized - vreg.bod().write(|w| w.set_vsel(voltage.recommended_bod())); + vreg.bod().write(|w| { + w.set_vsel(voltage.recommended_bod()); + w.set_en(true); // Enable brownout detection + }); } } @@ -1875,3 +1878,206 @@ pub fn dormant_sleep() { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(feature = "rp2040")] + #[test] + fn test_voltage_scale_bod_values() { + // Test that each voltage level maps to the correct BOD threshold (approx. 90% of VDD) + // This verifies our BOD settings match our documentation + { + assert_eq!(VoltageScale::V0_85.recommended_bod(), 0b0111); // ~0.774V (91% of 0.85V) + assert_eq!(VoltageScale::V0_90.recommended_bod(), 0b1000); // ~0.817V (91% of 0.90V) + assert_eq!(VoltageScale::V0_95.recommended_bod(), 0b1001); // ~0.860V (91% of 0.95V) + assert_eq!(VoltageScale::V1_00.recommended_bod(), 0b1010); // ~0.903V (90% of 1.00V) + assert_eq!(VoltageScale::V1_05.recommended_bod(), 0b1011); // ~0.946V (90% of 1.05V) + assert_eq!(VoltageScale::V1_10.recommended_bod(), 0b1100); // ~0.989V (90% of 1.10V) + assert_eq!(VoltageScale::V1_15.recommended_bod(), 0b1101); // ~1.032V (90% of 1.15V) + assert_eq!(VoltageScale::V1_20.recommended_bod(), 0b1110); // ~1.075V (90% of 1.20V) + assert_eq!(VoltageScale::V1_25.recommended_bod(), 0b1111); // ~1.118V (89% of 1.25V) + assert_eq!(VoltageScale::V1_30.recommended_bod(), 0b1111); // ~1.118V (86% of 1.30V) - using max available + } + } + + #[cfg(feature = "rp2040")] + #[test] + fn test_find_pll_params() { + #[cfg(feature = "rp2040")] + { + // Test standard 125 MHz configuration with 12 MHz crystal + let params = find_pll_params(12_000_000, 125_000_000).unwrap(); + assert_eq!(params.refdiv, 1); + assert_eq!(params.fbdiv, 125); + + // Test USB PLL configuration for 48MHz + // The algorithm may find different valid parameters than the SDK defaults + // We'll check that it generates a valid configuration that produces 48MHz + let params = find_pll_params(12_000_000, 48_000_000).unwrap(); + assert_eq!(params.refdiv, 1); + + // Calculate the actual output frequency + let ref_freq = 12_000_000 / params.refdiv as u32; + let vco_freq = ref_freq as u64 * params.fbdiv as u64; + let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32; + + // Verify the output frequency is correct + assert_eq!(output_freq, 48_000_000); + + // Verify VCO frequency is in valid range + assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); + + // Test overclocked configuration for 200 MHz + let params = find_pll_params(12_000_000, 200_000_000).unwrap(); + assert_eq!(params.refdiv, 1); + let vco_freq = 12_000_000 as u64 * params.fbdiv as u64; + let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32; + assert_eq!(output_freq, 200_000_000); + assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); // VCO in valid range + + // Test non-standard crystal with 16 MHz + let params = find_pll_params(16_000_000, 125_000_000).unwrap(); + let vco_freq = (16_000_000 / params.refdiv as u32) as u64 * params.fbdiv as u64; + let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32; + + // With a 16 MHz crystal, we might not get exactly 125 MHz + // Check that it's close enough (within 0.2% margin) + let freq_diff = if output_freq > 125_000_000 { + output_freq - 125_000_000 + } else { + 125_000_000 - output_freq + }; + let error_percentage = (freq_diff as f64 / 125_000_000.0) * 100.0; + assert!( + error_percentage < 0.2, + "Output frequency {} is not close enough to target 125 MHz. Error: {:.2}%", + output_freq, + error_percentage + ); + + assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); + } + } + + #[cfg(feature = "rp2040")] + #[test] + fn test_pll_config_validation() { + // Test PLL configuration validation logic + let valid_config = PllConfig { + refdiv: 1, + fbdiv: 125, + post_div1: 6, + post_div2: 2, + }; + + // Valid configuration should pass validation + assert!(valid_config.is_valid(12_000_000)); + + // Test fbdiv constraints + let mut invalid_config = valid_config; + invalid_config.fbdiv = 15; // Below minimum of 16 + assert!(!invalid_config.is_valid(12_000_000)); + + invalid_config.fbdiv = 321; // Above maximum of 320 + assert!(!invalid_config.is_valid(12_000_000)); + + // Test post_div constraints + invalid_config = valid_config; + invalid_config.post_div1 = 0; // Below minimum of 1 + assert!(!invalid_config.is_valid(12_000_000)); + + invalid_config = valid_config; + invalid_config.post_div1 = 8; // Above maximum of 7 + assert!(!invalid_config.is_valid(12_000_000)); + + // Test post_div2 must be <= post_div1 + invalid_config = valid_config; + invalid_config.post_div2 = 7; + invalid_config.post_div1 = 3; + assert!(!invalid_config.is_valid(12_000_000)); + + // Test reference frequency constraints + invalid_config = valid_config; + assert!(!invalid_config.is_valid(4_000_000)); // Below minimum of 5 MHz + assert!(!invalid_config.is_valid(900_000_000)); // Above maximum of 800 MHz + + // Test VCO frequency constraints + invalid_config = valid_config; + invalid_config.fbdiv = 16; + assert!(!invalid_config.is_valid(12_000_000)); // VCO too low: 12MHz * 16 = 192MHz + + // Test VCO frequency constraints - too high + invalid_config = valid_config; + invalid_config.fbdiv = 200; + invalid_config.refdiv = 1; + // This should be INVALID: 12MHz * 200 = 2400MHz exceeds max VCO of 1800MHz + assert!(!invalid_config.is_valid(12_000_000)); + + // Test a valid high VCO configuration + invalid_config.fbdiv = 150; // 12MHz * 150 = 1800MHz, exactly at the limit + assert!(invalid_config.is_valid(12_000_000)); + } + + #[cfg(feature = "rp2040")] + #[test] + fn test_manual_pll_helper() { + { + // Test the new manual_pll helper method + let config = ClockConfig::manual_pll( + 12_000_000, + PllConfig { + refdiv: 1, + fbdiv: 100, + post_div1: 3, + post_div2: 2, + }, + Some(VoltageScale::V1_15), + ); + + // Check voltage scale was set correctly + assert_eq!(config.voltage_scale, Some(VoltageScale::V1_15)); + + // Check PLL config was set correctly + assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().refdiv, 1); + assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().fbdiv, 100); + assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().post_div1, 3); + assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().post_div2, 2); + + // Check we get the expected frequency + assert_eq!( + config + .xosc + .as_ref() + .unwrap() + .sys_pll + .as_ref() + .unwrap() + .output_frequency(12_000_000), + 200_000_000 + ); + } + } + + #[cfg(feature = "rp2040")] + #[test] + fn test_auto_voltage_scaling() { + { + // Test automatic voltage scaling based on frequency + // Under 133 MHz should use default voltage (None) + let config = ClockConfig::at_sys_frequency_mhz(125); + assert_eq!(config.voltage_scale, None); + + // 133-200 MHz should use V1_15 + let config = ClockConfig::at_sys_frequency_mhz(150); + assert_eq!(config.voltage_scale, Some(VoltageScale::V1_15)); + let config = ClockConfig::at_sys_frequency_mhz(200); + assert_eq!(config.voltage_scale, Some(VoltageScale::V1_15)); + + // Above 200 MHz should use V1_20 + let config = ClockConfig::at_sys_frequency_mhz(250); + assert_eq!(config.voltage_scale, Some(VoltageScale::V1_20)); + } + } +} -- cgit From 3c559378a5f3662134cb7db2d902302d7cb8f937 Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Thu, 1 May 2025 18:29:13 +0200 Subject: Add overclocking test for RP2040 with timer and PWM tests at 200Mhz --- tests/rp/src/bin/overclock.rs | 62 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 tests/rp/src/bin/overclock.rs diff --git a/tests/rp/src/bin/overclock.rs b/tests/rp/src/bin/overclock.rs new file mode 100644 index 000000000..c7b9180a0 --- /dev/null +++ b/tests/rp/src/bin/overclock.rs @@ -0,0 +1,62 @@ +#![no_std] +#![no_main] +#[cfg(feature = "rp2040")] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::{assert, assert_eq, info}; +use embassy_executor::Spawner; +use embassy_rp::config::Config; +use embassy_rp::gpio::{Input, Pull}; +use embassy_rp::pwm::{Config as PwmConfig, Pwm}; +use embassy_time::{Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[cfg(feature = "rp2040")] +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Initialize with 200MHz clock configuration for RP2040 + let mut config = Config::default(); + config.clocks = embassy_rp::clocks::ClockConfig::at_sys_frequency_mhz(200); + + let p = embassy_rp::init(config); + + info!("RP2040 overclocked to 200MHz!"); + info!("System clock frequency: {} Hz", embassy_rp::clocks::clk_sys_freq()); + + // Test 1: Timer accuracy at 200MHz + info!("Testing timer accuracy at 200MHz..."); + let start = Instant::now(); + Timer::after_millis(100).await; + let end = Instant::now(); + let ms = (end - start).as_millis(); + info!("slept for {} ms", ms); + assert!(ms >= 99); + assert!(ms < 110); + info!("Timer test passed!"); + + // Test 2: PWM functionality at 200MHz + info!("Testing PWM functionality at 200MHz..."); + let pwm_cfg = { + let mut c = PwmConfig::default(); + c.divider = ((embassy_rp::clocks::clk_sys_freq() / 1_000_000) as u8).into(); + c.top = 10000; + c.compare_a = 5000; + c.compare_b = 5000; + c + }; + + // Test PWM output + let pin1 = Input::new(p.PIN_9, Pull::None); + let _pwm = Pwm::new_output_a(p.PWM_SLICE3, p.PIN_6, pwm_cfg); + Timer::after_millis(1).await; + let initial_state = pin1.is_low(); + Timer::after_millis(5).await; + assert_eq!(pin1.is_high(), initial_state); + Timer::after_millis(5).await; + assert_eq!(pin1.is_low(), initial_state); + info!("PWM test passed!"); + + info!("All tests passed at 200MHz!"); + info!("Overclock test successful"); + cortex_m::asm::bkpt(); +} -- cgit From 7fa59a6b31c7c9744e5cdef3826bc4e726736606 Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Thu, 1 May 2025 18:38:03 +0200 Subject: Refactor overclock test for RP2040: add unused imports conditionally and ensure placeholder main function for non-RP2040 targets --- tests/rp/src/bin/overclock.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/rp/src/bin/overclock.rs b/tests/rp/src/bin/overclock.rs index c7b9180a0..c4393dde4 100644 --- a/tests/rp/src/bin/overclock.rs +++ b/tests/rp/src/bin/overclock.rs @@ -1,14 +1,23 @@ #![no_std] #![no_main] +#![cfg_attr(not(feature = "rp2040"), allow(unused_imports))] + #[cfg(feature = "rp2040")] teleprobe_meta::target!(b"rpi-pico"); +#[cfg(feature = "rp2040")] use defmt::{assert, assert_eq, info}; +#[cfg(feature = "rp2040")] use embassy_executor::Spawner; +#[cfg(feature = "rp2040")] use embassy_rp::config::Config; +#[cfg(feature = "rp2040")] use embassy_rp::gpio::{Input, Pull}; +#[cfg(feature = "rp2040")] use embassy_rp::pwm::{Config as PwmConfig, Pwm}; +#[cfg(feature = "rp2040")] use embassy_time::{Instant, Timer}; +#[cfg(feature = "rp2040")] use {defmt_rtt as _, panic_probe as _}; #[cfg(feature = "rp2040")] @@ -60,3 +69,11 @@ async fn main(_spawner: Spawner) { info!("Overclock test successful"); cortex_m::asm::bkpt(); } + +#[cfg(not(feature = "rp2040"))] +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + // This is an empty placeholder main function for non-RP2040 targets + // It should never be called since the test only runs on RP2040 + cortex_m::asm::bkpt(); +} -- cgit From 561356f68ab2d3d6fbe1f85245eb320dbc3e59f0 Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Thu, 1 May 2025 22:17:25 +0200 Subject: overclock test, should now run on all rp chips --- tests/rp/src/bin/overclock.rs | 94 ++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 54 deletions(-) diff --git a/tests/rp/src/bin/overclock.rs b/tests/rp/src/bin/overclock.rs index c4393dde4..d6d6062f2 100644 --- a/tests/rp/src/bin/overclock.rs +++ b/tests/rp/src/bin/overclock.rs @@ -1,79 +1,65 @@ #![no_std] #![no_main] -#![cfg_attr(not(feature = "rp2040"), allow(unused_imports))] #[cfg(feature = "rp2040")] teleprobe_meta::target!(b"rpi-pico"); +#[cfg(feature = "rp235xb")] +teleprobe_meta::target!(b"pimoroni-pico-plus-2"); -#[cfg(feature = "rp2040")] use defmt::{assert, assert_eq, info}; -#[cfg(feature = "rp2040")] use embassy_executor::Spawner; +use embassy_rp::clocks; #[cfg(feature = "rp2040")] -use embassy_rp::config::Config; -#[cfg(feature = "rp2040")] -use embassy_rp::gpio::{Input, Pull}; -#[cfg(feature = "rp2040")] -use embassy_rp::pwm::{Config as PwmConfig, Pwm}; -#[cfg(feature = "rp2040")] -use embassy_time::{Instant, Timer}; +use embassy_rp::clocks::ClockConfig; #[cfg(feature = "rp2040")] +use embassy_rp::clocks::VoltageScale; +use embassy_rp::config::Config; +use embassy_time::Instant; use {defmt_rtt as _, panic_probe as _}; -#[cfg(feature = "rp2040")] +const COUNT_TO: i64 = 10_000_000; + #[embassy_executor::main] async fn main(_spawner: Spawner) { - // Initialize with 200MHz clock configuration for RP2040 let mut config = Config::default(); - config.clocks = embassy_rp::clocks::ClockConfig::at_sys_frequency_mhz(200); - let p = embassy_rp::init(config); + // Initialize with 200MHz clock configuration for RP2040, other chips will use default clock + #[cfg(feature = "rp2040")] + { + config.clocks = ClockConfig::at_sys_frequency_mhz(200); + let voltage = config.clocks.voltage_scale.unwrap(); + assert!(matches!(voltage, VoltageScale::V1_15), "Expected voltage scale V1_15"); + } - info!("RP2040 overclocked to 200MHz!"); - info!("System clock frequency: {} Hz", embassy_rp::clocks::clk_sys_freq()); + let _p = embassy_rp::init(config); - // Test 1: Timer accuracy at 200MHz - info!("Testing timer accuracy at 200MHz..."); - let start = Instant::now(); - Timer::after_millis(100).await; - let end = Instant::now(); - let ms = (end - start).as_millis(); - info!("slept for {} ms", ms); - assert!(ms >= 99); - assert!(ms < 110); - info!("Timer test passed!"); + let (time_elapsed, clk_sys_freq) = { + // Test the system speed + let mut counter = 0; + let start = Instant::now(); + while counter < COUNT_TO { + counter += 1; + } + let elapsed = Instant::now() - start; - // Test 2: PWM functionality at 200MHz - info!("Testing PWM functionality at 200MHz..."); - let pwm_cfg = { - let mut c = PwmConfig::default(); - c.divider = ((embassy_rp::clocks::clk_sys_freq() / 1_000_000) as u8).into(); - c.top = 10000; - c.compare_a = 5000; - c.compare_b = 5000; - c + (elapsed.as_millis(), clocks::clk_sys_freq()) }; - // Test PWM output - let pin1 = Input::new(p.PIN_9, Pull::None); - let _pwm = Pwm::new_output_a(p.PWM_SLICE3, p.PIN_6, pwm_cfg); - Timer::after_millis(1).await; - let initial_state = pin1.is_low(); - Timer::after_millis(5).await; - assert_eq!(pin1.is_high(), initial_state); - Timer::after_millis(5).await; - assert_eq!(pin1.is_low(), initial_state); - info!("PWM test passed!"); + // Report the elapsed time, so that the compiler doesn't optimize it away for chips other than RP2040 + info!( + "At {}Mhz: Elapsed time to count to {}: {}ms", + clk_sys_freq / 1_000_000, + COUNT_TO, + time_elapsed + ); - info!("All tests passed at 200MHz!"); - info!("Overclock test successful"); - cortex_m::asm::bkpt(); -} + #[cfg(feature = "rp2040")] + { + // we should be at 200MHz + assert_eq!(clk_sys_freq, 200_000_000, "System clock frequency is not 200MHz"); + // At 200MHz, the time to count to 10_000_000 should be at 600ms, testing with 1% margin + assert!(time_elapsed <= 606, "Elapsed time is too long"); + } -#[cfg(not(feature = "rp2040"))] -#[embassy_executor::main] -async fn main(_spawner: embassy_executor::Spawner) { - // This is an empty placeholder main function for non-RP2040 targets - // It should never be called since the test only runs on RP2040 cortex_m::asm::bkpt(); } -- cgit From a33e7172f6bf7dc9590432dd62c2b71d0215d99d Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Thu, 1 May 2025 22:23:14 +0200 Subject: make ci happy --- tests/rp/src/bin/overclock.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/rp/src/bin/overclock.rs b/tests/rp/src/bin/overclock.rs index d6d6062f2..e4845a55f 100644 --- a/tests/rp/src/bin/overclock.rs +++ b/tests/rp/src/bin/overclock.rs @@ -6,7 +6,9 @@ teleprobe_meta::target!(b"rpi-pico"); #[cfg(feature = "rp235xb")] teleprobe_meta::target!(b"pimoroni-pico-plus-2"); -use defmt::{assert, assert_eq, info}; +use defmt::info; +#[cfg(feature = "rp2040")] +use defmt::{assert, assert_eq}; use embassy_executor::Spawner; use embassy_rp::clocks; #[cfg(feature = "rp2040")] @@ -21,7 +23,10 @@ const COUNT_TO: i64 = 10_000_000; #[embassy_executor::main] async fn main(_spawner: Spawner) { + #[cfg(feature = "rp2040")] let mut config = Config::default(); + #[cfg(not(feature = "rp2040"))] + let config = Config::default(); // Initialize with 200MHz clock configuration for RP2040, other chips will use default clock #[cfg(feature = "rp2040")] -- cgit From 2fd803f7c336dd6aa042c34e11e213e6e4eb13ad Mon Sep 17 00:00:00 2001 From: Marc <35759328+marcemmers@users.noreply.github.com> Date: Fri, 2 May 2025 12:17:35 +0200 Subject: Removed instance from uart types --- embassy-rp/src/uart/buffered.rs | 306 +++++++++++++++++++++------------------- embassy-rp/src/uart/mod.rs | 291 +++++++++++++++++++++----------------- 2 files changed, 324 insertions(+), 273 deletions(-) diff --git a/embassy-rp/src/uart/buffered.rs b/embassy-rp/src/uart/buffered.rs index da18138b5..44b6ee469 100644 --- a/embassy-rp/src/uart/buffered.rs +++ b/embassy-rp/src/uart/buffered.rs @@ -34,28 +34,31 @@ impl State { } /// Buffered UART driver. -pub struct BufferedUart<'d, T: Instance> { - pub(crate) rx: BufferedUartRx<'d, T>, - pub(crate) tx: BufferedUartTx<'d, T>, +pub struct BufferedUart { + pub(super) rx: BufferedUartRx, + pub(super) tx: BufferedUartTx, } /// Buffered UART RX handle. -pub struct BufferedUartRx<'d, T: Instance> { - pub(crate) phantom: PhantomData<&'d mut T>, +pub struct BufferedUartRx { + pub(super) info: &'static Info, + pub(super) state: &'static State, + // pub(crate) phantom: PhantomData<&'d mut T>, } /// Buffered UART TX handle. -pub struct BufferedUartTx<'d, T: Instance> { - pub(crate) phantom: PhantomData<&'d mut T>, +pub struct BufferedUartTx { + pub(super) info: &'static Info, + pub(super) state: &'static State, + // pub(crate) phantom: PhantomData<&'d mut T>, } -pub(crate) fn init_buffers<'d, T: Instance + 'd>( - _irq: impl Binding>, +pub(super) fn init_buffers<'d>( + info: &Info, + state: &State, tx_buffer: Option<&'d mut [u8]>, rx_buffer: Option<&'d mut [u8]>, ) { - let state = T::buffered_state(); - if let Some(tx_buffer) = tx_buffer { let len = tx_buffer.len(); unsafe { state.tx_buf.init(tx_buffer.as_mut_ptr(), len) }; @@ -76,61 +79,73 @@ pub(crate) fn init_buffers<'d, T: Instance + 'd>( // This means we can leave the interrupt enabled the whole time as long as // we clear it after it happens. The downside is that the we manually have // to pend the ISR when we want data transmission to start. - let regs = T::regs(); - regs.uartimsc().write(|w| { + info.regs.uartimsc().write(|w| { w.set_rxim(true); w.set_rtim(true); w.set_txim(true); }); - T::Interrupt::unpend(); - unsafe { T::Interrupt::enable() }; + info.interrupt.unpend(); + unsafe { info.interrupt.enable() }; } -impl<'d, T: Instance> BufferedUart<'d, T> { +impl BufferedUart { /// Create a buffered UART instance. - pub fn new( + pub fn new<'d, T: Instance>( _uart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, rx: Peri<'d, impl RxPin>, - irq: impl Binding>, + _irq: impl Binding>, tx_buffer: &'d mut [u8], rx_buffer: &'d mut [u8], config: Config, ) -> Self { - super::Uart::<'d, T, Async>::init(Some(tx.into()), Some(rx.into()), None, None, config); - init_buffers::(irq, Some(tx_buffer), Some(rx_buffer)); + super::Uart::<'d, Async>::init(T::info(), Some(tx.into()), Some(rx.into()), None, None, config); + init_buffers(T::info(), T::buffered_state(), Some(tx_buffer), Some(rx_buffer)); Self { - rx: BufferedUartRx { phantom: PhantomData }, - tx: BufferedUartTx { phantom: PhantomData }, + rx: BufferedUartRx { + info: T::info(), + state: T::buffered_state(), + }, + tx: BufferedUartTx { + info: T::info(), + state: T::buffered_state(), + }, } } /// Create a buffered UART instance with flow control. - pub fn new_with_rtscts( + pub fn new_with_rtscts<'d, T: Instance>( _uart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, rx: Peri<'d, impl RxPin>, rts: Peri<'d, impl RtsPin>, cts: Peri<'d, impl CtsPin>, - irq: impl Binding>, + _irq: impl Binding>, tx_buffer: &'d mut [u8], rx_buffer: &'d mut [u8], config: Config, ) -> Self { - super::Uart::<'d, T, Async>::init( + super::Uart::<'d, Async>::init( + T::info(), Some(tx.into()), Some(rx.into()), Some(rts.into()), Some(cts.into()), config, ); - init_buffers::(irq, Some(tx_buffer), Some(rx_buffer)); + init_buffers(T::info(), T::buffered_state(), Some(tx_buffer), Some(rx_buffer)); Self { - rx: BufferedUartRx { phantom: PhantomData }, - tx: BufferedUartTx { phantom: PhantomData }, + rx: BufferedUartRx { + info: T::info(), + state: T::buffered_state(), + }, + tx: BufferedUartTx { + info: T::info(), + state: T::buffered_state(), + }, } } @@ -160,68 +175,75 @@ impl<'d, T: Instance> BufferedUart<'d, T> { } /// sets baudrate on runtime - pub fn set_baudrate(&mut self, baudrate: u32) { - super::Uart::<'d, T, Async>::set_baudrate_inner(baudrate); + pub fn set_baudrate<'d>(&mut self, baudrate: u32) { + super::Uart::<'d, Async>::set_baudrate_inner(self.rx.info, baudrate); } /// Split into separate RX and TX handles. - pub fn split(self) -> (BufferedUartTx<'d, T>, BufferedUartRx<'d, T>) { + pub fn split(self) -> (BufferedUartTx, BufferedUartRx) { (self.tx, self.rx) } /// Split the Uart into a transmitter and receiver by mutable reference, /// which is particularly useful when having two tasks correlating to /// transmitting and receiving. - pub fn split_ref(&mut self) -> (&mut BufferedUartTx<'d, T>, &mut BufferedUartRx<'d, T>) { + pub fn split_ref(&mut self) -> (&mut BufferedUartTx, &mut BufferedUartRx) { (&mut self.tx, &mut self.rx) } } -impl<'d, T: Instance> BufferedUartRx<'d, T> { +impl BufferedUartRx { /// Create a new buffered UART RX. - pub fn new( + pub fn new<'d, T: Instance>( _uart: Peri<'d, T>, - irq: impl Binding>, + _irq: impl Binding>, rx: Peri<'d, impl RxPin>, rx_buffer: &'d mut [u8], config: Config, ) -> Self { - super::Uart::<'d, T, Async>::init(None, Some(rx.into()), None, None, config); - init_buffers::(irq, None, Some(rx_buffer)); + super::Uart::<'d, Async>::init(T::info(), None, Some(rx.into()), None, None, config); + init_buffers(T::info(), T::buffered_state(), None, Some(rx_buffer)); - Self { phantom: PhantomData } + Self { + info: T::info(), + state: T::buffered_state(), + } } /// Create a new buffered UART RX with flow control. - pub fn new_with_rts( + pub fn new_with_rts<'d, T: Instance>( _uart: Peri<'d, T>, - irq: impl Binding>, + _irq: impl Binding>, rx: Peri<'d, impl RxPin>, rts: Peri<'d, impl RtsPin>, rx_buffer: &'d mut [u8], config: Config, ) -> Self { - super::Uart::<'d, T, Async>::init(None, Some(rx.into()), Some(rts.into()), None, config); - init_buffers::(irq, None, Some(rx_buffer)); + super::Uart::<'d, Async>::init(T::info(), None, Some(rx.into()), Some(rts.into()), None, config); + init_buffers(T::info(), T::buffered_state(), None, Some(rx_buffer)); - Self { phantom: PhantomData } + Self { + info: T::info(), + state: T::buffered_state(), + } } - fn read<'a>(buf: &'a mut [u8]) -> impl Future> + 'a - where - T: 'd, - { + fn read<'a>( + info: &'static Info, + state: &'static State, + buf: &'a mut [u8], + ) -> impl Future> + 'a { poll_fn(move |cx| { - if let Poll::Ready(r) = Self::try_read(buf) { + if let Poll::Ready(r) = Self::try_read(info, state, buf) { return Poll::Ready(r); } - T::buffered_state().rx_waker.register(cx.waker()); + state.rx_waker.register(cx.waker()); Poll::Pending }) } - fn get_rx_error() -> Option { - let errs = T::buffered_state().rx_error.swap(0, Ordering::Relaxed); + fn get_rx_error(state: &State) -> Option { + let errs = state.rx_error.swap(0, Ordering::Relaxed); if errs & RXE_OVERRUN != 0 { Some(Error::Overrun) } else if errs & RXE_BREAK != 0 { @@ -235,15 +257,11 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> { } } - fn try_read(buf: &mut [u8]) -> Poll> - where - T: 'd, - { + fn try_read(info: &Info, state: &State, buf: &mut [u8]) -> Poll> { if buf.is_empty() { return Poll::Ready(Ok(0)); } - let state = T::buffered_state(); let mut rx_reader = unsafe { state.rx_buf.reader() }; let n = rx_reader.pop(|data| { let n = data.len().min(buf.len()); @@ -252,7 +270,7 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> { }); let result = if n == 0 { - match Self::get_rx_error() { + match Self::get_rx_error(state) { None => return Poll::Pending, Some(e) => Err(e), } @@ -262,8 +280,7 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> { // (Re-)Enable the interrupt to receive more data in case it was // disabled because the buffer was full or errors were detected. - let regs = T::regs(); - regs.uartimsc().write_set(|w| { + info.regs.uartimsc().write_set(|w| { w.set_rxim(true); w.set_rtim(true); }); @@ -274,23 +291,19 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> { /// Read from UART RX buffer blocking execution until done. pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result { loop { - match Self::try_read(buf) { + match Self::try_read(self.info, self.state, buf) { Poll::Ready(res) => return res, Poll::Pending => continue, } } } - fn fill_buf<'a>() -> impl Future> - where - T: 'd, - { + fn fill_buf<'a>(state: &'static State) -> impl Future> { poll_fn(move |cx| { - let state = T::buffered_state(); let mut rx_reader = unsafe { state.rx_buf.reader() }; let (p, n) = rx_reader.pop_buf(); let result = if n == 0 { - match Self::get_rx_error() { + match Self::get_rx_error(state) { None => { state.rx_waker.register(cx.waker()); return Poll::Pending; @@ -306,64 +319,70 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> { }) } - fn consume(amt: usize) { - let state = T::buffered_state(); + fn consume(info: &Info, state: &State, amt: usize) { let mut rx_reader = unsafe { state.rx_buf.reader() }; rx_reader.pop_done(amt); // (Re-)Enable the interrupt to receive more data in case it was // disabled because the buffer was full or errors were detected. - let regs = T::regs(); - regs.uartimsc().write_set(|w| { + info.regs.uartimsc().write_set(|w| { w.set_rxim(true); w.set_rtim(true); }); } /// we are ready to read if there is data in the buffer - fn read_ready() -> Result { - let state = T::buffered_state(); + fn read_ready(state: &State) -> Result { Ok(!state.rx_buf.is_empty()) } } -impl<'d, T: Instance> BufferedUartTx<'d, T> { +impl BufferedUartTx { /// Create a new buffered UART TX. - pub fn new( + pub fn new<'d, T: Instance>( _uart: Peri<'d, T>, - irq: impl Binding>, + _irq: impl Binding>, tx: Peri<'d, impl TxPin>, tx_buffer: &'d mut [u8], config: Config, ) -> Self { - super::Uart::<'d, T, Async>::init(Some(tx.into()), None, None, None, config); - init_buffers::(irq, Some(tx_buffer), None); + super::Uart::<'d, Async>::init(T::info(), Some(tx.into()), None, None, None, config); + init_buffers(T::info(), T::buffered_state(), Some(tx_buffer), None); - Self { phantom: PhantomData } + Self { + info: T::info(), + state: T::buffered_state(), + } } /// Create a new buffered UART TX with flow control. - pub fn new_with_cts( + pub fn new_with_cts<'d, T: Instance>( _uart: Peri<'d, T>, - irq: impl Binding>, + _irq: impl Binding>, tx: Peri<'d, impl TxPin>, cts: Peri<'d, impl CtsPin>, tx_buffer: &'d mut [u8], config: Config, ) -> Self { - super::Uart::<'d, T, Async>::init(Some(tx.into()), None, None, Some(cts.into()), config); - init_buffers::(irq, Some(tx_buffer), None); + super::Uart::<'d, Async>::init(T::info(), Some(tx.into()), None, None, Some(cts.into()), config); + init_buffers(T::info(), T::buffered_state(), Some(tx_buffer), None); - Self { phantom: PhantomData } + Self { + info: T::info(), + state: T::buffered_state(), + } } - fn write(buf: &[u8]) -> impl Future> + '_ { + fn write<'d>( + info: &'static Info, + state: &'static State, + buf: &'d [u8], + ) -> impl Future> + 'd { poll_fn(move |cx| { if buf.is_empty() { return Poll::Ready(Ok(0)); } - let state = T::buffered_state(); let mut tx_writer = unsafe { state.tx_buf.writer() }; let n = tx_writer.push(|data| { let n = data.len().min(buf.len()); @@ -379,14 +398,13 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { // FIFO and the number of bytes drops below a threshold. When the // FIFO was empty we have to manually pend the interrupt to shovel // TX data from the buffer into the FIFO. - T::Interrupt::pend(); + info.interrupt.pend(); Poll::Ready(Ok(n)) }) } - fn flush() -> impl Future> { + fn flush(state: &'static State) -> impl Future> { poll_fn(move |cx| { - let state = T::buffered_state(); if !state.tx_buf.is_empty() { state.tx_waker.register(cx.waker()); return Poll::Pending; @@ -403,8 +421,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { } loop { - let state = T::buffered_state(); - let mut tx_writer = unsafe { state.tx_buf.writer() }; + let mut tx_writer = unsafe { self.state.tx_buf.writer() }; let n = tx_writer.push(|data| { let n = data.len().min(buf.len()); data[..n].copy_from_slice(&buf[..n]); @@ -416,7 +433,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { // FIFO and the number of bytes drops below a threshold. When the // FIFO was empty we have to manually pend the interrupt to shovel // TX data from the buffer into the FIFO. - T::Interrupt::pend(); + self.info.interrupt.pend(); return Ok(n); } } @@ -425,8 +442,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { /// Flush UART TX blocking execution until done. pub fn blocking_flush(&mut self) -> Result<(), Error> { loop { - let state = T::buffered_state(); - if state.tx_buf.is_empty() { + if self.state.tx_buf.is_empty() { return Ok(()); } } @@ -434,7 +450,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { /// Check if UART is busy. pub fn busy(&self) -> bool { - T::regs().uartfr().read().busy() + self.info.regs.uartfr().read().busy() } /// Assert a break condition after waiting for the transmit buffers to empty, @@ -445,7 +461,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { /// This method may block for a long amount of time since it has to wait /// for the transmit fifo to empty, which may take a while on slow links. pub async fn send_break(&mut self, bits: u32) { - let regs = T::regs(); + let regs = self.info.regs; let bits = bits.max({ let lcr = regs.uartlcr_h().read(); let width = lcr.wlen() as u32 + 5; @@ -458,7 +474,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { let div_clk = clk_peri_freq() as u64 * 64; let wait_usecs = (1_000_000 * bits as u64 * divx64 * 16 + div_clk - 1) / div_clk; - Self::flush().await.unwrap(); + Self::flush(self.state).await.unwrap(); while self.busy() {} regs.uartlcr_h().write_set(|w| w.set_brk(true)); Timer::after_micros(wait_usecs).await; @@ -466,28 +482,26 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { } } -impl<'d, T: Instance> Drop for BufferedUartRx<'d, T> { +impl Drop for BufferedUartRx { fn drop(&mut self) { - let state = T::buffered_state(); - unsafe { state.rx_buf.deinit() } + unsafe { self.state.rx_buf.deinit() } // TX is inactive if the buffer is not available. // We can now unregister the interrupt handler - if !state.tx_buf.is_available() { - T::Interrupt::disable(); + if !self.state.tx_buf.is_available() { + self.info.interrupt.disable(); } } } -impl<'d, T: Instance> Drop for BufferedUartTx<'d, T> { +impl Drop for BufferedUartTx { fn drop(&mut self) { - let state = T::buffered_state(); - unsafe { state.tx_buf.deinit() } + unsafe { self.state.tx_buf.deinit() } // RX is inactive if the buffer is not available. // We can now unregister the interrupt handler - if !state.rx_buf.is_available() { - T::Interrupt::disable(); + if !self.state.rx_buf.is_available() { + self.info.interrupt.disable(); } } } @@ -499,7 +513,7 @@ pub struct BufferedInterruptHandler { impl interrupt::typelevel::Handler for BufferedInterruptHandler { unsafe fn on_interrupt() { - let r = T::regs(); + let r = T::info().regs; if r.uartdmacr().read().rxdmae() { return; } @@ -603,95 +617,95 @@ impl embedded_io::Error for Error { } } -impl<'d, T: Instance> embedded_io_async::ErrorType for BufferedUart<'d, T> { +impl embedded_io_async::ErrorType for BufferedUart { type Error = Error; } -impl<'d, T: Instance> embedded_io_async::ErrorType for BufferedUartRx<'d, T> { +impl embedded_io_async::ErrorType for BufferedUartRx { type Error = Error; } -impl<'d, T: Instance> embedded_io_async::ErrorType for BufferedUartTx<'d, T> { +impl embedded_io_async::ErrorType for BufferedUartTx { type Error = Error; } -impl<'d, T: Instance + 'd> embedded_io_async::Read for BufferedUart<'d, T> { +impl embedded_io_async::Read for BufferedUart { async fn read(&mut self, buf: &mut [u8]) -> Result { - BufferedUartRx::<'d, T>::read(buf).await + BufferedUartRx::read(self.rx.info, self.rx.state, buf).await } } -impl<'d, T: Instance + 'd> embedded_io_async::Read for BufferedUartRx<'d, T> { +impl embedded_io_async::Read for BufferedUartRx { async fn read(&mut self, buf: &mut [u8]) -> Result { - Self::read(buf).await + Self::read(self.info, self.state, buf).await } } -impl<'d, T: Instance + 'd> embedded_io_async::ReadReady for BufferedUart<'d, T> { +impl embedded_io_async::ReadReady for BufferedUart { fn read_ready(&mut self) -> Result { - BufferedUartRx::<'d, T>::read_ready() + BufferedUartRx::read_ready(self.rx.state) } } -impl<'d, T: Instance + 'd> embedded_io_async::ReadReady for BufferedUartRx<'d, T> { +impl embedded_io_async::ReadReady for BufferedUartRx { fn read_ready(&mut self) -> Result { - Self::read_ready() + Self::read_ready(self.state) } } -impl<'d, T: Instance + 'd> embedded_io_async::BufRead for BufferedUart<'d, T> { +impl embedded_io_async::BufRead for BufferedUart { async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { - BufferedUartRx::<'d, T>::fill_buf().await + BufferedUartRx::fill_buf(self.rx.state).await } fn consume(&mut self, amt: usize) { - BufferedUartRx::<'d, T>::consume(amt) + BufferedUartRx::consume(self.rx.info, self.rx.state, amt) } } -impl<'d, T: Instance + 'd> embedded_io_async::BufRead for BufferedUartRx<'d, T> { +impl embedded_io_async::BufRead for BufferedUartRx { async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { - Self::fill_buf().await + Self::fill_buf(self.state).await } fn consume(&mut self, amt: usize) { - Self::consume(amt) + Self::consume(self.info, self.state, amt) } } -impl<'d, T: Instance + 'd> embedded_io_async::Write for BufferedUart<'d, T> { +impl embedded_io_async::Write for BufferedUart { async fn write(&mut self, buf: &[u8]) -> Result { - BufferedUartTx::<'d, T>::write(buf).await + BufferedUartTx::write(self.tx.info, self.tx.state, buf).await } async fn flush(&mut self) -> Result<(), Self::Error> { - BufferedUartTx::<'d, T>::flush().await + BufferedUartTx::flush(self.tx.state).await } } -impl<'d, T: Instance + 'd> embedded_io_async::Write for BufferedUartTx<'d, T> { +impl embedded_io_async::Write for BufferedUartTx { async fn write(&mut self, buf: &[u8]) -> Result { - Self::write(buf).await + Self::write(self.info, self.state, buf).await } async fn flush(&mut self) -> Result<(), Self::Error> { - Self::flush().await + Self::flush(self.state).await } } -impl<'d, T: Instance + 'd> embedded_io::Read for BufferedUart<'d, T> { +impl embedded_io::Read for BufferedUart { fn read(&mut self, buf: &mut [u8]) -> Result { self.rx.blocking_read(buf) } } -impl<'d, T: Instance + 'd> embedded_io::Read for BufferedUartRx<'d, T> { +impl embedded_io::Read for BufferedUartRx { fn read(&mut self, buf: &mut [u8]) -> Result { self.blocking_read(buf) } } -impl<'d, T: Instance + 'd> embedded_io::Write for BufferedUart<'d, T> { +impl embedded_io::Write for BufferedUart { fn write(&mut self, buf: &[u8]) -> Result { self.tx.blocking_write(buf) } @@ -701,7 +715,7 @@ impl<'d, T: Instance + 'd> embedded_io::Write for BufferedUart<'d, T> { } } -impl<'d, T: Instance + 'd> embedded_io::Write for BufferedUartTx<'d, T> { +impl embedded_io::Write for BufferedUartTx { fn write(&mut self, buf: &[u8]) -> Result { self.blocking_write(buf) } @@ -711,11 +725,11 @@ impl<'d, T: Instance + 'd> embedded_io::Write for BufferedUartTx<'d, T> { } } -impl<'d, T: Instance> embedded_hal_02::serial::Read for BufferedUartRx<'d, T> { +impl embedded_hal_02::serial::Read for BufferedUartRx { type Error = Error; fn read(&mut self) -> Result> { - let r = T::regs(); + let r = self.info.regs; if r.uartfr().read().rxfe() { return Err(nb::Error::WouldBlock); } @@ -736,7 +750,7 @@ impl<'d, T: Instance> embedded_hal_02::serial::Read for BufferedUartRx<'d, T } } -impl<'d, T: Instance> embedded_hal_02::blocking::serial::Write for BufferedUartTx<'d, T> { +impl embedded_hal_02::blocking::serial::Write for BufferedUartTx { type Error = Error; fn bwrite_all(&mut self, mut buffer: &[u8]) -> Result<(), Self::Error> { @@ -755,7 +769,7 @@ impl<'d, T: Instance> embedded_hal_02::blocking::serial::Write for BufferedU } } -impl<'d, T: Instance> embedded_hal_02::serial::Read for BufferedUart<'d, T> { +impl embedded_hal_02::serial::Read for BufferedUart { type Error = Error; fn read(&mut self) -> Result> { @@ -763,7 +777,7 @@ impl<'d, T: Instance> embedded_hal_02::serial::Read for BufferedUart<'d, T> } } -impl<'d, T: Instance> embedded_hal_02::blocking::serial::Write for BufferedUart<'d, T> { +impl embedded_hal_02::blocking::serial::Write for BufferedUart { type Error = Error; fn bwrite_all(&mut self, mut buffer: &[u8]) -> Result<(), Self::Error> { @@ -782,25 +796,25 @@ impl<'d, T: Instance> embedded_hal_02::blocking::serial::Write for BufferedU } } -impl<'d, T: Instance> embedded_hal_nb::serial::ErrorType for BufferedUartRx<'d, T> { +impl embedded_hal_nb::serial::ErrorType for BufferedUartRx { type Error = Error; } -impl<'d, T: Instance> embedded_hal_nb::serial::ErrorType for BufferedUartTx<'d, T> { +impl embedded_hal_nb::serial::ErrorType for BufferedUartTx { type Error = Error; } -impl<'d, T: Instance> embedded_hal_nb::serial::ErrorType for BufferedUart<'d, T> { +impl embedded_hal_nb::serial::ErrorType for BufferedUart { type Error = Error; } -impl<'d, T: Instance> embedded_hal_nb::serial::Read for BufferedUartRx<'d, T> { +impl embedded_hal_nb::serial::Read for BufferedUartRx { fn read(&mut self) -> nb::Result { embedded_hal_02::serial::Read::read(self) } } -impl<'d, T: Instance> embedded_hal_nb::serial::Write for BufferedUartTx<'d, T> { +impl embedded_hal_nb::serial::Write for BufferedUartTx { fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { self.blocking_write(&[char]).map(drop).map_err(nb::Error::Other) } @@ -810,13 +824,13 @@ impl<'d, T: Instance> embedded_hal_nb::serial::Write for BufferedUartTx<'d, T> { } } -impl<'d, T: Instance> embedded_hal_nb::serial::Read for BufferedUart<'d, T> { +impl embedded_hal_nb::serial::Read for BufferedUart { fn read(&mut self) -> Result> { embedded_hal_02::serial::Read::read(&mut self.rx) } } -impl<'d, T: Instance> embedded_hal_nb::serial::Write for BufferedUart<'d, T> { +impl embedded_hal_nb::serial::Write for BufferedUart { fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { self.blocking_write(&[char]).map(drop).map_err(nb::Error::Other) } diff --git a/embassy-rp/src/uart/mod.rs b/embassy-rp/src/uart/mod.rs index 7ce074a3f..d36884109 100644 --- a/embassy-rp/src/uart/mod.rs +++ b/embassy-rp/src/uart/mod.rs @@ -13,7 +13,8 @@ use pac::uart::regs::Uartris; use crate::clocks::clk_peri_freq; use crate::dma::{AnyChannel, Channel}; use crate::gpio::{AnyPin, SealedPin}; -use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::interrupt::typelevel::{Binding, Interrupt as _}; +use crate::interrupt::{Interrupt, InterruptExt}; use crate::pac::io::vals::{Inover, Outover}; use crate::{interrupt, pac, peripherals, RegExt}; @@ -135,37 +136,41 @@ pub struct DmaState { } /// UART driver. -pub struct Uart<'d, T: Instance, M: Mode> { - tx: UartTx<'d, T, M>, - rx: UartRx<'d, T, M>, +pub struct Uart<'d, M: Mode> { + tx: UartTx<'d, M>, + rx: UartRx<'d, M>, } /// UART TX driver. -pub struct UartTx<'d, T: Instance, M: Mode> { +pub struct UartTx<'d, M: Mode> { + info: &'static Info, tx_dma: Option>, - phantom: PhantomData<(&'d mut T, M)>, + phantom: PhantomData, } /// UART RX driver. -pub struct UartRx<'d, T: Instance, M: Mode> { +pub struct UartRx<'d, M: Mode> { + info: &'static Info, + dma_state: &'static DmaState, rx_dma: Option>, - phantom: PhantomData<(&'d mut T, M)>, + phantom: PhantomData, } -impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { +impl<'d, M: Mode> UartTx<'d, M> { /// Create a new DMA-enabled UART which can only send data - pub fn new( + pub fn new( _uart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, tx_dma: Peri<'d, impl Channel>, config: Config, ) -> Self { - Uart::::init(Some(tx.into()), None, None, None, config); - Self::new_inner(Some(tx_dma.into())) + Uart::::init(T::info(), Some(tx.into()), None, None, None, config); + Self::new_inner::(Some(tx_dma.into())) } - fn new_inner(tx_dma: Option>) -> Self { + fn new_inner(tx_dma: Option>) -> Self { Self { + info: T::info(), tx_dma, phantom: PhantomData, } @@ -173,7 +178,7 @@ impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { /// Transmit the provided buffer blocking execution until done. pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { - let r = T::regs(); + let r = self.info.regs; for &b in buffer { while r.uartfr().read().txff() {} r.uartdr().write(|w| w.set_data(b)); @@ -183,14 +188,13 @@ impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { /// Flush UART TX blocking execution until done. pub fn blocking_flush(&mut self) -> Result<(), Error> { - let r = T::regs(); - while !r.uartfr().read().txfe() {} + while !self.info.regs.uartfr().read().txfe() {} Ok(()) } /// Check if UART is busy transmitting. pub fn busy(&self) -> bool { - T::regs().uartfr().read().busy() + self.info.regs.uartfr().read().busy() } /// Assert a break condition after waiting for the transmit buffers to empty, @@ -201,7 +205,7 @@ impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { /// This method may block for a long amount of time since it has to wait /// for the transmit fifo to empty, which may take a while on slow links. pub async fn send_break(&mut self, bits: u32) { - let regs = T::regs(); + let regs = self.info.regs; let bits = bits.max({ let lcr = regs.uartlcr_h().read(); let width = lcr.wlen() as u32 + 5; @@ -222,65 +226,75 @@ impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { } } -impl<'d, T: Instance> UartTx<'d, T, Blocking> { +impl<'d> UartTx<'d, Blocking> { /// Create a new UART TX instance for blocking mode operations. - pub fn new_blocking(_uart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, config: Config) -> Self { - Uart::::init(Some(tx.into()), None, None, None, config); - Self::new_inner(None) + pub fn new_blocking(_uart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, config: Config) -> Self { + Uart::::init(T::info(), Some(tx.into()), None, None, None, config); + Self::new_inner::(None) } /// Convert this uart TX instance into a buffered uart using the provided /// irq and transmit buffer. - pub fn into_buffered( + pub fn into_buffered( self, - irq: impl Binding>, + _irq: impl Binding>, tx_buffer: &'d mut [u8], - ) -> BufferedUartTx<'d, T> { - buffered::init_buffers::(irq, Some(tx_buffer), None); + ) -> BufferedUartTx { + buffered::init_buffers(T::info(), T::buffered_state(), Some(tx_buffer), None); - BufferedUartTx { phantom: PhantomData } + BufferedUartTx { + info: T::info(), + state: T::buffered_state(), + } } } -impl<'d, T: Instance> UartTx<'d, T, Async> { +impl<'d> UartTx<'d, Async> { /// Write to UART TX from the provided buffer using DMA. pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { let ch = self.tx_dma.as_mut().unwrap().reborrow(); let transfer = unsafe { - T::regs().uartdmacr().write_set(|reg| { + self.info.regs.uartdmacr().write_set(|reg| { reg.set_txdmae(true); }); // If we don't assign future to a variable, the data register pointer // is held across an await and makes the future non-Send. - crate::dma::write(ch, buffer, T::regs().uartdr().as_ptr() as *mut _, T::TX_DREQ.into()) + crate::dma::write( + ch, + buffer, + self.info.regs.uartdr().as_ptr() as *mut _, + self.info.tx_dreq.into(), + ) }; transfer.await; Ok(()) } } -impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> { +impl<'d, M: Mode> UartRx<'d, M> { /// Create a new DMA-enabled UART which can only receive data - pub fn new( + pub fn new( _uart: Peri<'d, T>, rx: Peri<'d, impl RxPin>, _irq: impl Binding>, rx_dma: Peri<'d, impl Channel>, config: Config, ) -> Self { - Uart::::init(None, Some(rx.into()), None, None, config); - Self::new_inner(true, Some(rx_dma.into())) + Uart::::init(T::info(), None, Some(rx.into()), None, None, config); + Self::new_inner::(true, Some(rx_dma.into())) } - fn new_inner(has_irq: bool, rx_dma: Option>) -> Self { + fn new_inner(has_irq: bool, rx_dma: Option>) -> Self { debug_assert_eq!(has_irq, rx_dma.is_some()); if has_irq { // disable all error interrupts initially - T::regs().uartimsc().write(|w| w.0 = 0); + T::info().regs.uartimsc().write(|w| w.0 = 0); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; } Self { + info: T::info(), + dma_state: T::dma_state(), rx_dma, phantom: PhantomData, } @@ -299,7 +313,7 @@ impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> { /// encountered. in both cases, `len` is the number of *good* bytes copied into /// `buffer`. fn drain_fifo(&mut self, buffer: &mut [u8]) -> Result { - let r = T::regs(); + let r = self.info.regs; for (i, b) in buffer.iter_mut().enumerate() { if r.uartfr().read().rxfe() { return Ok(i); @@ -323,12 +337,12 @@ impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> { } } -impl<'d, T: Instance, M: Mode> Drop for UartRx<'d, T, M> { +impl<'d, M: Mode> Drop for UartRx<'d, M> { fn drop(&mut self) { if self.rx_dma.is_some() { - T::Interrupt::disable(); + self.info.interrupt.disable(); // clear dma flags. irq handlers use these to disambiguate among themselves. - T::regs().uartdmacr().write_clear(|reg| { + self.info.regs.uartdmacr().write_clear(|reg| { reg.set_rxdmae(true); reg.set_txdmae(true); reg.set_dmaonerr(true); @@ -337,23 +351,26 @@ impl<'d, T: Instance, M: Mode> Drop for UartRx<'d, T, M> { } } -impl<'d, T: Instance> UartRx<'d, T, Blocking> { +impl<'d> UartRx<'d, Blocking> { /// Create a new UART RX instance for blocking mode operations. - pub fn new_blocking(_uart: Peri<'d, T>, rx: Peri<'d, impl RxPin>, config: Config) -> Self { - Uart::::init(None, Some(rx.into()), None, None, config); - Self::new_inner(false, None) + pub fn new_blocking(_uart: Peri<'d, T>, rx: Peri<'d, impl RxPin>, config: Config) -> Self { + Uart::::init(T::info(), None, Some(rx.into()), None, None, config); + Self::new_inner::(false, None) } /// Convert this uart RX instance into a buffered uart using the provided /// irq and receive buffer. - pub fn into_buffered( + pub fn into_buffered( self, - irq: impl Binding>, + _irq: impl Binding>, rx_buffer: &'d mut [u8], - ) -> BufferedUartRx<'d, T> { - buffered::init_buffers::(irq, None, Some(rx_buffer)); + ) -> BufferedUartRx { + buffered::init_buffers(T::info(), T::buffered_state(), None, Some(rx_buffer)); - BufferedUartRx { phantom: PhantomData } + BufferedUartRx { + info: T::info(), + state: T::buffered_state(), + } } } @@ -364,7 +381,7 @@ pub struct InterruptHandler { impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { - let uart = T::regs(); + let uart = T::info().regs; if !uart.uartdmacr().read().rxdmae() { return; } @@ -380,13 +397,13 @@ impl interrupt::typelevel::Handler for InterruptHandl } } -impl<'d, T: Instance> UartRx<'d, T, Async> { +impl<'d> UartRx<'d, Async> { /// Read from UART RX into the provided buffer. pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { // clear error flags before we drain the fifo. errors that have accumulated // in the flags will also be present in the fifo. - T::dma_state().rx_errs.store(0, Ordering::Relaxed); - T::regs().uarticr().write(|w| { + self.dma_state.rx_errs.store(0, Ordering::Relaxed); + self.info.regs.uarticr().write(|w| { w.set_oeic(true); w.set_beic(true); w.set_peic(true); @@ -408,28 +425,33 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { // interrupt flags will have been raised, and those will be picked up immediately // by the interrupt handler. let ch = self.rx_dma.as_mut().unwrap().reborrow(); - T::regs().uartimsc().write_set(|w| { + self.info.regs.uartimsc().write_set(|w| { w.set_oeim(true); w.set_beim(true); w.set_peim(true); w.set_feim(true); }); - T::regs().uartdmacr().write_set(|reg| { + self.info.regs.uartdmacr().write_set(|reg| { reg.set_rxdmae(true); reg.set_dmaonerr(true); }); let transfer = unsafe { // If we don't assign future to a variable, the data register pointer // is held across an await and makes the future non-Send. - crate::dma::read(ch, T::regs().uartdr().as_ptr() as *const _, buffer, T::RX_DREQ.into()) + crate::dma::read( + ch, + self.info.regs.uartdr().as_ptr() as *const _, + buffer, + self.info.rx_dreq.into(), + ) }; // wait for either the transfer to complete or an error to happen. let transfer_result = select( transfer, poll_fn(|cx| { - T::dma_state().rx_err_waker.register(cx.waker()); - match T::dma_state().rx_errs.swap(0, Ordering::Relaxed) { + self.dma_state.rx_err_waker.register(cx.waker()); + match self.dma_state.rx_errs.swap(0, Ordering::Relaxed) { 0 => Poll::Pending, e => Poll::Ready(Uartris(e as u32)), } @@ -441,7 +463,7 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { Either::First(()) => { // We're here because the DMA finished, BUT if an error occurred on the LAST // byte, then we may still need to grab the error state! - Uartris(T::dma_state().rx_errs.swap(0, Ordering::Relaxed) as u32) + Uartris(self.dma_state.rx_errs.swap(0, Ordering::Relaxed) as u32) } Either::Second(e) => { // We're here because we errored, which means this is the error that @@ -521,8 +543,8 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { ) -> Result { // clear error flags before we drain the fifo. errors that have accumulated // in the flags will also be present in the fifo. - T::dma_state().rx_errs.store(0, Ordering::Relaxed); - T::regs().uarticr().write(|w| { + self.dma_state.rx_errs.store(0, Ordering::Relaxed); + self.info.regs.uarticr().write(|w| { w.set_oeic(true); w.set_beic(true); w.set_peic(true); @@ -555,13 +577,13 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { // interrupt flags will have been raised, and those will be picked up immediately // by the interrupt handler. let ch = self.rx_dma.as_mut().unwrap(); - T::regs().uartimsc().write_set(|w| { + self.info.regs.uartimsc().write_set(|w| { w.set_oeim(true); w.set_beim(true); w.set_peim(true); w.set_feim(true); }); - T::regs().uartdmacr().write_set(|reg| { + self.info.regs.uartdmacr().write_set(|reg| { reg.set_rxdmae(true); reg.set_dmaonerr(true); }); @@ -572,9 +594,9 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { // is held across an await and makes the future non-Send. crate::dma::read( ch.reborrow(), - T::regs().uartdr().as_ptr() as *const _, + self.info.regs.uartdr().as_ptr() as *const _, sbuffer, - T::RX_DREQ.into(), + self.info.rx_dreq.into(), ) }; @@ -582,8 +604,8 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { let transfer_result = select( transfer, poll_fn(|cx| { - T::dma_state().rx_err_waker.register(cx.waker()); - match T::dma_state().rx_errs.swap(0, Ordering::Relaxed) { + self.dma_state.rx_err_waker.register(cx.waker()); + match self.dma_state.rx_errs.swap(0, Ordering::Relaxed) { 0 => Poll::Pending, e => Poll::Ready(Uartris(e as u32)), } @@ -596,7 +618,7 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { Either::First(()) => { // We're here because the DMA finished, BUT if an error occurred on the LAST // byte, then we may still need to grab the error state! - Uartris(T::dma_state().rx_errs.swap(0, Ordering::Relaxed) as u32) + Uartris(self.dma_state.rx_errs.swap(0, Ordering::Relaxed) as u32) } Either::Second(e) => { // We're here because we errored, which means this is the error that @@ -635,7 +657,7 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { continue; } - let regs = T::regs(); + let regs = self.info.regs; let all_full = next_addr == eval; // NOTE: This is off label usage of RSR! See the issue below for @@ -685,9 +707,9 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { } } -impl<'d, T: Instance> Uart<'d, T, Blocking> { +impl<'d> Uart<'d, Blocking> { /// Create a new UART without hardware flow control - pub fn new_blocking( + pub fn new_blocking( uart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, rx: Peri<'d, impl RxPin>, @@ -697,7 +719,7 @@ impl<'d, T: Instance> Uart<'d, T, Blocking> { } /// Create a new UART with hardware flow control (RTS/CTS) - pub fn new_with_rtscts_blocking( + pub fn new_with_rtscts_blocking( uart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, rx: Peri<'d, impl RxPin>, @@ -720,24 +742,30 @@ impl<'d, T: Instance> Uart<'d, T, Blocking> { /// Convert this uart instance into a buffered uart using the provided /// irq, transmit and receive buffers. - pub fn into_buffered( + pub fn into_buffered( self, - irq: impl Binding>, + _irq: impl Binding>, tx_buffer: &'d mut [u8], rx_buffer: &'d mut [u8], - ) -> BufferedUart<'d, T> { - buffered::init_buffers::(irq, Some(tx_buffer), Some(rx_buffer)); + ) -> BufferedUart { + buffered::init_buffers(T::info(), T::buffered_state(), Some(tx_buffer), Some(rx_buffer)); BufferedUart { - rx: BufferedUartRx { phantom: PhantomData }, - tx: BufferedUartTx { phantom: PhantomData }, + rx: BufferedUartRx { + info: T::info(), + state: T::buffered_state(), + }, + tx: BufferedUartTx { + info: T::info(), + state: T::buffered_state(), + }, } } } -impl<'d, T: Instance> Uart<'d, T, Async> { +impl<'d> Uart<'d, Async> { /// Create a new DMA enabled UART without hardware flow control - pub fn new( + pub fn new( uart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, rx: Peri<'d, impl RxPin>, @@ -760,7 +788,7 @@ impl<'d, T: Instance> Uart<'d, T, Async> { } /// Create a new DMA enabled UART with hardware flow control (RTS/CTS) - pub fn new_with_rtscts( + pub fn new_with_rtscts( uart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, rx: Peri<'d, impl RxPin>, @@ -785,8 +813,8 @@ impl<'d, T: Instance> Uart<'d, T, Async> { } } -impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { - fn new_inner( +impl<'d, M: Mode> Uart<'d, M> { + fn new_inner( _uart: Peri<'d, T>, mut tx: Peri<'d, AnyPin>, mut rx: Peri<'d, AnyPin>, @@ -798,6 +826,7 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { config: Config, ) -> Self { Self::init( + T::info(), Some(tx.reborrow()), Some(rx.reborrow()), rts.as_mut().map(|x| x.reborrow()), @@ -806,19 +835,20 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { ); Self { - tx: UartTx::new_inner(tx_dma), - rx: UartRx::new_inner(has_irq, rx_dma), + tx: UartTx::new_inner::(tx_dma), + rx: UartRx::new_inner::(has_irq, rx_dma), } } fn init( + info: &Info, tx: Option>, rx: Option>, rts: Option>, cts: Option>, config: Config, ) { - let r = T::regs(); + let r = info.regs; if let Some(pin) = &tx { let funcsel = { let pin_number = ((pin.gpio().as_ptr() as u32) & 0x1FF) / 8; @@ -896,7 +926,7 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { }); } - Self::set_baudrate_inner(config.baudrate); + Self::set_baudrate_inner(info, config.baudrate); let (pen, eps) = match config.parity { Parity::ParityNone => (false, false), @@ -926,8 +956,8 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { }); } - fn lcr_modify(f: impl FnOnce(&mut crate::pac::uart::regs::UartlcrH) -> R) -> R { - let r = T::regs(); + fn lcr_modify(info: &Info, f: impl FnOnce(&mut crate::pac::uart::regs::UartlcrH) -> R) -> R { + let r = info.regs; // Notes from PL011 reference manual: // @@ -978,11 +1008,11 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { /// sets baudrate on runtime pub fn set_baudrate(&mut self, baudrate: u32) { - Self::set_baudrate_inner(baudrate); + Self::set_baudrate_inner(self.tx.info, baudrate); } - fn set_baudrate_inner(baudrate: u32) { - let r = T::regs(); + fn set_baudrate_inner(info: &Info, baudrate: u32) { + let r = info.regs; let clk_base = crate::clocks::clk_peri_freq(); @@ -1002,11 +1032,11 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { r.uartibrd().write_value(pac::uart::regs::Uartibrd(baud_ibrd)); r.uartfbrd().write_value(pac::uart::regs::Uartfbrd(baud_fbrd)); - Self::lcr_modify(|_| {}); + Self::lcr_modify(info, |_| {}); } } -impl<'d, T: Instance, M: Mode> Uart<'d, T, M> { +impl<'d, M: Mode> Uart<'d, M> { /// Transmit the provided buffer blocking execution until done. pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { self.tx.blocking_write(buffer) @@ -1034,19 +1064,19 @@ impl<'d, T: Instance, M: Mode> Uart<'d, T, M> { /// Split the Uart into a transmitter and receiver, which is particularly /// useful when having two tasks correlating to transmitting and receiving. - pub fn split(self) -> (UartTx<'d, T, M>, UartRx<'d, T, M>) { + pub fn split(self) -> (UartTx<'d, M>, UartRx<'d, M>) { (self.tx, self.rx) } /// Split the Uart into a transmitter and receiver by mutable reference, /// which is particularly useful when having two tasks correlating to /// transmitting and receiving. - pub fn split_ref(&mut self) -> (&mut UartTx<'d, T, M>, &mut UartRx<'d, T, M>) { + pub fn split_ref(&mut self) -> (&mut UartTx<'d, M>, &mut UartRx<'d, M>) { (&mut self.tx, &mut self.rx) } } -impl<'d, T: Instance> Uart<'d, T, Async> { +impl<'d> Uart<'d, Async> { /// Write to UART TX from the provided buffer. pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { self.tx.write(buffer).await @@ -1076,10 +1106,10 @@ impl<'d, T: Instance> Uart<'d, T, Async> { } } -impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Read for UartRx<'d, T, M> { +impl<'d, M: Mode> embedded_hal_02::serial::Read for UartRx<'d, M> { type Error = Error; fn read(&mut self) -> Result> { - let r = T::regs(); + let r = self.info.regs; if r.uartfr().read().rxfe() { return Err(nb::Error::WouldBlock); } @@ -1100,11 +1130,11 @@ impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Read for UartRx<'d, } } -impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Write for UartTx<'d, T, M> { +impl<'d, M: Mode> embedded_hal_02::serial::Write for UartTx<'d, M> { type Error = Error; fn write(&mut self, word: u8) -> Result<(), nb::Error> { - let r = T::regs(); + let r = self.info.regs; if r.uartfr().read().txff() { return Err(nb::Error::WouldBlock); } @@ -1114,7 +1144,7 @@ impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Write for UartTx<'d, } fn flush(&mut self) -> Result<(), nb::Error> { - let r = T::regs(); + let r = self.info.regs; if !r.uartfr().read().txfe() { return Err(nb::Error::WouldBlock); } @@ -1122,7 +1152,7 @@ impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Write for UartTx<'d, } } -impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::serial::Write for UartTx<'d, T, M> { +impl<'d, M: Mode> embedded_hal_02::blocking::serial::Write for UartTx<'d, M> { type Error = Error; fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { @@ -1134,7 +1164,7 @@ impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::serial::Write for } } -impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Read for Uart<'d, T, M> { +impl<'d, M: Mode> embedded_hal_02::serial::Read for Uart<'d, M> { type Error = Error; fn read(&mut self) -> Result> { @@ -1142,7 +1172,7 @@ impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Read for Uart<'d, T, } } -impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Write for Uart<'d, T, M> { +impl<'d, M: Mode> embedded_hal_02::serial::Write for Uart<'d, M> { type Error = Error; fn write(&mut self, word: u8) -> Result<(), nb::Error> { @@ -1154,7 +1184,7 @@ impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Write for Uart<'d, T } } -impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::serial::Write for Uart<'d, T, M> { +impl<'d, M: Mode> embedded_hal_02::blocking::serial::Write for Uart<'d, M> { type Error = Error; fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { @@ -1177,21 +1207,21 @@ impl embedded_hal_nb::serial::Error for Error { } } -impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::ErrorType for UartRx<'d, T, M> { +impl<'d, M: Mode> embedded_hal_nb::serial::ErrorType for UartRx<'d, M> { type Error = Error; } -impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::ErrorType for UartTx<'d, T, M> { +impl<'d, M: Mode> embedded_hal_nb::serial::ErrorType for UartTx<'d, M> { type Error = Error; } -impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::ErrorType for Uart<'d, T, M> { +impl<'d, M: Mode> embedded_hal_nb::serial::ErrorType for Uart<'d, M> { type Error = Error; } -impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Read for UartRx<'d, T, M> { +impl<'d, M: Mode> embedded_hal_nb::serial::Read for UartRx<'d, M> { fn read(&mut self) -> nb::Result { - let r = T::regs(); + let r = self.info.regs; if r.uartfr().read().rxfe() { return Err(nb::Error::WouldBlock); } @@ -1212,7 +1242,7 @@ impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Read for UartRx<'d, T, M } } -impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Write for UartTx<'d, T, M> { +impl<'d, M: Mode> embedded_hal_nb::serial::Write for UartTx<'d, M> { fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { self.blocking_write(&[char]).map_err(nb::Error::Other) } @@ -1222,11 +1252,11 @@ impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Write for UartTx<'d, T, } } -impl<'d, T: Instance> embedded_io::ErrorType for UartTx<'d, T, Blocking> { +impl<'d> embedded_io::ErrorType for UartTx<'d, Blocking> { type Error = Error; } -impl<'d, T: Instance> embedded_io::Write for UartTx<'d, T, Blocking> { +impl<'d> embedded_io::Write for UartTx<'d, Blocking> { fn write(&mut self, buf: &[u8]) -> Result { self.blocking_write(buf).map(|_| buf.len()) } @@ -1236,13 +1266,13 @@ impl<'d, T: Instance> embedded_io::Write for UartTx<'d, T, Blocking> { } } -impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Read for Uart<'d, T, M> { +impl<'d, M: Mode> embedded_hal_nb::serial::Read for Uart<'d, M> { fn read(&mut self) -> Result> { embedded_hal_02::serial::Read::read(&mut self.rx) } } -impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Write for Uart<'d, T, M> { +impl<'d, M: Mode> embedded_hal_nb::serial::Write for Uart<'d, M> { fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { self.blocking_write(&[char]).map_err(nb::Error::Other) } @@ -1252,11 +1282,11 @@ impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Write for Uart<'d, T, M> } } -impl<'d, T: Instance> embedded_io::ErrorType for Uart<'d, T, Blocking> { +impl<'d> embedded_io::ErrorType for Uart<'d, Blocking> { type Error = Error; } -impl<'d, T: Instance> embedded_io::Write for Uart<'d, T, Blocking> { +impl<'d> embedded_io::Write for Uart<'d, Blocking> { fn write(&mut self, buf: &[u8]) -> Result { self.blocking_write(buf).map(|_| buf.len()) } @@ -1266,13 +1296,17 @@ impl<'d, T: Instance> embedded_io::Write for Uart<'d, T, Blocking> { } } +struct Info { + regs: pac::uart::Uart, + tx_dreq: pac::dma::vals::TreqSel, + rx_dreq: pac::dma::vals::TreqSel, + interrupt: Interrupt, +} + trait SealedMode {} trait SealedInstance { - const TX_DREQ: pac::dma::vals::TreqSel; - const RX_DREQ: pac::dma::vals::TreqSel; - - fn regs() -> pac::uart::Uart; + fn info() -> &'static Info; fn buffered_state() -> &'static buffered::State; @@ -1308,11 +1342,14 @@ pub trait Instance: SealedInstance + PeripheralType { macro_rules! impl_instance { ($inst:ident, $irq:ident, $tx_dreq:expr, $rx_dreq:expr) => { impl SealedInstance for peripherals::$inst { - const TX_DREQ: pac::dma::vals::TreqSel = $tx_dreq; - const RX_DREQ: pac::dma::vals::TreqSel = $rx_dreq; - - fn regs() -> pac::uart::Uart { - pac::$inst + fn info() -> &'static Info { + static INFO: Info = Info { + regs: pac::$inst, + tx_dreq: $tx_dreq, + rx_dreq: $rx_dreq, + interrupt: crate::interrupt::typelevel::$irq::IRQ, + }; + &INFO } fn buffered_state() -> &'static buffered::State { -- cgit From 1efe59ec475aa38948d1fc95feb34860f08c8dcd Mon Sep 17 00:00:00 2001 From: Marc <35759328+marcemmers@users.noreply.github.com> Date: Fri, 2 May 2025 12:52:04 +0200 Subject: Update examples and tests --- examples/rp/src/bin/uart_buffered_split.rs | 2 +- examples/rp/src/bin/uart_unidir.rs | 2 +- tests/rp/src/bin/uart.rs | 6 +++--- tests/rp/src/bin/uart_buffered.rs | 6 +++--- tests/rp/src/bin/uart_dma.rs | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/rp/src/bin/uart_buffered_split.rs b/examples/rp/src/bin/uart_buffered_split.rs index da7e94139..3adbc18ab 100644 --- a/examples/rp/src/bin/uart_buffered_split.rs +++ b/examples/rp/src/bin/uart_buffered_split.rs @@ -48,7 +48,7 @@ async fn main(spawner: Spawner) { } #[embassy_executor::task] -async fn reader(mut rx: BufferedUartRx<'static, UART0>) { +async fn reader(mut rx: BufferedUartRx) { info!("Reading..."); loop { let mut buf = [0; 31]; diff --git a/examples/rp/src/bin/uart_unidir.rs b/examples/rp/src/bin/uart_unidir.rs index a45f40756..c2c8dfad8 100644 --- a/examples/rp/src/bin/uart_unidir.rs +++ b/examples/rp/src/bin/uart_unidir.rs @@ -39,7 +39,7 @@ async fn main(spawner: Spawner) { } #[embassy_executor::task] -async fn reader(mut rx: UartRx<'static, UART1, Async>) { +async fn reader(mut rx: UartRx<'static, Async>) { info!("Reading..."); loop { // read a total of 4 transmissions (32 / 8) and then print the result diff --git a/tests/rp/src/bin/uart.rs b/tests/rp/src/bin/uart.rs index 84744ab77..80230f3fe 100644 --- a/tests/rp/src/bin/uart.rs +++ b/tests/rp/src/bin/uart.rs @@ -8,17 +8,17 @@ teleprobe_meta::target!(b"pimoroni-pico-plus-2"); use defmt::{assert_eq, *}; use embassy_executor::Spawner; use embassy_rp::gpio::{Level, Output}; -use embassy_rp::uart::{Blocking, Config, Error, Instance, Parity, Uart, UartRx}; +use embassy_rp::uart::{Blocking, Config, Error, Parity, Uart, UartRx}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; -fn read(uart: &mut Uart<'_, impl Instance, Blocking>) -> Result<[u8; N], Error> { +fn read(uart: &mut Uart<'_, Blocking>) -> Result<[u8; N], Error> { let mut buf = [255; N]; uart.blocking_read(&mut buf)?; Ok(buf) } -fn read1(uart: &mut UartRx<'_, impl Instance, Blocking>) -> Result<[u8; N], Error> { +fn read1(uart: &mut UartRx<'_, Blocking>) -> Result<[u8; N], Error> { let mut buf = [255; N]; uart.blocking_read(&mut buf)?; Ok(buf) diff --git a/tests/rp/src/bin/uart_buffered.rs b/tests/rp/src/bin/uart_buffered.rs index d5f655e9b..cb78fc142 100644 --- a/tests/rp/src/bin/uart_buffered.rs +++ b/tests/rp/src/bin/uart_buffered.rs @@ -10,7 +10,7 @@ use embassy_executor::Spawner; use embassy_rp::bind_interrupts; use embassy_rp::gpio::{Level, Output}; use embassy_rp::peripherals::UART0; -use embassy_rp::uart::{BufferedInterruptHandler, BufferedUart, BufferedUartRx, Config, Error, Instance, Parity}; +use embassy_rp::uart::{BufferedInterruptHandler, BufferedUart, BufferedUartRx, Config, Error, Parity}; use embassy_time::Timer; use embedded_io_async::{Read, ReadExactError, Write}; use {defmt_rtt as _, panic_probe as _}; @@ -19,7 +19,7 @@ bind_interrupts!(struct Irqs { UART0_IRQ => BufferedInterruptHandler; }); -async fn read(uart: &mut BufferedUart<'_, impl Instance>) -> Result<[u8; N], Error> { +async fn read(uart: &mut BufferedUart) -> Result<[u8; N], Error> { let mut buf = [255; N]; match uart.read_exact(&mut buf).await { Ok(()) => Ok(buf), @@ -29,7 +29,7 @@ async fn read(uart: &mut BufferedUart<'_, impl Instance>) -> Res } } -async fn read1(uart: &mut BufferedUartRx<'_, impl Instance>) -> Result<[u8; N], Error> { +async fn read1(uart: &mut BufferedUartRx) -> Result<[u8; N], Error> { let mut buf = [255; N]; match uart.read_exact(&mut buf).await { Ok(()) => Ok(buf), diff --git a/tests/rp/src/bin/uart_dma.rs b/tests/rp/src/bin/uart_dma.rs index a09101223..a7af81f5f 100644 --- a/tests/rp/src/bin/uart_dma.rs +++ b/tests/rp/src/bin/uart_dma.rs @@ -10,7 +10,7 @@ use embassy_executor::Spawner; use embassy_rp::bind_interrupts; use embassy_rp::gpio::{Level, Output}; use embassy_rp::peripherals::UART0; -use embassy_rp::uart::{Async, Config, Error, Instance, InterruptHandler, Parity, Uart, UartRx}; +use embassy_rp::uart::{Async, Config, Error, InterruptHandler, Parity, Uart, UartRx}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -18,13 +18,13 @@ bind_interrupts!(struct Irqs { UART0_IRQ => InterruptHandler; }); -async fn read(uart: &mut Uart<'_, impl Instance, Async>) -> Result<[u8; N], Error> { +async fn read(uart: &mut Uart<'_, Async>) -> Result<[u8; N], Error> { let mut buf = [255; N]; uart.read(&mut buf).await?; Ok(buf) } -async fn read1(uart: &mut UartRx<'_, impl Instance, Async>) -> Result<[u8; N], Error> { +async fn read1(uart: &mut UartRx<'_, Async>) -> Result<[u8; N], Error> { let mut buf = [255; N]; uart.read(&mut buf).await?; Ok(buf) -- cgit From 407540c8fe5061dfd245d605775fea36c2dd00ce Mon Sep 17 00:00:00 2001 From: Marc <35759328+marcemmers@users.noreply.github.com> Date: Fri, 2 May 2025 13:32:09 +0200 Subject: Remove some forgotten commented out code --- embassy-rp/src/uart/buffered.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/embassy-rp/src/uart/buffered.rs b/embassy-rp/src/uart/buffered.rs index 44b6ee469..02649ad81 100644 --- a/embassy-rp/src/uart/buffered.rs +++ b/embassy-rp/src/uart/buffered.rs @@ -43,14 +43,12 @@ pub struct BufferedUart { pub struct BufferedUartRx { pub(super) info: &'static Info, pub(super) state: &'static State, - // pub(crate) phantom: PhantomData<&'d mut T>, } /// Buffered UART TX handle. pub struct BufferedUartTx { pub(super) info: &'static Info, pub(super) state: &'static State, - // pub(crate) phantom: PhantomData<&'d mut T>, } pub(super) fn init_buffers<'d>( -- cgit From d799af9dd82cda9cef3e5de91ae3e8555ad1d2d2 Mon Sep 17 00:00:00 2001 From: Marc <35759328+marcemmers@users.noreply.github.com> Date: Fri, 2 May 2025 13:40:50 +0200 Subject: Replace generic inner with arguments --- embassy-rp/src/uart/mod.rs | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/embassy-rp/src/uart/mod.rs b/embassy-rp/src/uart/mod.rs index d36884109..c3a15fda5 100644 --- a/embassy-rp/src/uart/mod.rs +++ b/embassy-rp/src/uart/mod.rs @@ -165,12 +165,12 @@ impl<'d, M: Mode> UartTx<'d, M> { config: Config, ) -> Self { Uart::::init(T::info(), Some(tx.into()), None, None, None, config); - Self::new_inner::(Some(tx_dma.into())) + Self::new_inner(T::info(), Some(tx_dma.into())) } - fn new_inner(tx_dma: Option>) -> Self { + fn new_inner(info: &'static Info, tx_dma: Option>) -> Self { Self { - info: T::info(), + info, tx_dma, phantom: PhantomData, } @@ -230,7 +230,7 @@ impl<'d> UartTx<'d, Blocking> { /// Create a new UART TX instance for blocking mode operations. pub fn new_blocking(_uart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, config: Config) -> Self { Uart::::init(T::info(), Some(tx.into()), None, None, None, config); - Self::new_inner::(None) + Self::new_inner(T::info(), None) } /// Convert this uart TX instance into a buffered uart using the provided @@ -281,20 +281,25 @@ impl<'d, M: Mode> UartRx<'d, M> { config: Config, ) -> Self { Uart::::init(T::info(), None, Some(rx.into()), None, None, config); - Self::new_inner::(true, Some(rx_dma.into())) + Self::new_inner(T::info(), T::dma_state(), true, Some(rx_dma.into())) } - fn new_inner(has_irq: bool, rx_dma: Option>) -> Self { + fn new_inner( + info: &'static Info, + dma_state: &'static DmaState, + has_irq: bool, + rx_dma: Option>, + ) -> Self { debug_assert_eq!(has_irq, rx_dma.is_some()); if has_irq { // disable all error interrupts initially - T::info().regs.uartimsc().write(|w| w.0 = 0); - T::Interrupt::unpend(); - unsafe { T::Interrupt::enable() }; + info.regs.uartimsc().write(|w| w.0 = 0); + info.interrupt.unpend(); + unsafe { info.interrupt.enable() }; } Self { - info: T::info(), - dma_state: T::dma_state(), + info, + dma_state, rx_dma, phantom: PhantomData, } @@ -355,7 +360,7 @@ impl<'d> UartRx<'d, Blocking> { /// Create a new UART RX instance for blocking mode operations. pub fn new_blocking(_uart: Peri<'d, T>, rx: Peri<'d, impl RxPin>, config: Config) -> Self { Uart::::init(T::info(), None, Some(rx.into()), None, None, config); - Self::new_inner::(false, None) + Self::new_inner(T::info(), T::dma_state(), false, None) } /// Convert this uart RX instance into a buffered uart using the provided @@ -835,8 +840,8 @@ impl<'d, M: Mode> Uart<'d, M> { ); Self { - tx: UartTx::new_inner::(tx_dma), - rx: UartRx::new_inner::(has_irq, rx_dma), + tx: UartTx::new_inner(T::info(), tx_dma), + rx: UartRx::new_inner(T::info(), T::dma_state(), has_irq, rx_dma), } } -- cgit From 371373886bb6cfa2a082835d87786a2916c74562 Mon Sep 17 00:00:00 2001 From: Marc <35759328+marcemmers@users.noreply.github.com> Date: Fri, 2 May 2025 14:23:58 +0200 Subject: Fix examples for rp235x --- examples/rp235x/src/bin/sharing.rs | 2 +- examples/rp235x/src/bin/uart_buffered_split.rs | 2 +- examples/rp235x/src/bin/uart_unidir.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/rp235x/src/bin/sharing.rs b/examples/rp235x/src/bin/sharing.rs index 5416e20ce..497c4f845 100644 --- a/examples/rp235x/src/bin/sharing.rs +++ b/examples/rp235x/src/bin/sharing.rs @@ -31,7 +31,7 @@ use rand::RngCore; use static_cell::{ConstStaticCell, StaticCell}; use {defmt_rtt as _, panic_probe as _}; -type UartAsyncMutex = mutex::Mutex>; +type UartAsyncMutex = mutex::Mutex>; struct MyType { inner: u32, diff --git a/examples/rp235x/src/bin/uart_buffered_split.rs b/examples/rp235x/src/bin/uart_buffered_split.rs index f707c4b5e..7cad09f9b 100644 --- a/examples/rp235x/src/bin/uart_buffered_split.rs +++ b/examples/rp235x/src/bin/uart_buffered_split.rs @@ -48,7 +48,7 @@ async fn main(spawner: Spawner) { } #[embassy_executor::task] -async fn reader(mut rx: BufferedUartRx<'static, UART0>) { +async fn reader(mut rx: BufferedUartRx) { info!("Reading..."); loop { let mut buf = [0; 31]; diff --git a/examples/rp235x/src/bin/uart_unidir.rs b/examples/rp235x/src/bin/uart_unidir.rs index 4e98f9e1e..45c9c8407 100644 --- a/examples/rp235x/src/bin/uart_unidir.rs +++ b/examples/rp235x/src/bin/uart_unidir.rs @@ -39,7 +39,7 @@ async fn main(spawner: Spawner) { } #[embassy_executor::task] -async fn reader(mut rx: UartRx<'static, UART1, Async>) { +async fn reader(mut rx: UartRx<'static, Async>) { info!("Reading..."); loop { // read a total of 4 transmissions (32 / 8) and then print the result -- cgit From 8f3b6643b311a5a16fe6a64227066627990452b9 Mon Sep 17 00:00:00 2001 From: Marc <35759328+marcemmers@users.noreply.github.com> Date: Fri, 2 May 2025 14:36:08 +0200 Subject: And another fix, should be the last failing example --- examples/rp/src/bin/sharing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/rp/src/bin/sharing.rs b/examples/rp/src/bin/sharing.rs index 5416e20ce..497c4f845 100644 --- a/examples/rp/src/bin/sharing.rs +++ b/examples/rp/src/bin/sharing.rs @@ -31,7 +31,7 @@ use rand::RngCore; use static_cell::{ConstStaticCell, StaticCell}; use {defmt_rtt as _, panic_probe as _}; -type UartAsyncMutex = mutex::Mutex>; +type UartAsyncMutex = mutex::Mutex>; struct MyType { inner: u32, -- cgit From 3441e805070c7efb7cad20a84d1986e215b4de3d Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Fri, 2 May 2025 23:51:28 +0200 Subject: first batch of changes after review --- embassy-rp/src/clocks.rs | 324 ++++++++++++-------------------- examples/rp/src/bin/overclock.rs | 11 +- examples/rp/src/bin/overclock_manual.rs | 20 +- tests/rp/src/bin/overclock.rs | 10 +- 4 files changed, 134 insertions(+), 231 deletions(-) diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index 005564b8b..107e499b7 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -9,9 +9,8 @@ //! //! For most users, these functions provide an easy way to configure clocks: //! -//! - `ClockConfig::crystal(12_000_000)` - Default configuration with 12MHz crystal giving 125MHz system clock //! - `ClockConfig::at_sys_frequency_mhz(200)` - Set system clock to a specific frequency with automatic voltage scaling -//! - `ClockConfig::with_external_crystal(16_000_000)` - Configure with a non-standard crystal frequency +//! - `ClockConfig::crystal(12_000_000)` - Default configuration with 12MHz crystal giving 125MHz system clock //! //! ## Manual Configuration //! @@ -34,19 +33,9 @@ //! }); //! //! // Set voltage for overclocking -//! config.voltage_scale = Some(VoltageScale::V1_15); +//! config.core_voltage = CoreVoltage::V1_15; //! ``` //! -//! ## Voltage Scaling for Overclocking (RP2040 only) -//! -//! When overclocking beyond 133MHz, higher core voltages are needed: -//! -//! - Up to 133MHz: `VoltageScale::V1_10` (default) -//! - 133-200MHz: `VoltageScale::V1_15` -//! - Above 200MHz: `VoltageScale::V1_20` or higher -//! -//! The `at_sys_frequency_mhz()` function automatically sets appropriate voltages. -//! //! ## Examples //! //! ### Standard 125MHz configuration @@ -61,16 +50,16 @@ //! //! ### Overclock to 200MHz //! ```rust,ignore -//! let config = ClockConfig::at_sys_frequency_mhz(200); +//! let config = ClockConfig::crystal_freq(200_000_000); //! ``` //! //! ### Manual configuration for advanced scenarios //! ```rust,ignore -//! use embassy_rp::clocks::{ClockConfig, XoscConfig, PllConfig, VoltageScale}; +//! use embassy_rp::clocks::{ClockConfig, XoscConfig, PllConfig, CoreVoltage}; //! //! // Start with defaults and customize //! let mut config = ClockConfig::default(); -//! config.voltage_scale = Some(VoltageScale::V1_15); +//! config.core_voltage = CoreVoltage::V1_15; //! // Set other parameters as needed... //! ``` @@ -144,14 +133,16 @@ pub enum PeriClkSrc { // Gpin1 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN1 as _ , } -/// Core voltage scaling options for RP2040. +/// Core voltage regulator settings for RP2040. /// /// The RP2040 voltage regulator can be configured for different output voltages. /// Higher voltages allow for higher clock frequencies but increase power consumption and heat. #[cfg(feature = "rp2040")] #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(u8)] -pub enum VoltageScale { +pub enum CoreVoltage { + /// 0.80V - Suitable for lower frequencies + V0_80 = 0b0000, /// 0.85V V0_85 = 0b0110, /// 0.90V @@ -162,11 +153,11 @@ pub enum VoltageScale { V1_00 = 0b1001, /// 1.05V V1_05 = 0b1010, - /// 1.10V + /// 1.10V - Default voltage level V1_10 = 0b1011, - /// 1.15V + /// 1.15V - Required for overclocking to 133-200MHz V1_15 = 0b1100, - /// 1.20V + /// 1.20V - Required for overclocking above 200MHz V1_20 = 0b1101, /// 1.25V V1_25 = 0b1110, @@ -175,21 +166,22 @@ pub enum VoltageScale { } #[cfg(feature = "rp2040")] -impl VoltageScale { +impl CoreVoltage { /// Get the recommended Brown-Out Detection (BOD) setting for this voltage. /// Sets the BOD threshold to approximately 90% of the core voltage. fn recommended_bod(self) -> u8 { match self { - VoltageScale::V0_85 => 0b0111, // 0.774V (~91% of 0.85V) - VoltageScale::V0_90 => 0b1000, // 0.817V (~91% of 0.90V) - VoltageScale::V0_95 => 0b1001, // 0.860V (~91% of 0.95V) - VoltageScale::V1_00 => 0b1010, // 0.903V (~90% of 1.00V) - VoltageScale::V1_05 => 0b1011, // 0.946V (~90% of 1.05V) - VoltageScale::V1_10 => 0b1100, // 0.989V (~90% of 1.10V) - VoltageScale::V1_15 => 0b1101, // 1.032V (~90% of 1.15V) - VoltageScale::V1_20 => 0b1110, // 1.075V (~90% of 1.20V) - VoltageScale::V1_25 => 0b1111, // 1.118V (~89% of 1.25V) - VoltageScale::V1_30 => 0b1111, // 1.118V (~86% of 1.30V) - using max available threshold + CoreVoltage::V0_80 => 0b0110, // 0.720V (~90% of 0.80V) + CoreVoltage::V0_85 => 0b0111, // 0.774V (~91% of 0.85V) + CoreVoltage::V0_90 => 0b1000, // 0.817V (~91% of 0.90V) + CoreVoltage::V0_95 => 0b1001, // 0.860V (~91% of 0.95V) + CoreVoltage::V1_00 => 0b1010, // 0.903V (~90% of 1.00V) + CoreVoltage::V1_05 => 0b1011, // 0.946V (~90% of 1.05V) + CoreVoltage::V1_10 => 0b1100, // 0.989V (~90% of 1.10V) + CoreVoltage::V1_15 => 0b1101, // 1.032V (~90% of 1.15V) + CoreVoltage::V1_20 => 0b1110, // 1.075V (~90% of 1.20V) + CoreVoltage::V1_25 => 0b1111, // 1.118V (~89% of 1.25V) + CoreVoltage::V1_30 => 0b1111, // 1.118V (~86% of 1.30V) - using max available threshold } } } @@ -214,9 +206,9 @@ pub struct ClockConfig { /// RTC clock configuration. #[cfg(feature = "rp2040")] pub rtc_clk: Option, - /// Core voltage scaling (RP2040 only). Defaults to 1.10V if None. + /// Core voltage scaling (RP2040 only). Defaults to 1.10V. #[cfg(feature = "rp2040")] - pub voltage_scale: Option, + pub core_voltage: CoreVoltage, /// Voltage stabilization delay in microseconds. /// If not set, defaults will be used based on voltage level. #[cfg(feature = "rp2040")] @@ -255,7 +247,7 @@ impl Default for ClockConfig { #[cfg(feature = "rp2040")] rtc_clk: None, #[cfg(feature = "rp2040")] - voltage_scale: None, + core_voltage: CoreVoltage::V1_10, #[cfg(feature = "rp2040")] voltage_stabilization_delay_us: None, // gpin0: None, @@ -327,7 +319,7 @@ impl ClockConfig { phase: 0, }), #[cfg(feature = "rp2040")] - voltage_scale: None, // Use hardware default (1.10V) + core_voltage: CoreVoltage::V1_10, // Use hardware default (1.10V) #[cfg(feature = "rp2040")] voltage_stabilization_delay_us: None, // gpin0: None, @@ -371,7 +363,7 @@ impl ClockConfig { phase: 0, }), #[cfg(feature = "rp2040")] - voltage_scale: None, // Use hardware default (1.10V) + core_voltage: CoreVoltage::V1_10, // Use hardware default (1.10V) #[cfg(feature = "rp2040")] voltage_stabilization_delay_us: None, // gpin0: None, @@ -379,146 +371,59 @@ impl ClockConfig { } } - /// Configure the system clock to a specific frequency in MHz. - /// - /// This is a user-friendly way to configure the system clock, similar to - /// the Pico SDK's approach. It automatically handles voltage scaling based on the - /// requested frequency and uses the standard 12MHz crystal found on most RP2040 boards. - /// - /// # Arguments - /// - /// * `sys_freq_mhz` - The target system clock frequency in MHz - /// - /// # Example - /// - /// ```rust,ignore - /// // Overclock to 200MHz - /// let config = ClockConfig::at_sys_frequency_mhz(200); - /// ``` - #[cfg(feature = "rp2040")] - pub fn at_sys_frequency_mhz(sys_freq_mhz: u32) -> Self { - // For 125MHz, use exactly the same config as the default to avoid any differences - if sys_freq_mhz == 125 { - return Self::crystal(12_000_000); - } - - // For other frequencies, provide appropriate voltage scaling and PLL configuration - // Standard crystal on Raspberry Pi Pico boards is 12MHz - const DEFAULT_CRYSTAL_HZ: u32 = 12_000_000; - - let sys_freq_hz = sys_freq_mhz * 1_000_000; - let config = Self::crystal_freq(DEFAULT_CRYSTAL_HZ, sys_freq_hz); - - config - } - - /// Configure the system clock to a specific frequency in Hz, using a custom crystal frequency. - /// - /// This more flexible version allows specifying both the crystal frequency and target - /// system frequency for boards with non-standard crystals. - /// - /// # Arguments - /// - /// * `crystal_hz` - The frequency of the external crystal in Hz - /// * `sys_freq_hz` - The target system clock frequency in Hz - /// - /// # Example - /// - /// ```rust,ignore - /// // Use a non-standard 16MHz crystal to achieve 250MHz - /// let config = ClockConfig::with_custom_crystal(16_000_000, 250_000_000); - /// ``` - #[cfg(feature = "rp2040")] - pub fn with_custom_crystal(crystal_hz: u32, sys_freq_hz: u32) -> Self { - Self::crystal_freq(crystal_hz, sys_freq_hz) - } - /// Configure clocks derived from an external crystal with specific system frequency. /// /// This function calculates optimal PLL parameters to achieve the requested system - /// frequency from the given crystal frequency. It's used internally by higher-level - /// configuration functions. + /// frequency. This only works for the usual 12MHz crystal. In case a different crystal is used, + /// You will have to set the PLL parameters manually. /// /// # Arguments /// - /// * `crystal_hz` - The frequency of the external crystal in Hz /// * `sys_freq_hz` - The desired system clock frequency in Hz /// /// # Returns /// /// A ClockConfig configured to achieve the requested system frequency using the - /// specified crystal, or panic if no valid parameters can be found. + /// the usual 12Mhz crystal, or panic if no valid parameters can be found. + /// + /// # Note on core voltage: + /// To date the only officially documented core voltages (see Datasheet section 2.15.3.1. Instances) are: + /// - Up to 133MHz: V1_10 (default) + /// - Above 133MHz: V1_15, but in the context of the datasheet covering reaching up to 200Mhz + /// That way all other frequencies below 133MHz or above 200MHz are not explicitly documented and not covered here. + /// In case You want to go below 133MHz or above 200MHz and want a different voltage, You will have to set that manually and with caution. #[cfg(feature = "rp2040")] - fn crystal_freq(crystal_hz: u32, sys_freq_hz: u32) -> Self { + pub fn crystal_freq(sys_freq_hz: u32) -> Self { + // Start with the standard configuration from crystal() + const DEFAULT_CRYSTAL_HZ: u32 = 12_000_000; + let mut config = Self::crystal(DEFAULT_CRYSTAL_HZ); + + // No need to modify anything if target frequency is already 125MHz + // (which is what crystal() configures by default) + if sys_freq_hz == 125_000_000 { + return config; + } + // Find optimal PLL parameters for the requested frequency - let sys_pll_params = find_pll_params(crystal_hz, sys_freq_hz) + let sys_pll_params = find_pll_params(DEFAULT_CRYSTAL_HZ, sys_freq_hz) .unwrap_or_else(|| panic!("Could not find valid PLL parameters for system clock")); + // Replace the sys_pll configuration with our custom parameters + if let Some(xosc) = &mut config.xosc { + xosc.sys_pll = Some(sys_pll_params); + } + // Set the voltage scale based on the target frequency // Higher frequencies require higher voltage - let voltage_scale = match sys_freq_hz { - freq if freq > 200_000_000 => Some(VoltageScale::V1_20), - freq if freq > 133_000_000 => Some(VoltageScale::V1_15), - _ => None, // Use default voltage (V1_10) - }; - - // For USB PLL, we always want 48MHz for USB - let usb_pll_params = if crystal_hz == 12_000_000 { - // For standard 12MHz crystal, use the default parameters - PllConfig { - refdiv: 1, - fbdiv: 120, - post_div1: 6, - post_div2: 5, - } - } else { - // For other crystals, calculate parameters to get 48MHz - find_pll_params(crystal_hz, 48_000_000) - .unwrap_or_else(|| panic!("Could not find valid PLL parameters for USB clock")) - }; - - Self { - rosc: Some(RoscConfig { - hz: 6_500_000, - range: RoscRange::Medium, - drive_strength: [0; 8], - div: 16, - }), - xosc: Some(XoscConfig { - hz: crystal_hz, - sys_pll: Some(sys_pll_params), - usb_pll: Some(usb_pll_params), - delay_multiplier: 128, - }), - ref_clk: RefClkConfig { - src: RefClkSrc::Xosc, - div: 1, - }, - sys_clk: SysClkConfig { - src: SysClkSrc::PllSys, - div_int: 1, - div_frac: 0, - }, - peri_clk_src: Some(PeriClkSrc::Sys), - usb_clk: Some(UsbClkConfig { - src: UsbClkSrc::PllUsb, - div: 1, - phase: 0, - }), - adc_clk: Some(AdcClkConfig { - src: AdcClkSrc::PllUsb, - div: 1, - phase: 0, - }), - rtc_clk: Some(RtcClkConfig { - src: RtcClkSrc::PllUsb, - div_int: 1024, - div_frac: 0, - phase: 0, - }), - voltage_scale, - voltage_stabilization_delay_us: None, + #[cfg(feature = "rp2040")] + { + config.core_voltage = match sys_freq_hz { + freq if freq > 133_000_000 => CoreVoltage::V1_15, + _ => CoreVoltage::V1_10, // Use default voltage (V1_10) + }; } + + config } /// Configure with manual PLL settings for full control over system clock @@ -530,7 +435,7 @@ impl ClockConfig { /// /// * `xosc_hz` - The frequency of the external crystal in Hz /// * `pll_config` - The PLL configuration parameters to achieve desired frequency - /// * `voltage_scale` - Optional voltage scaling for overclocking (required for >133MHz) + /// * `core_voltage` - Voltage scaling for overclocking (required for >133MHz) /// /// # Returns /// @@ -549,11 +454,11 @@ impl ClockConfig { /// post_div1: 3, // First post divider (1200 MHz / 3 = 400 MHz) /// post_div2: 2, // Second post divider (400 MHz / 2 = 200 MHz) /// }, - /// Some(VoltageScale::V1_15) + /// CoreVoltage::V1_15 /// ); /// ``` #[cfg(feature = "rp2040")] - pub fn manual_pll(xosc_hz: u32, pll_config: PllConfig, voltage_scale: Option) -> Self { + pub fn manual_pll(xosc_hz: u32, pll_config: PllConfig, core_voltage: CoreVoltage) -> Self { // Calculate the actual output frequency for documentation // let ref_freq = xosc_hz / pll_config.refdiv as u32; // let vco_freq = ref_freq * pll_config.fbdiv as u32; @@ -587,7 +492,7 @@ impl ClockConfig { div_frac: 0, }; - config.voltage_scale = voltage_scale; + config.core_voltage = core_voltage; config.peri_clk_src = Some(PeriClkSrc::Sys); // Set reasonable defaults for other clocks @@ -865,7 +770,7 @@ pub struct RtcClkConfig { /// /// # Parameters /// -/// * `input_hz`: The input frequency in Hz (typically the crystal frequency, e.g. 12MHz) +/// * `input_hz`: The input frequency in Hz (typically the crystal frequency, e.g. 12MHz for th most common one used on rp2040 boards) /// * `target_hz`: The desired output frequency in Hz (e.g. 125MHz for standard RP2040 operation) /// /// # Returns @@ -990,7 +895,8 @@ pub(crate) unsafe fn init(config: ClockConfig) { // Set Core Voltage (RP2040 only), if we have config for it and we're not using the default #[cfg(feature = "rp2040")] - if let Some(voltage) = config.voltage_scale { + { + let voltage = config.core_voltage; let vreg = pac::VREG_AND_CHIP_RESET; let current_vsel = vreg.vreg().read().vsel(); let target_vsel = voltage as u8; @@ -1002,9 +908,9 @@ pub(crate) unsafe fn init(config: ClockConfig) { // Wait for the voltage to stabilize. Use the provided delay or default based on voltage let settling_time_us = config.voltage_stabilization_delay_us.unwrap_or_else(|| { match voltage { - VoltageScale::V1_15 => 1000, // 1ms for 1.15V - VoltageScale::V1_20 | VoltageScale::V1_25 | VoltageScale::V1_30 => 2000, // 2ms for higher voltages - _ => 0, // no delay for all others + CoreVoltage::V1_15 => 1000, // 1ms for 1.15V + CoreVoltage::V1_20 | CoreVoltage::V1_25 | CoreVoltage::V1_30 => 2000, // 2ms for higher voltages + _ => 0, // no delay for all others } }); @@ -1042,9 +948,8 @@ pub(crate) unsafe fn init(config: ClockConfig) { }; CLOCKS.xosc.store(xosc_freq, Ordering::Relaxed); - // SETUP TEMPORARY STABLE CLOCKS FIRST + // Setup temporary stable clocks first // Configure USB PLL for our stable temporary clock - // This follows the SDK's approach of using USB PLL as a stable intermediate clock let pll_usb_freq = match &config.xosc { Some(config) => match &config.usb_pll { Some(pll_usb_config) => { @@ -1055,7 +960,13 @@ pub(crate) unsafe fn init(config: ClockConfig) { reset::unreset_wait(peris); // Configure the USB PLL - this should give us 48MHz - let usb_pll_freq = configure_pll(pac::PLL_USB, xosc_freq, *pll_usb_config); + let usb_pll_freq = match configure_pll(pac::PLL_USB, xosc_freq, *pll_usb_config) { + Ok(freq) => freq, + Err(_) => { + panic!("Failed to configure USB PLL"); + } + }; + CLOCKS.pll_usb.store(usb_pll_freq, Ordering::Relaxed); usb_pll_freq } @@ -1074,7 +985,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { while c.clk_ref_selected().read() != pac::clocks::regs::ClkRefSelected(1 << ClkRefCtrlSrc::XOSC_CLKSRC as u32) {} // First switch the system clock to a stable source (USB PLL at 48MHz) - // This follows the Pico SDK's approach to ensure stability during reconfiguration + // This follows the official Pico SDK's approach to ensure stability during reconfiguration c.clk_sys_ctrl().write(|w| { w.set_auxsrc(ClkSysCtrlAuxsrc::CLKSRC_PLL_USB); w.set_src(ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX); @@ -1101,7 +1012,12 @@ pub(crate) unsafe fn init(config: ClockConfig) { reset::unreset_wait(peris); // Configure the SYS PLL - let pll_sys_freq = configure_pll(pac::PLL_SYS, xosc_freq, *sys_pll_config); + let pll_sys_freq = match configure_pll(pac::PLL_SYS, xosc_freq, *sys_pll_config) { + Ok(freq) => freq, + Err(_) => { + panic!("Failed to configure system PLL"); + } + }; // Ensure PLL is locked and stable cortex_m::asm::delay(100); @@ -1411,7 +1327,7 @@ fn start_xosc(crystal_hz: u32, delay_multiplier: u32) { /// PLL (Phase-Locked Loop) configuration #[inline(always)] -fn configure_pll(p: pac::pll::Pll, input_freq: u32, config: PllConfig) -> u32 { +fn configure_pll(p: pac::pll::Pll, input_freq: u32, config: PllConfig) -> Result { // Calculate reference frequency let ref_freq = input_freq / config.refdiv as u32; @@ -1482,7 +1398,7 @@ fn configure_pll(p: pac::pll::Pll, input_freq: u32, config: PllConfig) -> u32 { timeout -= 1; if timeout == 0 { // PLL failed to lock, return 0 to indicate failure - return 0; + return Err("PLL failed to lock"); } } @@ -1502,7 +1418,7 @@ fn configure_pll(p: pac::pll::Pll, input_freq: u32, config: PllConfig) -> u32 { cortex_m::asm::delay(100); // Calculate and return actual output frequency - vco_freq / ((config.post_div1 * config.post_div2) as u32) + Ok(vco_freq / ((config.post_div1 * config.post_div2) as u32)) } /// General purpose input clock pin. @@ -1883,25 +1799,6 @@ pub fn dormant_sleep() { mod tests { use super::*; - #[cfg(feature = "rp2040")] - #[test] - fn test_voltage_scale_bod_values() { - // Test that each voltage level maps to the correct BOD threshold (approx. 90% of VDD) - // This verifies our BOD settings match our documentation - { - assert_eq!(VoltageScale::V0_85.recommended_bod(), 0b0111); // ~0.774V (91% of 0.85V) - assert_eq!(VoltageScale::V0_90.recommended_bod(), 0b1000); // ~0.817V (91% of 0.90V) - assert_eq!(VoltageScale::V0_95.recommended_bod(), 0b1001); // ~0.860V (91% of 0.95V) - assert_eq!(VoltageScale::V1_00.recommended_bod(), 0b1010); // ~0.903V (90% of 1.00V) - assert_eq!(VoltageScale::V1_05.recommended_bod(), 0b1011); // ~0.946V (90% of 1.05V) - assert_eq!(VoltageScale::V1_10.recommended_bod(), 0b1100); // ~0.989V (90% of 1.10V) - assert_eq!(VoltageScale::V1_15.recommended_bod(), 0b1101); // ~1.032V (90% of 1.15V) - assert_eq!(VoltageScale::V1_20.recommended_bod(), 0b1110); // ~1.075V (90% of 1.20V) - assert_eq!(VoltageScale::V1_25.recommended_bod(), 0b1111); // ~1.118V (89% of 1.25V) - assert_eq!(VoltageScale::V1_30.recommended_bod(), 0b1111); // ~1.118V (86% of 1.30V) - using max available - } - } - #[cfg(feature = "rp2040")] #[test] fn test_find_pll_params() { @@ -1942,7 +1839,12 @@ mod tests { let vco_freq = (16_000_000 / params.refdiv as u32) as u64 * params.fbdiv as u64; let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32; - // With a 16 MHz crystal, we might not get exactly 125 MHz + // Test non-standard crystal with 15 MHz + let params = find_pll_params(15_000_000, 125_000_000).unwrap(); + let vco_freq = (15_000_000 / params.refdiv as u32) as u64 * params.fbdiv as u64; + let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32; + + // With a 15 MHz crystal, we might not get exactly 125 MHz // Check that it's close enough (within 0.2% margin) let freq_diff = if output_freq > 125_000_000 { output_freq - 125_000_000 @@ -2033,11 +1935,11 @@ mod tests { post_div1: 3, post_div2: 2, }, - Some(VoltageScale::V1_15), + CoreVoltage::V1_15, ); // Check voltage scale was set correctly - assert_eq!(config.voltage_scale, Some(VoltageScale::V1_15)); + assert_eq!(config.core_voltage, CoreVoltage::V1_15); // Check PLL config was set correctly assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().refdiv, 1); @@ -2065,19 +1967,23 @@ mod tests { fn test_auto_voltage_scaling() { { // Test automatic voltage scaling based on frequency - // Under 133 MHz should use default voltage (None) - let config = ClockConfig::at_sys_frequency_mhz(125); - assert_eq!(config.voltage_scale, None); + // Under 133 MHz should use default voltage (V1_10) + let config = ClockConfig::crystal_freq(125_000_000); + assert_eq!(config.core_voltage, CoreVoltage::V1_10); // 133-200 MHz should use V1_15 - let config = ClockConfig::at_sys_frequency_mhz(150); - assert_eq!(config.voltage_scale, Some(VoltageScale::V1_15)); - let config = ClockConfig::at_sys_frequency_mhz(200); - assert_eq!(config.voltage_scale, Some(VoltageScale::V1_15)); - - // Above 200 MHz should use V1_20 - let config = ClockConfig::at_sys_frequency_mhz(250); - assert_eq!(config.voltage_scale, Some(VoltageScale::V1_20)); + let config = ClockConfig::crystal_freq(150_000_000); + assert_eq!(config.core_voltage, CoreVoltage::V1_15); + let config = ClockConfig::crystal_freq(200_000_000); + assert_eq!(config.core_voltage, CoreVoltage::V1_15); + + // Above 200 MHz should use V1_25 + let config = ClockConfig::crystal_freq(250_000_000); + assert_eq!(config.core_voltage, CoreVoltage::V1_15); + + // Below 125 MHz should use V1_10 + let config = ClockConfig::crystal_freq(100_000_000); + assert_eq!(config.core_voltage, CoreVoltage::V1_10); } } } diff --git a/examples/rp/src/bin/overclock.rs b/examples/rp/src/bin/overclock.rs index e3ac77340..f9a8c94d0 100644 --- a/examples/rp/src/bin/overclock.rs +++ b/examples/rp/src/bin/overclock.rs @@ -1,6 +1,6 @@ //! # Overclocking the RP2040 to 200 MHz //! -//! This example demonstrates how to configure the RP2040 to run at 200 MHz using a higher level API. +//! This example demonstrates how to configure the RP2040 to run at 200 MHz. #![no_std] #![no_main] @@ -17,19 +17,18 @@ const COUNT_TO: i64 = 10_000_000; #[embassy_executor::main] async fn main(_spawner: Spawner) -> ! { - // Set up for clock frequency of 200 MHz - // This will set all the necessary defaults including slightly raised voltage - let config = Config::new(ClockConfig::at_sys_frequency_mhz(200)); + // Set up for clock frequency of 200 MHz, setting all necessary defaults. + let config = Config::new(ClockConfig::crystal_freq(200_000_000)); // Show the voltage scale for verification - info!("System core voltage: {}", Debug2Format(&config.clocks.voltage_scale)); + info!("System core voltage: {}", Debug2Format(&config.clocks.core_voltage)); // Initialize the peripherals let p = embassy_rp::init(config); // Show CPU frequency for verification let sys_freq = clk_sys_freq(); - info!("System clock frequency: {} Hz", sys_freq); + info!("System clock frequency: {} MHz", sys_freq / 1_000_000); // LED to indicate the system is running let mut led = Output::new(p.PIN_25, Level::Low); diff --git a/examples/rp/src/bin/overclock_manual.rs b/examples/rp/src/bin/overclock_manual.rs index ad6abf0e7..35160b250 100644 --- a/examples/rp/src/bin/overclock_manual.rs +++ b/examples/rp/src/bin/overclock_manual.rs @@ -8,7 +8,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_rp::clocks; -use embassy_rp::clocks::{ClockConfig, PllConfig, VoltageScale}; +use embassy_rp::clocks::{ClockConfig, CoreVoltage, PllConfig}; use embassy_rp::config::Config; use embassy_rp::gpio::{Level, Output}; use embassy_time::{Duration, Instant, Timer}; @@ -16,23 +16,21 @@ use {defmt_rtt as _, panic_probe as _}; const COUNT_TO: i64 = 10_000_000; -/// Configure the RP2040 for 200 MHz operation by manually specifying -/// all the required parameters instead of using higher-level APIs. +/// Configure the RP2040 for 200 MHz operation by manually specifying the PLL settings. fn configure_manual_overclock() -> Config { // Set the PLL configuration manually, starting from default values let mut config = Config::default(); - // Set the system clock to 200 MHz using a PLL with a reference frequency of 12 MHz + // Set the system clock to 200 MHz config.clocks = ClockConfig::manual_pll( - 12_000_000, + 12_000_000, // Crystal frequency, 12 MHz is common. If using custom, set to your value. PllConfig { - refdiv: 1, - fbdiv: 100, - post_div1: 3, - post_div2: 2, + refdiv: 1, // Reference divider + fbdiv: 100, // Feedback divider + post_div1: 3, // Post divider 1 + post_div2: 2, // Post divider 2 }, - // For 200 MHz, we need a voltage scale of 1.15V - Some(VoltageScale::V1_15), + CoreVoltage::V1_15, // Core voltage, should be set to V1_15 for 200 MHz ); config diff --git a/tests/rp/src/bin/overclock.rs b/tests/rp/src/bin/overclock.rs index e4845a55f..6c58a6b90 100644 --- a/tests/rp/src/bin/overclock.rs +++ b/tests/rp/src/bin/overclock.rs @@ -14,7 +14,7 @@ use embassy_rp::clocks; #[cfg(feature = "rp2040")] use embassy_rp::clocks::ClockConfig; #[cfg(feature = "rp2040")] -use embassy_rp::clocks::VoltageScale; +use embassy_rp::clocks::CoreVoltage; use embassy_rp::config::Config; use embassy_time::Instant; use {defmt_rtt as _, panic_probe as _}; @@ -31,15 +31,15 @@ async fn main(_spawner: Spawner) { // Initialize with 200MHz clock configuration for RP2040, other chips will use default clock #[cfg(feature = "rp2040")] { - config.clocks = ClockConfig::at_sys_frequency_mhz(200); - let voltage = config.clocks.voltage_scale.unwrap(); - assert!(matches!(voltage, VoltageScale::V1_15), "Expected voltage scale V1_15"); + config.clocks = ClockConfig::crystal_freq(200_000_000); + let voltage = config.clocks.core_voltage; + assert!(matches!(voltage, CoreVoltage::V1_15), "Expected voltage scale V1_15"); } let _p = embassy_rp::init(config); + // Test the system speed let (time_elapsed, clk_sys_freq) = { - // Test the system speed let mut counter = 0; let start = Instant::now(); while counter < COUNT_TO { -- cgit From 3d9cac361ed6bd0cd00cab7bf924608c89de9108 Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Sat, 3 May 2025 14:46:30 +0200 Subject: Add PIO clock divider utility and refactor related modules --- embassy-rp/src/pio_programs/clock_divider.rs | 25 +++++++++++++++++++++++++ embassy-rp/src/pio_programs/hd44780.rs | 14 +++++--------- embassy-rp/src/pio_programs/mod.rs | 1 + embassy-rp/src/pio_programs/rotary_encoder.rs | 10 +++------- embassy-rp/src/pio_programs/stepper.rs | 18 +++++++++--------- 5 files changed, 43 insertions(+), 25 deletions(-) create mode 100644 embassy-rp/src/pio_programs/clock_divider.rs diff --git a/embassy-rp/src/pio_programs/clock_divider.rs b/embassy-rp/src/pio_programs/clock_divider.rs new file mode 100644 index 000000000..02e353f53 --- /dev/null +++ b/embassy-rp/src/pio_programs/clock_divider.rs @@ -0,0 +1,25 @@ +//! Helper functions for calculating PIO clock dividers + +use fixed::traits::ToFixed; +use fixed::types::extra::U8; + +use crate::clocks::clk_sys_freq; + +/// Calculate a PIO clock divider value based on the desired target frequency. +/// +/// # Arguments +/// +/// * `target_hz` - The desired PIO clock frequency in Hz +/// +/// # Returns +/// +/// A fixed-point divider value suitable for use in a PIO state machine configuration +#[inline] +pub fn calculate_pio_clock_divider(target_hz: u32) -> fixed::FixedU32 { + // Requires a non-zero frequency + assert!(target_hz > 0, "PIO clock frequency cannot be zero"); + + // Calculate the divider + let divider = (clk_sys_freq() + target_hz / 2) / target_hz; + divider.to_fixed() +} diff --git a/embassy-rp/src/pio_programs/hd44780.rs b/embassy-rp/src/pio_programs/hd44780.rs index 3aa54495f..546c85a89 100644 --- a/embassy-rp/src/pio_programs/hd44780.rs +++ b/embassy-rp/src/pio_programs/hd44780.rs @@ -1,11 +1,11 @@ //! [HD44780 display driver](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) -use crate::clocks::clk_sys_freq; use crate::dma::{AnyChannel, Channel}; use crate::pio::{ Common, Config, Direction, FifoJoin, Instance, Irq, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine, }; +use crate::pio_programs::clock_divider::calculate_pio_clock_divider; use crate::Peri; /// This struct represents a HD44780 program that takes command words ( <0:4>) @@ -136,10 +136,8 @@ impl<'l, P: Instance, const S: usize> PioHD44780<'l, P, S> { let mut cfg = Config::default(); cfg.use_program(&word_prg.prg, &[&e]); - // Scale the divider based on system clock frequency - // Original: 125 at 125 MHz (1 MHz PIO clock) - let word_divider = (clk_sys_freq() / 1_000_000) as u8; // Target 1 MHz PIO clock - cfg.clock_divider = word_divider.into(); + // Target 1 MHz PIO clock (each cycle is 1µs) + cfg.clock_divider = calculate_pio_clock_divider(1_000_000); cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); cfg.shift_out = ShiftConfig { @@ -167,10 +165,8 @@ impl<'l, P: Instance, const S: usize> PioHD44780<'l, P, S> { let mut cfg = Config::default(); cfg.use_program(&seq_prg.prg, &[&e]); - // Original: 8 at 125 MHz (~15.6 MHz PIO clock) - // Comment says ~64ns/insn which is 1/(15.6 MHz) = ~64ns - let seq_divider = (clk_sys_freq() / 15_600_000) as u8; // Target ~15.6 MHz PIO clock (~64ns/insn) - cfg.clock_divider = seq_divider.into(); + // Target ~15.6 MHz PIO clock (~64ns/insn) + cfg.clock_divider = calculate_pio_clock_divider(15_600_000); cfg.set_jmp_pin(&db7); cfg.set_set_pins(&[&rs, &rw]); diff --git a/embassy-rp/src/pio_programs/mod.rs b/embassy-rp/src/pio_programs/mod.rs index 74537825b..8eac328b3 100644 --- a/embassy-rp/src/pio_programs/mod.rs +++ b/embassy-rp/src/pio_programs/mod.rs @@ -1,5 +1,6 @@ //! Pre-built pio programs for common interfaces +pub mod clock_divider; pub mod hd44780; pub mod i2s; pub mod onewire; diff --git a/embassy-rp/src/pio_programs/rotary_encoder.rs b/embassy-rp/src/pio_programs/rotary_encoder.rs index 71567a602..70b3795e9 100644 --- a/embassy-rp/src/pio_programs/rotary_encoder.rs +++ b/embassy-rp/src/pio_programs/rotary_encoder.rs @@ -1,12 +1,10 @@ //! PIO backed quadrature encoder -use fixed::traits::ToFixed; - -use crate::clocks::clk_sys_freq; use crate::gpio::Pull; use crate::pio::{ Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine, }; +use crate::pio_programs::clock_divider::calculate_pio_clock_divider; use crate::Peri; /// This struct represents an Encoder program loaded into pio instruction memory. @@ -50,10 +48,8 @@ impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> { cfg.fifo_join = FifoJoin::RxOnly; cfg.shift_in.direction = ShiftDirection::Left; - // Original: 10_000 at 125 MHz (12.5 KHz PIO clock) - // Scale divider to maintain same PIO clock frequency at different system clocks - let divider = (clk_sys_freq() as f32 / 12_500.0).to_fixed(); - cfg.clock_divider = divider; + // Target 12.5 KHz PIO clock + cfg.clock_divider = calculate_pio_clock_divider(12_500); cfg.use_program(&program.prg, &[]); sm.set_config(&cfg); diff --git a/embassy-rp/src/pio_programs/stepper.rs b/embassy-rp/src/pio_programs/stepper.rs index 6878c32f5..0e9a8daf9 100644 --- a/embassy-rp/src/pio_programs/stepper.rs +++ b/embassy-rp/src/pio_programs/stepper.rs @@ -2,12 +2,8 @@ use core::mem::{self, MaybeUninit}; -use fixed::traits::ToFixed; -use fixed::types::extra::U8; -use fixed::FixedU32; - -use crate::clocks::clk_sys_freq; use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, PioPin, StateMachine}; +use crate::pio_programs::clock_divider::calculate_pio_clock_divider; use crate::Peri; /// This struct represents a Stepper driver program loaded into pio instruction memory. @@ -65,7 +61,9 @@ impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> { sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]); let mut cfg = Config::default(); cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]); - cfg.clock_divider = (clk_sys_freq() / (100 * 136)).to_fixed(); + + cfg.clock_divider = calculate_pio_clock_divider(100 * 136); + cfg.use_program(&program.prg, &[]); sm.set_config(&cfg); sm.set_enable(true); @@ -74,9 +72,11 @@ impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> { /// Set pulse frequency pub fn set_frequency(&mut self, freq: u32) { - let clock_divider: FixedU32 = (clk_sys_freq() / (freq * 136)).to_fixed(); - assert!(clock_divider <= 65536, "clkdiv must be <= 65536"); - assert!(clock_divider >= 1, "clkdiv must be >= 1"); + let clock_divider = calculate_pio_clock_divider(freq * 136); + let divider_f32 = clock_divider.to_num::(); + assert!(divider_f32 <= 65536.0, "clkdiv must be <= 65536"); + assert!(divider_f32 >= 1.0, "clkdiv must be >= 1"); + self.sm.set_clock_divider(clock_divider); self.sm.clkdiv_restart(); } -- cgit From 0d03aa0bec01fb0289915861d997bf7f2cf8d232 Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Mon, 5 May 2025 22:55:09 +0200 Subject: rework init() --- embassy-rp/src/clocks.rs | 256 +++++++++++++++++++---------------------------- 1 file changed, 103 insertions(+), 153 deletions(-) diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index 107e499b7..a4cc129ab 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -168,20 +168,20 @@ pub enum CoreVoltage { #[cfg(feature = "rp2040")] impl CoreVoltage { /// Get the recommended Brown-Out Detection (BOD) setting for this voltage. - /// Sets the BOD threshold to approximately 90% of the core voltage. + /// Sets the BOD threshold to approximately 80% of the core voltage. fn recommended_bod(self) -> u8 { match self { - CoreVoltage::V0_80 => 0b0110, // 0.720V (~90% of 0.80V) - CoreVoltage::V0_85 => 0b0111, // 0.774V (~91% of 0.85V) - CoreVoltage::V0_90 => 0b1000, // 0.817V (~91% of 0.90V) - CoreVoltage::V0_95 => 0b1001, // 0.860V (~91% of 0.95V) - CoreVoltage::V1_00 => 0b1010, // 0.903V (~90% of 1.00V) - CoreVoltage::V1_05 => 0b1011, // 0.946V (~90% of 1.05V) - CoreVoltage::V1_10 => 0b1100, // 0.989V (~90% of 1.10V) - CoreVoltage::V1_15 => 0b1101, // 1.032V (~90% of 1.15V) - CoreVoltage::V1_20 => 0b1110, // 1.075V (~90% of 1.20V) - CoreVoltage::V1_25 => 0b1111, // 1.118V (~89% of 1.25V) - CoreVoltage::V1_30 => 0b1111, // 1.118V (~86% of 1.30V) - using max available threshold + CoreVoltage::V0_80 => 0b0100, // 0.645V (~81% of 0.80V) + CoreVoltage::V0_85 => 0b0101, // 0.688V (~81% of 0.85V) + CoreVoltage::V0_90 => 0b0110, // 0.731V (~81% of 0.90V) + CoreVoltage::V0_95 => 0b0111, // 0.774V (~81% of 0.95V) + CoreVoltage::V1_00 => 0b1000, // 0.817V (~82% of 1.00V) + CoreVoltage::V1_05 => 0b1000, // 0.817V (~78% of 1.05V) + CoreVoltage::V1_10 => 0b1001, // 0.860V (~78% of 1.10V) + CoreVoltage::V1_15 => 0b1010, // 0.903V (~79% of 1.15V) + CoreVoltage::V1_20 => 0b1011, // 0.946V (~79% of 1.20V) + CoreVoltage::V1_25 => 0b1100, // 0.989V (~79% of 1.25V) + CoreVoltage::V1_30 => 0b1101, // 1.032V (~79% of 1.30V) } } } @@ -459,11 +459,6 @@ impl ClockConfig { /// ``` #[cfg(feature = "rp2040")] pub fn manual_pll(xosc_hz: u32, pll_config: PllConfig, core_voltage: CoreVoltage) -> Self { - // Calculate the actual output frequency for documentation - // let ref_freq = xosc_hz / pll_config.refdiv as u32; - // let vco_freq = ref_freq * pll_config.fbdiv as u32; - // let sys_freq = vco_freq / ((pll_config.post_div1 * pll_config.post_div2) as u32); - // Validate PLL parameters assert!(pll_config.is_valid(xosc_hz), "Invalid PLL parameters"); @@ -893,6 +888,30 @@ pub(crate) unsafe fn init(config: ClockConfig) { #[cfg(feature = "_rp235x")] while c.clk_ref_selected().read() != pac::clocks::regs::ClkRefSelected(1) {} + // Reset the PLLs + let mut peris = reset::Peripherals(0); + peris.set_pll_sys(true); + peris.set_pll_usb(true); + reset::reset(peris); + reset::unreset_wait(peris); + + // let gpin0_freq = config.gpin0.map_or(0, |p| { + // core::mem::forget(p.1); + // p.0 + // }); + // CLOCKS.gpin0.store(gpin0_freq, Ordering::Relaxed); + // let gpin1_freq = config.gpin1.map_or(0, |p| { + // core::mem::forget(p.1); + // p.0 + // }); + // CLOCKS.gpin1.store(gpin1_freq, Ordering::Relaxed); + + let rosc_freq = match config.rosc { + Some(config) => configure_rosc(config), + None => 0, + }; + CLOCKS.rosc.store(rosc_freq, Ordering::Relaxed); + // Set Core Voltage (RP2040 only), if we have config for it and we're not using the default #[cfg(feature = "rp2040")] { @@ -901,8 +920,9 @@ pub(crate) unsafe fn init(config: ClockConfig) { let current_vsel = vreg.vreg().read().vsel(); let target_vsel = voltage as u8; + // If the target voltage is different from the current one, we need to change it if target_vsel != current_vsel { - // Use modify() instead of write() to preserve the HIZ and EN bits - otherwise we will disable the regulator when changing voltage + // Use modify() to preserve the HIZ and EN bits - otherwise we will disable the regulator when changing voltage vreg.vreg().modify(|w| w.set_vsel(target_vsel)); // Wait for the voltage to stabilize. Use the provided delay or default based on voltage @@ -914,16 +934,14 @@ pub(crate) unsafe fn init(config: ClockConfig) { } }); - // We need a clock that's guaranteed to be running at this point - // Use ROSC which should be configured by now - let rosc_freq_rough = 6_000_000; // Rough ROSC frequency estimate - let cycles_per_us = rosc_freq_rough / 1_000_000; - let delay_cycles = settling_time_us * cycles_per_us; - - // Wait for voltage to stabilize - cortex_m::asm::delay(delay_cycles); + if settling_time_us != 0 { + // Delay in microseconds, using the ROSC frequency to calculate cycles + let cycles_per_us = rosc_freq / 1_000_000; + let delay_cycles = settling_time_us * cycles_per_us; + cortex_m::asm::delay(delay_cycles); + } - // Only now set the BOD level after voltage has stabilized + // Only now set the BOD level. At htis point the voltage is considered stable. vreg.bod().write(|w| { w.set_vsel(voltage.recommended_bod()); w.set_en(true); // Enable brownout detection @@ -931,108 +949,64 @@ pub(crate) unsafe fn init(config: ClockConfig) { } } - // Configure ROSC first if present - let rosc_freq = match config.rosc { - Some(config) => configure_rosc(config), - None => 0, - }; - CLOCKS.rosc.store(rosc_freq, Ordering::Relaxed); - - // Configure XOSC - we'll need this for our temporary stable clock - let xosc_freq = match &config.xosc { + let (xosc_freq, pll_sys_freq, pll_usb_freq) = match config.xosc { Some(config) => { + // start XOSC + // datasheet mentions support for clock inputs into XIN, but doesn't go into + // how this is achieved. pico-sdk doesn't support this at all. start_xosc(config.hz, config.delay_multiplier); - config.hz - } - None => 0, - }; - CLOCKS.xosc.store(xosc_freq, Ordering::Relaxed); - // Setup temporary stable clocks first - // Configure USB PLL for our stable temporary clock - let pll_usb_freq = match &config.xosc { - Some(config) => match &config.usb_pll { - Some(pll_usb_config) => { - // Reset USB PLL - let mut peris = reset::Peripherals(0); - peris.set_pll_usb(true); - reset::reset(peris); - reset::unreset_wait(peris); - - // Configure the USB PLL - this should give us 48MHz - let usb_pll_freq = match configure_pll(pac::PLL_USB, xosc_freq, *pll_usb_config) { + let pll_sys_freq = match config.sys_pll { + Some(sys_pll_config) => match configure_pll(pac::PLL_SYS, config.hz, sys_pll_config) { Ok(freq) => freq, - Err(_) => { - panic!("Failed to configure USB PLL"); - } - }; + Err(e) => panic!("Failed to configure PLL_SYS: {}", e), + }, + None => 0, + }; + let pll_usb_freq = match config.usb_pll { + Some(usb_pll_config) => match configure_pll(pac::PLL_USB, config.hz, usb_pll_config) { + Ok(freq) => freq, + Err(e) => panic!("Failed to configure PLL_USB: {}", e), + }, + None => 0, + }; - CLOCKS.pll_usb.store(usb_pll_freq, Ordering::Relaxed); - usb_pll_freq - } - None => 0, - }, - None => 0, + (config.hz, pll_sys_freq, pll_usb_freq) + } + None => (0, 0, 0), }; - // Configure REF clock to use XOSC + CLOCKS.xosc.store(xosc_freq, Ordering::Relaxed); + CLOCKS.pll_sys.store(pll_sys_freq, Ordering::Relaxed); + CLOCKS.pll_usb.store(pll_usb_freq, Ordering::Relaxed); + + let (ref_src, ref_aux, clk_ref_freq) = { + use {ClkRefCtrlAuxsrc as Aux, ClkRefCtrlSrc as Src}; + let div = config.ref_clk.div as u32; + assert!(div >= 1 && div <= 4); + match config.ref_clk.src { + RefClkSrc::Xosc => (Src::XOSC_CLKSRC, Aux::CLKSRC_PLL_USB, xosc_freq / div), + RefClkSrc::Rosc => (Src::ROSC_CLKSRC_PH, Aux::CLKSRC_PLL_USB, rosc_freq / div), + RefClkSrc::PllUsb => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq / div), + // RefClkSrc::Gpin0 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN0, gpin0_freq / div), + // RefClkSrc::Gpin1 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN1, gpin1_freq / div), + } + }; + assert!(clk_ref_freq != 0); + CLOCKS.reference.store(clk_ref_freq, Ordering::Relaxed); c.clk_ref_ctrl().write(|w| { - w.set_src(ClkRefCtrlSrc::XOSC_CLKSRC); + w.set_src(ref_src); + w.set_auxsrc(ref_aux); }); #[cfg(feature = "rp2040")] - while c.clk_ref_selected().read() != (1 << ClkRefCtrlSrc::XOSC_CLKSRC as u32) {} + while c.clk_ref_selected().read() != (1 << ref_src as u32) {} #[cfg(feature = "_rp235x")] - while c.clk_ref_selected().read() != pac::clocks::regs::ClkRefSelected(1 << ClkRefCtrlSrc::XOSC_CLKSRC as u32) {} - - // First switch the system clock to a stable source (USB PLL at 48MHz) - // This follows the official Pico SDK's approach to ensure stability during reconfiguration - c.clk_sys_ctrl().write(|w| { - w.set_auxsrc(ClkSysCtrlAuxsrc::CLKSRC_PLL_USB); - w.set_src(ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX); + while c.clk_ref_selected().read() != pac::clocks::regs::ClkRefSelected(1 << ref_src as u32) {} + c.clk_ref_div().write(|w| { + w.set_int(config.ref_clk.div); }); - #[cfg(feature = "rp2040")] - while c.clk_sys_selected().read() != (1 << ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX as u32) {} - #[cfg(feature = "_rp235x")] - while c.clk_sys_selected().read() - != pac::clocks::regs::ClkSysSelected(1 << ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX as u32) - {} - - // Short delay after switching to USB PLL to ensure stability - cortex_m::asm::delay(100); - - // NOW CONFIGURE THE SYSTEM PLL (safely, since we're running from the USB PLL) - let pll_sys_freq = match &config.xosc { - Some(config) => match &config.sys_pll { - Some(sys_pll_config) => { - // Reset SYS PLL - let mut peris = reset::Peripherals(0); - peris.set_pll_sys(true); - reset::reset(peris); - reset::unreset_wait(peris); - - // Configure the SYS PLL - let pll_sys_freq = match configure_pll(pac::PLL_SYS, xosc_freq, *sys_pll_config) { - Ok(freq) => freq, - Err(_) => { - panic!("Failed to configure system PLL"); - } - }; - - // Ensure PLL is locked and stable - cortex_m::asm::delay(100); - - CLOCKS.pll_sys.store(pll_sys_freq, Ordering::Relaxed); - pll_sys_freq - } - None => 0, - }, - None => 0, - }; - - // Configure tick generation using REF clock - let clk_ref_freq = xosc_freq; // REF clock is now XOSC - CLOCKS.reference.store(clk_ref_freq, Ordering::Relaxed); + // Configure tick generation on the 2040. #[cfg(feature = "rp2040")] pac::WATCHDOG.tick().write(|w| { w.set_cycles((clk_ref_freq / 1_000_000) as u16); @@ -1050,8 +1024,6 @@ pub(crate) unsafe fn init(config: ClockConfig) { pac::TICKS.watchdog_ctrl().write(|w| w.set_enable(true)); } - // NOW SWITCH THE SYSTEM CLOCK TO THE CONFIGURED SOURCE - // The SYS PLL is now stable and we can safely switch to it let (sys_src, sys_aux, clk_sys_freq) = { use {ClkSysCtrlAuxsrc as Aux, ClkSysCtrlSrc as Src}; let (src, aux, freq) = match config.sys_clk.src { @@ -1068,48 +1040,28 @@ pub(crate) unsafe fn init(config: ClockConfig) { }; assert!(clk_sys_freq != 0); CLOCKS.sys.store(clk_sys_freq, Ordering::Relaxed); - - // Set the divider before changing the source if it's increasing - if config.sys_clk.div_int > 1 || config.sys_clk.div_frac > 0 { - c.clk_sys_div().write(|w| { - w.set_int(config.sys_clk.div_int); - w.set_frac(config.sys_clk.div_frac); - }); - } - - // Configure aux source first if needed - if sys_src == ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX { - c.clk_sys_ctrl().modify(|w| { - w.set_auxsrc(sys_aux); - }); + if sys_src != ClkSysCtrlSrc::CLK_REF { + c.clk_sys_ctrl().write(|w| w.set_src(ClkSysCtrlSrc::CLK_REF)); + #[cfg(feature = "rp2040")] + while c.clk_sys_selected().read() != (1 << ClkSysCtrlSrc::CLK_REF as u32) {} + #[cfg(feature = "_rp235x")] + while c.clk_sys_selected().read() != pac::clocks::regs::ClkSysSelected(1 << ClkSysCtrlSrc::CLK_REF as u32) {} } - - // Now set the source c.clk_sys_ctrl().write(|w| { - if sys_src == ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX { - w.set_auxsrc(sys_aux); - } + w.set_auxsrc(sys_aux); w.set_src(sys_src); }); - // Wait for the clock to be selected #[cfg(feature = "rp2040")] while c.clk_sys_selected().read() != (1 << sys_src as u32) {} #[cfg(feature = "_rp235x")] while c.clk_sys_selected().read() != pac::clocks::regs::ClkSysSelected(1 << sys_src as u32) {} - // Short delay after final clock switch to ensure stability - cortex_m::asm::delay(100); - - // Set the divider after changing the source if it's decreasing - if config.sys_clk.div_int == 1 && config.sys_clk.div_frac == 0 { - c.clk_sys_div().write(|w| { - w.set_int(config.sys_clk.div_int); - w.set_frac(config.sys_clk.div_frac); - }); - } + c.clk_sys_div().write(|w| { + w.set_int(config.sys_clk.div_int); + w.set_frac(config.sys_clk.div_frac); + }); - // CONFIGURE PERIPHERAL CLOCK let mut peris = reset::ALL_PERIPHERALS; if let Some(src) = config.peri_clk_src { @@ -1136,7 +1088,6 @@ pub(crate) unsafe fn init(config: ClockConfig) { CLOCKS.peri.store(0, Ordering::Relaxed); } - // CONFIGURE USB CLOCK if let Some(conf) = config.usb_clk { c.clk_usb_div().write(|w| w.set_int(conf.div)); c.clk_usb_ctrl().write(|w| { @@ -1160,7 +1111,6 @@ pub(crate) unsafe fn init(config: ClockConfig) { CLOCKS.usb.store(0, Ordering::Relaxed); } - // CONFIGURE ADC CLOCK if let Some(conf) = config.adc_clk { c.clk_adc_div().write(|w| w.set_int(conf.div)); c.clk_adc_ctrl().write(|w| { @@ -1184,7 +1134,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { CLOCKS.adc.store(0, Ordering::Relaxed); } - // CONFIGURE RTC CLOCK + // rp2040 specific clocks #[cfg(feature = "rp2040")] if let Some(conf) = config.rtc_clk { c.clk_rtc_div().write(|w| { @@ -1393,7 +1343,7 @@ fn configure_pll(p: pac::pll::Pll, input_freq: u32, config: PllConfig) -> Result }); // 5. Wait for PLL to lock with a timeout - let mut timeout = 1_000_000; // Reasonable timeout value + let mut timeout = 1_000_000; while !p.cs().read().lock() { timeout -= 1; if timeout == 0 { -- cgit From bd3b3b45266c3dc3bf0d443a2e727814968cb245 Mon Sep 17 00:00:00 2001 From: Süha Ünüvar <87157627+phycrax@users.noreply.github.com> Date: Wed, 7 May 2025 17:14:28 +0800 Subject: derive debug copy clone defmt for pwmpinconfig --- embassy-stm32/src/timer/simple_pwm.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 54ab7d0d5..8fd7e8df4 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -31,6 +31,8 @@ pub struct PwmPin<'d, T, C> { /// PWM pin config /// /// This configures the pwm pin settings +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PwmPinConfig { /// PWM Pin output type pub output_type: OutputType, -- cgit From a254daf4fffe74c65d1846f620dd674fa4e14aac Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Wed, 7 May 2025 21:19:09 +0200 Subject: Changes after review --- embassy-rp/src/clocks.rs | 51 +++++++++++++++++++++++++++------------- examples/rp/src/bin/overclock.rs | 2 +- tests/rp/src/bin/overclock.rs | 2 +- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index a4cc129ab..0c3988aac 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -9,7 +9,7 @@ //! //! For most users, these functions provide an easy way to configure clocks: //! -//! - `ClockConfig::at_sys_frequency_mhz(200)` - Set system clock to a specific frequency with automatic voltage scaling +//! - `ClockConfig::system_freq(125_000_000)` - Set system clock to a specific frequency with automatic voltage scaling //! - `ClockConfig::crystal(12_000_000)` - Default configuration with 12MHz crystal giving 125MHz system clock //! //! ## Manual Configuration @@ -50,7 +50,7 @@ //! //! ### Overclock to 200MHz //! ```rust,ignore -//! let config = ClockConfig::crystal_freq(200_000_000); +//! let config = ClockConfig::system_freq(200_000_000); //! ``` //! //! ### Manual configuration for advanced scenarios @@ -90,6 +90,7 @@ struct Clocks { pll_usb: AtomicU32, usb: AtomicU32, adc: AtomicU32, + // See above re gpin handling being commented out // gpin0: AtomicU32, // gpin1: AtomicU32, rosc: AtomicU32, @@ -106,6 +107,7 @@ static CLOCKS: Clocks = Clocks { pll_usb: AtomicU32::new(0), usb: AtomicU32::new(0), adc: AtomicU32::new(0), + // See above re gpin handling being commented out // gpin0: AtomicU32::new(0), // gpin1: AtomicU32::new(0), rosc: AtomicU32::new(0), @@ -129,6 +131,7 @@ pub enum PeriClkSrc { Rosc = ClkPeriCtrlAuxsrc::ROSC_CLKSRC_PH as _, /// XOSC. Xosc = ClkPeriCtrlAuxsrc::XOSC_CLKSRC as _, + // See above re gpin handling being commented out // Gpin0 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN0 as _ , // Gpin1 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN1 as _ , } @@ -213,6 +216,7 @@ pub struct ClockConfig { /// If not set, defaults will be used based on voltage level. #[cfg(feature = "rp2040")] pub voltage_stabilization_delay_us: Option, + // See above re gpin handling being commented out // gpin0: Option<(u32, Gpin<'static, AnyPin>)>, // gpin1: Option<(u32, Gpin<'static, AnyPin>)>, } @@ -227,7 +231,7 @@ impl Default for ClockConfig { /// Most users should use one of the more specific configuration functions: /// - `ClockConfig::crystal()` - Standard configuration with external crystal /// - `ClockConfig::rosc()` - Configuration using only the internal oscillator - /// - `ClockConfig::at_sys_frequency_mhz()` - Configuration for a specific system frequency + /// - `ClockConfig::system_freq()` - Configuration for a specific system frequency fn default() -> Self { Self { rosc: None, @@ -250,6 +254,7 @@ impl Default for ClockConfig { core_voltage: CoreVoltage::V1_10, #[cfg(feature = "rp2040")] voltage_stabilization_delay_us: None, + // See above re gpin handling being commented out // gpin0: None, // gpin1: None, } @@ -322,6 +327,7 @@ impl ClockConfig { core_voltage: CoreVoltage::V1_10, // Use hardware default (1.10V) #[cfg(feature = "rp2040")] voltage_stabilization_delay_us: None, + // See above re gpin handling being commented out // gpin0: None, // gpin1: None, } @@ -366,6 +372,7 @@ impl ClockConfig { core_voltage: CoreVoltage::V1_10, // Use hardware default (1.10V) #[cfg(feature = "rp2040")] voltage_stabilization_delay_us: None, + // See above re gpin handling being commented out // gpin0: None, // gpin1: None, } @@ -393,19 +400,19 @@ impl ClockConfig { /// That way all other frequencies below 133MHz or above 200MHz are not explicitly documented and not covered here. /// In case You want to go below 133MHz or above 200MHz and want a different voltage, You will have to set that manually and with caution. #[cfg(feature = "rp2040")] - pub fn crystal_freq(sys_freq_hz: u32) -> Self { + pub fn system_freq(hz: u32) -> Self { // Start with the standard configuration from crystal() const DEFAULT_CRYSTAL_HZ: u32 = 12_000_000; let mut config = Self::crystal(DEFAULT_CRYSTAL_HZ); // No need to modify anything if target frequency is already 125MHz // (which is what crystal() configures by default) - if sys_freq_hz == 125_000_000 { + if hz == 125_000_000 { return config; } // Find optimal PLL parameters for the requested frequency - let sys_pll_params = find_pll_params(DEFAULT_CRYSTAL_HZ, sys_freq_hz) + let sys_pll_params = find_pll_params(DEFAULT_CRYSTAL_HZ, hz) .unwrap_or_else(|| panic!("Could not find valid PLL parameters for system clock")); // Replace the sys_pll configuration with our custom parameters @@ -417,7 +424,7 @@ impl ClockConfig { // Higher frequencies require higher voltage #[cfg(feature = "rp2040")] { - config.core_voltage = match sys_freq_hz { + config.core_voltage = match hz { freq if freq > 133_000_000 => CoreVoltage::V1_15, _ => CoreVoltage::V1_10, // Use default voltage (V1_10) }; @@ -631,6 +638,7 @@ pub enum RefClkSrc { Rosc, /// PLL USB. PllUsb, + // See above re gpin handling being commented out // Gpin0, // Gpin1, } @@ -649,6 +657,7 @@ pub enum SysClkSrc { Rosc, /// XOSC. Xosc, + // See above re gpin handling being commented out // Gpin0, // Gpin1, } @@ -684,6 +693,7 @@ pub enum UsbClkSrc { Rosc = ClkUsbCtrlAuxsrc::ROSC_CLKSRC_PH as _, /// XOSC. Xosc = ClkUsbCtrlAuxsrc::XOSC_CLKSRC as _, + // See above re gpin handling being commented out // Gpin0 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN0 as _ , // Gpin1 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN1 as _ , } @@ -711,6 +721,7 @@ pub enum AdcClkSrc { Rosc = ClkAdcCtrlAuxsrc::ROSC_CLKSRC_PH as _, /// XOSC. Xosc = ClkAdcCtrlAuxsrc::XOSC_CLKSRC as _, + // See above re gpin handling being commented out // Gpin0 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN0 as _ , // Gpin1 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN1 as _ , } @@ -739,6 +750,7 @@ pub enum RtcClkSrc { Rosc = ClkRtcCtrlAuxsrc::ROSC_CLKSRC_PH as _, /// XOSC. Xosc = ClkRtcCtrlAuxsrc::XOSC_CLKSRC as _, + // See above re gpin handling being commented out // Gpin0 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN0 as _ , // Gpin1 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN1 as _ , } @@ -895,6 +907,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { reset::reset(peris); reset::unreset_wait(peris); + // See above re gpin handling being commented out // let gpin0_freq = config.gpin0.map_or(0, |p| { // core::mem::forget(p.1); // p.0 @@ -941,7 +954,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { cortex_m::asm::delay(delay_cycles); } - // Only now set the BOD level. At htis point the voltage is considered stable. + // Only now set the BOD level. At this point the voltage is considered stable. vreg.bod().write(|w| { w.set_vsel(voltage.recommended_bod()); w.set_en(true); // Enable brownout detection @@ -952,8 +965,6 @@ pub(crate) unsafe fn init(config: ClockConfig) { let (xosc_freq, pll_sys_freq, pll_usb_freq) = match config.xosc { Some(config) => { // start XOSC - // datasheet mentions support for clock inputs into XIN, but doesn't go into - // how this is achieved. pico-sdk doesn't support this at all. start_xosc(config.hz, config.delay_multiplier); let pll_sys_freq = match config.sys_pll { @@ -988,6 +999,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { RefClkSrc::Xosc => (Src::XOSC_CLKSRC, Aux::CLKSRC_PLL_USB, xosc_freq / div), RefClkSrc::Rosc => (Src::ROSC_CLKSRC_PH, Aux::CLKSRC_PLL_USB, rosc_freq / div), RefClkSrc::PllUsb => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq / div), + // See above re gpin handling being commented out // RefClkSrc::Gpin0 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN0, gpin0_freq / div), // RefClkSrc::Gpin1 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN1, gpin1_freq / div), } @@ -1032,6 +1044,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { SysClkSrc::PllUsb => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq), SysClkSrc::Rosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::ROSC_CLKSRC, rosc_freq), SysClkSrc::Xosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::XOSC_CLKSRC, xosc_freq), + // See above re gpin handling being commented out // SysClkSrc::Gpin0 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN0, gpin0_freq), // SysClkSrc::Gpin1 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN1, gpin1_freq), }; @@ -1075,6 +1088,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { PeriClkSrc::PllUsb => pll_usb_freq, PeriClkSrc::Rosc => rosc_freq, PeriClkSrc::Xosc => xosc_freq, + // See above re gpin handling being commented out // PeriClkSrc::Gpin0 => gpin0_freq, // PeriClkSrc::Gpin1 => gpin1_freq, }; @@ -1100,6 +1114,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { UsbClkSrc::PllSys => pll_sys_freq, UsbClkSrc::Rosc => rosc_freq, UsbClkSrc::Xosc => xosc_freq, + // See above re gpin handling being commented out // UsbClkSrc::Gpin0 => gpin0_freq, // UsbClkSrc::Gpin1 => gpin1_freq, }; @@ -1123,6 +1138,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { AdcClkSrc::PllSys => pll_sys_freq, AdcClkSrc::Rosc => rosc_freq, AdcClkSrc::Xosc => xosc_freq, + // See above re gpin handling being commented out // AdcClkSrc::Gpin0 => gpin0_freq, // AdcClkSrc::Gpin1 => gpin1_freq, }; @@ -1151,6 +1167,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { RtcClkSrc::PllSys => pll_sys_freq, RtcClkSrc::Rosc => rosc_freq, RtcClkSrc::Xosc => xosc_freq, + // See above re gpin handling being commented out // RtcClkSrc::Gpin0 => gpin0_freq, // RtcClkSrc::Gpin1 => gpin1_freq, }; @@ -1217,6 +1234,7 @@ pub fn xosc_freq() -> u32 { CLOCKS.xosc.load(Ordering::Relaxed) } +// See above re gpin handling being commented out // pub fn gpin0_freq() -> u32 { // CLOCKS.gpin0.load(Ordering::Relaxed) // } @@ -1452,6 +1470,7 @@ impl_gpoutpin!(PIN_25, 3); pub enum GpoutSrc { /// Sys PLL. PllSys = ClkGpoutCtrlAuxsrc::CLKSRC_PLL_SYS as _, + // See above re gpin handling being commented out // Gpin0 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN0 as _ , // Gpin1 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN1 as _ , /// USB PLL. @@ -1547,6 +1566,7 @@ impl<'d, T: GpoutPin> Gpout<'d, T> { let base = match src { ClkGpoutCtrlAuxsrc::CLKSRC_PLL_SYS => pll_sys_freq(), + // See above re gpin handling being commented out // ClkGpoutCtrlAuxsrc::CLKSRC_GPIN0 => gpin0_freq(), // ClkGpoutCtrlAuxsrc::CLKSRC_GPIN1 => gpin1_freq(), ClkGpoutCtrlAuxsrc::CLKSRC_PLL_USB => pll_usb_freq(), @@ -1555,7 +1575,6 @@ impl<'d, T: GpoutPin> Gpout<'d, T> { ClkGpoutCtrlAuxsrc::CLK_SYS => clk_sys_freq(), ClkGpoutCtrlAuxsrc::CLK_USB => clk_usb_freq(), ClkGpoutCtrlAuxsrc::CLK_ADC => clk_adc_freq(), - //ClkGpoutCtrlAuxsrc::CLK_RTC => clk_rtc_freq() as _, ClkGpoutCtrlAuxsrc::CLK_REF => clk_ref_freq(), _ => unreachable!(), }; @@ -1918,21 +1937,21 @@ mod tests { { // Test automatic voltage scaling based on frequency // Under 133 MHz should use default voltage (V1_10) - let config = ClockConfig::crystal_freq(125_000_000); + let config = ClockConfig::system_freq(125_000_000); assert_eq!(config.core_voltage, CoreVoltage::V1_10); // 133-200 MHz should use V1_15 - let config = ClockConfig::crystal_freq(150_000_000); + let config = ClockConfig::system_freq(150_000_000); assert_eq!(config.core_voltage, CoreVoltage::V1_15); - let config = ClockConfig::crystal_freq(200_000_000); + let config = ClockConfig::system_freq(200_000_000); assert_eq!(config.core_voltage, CoreVoltage::V1_15); // Above 200 MHz should use V1_25 - let config = ClockConfig::crystal_freq(250_000_000); + let config = ClockConfig::system_freq(250_000_000); assert_eq!(config.core_voltage, CoreVoltage::V1_15); // Below 125 MHz should use V1_10 - let config = ClockConfig::crystal_freq(100_000_000); + let config = ClockConfig::system_freq(100_000_000); assert_eq!(config.core_voltage, CoreVoltage::V1_10); } } diff --git a/examples/rp/src/bin/overclock.rs b/examples/rp/src/bin/overclock.rs index f9a8c94d0..9c78e0c9d 100644 --- a/examples/rp/src/bin/overclock.rs +++ b/examples/rp/src/bin/overclock.rs @@ -18,7 +18,7 @@ const COUNT_TO: i64 = 10_000_000; #[embassy_executor::main] async fn main(_spawner: Spawner) -> ! { // Set up for clock frequency of 200 MHz, setting all necessary defaults. - let config = Config::new(ClockConfig::crystal_freq(200_000_000)); + let config = Config::new(ClockConfig::system_freq(200_000_000)); // Show the voltage scale for verification info!("System core voltage: {}", Debug2Format(&config.clocks.core_voltage)); diff --git a/tests/rp/src/bin/overclock.rs b/tests/rp/src/bin/overclock.rs index 6c58a6b90..be8e85a3f 100644 --- a/tests/rp/src/bin/overclock.rs +++ b/tests/rp/src/bin/overclock.rs @@ -31,7 +31,7 @@ async fn main(_spawner: Spawner) { // Initialize with 200MHz clock configuration for RP2040, other chips will use default clock #[cfg(feature = "rp2040")] { - config.clocks = ClockConfig::crystal_freq(200_000_000); + config.clocks = ClockConfig::system_freq(200_000_000); let voltage = config.clocks.core_voltage; assert!(matches!(voltage, CoreVoltage::V1_15), "Expected voltage scale V1_15"); } -- cgit From 4621c8aa7a1ee1b55f2f0bf80fc48eddf76af320 Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Wed, 7 May 2025 22:16:29 +0200 Subject: Clarify comment for CoreVoltage enum regarding V1_20 --- embassy-rp/src/clocks.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index 0c3988aac..6694aab66 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -160,7 +160,7 @@ pub enum CoreVoltage { V1_10 = 0b1011, /// 1.15V - Required for overclocking to 133-200MHz V1_15 = 0b1100, - /// 1.20V - Required for overclocking above 200MHz + /// 1.20V V1_20 = 0b1101, /// 1.25V V1_25 = 0b1110, -- cgit From d4c378e059443dbaaaece02a0f5148db62bd4484 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Fri, 11 Apr 2025 14:28:59 -0700 Subject: Add embassy-imxrt CRC driver --- docs/pages/imxrt.adoc | 2 +- embassy-imxrt/src/crc.rs | 190 ++++++++++++++++++++++++++++++++++++++++ embassy-imxrt/src/lib.rs | 1 + examples/mimxrt6/src/bin/crc.rs | 175 ++++++++++++++++++++++++++++++++++++ 4 files changed, 367 insertions(+), 1 deletion(-) create mode 100644 embassy-imxrt/src/crc.rs create mode 100644 examples/mimxrt6/src/bin/crc.rs diff --git a/docs/pages/imxrt.adoc b/docs/pages/imxrt.adoc index adf5218e8..a84fbae03 100644 --- a/docs/pages/imxrt.adoc +++ b/docs/pages/imxrt.adoc @@ -9,5 +9,5 @@ The link: link:https://github.com/embassy-rs/embassy/tree/main/embassy-imxrt[Emb The following peripherals have a HAL implementation at present +* CRC * GPIO - diff --git a/embassy-imxrt/src/crc.rs b/embassy-imxrt/src/crc.rs new file mode 100644 index 000000000..24d6ba5bd --- /dev/null +++ b/embassy-imxrt/src/crc.rs @@ -0,0 +1,190 @@ +//! Cyclic Redundancy Check (CRC) + +use core::marker::PhantomData; + +use crate::clocks::{enable_and_reset, SysconPeripheral}; +pub use crate::pac::crc_engine::mode::CrcPolynomial as Polynomial; +use crate::{peripherals, Peri, PeripheralType}; + +/// CRC driver. +pub struct Crc<'d> { + info: Info, + _config: Config, + _lifetime: PhantomData<&'d ()>, +} + +/// CRC configuration +pub struct Config { + /// Polynomial to be used + pub polynomial: Polynomial, + + /// Reverse bit order of input? + pub reverse_in: bool, + + /// 1's complement input? + pub complement_in: bool, + + /// Reverse CRC bit order? + pub reverse_out: bool, + + /// 1's complement CRC? + pub complement_out: bool, + + /// CRC Seed + pub seed: u32, +} + +impl Config { + /// Create a new CRC config. + #[must_use] + pub fn new( + polynomial: Polynomial, + reverse_in: bool, + complement_in: bool, + reverse_out: bool, + complement_out: bool, + seed: u32, + ) -> Self { + Config { + polynomial, + reverse_in, + complement_in, + reverse_out, + complement_out, + seed, + } + } +} + +impl Default for Config { + fn default() -> Self { + Self { + polynomial: Polynomial::CrcCcitt, + reverse_in: false, + complement_in: false, + reverse_out: false, + complement_out: false, + seed: 0xffff, + } + } +} + +impl<'d> Crc<'d> { + /// Instantiates new CRC peripheral and initializes to default values. + pub fn new(_peripheral: Peri<'d, T>, config: Config) -> Self { + // enable CRC clock + enable_and_reset::(); + + let mut instance = Self { + info: T::info(), + _config: config, + _lifetime: PhantomData, + }; + + instance.reconfigure(); + instance + } + + /// Reconfigured the CRC peripheral. + fn reconfigure(&mut self) { + self.info.regs.mode().write(|w| { + w.crc_poly() + .variant(self._config.polynomial) + .bit_rvs_wr() + .variant(self._config.reverse_in) + .cmpl_wr() + .variant(self._config.complement_in) + .bit_rvs_sum() + .variant(self._config.reverse_out) + .cmpl_sum() + .variant(self._config.complement_out) + }); + + // Init CRC value + self.info + .regs + .seed() + .write(|w| unsafe { w.crc_seed().bits(self._config.seed) }); + } + + /// Feeds a byte into the CRC peripheral. Returns the computed checksum. + pub fn feed_byte(&mut self, byte: u8) -> u32 { + self.info.regs.wr_data8().write(|w| unsafe { w.bits(byte) }); + + self.info.regs.sum().read().bits() + } + + /// Feeds an slice of bytes into the CRC peripheral. Returns the computed checksum. + pub fn feed_bytes(&mut self, bytes: &[u8]) -> u32 { + let (prefix, data, suffix) = unsafe { bytes.align_to::() }; + + for b in prefix { + self.info.regs.wr_data8().write(|w| unsafe { w.bits(*b) }); + } + + for d in data { + self.info.regs.wr_data32().write(|w| unsafe { w.bits(*d) }); + } + + for b in suffix { + self.info.regs.wr_data8().write(|w| unsafe { w.bits(*b) }); + } + + self.info.regs.sum().read().bits() + } + + /// Feeds a halfword into the CRC peripheral. Returns the computed checksum. + pub fn feed_halfword(&mut self, halfword: u16) -> u32 { + self.info.regs.wr_data16().write(|w| unsafe { w.bits(halfword) }); + + self.info.regs.sum().read().bits() + } + + /// Feeds an slice of halfwords into the CRC peripheral. Returns the computed checksum. + pub fn feed_halfwords(&mut self, halfwords: &[u16]) -> u32 { + for halfword in halfwords { + self.info.regs.wr_data16().write(|w| unsafe { w.bits(*halfword) }); + } + + self.info.regs.sum().read().bits() + } + + /// Feeds a words into the CRC peripheral. Returns the computed checksum. + pub fn feed_word(&mut self, word: u32) -> u32 { + self.info.regs.wr_data32().write(|w| unsafe { w.bits(word) }); + + self.info.regs.sum().read().bits() + } + + /// Feeds an slice of words into the CRC peripheral. Returns the computed checksum. + pub fn feed_words(&mut self, words: &[u32]) -> u32 { + for word in words { + self.info.regs.wr_data32().write(|w| unsafe { w.bits(*word) }); + } + + self.info.regs.sum().read().bits() + } +} + +struct Info { + regs: crate::pac::CrcEngine, +} + +trait SealedInstance { + fn info() -> Info; +} + +/// CRC instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType + SysconPeripheral + 'static + Send {} + +impl Instance for peripherals::CRC {} + +impl SealedInstance for peripherals::CRC { + fn info() -> Info { + // SAFETY: safe from single executor + Info { + regs: unsafe { crate::pac::CrcEngine::steal() }, + } + } +} diff --git a/embassy-imxrt/src/lib.rs b/embassy-imxrt/src/lib.rs index ad9f81e88..d8ce97f76 100644 --- a/embassy-imxrt/src/lib.rs +++ b/embassy-imxrt/src/lib.rs @@ -18,6 +18,7 @@ compile_error!( pub(crate) mod fmt; pub mod clocks; +pub mod crc; pub mod gpio; pub mod iopctl; diff --git a/examples/mimxrt6/src/bin/crc.rs b/examples/mimxrt6/src/bin/crc.rs new file mode 100644 index 000000000..005a250e5 --- /dev/null +++ b/examples/mimxrt6/src/bin/crc.rs @@ -0,0 +1,175 @@ +#![no_std] +#![no_main] + +extern crate embassy_imxrt_examples; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_imxrt::crc::{Config, Crc, Polynomial}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_imxrt::init(Default::default()); + let data = b"123456789"; + + info!("Initializing CRC"); + + // CRC-CCITT + let mut crc = Crc::new(p.CRC.reborrow(), Default::default()); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0x29b1); + + // CRC16-ARC + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc16, + reverse_in: true, + reverse_out: true, + complement_out: false, + seed: 0, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0xbb3d); + + // CRC16-CMS + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc16, + reverse_in: false, + reverse_out: false, + complement_out: false, + seed: 0xffff, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0xaee7); + + // CRC16-DDS-110 + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc16, + reverse_in: false, + reverse_out: false, + complement_out: false, + seed: 0x800d, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0x9ecf); + + // CRC16-MAXIM-DOW + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc16, + reverse_in: true, + reverse_out: true, + complement_out: true, + seed: 0, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0x44c2); + + // CRC16-MODBUS + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc16, + reverse_in: true, + reverse_out: true, + complement_out: false, + seed: 0xffff, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0x4b37); + + // CRC32-BZIP2 + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc32, + reverse_in: false, + reverse_out: false, + complement_out: true, + seed: 0xffff_ffff, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0xfc89_1918); + + // CRC32-CKSUM + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc32, + reverse_in: false, + reverse_out: false, + complement_out: true, + seed: 0, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0x765e_7680); + + // CRC32-ISO-HDLC + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc32, + reverse_in: true, + reverse_out: true, + complement_out: true, + seed: 0xffff_ffff, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0xcbf4_3926); + + // CRC32-JAMCRC + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc32, + reverse_in: true, + reverse_out: true, + complement_out: false, + seed: 0xffff_ffff, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0x340b_c6d9); + + // CRC32-MPEG-2 + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc32, + reverse_in: false, + reverse_out: false, + complement_out: false, + seed: 0xffff_ffff, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0x0376_e6e7); + + info!("end program"); + cortex_m::asm::bkpt(); +} -- cgit From ee71dda6317ef8de66b09612a020416466aeb2d3 Mon Sep 17 00:00:00 2001 From: 9names <60134748+9names@users.noreply.github.com> Date: Sat, 10 May 2025 19:58:03 +1000 Subject: Clarify embassy-rp rt feature purpose --- embassy-rp/Cargo.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml index b440591cf..8fb8a50fd 100644 --- a/embassy-rp/Cargo.toml +++ b/embassy-rp/Cargo.toml @@ -26,7 +26,10 @@ features = ["defmt", "unstable-pac", "time-driver", "rp2040"] [features] default = [ "rt" ] -## Enable the rt feature of [`rp-pac`](https://docs.rs/rp-pac). This brings in the [`cortex-m-rt`](https://docs.rs/cortex-m-rt) crate, which adds startup code and minimal runtime initialization. + +## Enable the `rt` feature of [`rp-pac`](https://docs.rs/rp-pac). +## With `rt` enabled the PAC provides interrupt vectors instead of letting [`cortex-m-rt`](https://docs.rs/cortex-m-rt) do that. +## See for more info. rt = [ "rp-pac/rt" ] ## Enable [defmt support](https://docs.rs/defmt) and enables `defmt` debug-log messages and formatting in embassy drivers. -- cgit From 967a98fd4448a8e00c8cbfb509c1cc32e2bdcd29 Mon Sep 17 00:00:00 2001 From: Alessandro Gasbarroni Date: Tue, 17 Dec 2024 13:25:58 +0100 Subject: nrf: Add IPC peripheral for nRF5340 --- embassy-nrf/src/chips/nrf5340_app.rs | 5 + embassy-nrf/src/chips/nrf5340_net.rs | 5 + embassy-nrf/src/ipc.rs | 360 +++++++++++++++++++++++++++++++++++ embassy-nrf/src/lib.rs | 2 + 4 files changed, 372 insertions(+) create mode 100644 embassy-nrf/src/ipc.rs diff --git a/embassy-nrf/src/chips/nrf5340_app.rs b/embassy-nrf/src/chips/nrf5340_app.rs index 0103fa7ae..99cf29487 100644 --- a/embassy-nrf/src/chips/nrf5340_app.rs +++ b/embassy-nrf/src/chips/nrf5340_app.rs @@ -262,6 +262,9 @@ embassy_hal_internal::peripherals! { PPI_GROUP4, PPI_GROUP5, + // IPC + IPC, + // GPIO port 0 #[cfg(feature = "lfxo-pins-as-gpio")] P0_00, @@ -327,6 +330,8 @@ embassy_hal_internal::peripherals! { EGU5, } +impl_ipc!(IPC, IPC, IPC); + impl_usb!(USBD, USBD, USBD); impl_uarte!(SERIAL0, UARTE0, SERIAL0); diff --git a/embassy-nrf/src/chips/nrf5340_net.rs b/embassy-nrf/src/chips/nrf5340_net.rs index 22d33d080..c2932be31 100644 --- a/embassy-nrf/src/chips/nrf5340_net.rs +++ b/embassy-nrf/src/chips/nrf5340_net.rs @@ -141,6 +141,9 @@ embassy_hal_internal::peripherals! { PPI_GROUP4, PPI_GROUP5, + // IPC + IPC, + // GPIO port 0 P0_00, P0_01, @@ -200,6 +203,8 @@ embassy_hal_internal::peripherals! { EGU0, } +impl_ipc!(IPC, IPC, IPC); + impl_uarte!(SERIAL0, UARTE0, SERIAL0); impl_spim!(SERIAL0, SPIM0, SERIAL0); impl_spis!(SERIAL0, SPIS0, SERIAL0); diff --git a/embassy-nrf/src/ipc.rs b/embassy-nrf/src/ipc.rs new file mode 100644 index 000000000..410783ef4 --- /dev/null +++ b/embassy-nrf/src/ipc.rs @@ -0,0 +1,360 @@ +//! InterProcessor Communication (IPC) + +#![macro_use] + +use core::future::poll_fn; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::peripherals::IPC; +use crate::{interrupt, pac}; + +/// IPC Event +#[derive(Debug, Clone, Copy)] +pub enum IpcEvent { + /// IPC Event 0 + Event0 = 0, + /// IPC Event 1 + Event1 = 1, + /// IPC Event 2 + Event2 = 2, + /// IPC Event 3 + Event3 = 3, + /// IPC Event 4 + Event4 = 4, + /// IPC Event 5 + Event5 = 5, + /// IPC Event 6 + Event6 = 6, + /// IPC Event 7 + Event7 = 7, + /// IPC Event 8 + Event8 = 8, + /// IPC Event 9 + Event9 = 9, + /// IPC Event 10 + Event10 = 10, + /// IPC Event 11 + Event11 = 11, + /// IPC Event 12 + Event12 = 12, + /// IPC Event 13 + Event13 = 13, + /// IPC Event 14 + Event14 = 14, + /// IPC Event 15 + Event15 = 15, +} + +const EVENTS: [IpcEvent; 16] = [ + IpcEvent::Event0, + IpcEvent::Event1, + IpcEvent::Event2, + IpcEvent::Event3, + IpcEvent::Event4, + IpcEvent::Event5, + IpcEvent::Event6, + IpcEvent::Event7, + IpcEvent::Event8, + IpcEvent::Event9, + IpcEvent::Event10, + IpcEvent::Event11, + IpcEvent::Event12, + IpcEvent::Event13, + IpcEvent::Event14, + IpcEvent::Event15, +]; + +/// IPC Channel +#[derive(Debug, Clone, Copy)] +pub enum IpcChannel { + /// IPC Channel 0 + Channel0, + /// IPC Channel 1 + Channel1, + /// IPC Channel 2 + Channel2, + /// IPC Channel 3 + Channel3, + /// IPC Channel 4 + Channel4, + /// IPC Channel 5 + Channel5, + /// IPC Channel 6 + Channel6, + /// IPC Channel 7 + Channel7, + /// IPC Channel 8 + Channel8, + /// IPC Channel 9 + Channel9, + /// IPC Channel 10 + Channel10, + /// IPC Channel 11 + Channel11, + /// IPC Channel 12 + Channel12, + /// IPC Channel 13 + Channel13, + /// IPC Channel 14 + Channel14, + /// IPC Channel 15 + Channel15, +} + +/// Interrupt Handler +pub struct InterruptHandler {} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let regs = IPC::regs(); + + // Check if an event was generated, and if it was, trigger the corresponding waker + for event in EVENTS { + if regs.events_receive(event as usize).read() & 0x01 == 0x01 { + // Event is set. Reset and wake waker + regs.events_receive(event as usize).write_value(0); + IPC::state().waker_for(event); + } + + // Ensure the state is actually cleared + // Ref: nRF5340 PS v1.5 7.1.9.1 p.153 + compiler_fence(Ordering::SeqCst); + while regs.events_receive(event as usize).read() & 0x01 != 0x00 {} + } + } +} + +/// IPC driver +pub struct Ipc<'d, T: Instance> { + _peri: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> From> for Ipc<'d, T> { + fn from(value: PeripheralRef<'d, T>) -> Self { + Self { _peri: value } + } +} + +impl<'d, T: Instance> Ipc<'d, T> { + /// Create IPC driver + pub fn new(ipc: impl Peripheral

+ 'd) -> Self { + into_ref!(ipc); + + Self { _peri: ipc } + } + + /// Duplicates the peripheral singleton + /// + /// # Safety + /// + /// Ensure manually that only one peripheral is in use at one time + pub unsafe fn clone_unchecked(&self) -> Self { + Self { + _peri: self._peri.clone_unchecked(), + } + } + + /// Configures the sending of events + /// + /// Events can be configured to broadcast on one or multiple IPC channels. + pub fn configure_send_event>(&self, ev: IpcEvent, channels: I) { + let regs = T::regs(); + + regs.send_cnf(ev as usize).write(|w| { + for channel in channels { + match channel { + IpcChannel::Channel0 => w.set_chen0(true), + IpcChannel::Channel1 => w.set_chen1(true), + IpcChannel::Channel2 => w.set_chen2(true), + IpcChannel::Channel3 => w.set_chen3(true), + IpcChannel::Channel4 => w.set_chen4(true), + IpcChannel::Channel5 => w.set_chen5(true), + IpcChannel::Channel6 => w.set_chen6(true), + IpcChannel::Channel7 => w.set_chen7(true), + IpcChannel::Channel8 => w.set_chen8(true), + IpcChannel::Channel9 => w.set_chen9(true), + IpcChannel::Channel10 => w.set_chen10(true), + IpcChannel::Channel11 => w.set_chen11(true), + IpcChannel::Channel12 => w.set_chen12(true), + IpcChannel::Channel13 => w.set_chen13(true), + IpcChannel::Channel14 => w.set_chen14(true), + IpcChannel::Channel15 => w.set_chen15(true), + } + } + }) + } + + /// Configures the receiving of events + /// + /// Events can be configured to be received by one or multiple IPC channels. + pub fn configure_receive_event>(&self, ev: IpcEvent, channels: I) { + let regs = T::regs(); + + regs.receive_cnf(ev as usize).write(|w| { + for channel in channels { + match channel { + IpcChannel::Channel0 => w.set_chen0(true), + IpcChannel::Channel1 => w.set_chen1(true), + IpcChannel::Channel2 => w.set_chen2(true), + IpcChannel::Channel3 => w.set_chen3(true), + IpcChannel::Channel4 => w.set_chen4(true), + IpcChannel::Channel5 => w.set_chen5(true), + IpcChannel::Channel6 => w.set_chen6(true), + IpcChannel::Channel7 => w.set_chen7(true), + IpcChannel::Channel8 => w.set_chen8(true), + IpcChannel::Channel9 => w.set_chen9(true), + IpcChannel::Channel10 => w.set_chen10(true), + IpcChannel::Channel11 => w.set_chen11(true), + IpcChannel::Channel12 => w.set_chen12(true), + IpcChannel::Channel13 => w.set_chen13(true), + IpcChannel::Channel14 => w.set_chen14(true), + IpcChannel::Channel15 => w.set_chen15(true), + } + } + }); + } + + /// Triggers an event + pub fn trigger_event(&self, ev: IpcEvent) { + let regs = T::regs(); + + regs.tasks_send(ev as usize).write_value(0x01); + } + + /// Wait for event to be triggered + pub async fn wait_for_event(&self, ev: IpcEvent) { + let regs = T::regs(); + + // Enable interrupt + match ev { + IpcEvent::Event0 => { + regs.inten().modify(|m| m.set_receive0(true)); + } + IpcEvent::Event1 => { + regs.inten().modify(|m| m.set_receive1(true)); + } + IpcEvent::Event2 => { + regs.inten().modify(|m| m.set_receive2(true)); + } + IpcEvent::Event3 => { + regs.inten().modify(|m| m.set_receive3(true)); + } + IpcEvent::Event4 => { + regs.inten().modify(|m| m.set_receive4(true)); + } + IpcEvent::Event5 => { + regs.inten().modify(|m| m.set_receive5(true)); + } + IpcEvent::Event6 => { + regs.inten().modify(|m| m.set_receive6(true)); + } + IpcEvent::Event7 => { + regs.inten().modify(|m| m.set_receive7(true)); + } + IpcEvent::Event8 => { + regs.inten().modify(|m| m.set_receive8(true)); + } + IpcEvent::Event9 => { + regs.inten().modify(|m| m.set_receive9(true)); + } + IpcEvent::Event10 => { + regs.inten().modify(|m| m.set_receive10(true)); + } + IpcEvent::Event11 => { + regs.inten().modify(|m| m.set_receive11(true)); + } + IpcEvent::Event12 => { + regs.inten().modify(|m| m.set_receive12(true)); + } + IpcEvent::Event13 => { + regs.inten().modify(|m| m.set_receive13(true)); + } + IpcEvent::Event14 => { + regs.inten().modify(|m| m.set_receive14(true)); + } + IpcEvent::Event15 => { + regs.inten().modify(|m| m.set_receive15(true)); + } + }; + + poll_fn(|cx| { + IPC::state().waker_for(ev).register(cx.waker()); + + if regs.events_receive(ev as usize).read() & 0x01 == 0x01 { + regs.events_receive(ev as usize).write_value(0x00); + + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + } +} + +pub(crate) struct State { + wakers: [AtomicWaker; 16], +} + +impl State { + pub(crate) const fn new() -> Self { + const WAKER: AtomicWaker = AtomicWaker::new(); + + Self { wakers: [WAKER; 16] } + } + + const fn waker_for(&self, ev: IpcEvent) -> &AtomicWaker { + match ev { + IpcEvent::Event0 => &self.wakers[0], + IpcEvent::Event1 => &self.wakers[1], + IpcEvent::Event2 => &self.wakers[2], + IpcEvent::Event3 => &self.wakers[3], + IpcEvent::Event4 => &self.wakers[4], + IpcEvent::Event5 => &self.wakers[5], + IpcEvent::Event6 => &self.wakers[6], + IpcEvent::Event7 => &self.wakers[7], + IpcEvent::Event8 => &self.wakers[8], + IpcEvent::Event9 => &self.wakers[9], + IpcEvent::Event10 => &self.wakers[10], + IpcEvent::Event11 => &self.wakers[11], + IpcEvent::Event12 => &self.wakers[12], + IpcEvent::Event13 => &self.wakers[13], + IpcEvent::Event14 => &self.wakers[14], + IpcEvent::Event15 => &self.wakers[15], + } + } +} + +pub(crate) trait SealedInstance { + fn regs() -> pac::ipc::Ipc; + fn state() -> &'static State; +} + +/// IPC peripheral instance. +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_ipc { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::ipc::SealedInstance for peripherals::$type { + fn regs() -> pac::ipc::Ipc { + pac::$pac_type + } + + fn state() -> &'static crate::ipc::State { + static STATE: crate::ipc::State = crate::ipc::State::new(); + &STATE + } + } + impl crate::ipc::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 07ba2f6d4..5bce65a98 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -88,6 +88,8 @@ pub mod gpiote; #[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840"))] pub mod i2s; +#[cfg(feature = "_nrf5340")] +pub mod ipc; #[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(any( feature = "nrf52832", -- cgit From a0a339d01ade57aa6e66835a27b1e6ea54333b54 Mon Sep 17 00:00:00 2001 From: Matthew Tran <0e4ef622@gmail.com> Date: Thu, 8 May 2025 23:26:10 -0500 Subject: nrf: Rework IPC module --- embassy-nrf/src/ipc.rs | 391 +++++++++++++++++++++++++------------------------ 1 file changed, 197 insertions(+), 194 deletions(-) diff --git a/embassy-nrf/src/ipc.rs b/embassy-nrf/src/ipc.rs index 410783ef4..a8a08c911 100644 --- a/embassy-nrf/src/ipc.rs +++ b/embassy-nrf/src/ipc.rs @@ -3,18 +3,20 @@ #![macro_use] use core::future::poll_fn; -use core::sync::atomic::{compiler_fence, Ordering}; +use core::marker::PhantomData; use core::task::Poll; -use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; -use crate::peripherals::IPC; -use crate::{interrupt, pac}; +use crate::interrupt::typelevel::Interrupt; +use crate::{interrupt, pac, ppi}; + +const EVENT_COUNT: usize = 16; /// IPC Event -#[derive(Debug, Clone, Copy)] -pub enum IpcEvent { +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum EventNumber { /// IPC Event 0 Event0 = 0, /// IPC Event 1 @@ -49,27 +51,27 @@ pub enum IpcEvent { Event15 = 15, } -const EVENTS: [IpcEvent; 16] = [ - IpcEvent::Event0, - IpcEvent::Event1, - IpcEvent::Event2, - IpcEvent::Event3, - IpcEvent::Event4, - IpcEvent::Event5, - IpcEvent::Event6, - IpcEvent::Event7, - IpcEvent::Event8, - IpcEvent::Event9, - IpcEvent::Event10, - IpcEvent::Event11, - IpcEvent::Event12, - IpcEvent::Event13, - IpcEvent::Event14, - IpcEvent::Event15, +const EVENTS: [EventNumber; EVENT_COUNT] = [ + EventNumber::Event0, + EventNumber::Event1, + EventNumber::Event2, + EventNumber::Event3, + EventNumber::Event4, + EventNumber::Event5, + EventNumber::Event6, + EventNumber::Event7, + EventNumber::Event8, + EventNumber::Event9, + EventNumber::Event10, + EventNumber::Event11, + EventNumber::Event12, + EventNumber::Event13, + EventNumber::Event14, + EventNumber::Event15, ]; /// IPC Channel -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum IpcChannel { /// IPC Channel 0 Channel0, @@ -105,226 +107,227 @@ pub enum IpcChannel { Channel15, } +impl IpcChannel { + fn mask(self) -> u32 { + 1 << (self as u32) + } +} + /// Interrupt Handler -pub struct InterruptHandler {} +pub struct InterruptHandler { + _phantom: PhantomData, +} -impl interrupt::typelevel::Handler for InterruptHandler { +impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { - let regs = IPC::regs(); + let regs = T::regs(); // Check if an event was generated, and if it was, trigger the corresponding waker for event in EVENTS { if regs.events_receive(event as usize).read() & 0x01 == 0x01 { - // Event is set. Reset and wake waker - regs.events_receive(event as usize).write_value(0); - IPC::state().waker_for(event); + regs.intenclr().write(|w| w.0 = 0x01 << event as u32); + T::state().wakers[event as usize].wake(); } - - // Ensure the state is actually cleared - // Ref: nRF5340 PS v1.5 7.1.9.1 p.153 - compiler_fence(Ordering::SeqCst); - while regs.events_receive(event as usize).read() & 0x01 != 0x00 {} } } } /// IPC driver +#[non_exhaustive] pub struct Ipc<'d, T: Instance> { - _peri: PeripheralRef<'d, T>, + /// Event 0 + pub event0: Event<'d, T>, + /// Event 1 + pub event1: Event<'d, T>, + /// Event 2 + pub event2: Event<'d, T>, + /// Event 3 + pub event3: Event<'d, T>, + /// Event 4 + pub event4: Event<'d, T>, + /// Event 5 + pub event5: Event<'d, T>, + /// Event 6 + pub event6: Event<'d, T>, + /// Event 7 + pub event7: Event<'d, T>, + /// Event 8 + pub event8: Event<'d, T>, + /// Event 9 + pub event9: Event<'d, T>, + /// Event 10 + pub event10: Event<'d, T>, + /// Event 11 + pub event11: Event<'d, T>, + /// Event 12 + pub event12: Event<'d, T>, + /// Event 13 + pub event13: Event<'d, T>, + /// Event 14 + pub event14: Event<'d, T>, + /// Event 15 + pub event15: Event<'d, T>, } -impl<'d, T: Instance> From> for Ipc<'d, T> { - fn from(value: PeripheralRef<'d, T>) -> Self { - Self { _peri: value } +impl<'d, T: Instance> Ipc<'d, T> { + /// Create a new IPC driver. + pub fn new( + _p: Peri<'d, T>, + _irq: impl interrupt::typelevel::Binding> + 'd, + ) -> Self { + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + let _phantom = PhantomData; + #[rustfmt::skip] + let r = Self { // attributes on expressions are experimental + event0: Event { number: EventNumber::Event0, _phantom }, + event1: Event { number: EventNumber::Event1, _phantom }, + event2: Event { number: EventNumber::Event2, _phantom }, + event3: Event { number: EventNumber::Event3, _phantom }, + event4: Event { number: EventNumber::Event4, _phantom }, + event5: Event { number: EventNumber::Event5, _phantom }, + event6: Event { number: EventNumber::Event6, _phantom }, + event7: Event { number: EventNumber::Event7, _phantom }, + event8: Event { number: EventNumber::Event8, _phantom }, + event9: Event { number: EventNumber::Event9, _phantom }, + event10: Event { number: EventNumber::Event10, _phantom }, + event11: Event { number: EventNumber::Event11, _phantom }, + event12: Event { number: EventNumber::Event12, _phantom }, + event13: Event { number: EventNumber::Event13, _phantom }, + event14: Event { number: EventNumber::Event14, _phantom }, + event15: Event { number: EventNumber::Event15, _phantom }, + }; + r } } -impl<'d, T: Instance> Ipc<'d, T> { - /// Create IPC driver - pub fn new(ipc: impl Peripheral

+ 'd) -> Self { - into_ref!(ipc); +/// IPC event +pub struct Event<'d, T: Instance> { + number: EventNumber, + _phantom: PhantomData<&'d T>, +} - Self { _peri: ipc } +impl<'d, T: Instance> Event<'d, T> { + /// Trigger the event. + pub fn trigger(&self) { + let nr = self.number; + T::regs().tasks_send(nr as usize).write_value(1); } - /// Duplicates the peripheral singleton - /// - /// # Safety - /// - /// Ensure manually that only one peripheral is in use at one time - pub unsafe fn clone_unchecked(&self) -> Self { - Self { - _peri: self._peri.clone_unchecked(), - } + /// Wait for the event to be triggered. + pub async fn wait(&mut self) { + let regs = T::regs(); + let nr = self.number as usize; + regs.intenset().write(|w| w.0 = 1 << nr); + poll_fn(|cx| { + T::state().wakers[nr].register(cx.waker()); + + if regs.events_receive(nr).read() == 1 { + regs.events_receive(nr).write_value(0x00); + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; } - /// Configures the sending of events - /// - /// Events can be configured to broadcast on one or multiple IPC channels. - pub fn configure_send_event>(&self, ev: IpcEvent, channels: I) { - let regs = T::regs(); + /// Returns the [`EventNumber`] of the event. + pub fn number(&self) -> EventNumber { + self.number + } - regs.send_cnf(ev as usize).write(|w| { + /// Create a handle that can trigger the event. + pub fn trigger_handle(&self) -> EventTrigger<'d, T> { + EventTrigger { + number: self.number, + _phantom: PhantomData, + } + } + + /// Configure the channels the event will broadcast to + pub fn configure_trigger>(&mut self, channels: I) { + T::regs().send_cnf(self.number as usize).write(|w| { for channel in channels { - match channel { - IpcChannel::Channel0 => w.set_chen0(true), - IpcChannel::Channel1 => w.set_chen1(true), - IpcChannel::Channel2 => w.set_chen2(true), - IpcChannel::Channel3 => w.set_chen3(true), - IpcChannel::Channel4 => w.set_chen4(true), - IpcChannel::Channel5 => w.set_chen5(true), - IpcChannel::Channel6 => w.set_chen6(true), - IpcChannel::Channel7 => w.set_chen7(true), - IpcChannel::Channel8 => w.set_chen8(true), - IpcChannel::Channel9 => w.set_chen9(true), - IpcChannel::Channel10 => w.set_chen10(true), - IpcChannel::Channel11 => w.set_chen11(true), - IpcChannel::Channel12 => w.set_chen12(true), - IpcChannel::Channel13 => w.set_chen13(true), - IpcChannel::Channel14 => w.set_chen14(true), - IpcChannel::Channel15 => w.set_chen15(true), - } + w.0 |= channel.mask(); } }) } - /// Configures the receiving of events - /// - /// Events can be configured to be received by one or multiple IPC channels. - pub fn configure_receive_event>(&self, ev: IpcEvent, channels: I) { - let regs = T::regs(); - - regs.receive_cnf(ev as usize).write(|w| { + /// Configure the channels the event will listen on + pub fn configure_wait>(&mut self, channels: I) { + T::regs().receive_cnf(self.number as usize).write(|w| { for channel in channels { - match channel { - IpcChannel::Channel0 => w.set_chen0(true), - IpcChannel::Channel1 => w.set_chen1(true), - IpcChannel::Channel2 => w.set_chen2(true), - IpcChannel::Channel3 => w.set_chen3(true), - IpcChannel::Channel4 => w.set_chen4(true), - IpcChannel::Channel5 => w.set_chen5(true), - IpcChannel::Channel6 => w.set_chen6(true), - IpcChannel::Channel7 => w.set_chen7(true), - IpcChannel::Channel8 => w.set_chen8(true), - IpcChannel::Channel9 => w.set_chen9(true), - IpcChannel::Channel10 => w.set_chen10(true), - IpcChannel::Channel11 => w.set_chen11(true), - IpcChannel::Channel12 => w.set_chen12(true), - IpcChannel::Channel13 => w.set_chen13(true), - IpcChannel::Channel14 => w.set_chen14(true), - IpcChannel::Channel15 => w.set_chen15(true), - } + w.0 |= channel.mask(); } }); } - /// Triggers an event - pub fn trigger_event(&self, ev: IpcEvent) { + /// Get the task for the IPC event to use with PPI. + pub fn task(&self) -> ppi::Task<'d> { + let nr = self.number as usize; let regs = T::regs(); - - regs.tasks_send(ev as usize).write_value(0x01); + ppi::Task::from_reg(regs.tasks_send(nr)) } - /// Wait for event to be triggered - pub async fn wait_for_event(&self, ev: IpcEvent) { + /// Get the event for the IPC event to use with PPI. + pub fn event(&self) -> ppi::Event<'d> { + let nr = self.number as usize; let regs = T::regs(); + ppi::Event::from_reg(regs.events_receive(nr)) + } - // Enable interrupt - match ev { - IpcEvent::Event0 => { - regs.inten().modify(|m| m.set_receive0(true)); - } - IpcEvent::Event1 => { - regs.inten().modify(|m| m.set_receive1(true)); - } - IpcEvent::Event2 => { - regs.inten().modify(|m| m.set_receive2(true)); - } - IpcEvent::Event3 => { - regs.inten().modify(|m| m.set_receive3(true)); - } - IpcEvent::Event4 => { - regs.inten().modify(|m| m.set_receive4(true)); - } - IpcEvent::Event5 => { - regs.inten().modify(|m| m.set_receive5(true)); - } - IpcEvent::Event6 => { - regs.inten().modify(|m| m.set_receive6(true)); - } - IpcEvent::Event7 => { - regs.inten().modify(|m| m.set_receive7(true)); - } - IpcEvent::Event8 => { - regs.inten().modify(|m| m.set_receive8(true)); - } - IpcEvent::Event9 => { - regs.inten().modify(|m| m.set_receive9(true)); - } - IpcEvent::Event10 => { - regs.inten().modify(|m| m.set_receive10(true)); - } - IpcEvent::Event11 => { - regs.inten().modify(|m| m.set_receive11(true)); - } - IpcEvent::Event12 => { - regs.inten().modify(|m| m.set_receive12(true)); - } - IpcEvent::Event13 => { - regs.inten().modify(|m| m.set_receive13(true)); - } - IpcEvent::Event14 => { - regs.inten().modify(|m| m.set_receive14(true)); - } - IpcEvent::Event15 => { - regs.inten().modify(|m| m.set_receive15(true)); - } - }; + /// Reborrow into a "child" Event. + /// + /// `self` will stay borrowed until the child Event is dropped. + pub fn reborrow(&mut self) -> Event<'_, T> { + Self { ..*self } + } - poll_fn(|cx| { - IPC::state().waker_for(ev).register(cx.waker()); + /// Steal an IPC event by number. + /// + /// # Safety + /// + /// The event number must not be in use by another [`Event`]. + pub unsafe fn steal(number: EventNumber) -> Self { + Self { + number, + _phantom: PhantomData, + } + } +} - if regs.events_receive(ev as usize).read() & 0x01 == 0x01 { - regs.events_receive(ev as usize).write_value(0x00); +/// A handle that can trigger an IPC event. +/// +/// This `struct` is returned by [`Event::trigger_handle`]. +#[derive(Debug, Copy, Clone)] +pub struct EventTrigger<'d, T: Instance> { + number: EventNumber, + _phantom: PhantomData<&'d T>, +} - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; +impl EventTrigger<'_, T> { + /// Trigger the event. + pub fn trigger(&self) { + let nr = self.number; + T::regs().tasks_send(nr as usize).write_value(1); + } + + /// Returns the [`EventNumber`] of the event. + pub fn number(&self) -> EventNumber { + self.number } } pub(crate) struct State { - wakers: [AtomicWaker; 16], + wakers: [AtomicWaker; EVENT_COUNT], } impl State { pub(crate) const fn new() -> Self { - const WAKER: AtomicWaker = AtomicWaker::new(); - - Self { wakers: [WAKER; 16] } - } - - const fn waker_for(&self, ev: IpcEvent) -> &AtomicWaker { - match ev { - IpcEvent::Event0 => &self.wakers[0], - IpcEvent::Event1 => &self.wakers[1], - IpcEvent::Event2 => &self.wakers[2], - IpcEvent::Event3 => &self.wakers[3], - IpcEvent::Event4 => &self.wakers[4], - IpcEvent::Event5 => &self.wakers[5], - IpcEvent::Event6 => &self.wakers[6], - IpcEvent::Event7 => &self.wakers[7], - IpcEvent::Event8 => &self.wakers[8], - IpcEvent::Event9 => &self.wakers[9], - IpcEvent::Event10 => &self.wakers[10], - IpcEvent::Event11 => &self.wakers[11], - IpcEvent::Event12 => &self.wakers[12], - IpcEvent::Event13 => &self.wakers[13], - IpcEvent::Event14 => &self.wakers[14], - IpcEvent::Event15 => &self.wakers[15], + Self { + wakers: [const { AtomicWaker::new() }; EVENT_COUNT], } } } @@ -336,7 +339,7 @@ pub(crate) trait SealedInstance { /// IPC peripheral instance. #[allow(private_bounds)] -pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { +pub trait Instance: PeripheralType + SealedInstance + 'static + Send { /// Interrupt for this peripheral. type Interrupt: interrupt::typelevel::Interrupt; } -- cgit