From 4ce3bdb3703e5120c7936b5e3762744ae4461e75 Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Sat, 26 Apr 2025 21:54:40 +0200 Subject: [PATCH 01/18] 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 { From 713d6291d569cf44ce3a53bc93ddd7d569fb93ed Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Sat, 26 Apr 2025 21:54:48 +0200 Subject: [PATCH 02/18] 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); From 45b7127d614ddc65181249e70d94422500865ecd Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Sat, 26 Apr 2025 21:55:16 +0200 Subject: [PATCH 03/18] 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); From b0594d16f238f803a0192810833ae2b0c3941ec3 Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Sat, 26 Apr 2025 22:55:24 +0200 Subject: [PATCH 04/18] 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; + } +} From 3a6dc910ffc66d4a30b89f299432b383271a719f Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Mon, 28 Apr 2025 22:54:15 +0200 Subject: [PATCH 05/18] first working draft --- embassy-rp/src/clocks.rs | 666 +++++++++++++++++++++++-------- examples/rp/src/bin/overclock.rs | 26 +- 2 files changed, 509 insertions(+), 183 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; - // 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, - }); - } + // 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, + }); } } } } - None // No valid parameters found + // If we couldn't find an exact match, find the closest match + let mut best_config = None; + let mut min_diff = u32::MAX; + + for fbdiv in (16..=320).rev() { + let vco_freq = reference_freq * fbdiv; + + 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, + }); + } + } + } + } + + // 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" + ); - // Load VCO-related dividers before starting VCO - p.cs().write(|w| w.set_refdiv(config.refdiv as _)); - p.fbdiv_int().write(|w| w.set_fbdiv_int(config.fbdiv)); + // 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 - // 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); + // 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 }); - // Wait for PLL to lock - while !p.cs().read().lock() {} + // Short delay after powering down + cortex_m::asm::delay(10); - // Set post-dividers + // 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)); + + // 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 + }); + + // 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; + } + } + + // 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 From 77e8bc9b28d6988b2703029679f290b351fc54a0 Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Tue, 29 Apr 2025 22:49:05 +0200 Subject: [PATCH 06/18] 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,14 +246,57 @@ 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 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: None, + xosc: None, + ref_clk: RefClkConfig { + src: RefClkSrc::Rosc, + div: 1, + }, + sys_clk: SysClkConfig { + src: SysClkSrc::Rosc, + div_int: 1, + div_frac: 0, + }, + peri_clk_src: None, + usb_clk: None, + adc_clk: None, + #[cfg(feature = "rp2040")] + rtc_clk: None, + #[cfg(feature = "rp2040")] + voltage_scale: None, + #[cfg(feature = "rp2040")] + voltage_stabilization_delay_us: None, + // gpin0: None, + // gpin1: None, + } + } +} + impl ClockConfig { /// Clock configuration derived from external crystal. + /// + /// 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 { @@ -217,101 +363,6 @@ impl ClockConfig { } } - /// 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 = 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 { - 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), - #[cfg(feature = "rp2040")] - voltage_stabilization_delay_us: None, - // gpin0: None, - // gpin1: None, - } - } - /// Clock configuration from internal oscillator. pub fn rosc() -> Self { Self { @@ -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 From d44b94523576d3f8c1f586811f600eb3ba223606 Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Tue, 29 Apr 2025 22:49:53 +0200 Subject: [PATCH 07/18] 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 From 22b5f73811a7cc0dbca920e02b5d001d252d344c Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Thu, 1 May 2025 00:11:56 +0200 Subject: [PATCH 08/18] 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; + } +} From c01776a3d779caf5f43fda03e5506f569f1bf9ac Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Thu, 1 May 2025 17:07:48 +0200 Subject: [PATCH 09/18] - 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)); + } + } +} From 3c559378a5f3662134cb7db2d902302d7cb8f937 Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Thu, 1 May 2025 18:29:13 +0200 Subject: [PATCH 10/18] 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(); +} From 7fa59a6b31c7c9744e5cdef3826bc4e726736606 Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Thu, 1 May 2025 18:38:03 +0200 Subject: [PATCH 11/18] 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(); +} From 561356f68ab2d3d6fbe1f85245eb320dbc3e59f0 Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Thu, 1 May 2025 22:17:25 +0200 Subject: [PATCH 12/18] overclock test, should now run on all rp chips --- tests/rp/src/bin/overclock.rs | 96 +++++++++++++++-------------------- 1 file changed, 41 insertions(+), 55 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::clocks::ClockConfig; +#[cfg(feature = "rp2040")] +use embassy_rp::clocks::VoltageScale; 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 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 + ); + + #[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"); + } - info!("All tests passed at 200MHz!"); - 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(); } From a33e7172f6bf7dc9590432dd62c2b71d0215d99d Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Thu, 1 May 2025 22:23:14 +0200 Subject: [PATCH 13/18] 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")] From 3441e805070c7efb7cad20a84d1986e215b4de3d Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Fri, 2 May 2025 23:51:28 +0200 Subject: [PATCH 14/18] first batch of changes after review --- embassy-rp/src/clocks.rs | 322 +++++++++--------------- 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, 133 insertions(+), 230 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)); + 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_20 - let config = ClockConfig::at_sys_frequency_mhz(250); - assert_eq!(config.voltage_scale, Some(VoltageScale::V1_20)); + // 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 { From 3d9cac361ed6bd0cd00cab7bf924608c89de9108 Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Sat, 3 May 2025 14:46:30 +0200 Subject: [PATCH 15/18] 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(); } From 0d03aa0bec01fb0289915861d997bf7f2cf8d232 Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Mon, 5 May 2025 22:55:09 +0200 Subject: [PATCH 16/18] rework init() --- embassy-rp/src/clocks.rs | 266 ++++++++++++++++----------------------- 1 file changed, 108 insertions(+), 158 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; + 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); + } - // 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. 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 + + 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(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, + }; + + (config.hz, pll_sys_freq, pll_usb_freq) } - None => 0, + None => (0, 0, 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); - // 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) { - Ok(freq) => freq, - Err(_) => { - panic!("Failed to configure USB PLL"); - } - }; - - CLOCKS.pll_usb.store(usb_pll_freq, Ordering::Relaxed); - usb_pll_freq - } - None => 0, - }, - None => 0, + 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), + } }; - - // Configure REF clock to use XOSC - c.clk_ref_ctrl().write(|w| { - w.set_src(ClkRefCtrlSrc::XOSC_CLKSRC); - }); - #[cfg(feature = "rp2040")] - 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 << 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); - }); - - #[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 + assert!(clk_ref_freq != 0); CLOCKS.reference.store(clk_ref_freq, Ordering::Relaxed); + c.clk_ref_ctrl().write(|w| { + w.set_src(ref_src); + w.set_auxsrc(ref_aux); + }); + #[cfg(feature = "rp2040")] + 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 << ref_src as u32) {} + c.clk_ref_div().write(|w| { + w.set_int(config.ref_clk.div); + }); + + // 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); - }); + 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) {} } - - // 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| { - 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); + c.clk_sys_div().write(|w| { + w.set_int(config.sys_clk.div_int); + w.set_frac(config.sys_clk.div_frac); + }); - // 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 { @@ -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 { From a254daf4fffe74c65d1846f620dd674fa4e14aac Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Wed, 7 May 2025 21:19:09 +0200 Subject: [PATCH 17/18] 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"); } From 4621c8aa7a1ee1b55f2f0bf80fc48eddf76af320 Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Wed, 7 May 2025 22:16:29 +0200 Subject: [PATCH 18/18] 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,