diff options
| author | Ulf Lilleengen <[email protected]> | 2025-05-09 19:34:43 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-05-09 19:34:43 +0200 |
| commit | 11364077a7bb6d14bd37567d17ddb21249409ec7 (patch) | |
| tree | c1d3fb45984b5ae17a684a32f6d45aff63b5592d | |
| parent | 2a27aa828cdc310b17c379183bdfaba6585ad933 (diff) | |
| parent | 4621c8aa7a1ee1b55f2f0bf80fc48eddf76af320 (diff) | |
Merge pull request #4150 from 1-rafael-1/rp2040-overclocking
RP: rp2040 overclocking
| -rw-r--r-- | embassy-rp/src/clocks.rs | 821 | ||||
| -rw-r--r-- | embassy-rp/src/pio_programs/clock_divider.rs | 25 | ||||
| -rw-r--r-- | embassy-rp/src/pio_programs/hd44780.rs | 11 | ||||
| -rw-r--r-- | embassy-rp/src/pio_programs/mod.rs | 1 | ||||
| -rw-r--r-- | embassy-rp/src/pio_programs/rotary_encoder.rs | 8 | ||||
| -rw-r--r-- | embassy-rp/src/pio_programs/stepper.rs | 17 | ||||
| -rw-r--r-- | examples/rp/src/bin/overclock.rs | 64 | ||||
| -rw-r--r-- | examples/rp/src/bin/overclock_manual.rs | 79 | ||||
| -rw-r--r-- | tests/rp/src/bin/overclock.rs | 70 |
9 files changed, 1052 insertions, 44 deletions
diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index 67aa5e540..6694aab66 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs | |||
| @@ -1,4 +1,67 @@ | |||
| 1 | //! Clock configuration for the RP2040 | 1 | //! # Clock configuration for the RP2040 and RP235x microcontrollers. |
| 2 | //! | ||
| 3 | //! # Clock Configuration API | ||
| 4 | //! | ||
| 5 | //! This module provides both high-level convenience functions and low-level manual | ||
| 6 | //! configuration options for the RP2040 clock system. | ||
| 7 | //! | ||
| 8 | //! ## High-Level Convenience Functions | ||
| 9 | //! | ||
| 10 | //! For most users, these functions provide an easy way to configure clocks: | ||
| 11 | //! | ||
| 12 | //! - `ClockConfig::system_freq(125_000_000)` - Set system clock to a specific frequency with automatic voltage scaling | ||
| 13 | //! - `ClockConfig::crystal(12_000_000)` - Default configuration with 12MHz crystal giving 125MHz system clock | ||
| 14 | //! | ||
| 15 | //! ## Manual Configuration | ||
| 16 | //! | ||
| 17 | //! For advanced users who need precise control: | ||
| 18 | //! | ||
| 19 | //! ```rust,ignore | ||
| 20 | //! // Start with default configuration and customize it | ||
| 21 | //! let mut config = ClockConfig::default(); | ||
| 22 | //! | ||
| 23 | //! // Set custom PLL parameters | ||
| 24 | //! config.xosc = Some(XoscConfig { | ||
| 25 | //! hz: 12_000_000, | ||
| 26 | //! sys_pll: Some(PllConfig { | ||
| 27 | //! refdiv: 1, | ||
| 28 | //! fbdiv: 200, | ||
| 29 | //! post_div1: 6, | ||
| 30 | //! post_div2: 2, | ||
| 31 | //! }), | ||
| 32 | //! // ... other fields | ||
| 33 | //! }); | ||
| 34 | //! | ||
| 35 | //! // Set voltage for overclocking | ||
| 36 | //! config.core_voltage = CoreVoltage::V1_15; | ||
| 37 | //! ``` | ||
| 38 | //! | ||
| 39 | //! ## Examples | ||
| 40 | //! | ||
| 41 | //! ### Standard 125MHz configuration | ||
| 42 | //! ```rust,ignore | ||
| 43 | //! let config = ClockConfig::crystal(12_000_000); | ||
| 44 | //! ``` | ||
| 45 | //! | ||
| 46 | //! Or using the default configuration: | ||
| 47 | //! ```rust,ignore | ||
| 48 | //! let config = ClockConfig::default(); | ||
| 49 | //! ``` | ||
| 50 | //! | ||
| 51 | //! ### Overclock to 200MHz | ||
| 52 | //! ```rust,ignore | ||
| 53 | //! let config = ClockConfig::system_freq(200_000_000); | ||
| 54 | //! ``` | ||
| 55 | //! | ||
| 56 | //! ### Manual configuration for advanced scenarios | ||
| 57 | //! ```rust,ignore | ||
| 58 | //! use embassy_rp::clocks::{ClockConfig, XoscConfig, PllConfig, CoreVoltage}; | ||
| 59 | //! | ||
| 60 | //! // Start with defaults and customize | ||
| 61 | //! let mut config = ClockConfig::default(); | ||
| 62 | //! config.core_voltage = CoreVoltage::V1_15; | ||
| 63 | //! // Set other parameters as needed... | ||
| 64 | //! ``` | ||
| 2 | 65 | ||
| 3 | #[cfg(feature = "rp2040")] | 66 | #[cfg(feature = "rp2040")] |
| 4 | use core::arch::asm; | 67 | use core::arch::asm; |
| @@ -18,6 +81,7 @@ use crate::{pac, reset, Peri}; | |||
| 18 | // gpin is not usually safe to use during the boot init() call, so it won't | 81 | // gpin is not usually safe to use during the boot init() call, so it won't |
| 19 | // be very useful until we have runtime clock reconfiguration. once this | 82 | // be very useful until we have runtime clock reconfiguration. once this |
| 20 | // happens we can resurrect the commented-out gpin bits. | 83 | // happens we can resurrect the commented-out gpin bits. |
| 84 | |||
| 21 | struct Clocks { | 85 | struct Clocks { |
| 22 | xosc: AtomicU32, | 86 | xosc: AtomicU32, |
| 23 | sys: AtomicU32, | 87 | sys: AtomicU32, |
| @@ -26,6 +90,7 @@ struct Clocks { | |||
| 26 | pll_usb: AtomicU32, | 90 | pll_usb: AtomicU32, |
| 27 | usb: AtomicU32, | 91 | usb: AtomicU32, |
| 28 | adc: AtomicU32, | 92 | adc: AtomicU32, |
| 93 | // See above re gpin handling being commented out | ||
| 29 | // gpin0: AtomicU32, | 94 | // gpin0: AtomicU32, |
| 30 | // gpin1: AtomicU32, | 95 | // gpin1: AtomicU32, |
| 31 | rosc: AtomicU32, | 96 | rosc: AtomicU32, |
| @@ -42,6 +107,7 @@ static CLOCKS: Clocks = Clocks { | |||
| 42 | pll_usb: AtomicU32::new(0), | 107 | pll_usb: AtomicU32::new(0), |
| 43 | usb: AtomicU32::new(0), | 108 | usb: AtomicU32::new(0), |
| 44 | adc: AtomicU32::new(0), | 109 | adc: AtomicU32::new(0), |
| 110 | // See above re gpin handling being commented out | ||
| 45 | // gpin0: AtomicU32::new(0), | 111 | // gpin0: AtomicU32::new(0), |
| 46 | // gpin1: AtomicU32::new(0), | 112 | // gpin1: AtomicU32::new(0), |
| 47 | rosc: AtomicU32::new(0), | 113 | rosc: AtomicU32::new(0), |
| @@ -65,10 +131,64 @@ pub enum PeriClkSrc { | |||
| 65 | Rosc = ClkPeriCtrlAuxsrc::ROSC_CLKSRC_PH as _, | 131 | Rosc = ClkPeriCtrlAuxsrc::ROSC_CLKSRC_PH as _, |
| 66 | /// XOSC. | 132 | /// XOSC. |
| 67 | Xosc = ClkPeriCtrlAuxsrc::XOSC_CLKSRC as _, | 133 | Xosc = ClkPeriCtrlAuxsrc::XOSC_CLKSRC as _, |
| 134 | // See above re gpin handling being commented out | ||
| 68 | // Gpin0 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN0 as _ , | 135 | // Gpin0 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN0 as _ , |
| 69 | // Gpin1 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN1 as _ , | 136 | // Gpin1 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN1 as _ , |
| 70 | } | 137 | } |
| 71 | 138 | ||
| 139 | /// Core voltage regulator settings for RP2040. | ||
| 140 | /// | ||
| 141 | /// The RP2040 voltage regulator can be configured for different output voltages. | ||
| 142 | /// Higher voltages allow for higher clock frequencies but increase power consumption and heat. | ||
| 143 | #[cfg(feature = "rp2040")] | ||
| 144 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||
| 145 | #[repr(u8)] | ||
| 146 | pub enum CoreVoltage { | ||
| 147 | /// 0.80V - Suitable for lower frequencies | ||
| 148 | V0_80 = 0b0000, | ||
| 149 | /// 0.85V | ||
| 150 | V0_85 = 0b0110, | ||
| 151 | /// 0.90V | ||
| 152 | V0_90 = 0b0111, | ||
| 153 | /// 0.95V | ||
| 154 | V0_95 = 0b1000, | ||
| 155 | /// 1.00V | ||
| 156 | V1_00 = 0b1001, | ||
| 157 | /// 1.05V | ||
| 158 | V1_05 = 0b1010, | ||
| 159 | /// 1.10V - Default voltage level | ||
| 160 | V1_10 = 0b1011, | ||
| 161 | /// 1.15V - Required for overclocking to 133-200MHz | ||
| 162 | V1_15 = 0b1100, | ||
| 163 | /// 1.20V | ||
| 164 | V1_20 = 0b1101, | ||
| 165 | /// 1.25V | ||
| 166 | V1_25 = 0b1110, | ||
| 167 | /// 1.30V | ||
| 168 | V1_30 = 0b1111, | ||
| 169 | } | ||
| 170 | |||
| 171 | #[cfg(feature = "rp2040")] | ||
| 172 | impl CoreVoltage { | ||
| 173 | /// Get the recommended Brown-Out Detection (BOD) setting for this voltage. | ||
| 174 | /// Sets the BOD threshold to approximately 80% of the core voltage. | ||
| 175 | fn recommended_bod(self) -> u8 { | ||
| 176 | match self { | ||
| 177 | CoreVoltage::V0_80 => 0b0100, // 0.645V (~81% of 0.80V) | ||
| 178 | CoreVoltage::V0_85 => 0b0101, // 0.688V (~81% of 0.85V) | ||
| 179 | CoreVoltage::V0_90 => 0b0110, // 0.731V (~81% of 0.90V) | ||
| 180 | CoreVoltage::V0_95 => 0b0111, // 0.774V (~81% of 0.95V) | ||
| 181 | CoreVoltage::V1_00 => 0b1000, // 0.817V (~82% of 1.00V) | ||
| 182 | CoreVoltage::V1_05 => 0b1000, // 0.817V (~78% of 1.05V) | ||
| 183 | CoreVoltage::V1_10 => 0b1001, // 0.860V (~78% of 1.10V) | ||
| 184 | CoreVoltage::V1_15 => 0b1010, // 0.903V (~79% of 1.15V) | ||
| 185 | CoreVoltage::V1_20 => 0b1011, // 0.946V (~79% of 1.20V) | ||
| 186 | CoreVoltage::V1_25 => 0b1100, // 0.989V (~79% of 1.25V) | ||
| 187 | CoreVoltage::V1_30 => 0b1101, // 1.032V (~79% of 1.30V) | ||
| 188 | } | ||
| 189 | } | ||
| 190 | } | ||
| 191 | |||
| 72 | /// CLock configuration. | 192 | /// CLock configuration. |
| 73 | #[non_exhaustive] | 193 | #[non_exhaustive] |
| 74 | pub struct ClockConfig { | 194 | pub struct ClockConfig { |
| @@ -89,12 +209,63 @@ pub struct ClockConfig { | |||
| 89 | /// RTC clock configuration. | 209 | /// RTC clock configuration. |
| 90 | #[cfg(feature = "rp2040")] | 210 | #[cfg(feature = "rp2040")] |
| 91 | pub rtc_clk: Option<RtcClkConfig>, | 211 | pub rtc_clk: Option<RtcClkConfig>, |
| 212 | /// Core voltage scaling (RP2040 only). Defaults to 1.10V. | ||
| 213 | #[cfg(feature = "rp2040")] | ||
| 214 | pub core_voltage: CoreVoltage, | ||
| 215 | /// Voltage stabilization delay in microseconds. | ||
| 216 | /// If not set, defaults will be used based on voltage level. | ||
| 217 | #[cfg(feature = "rp2040")] | ||
| 218 | pub voltage_stabilization_delay_us: Option<u32>, | ||
| 219 | // See above re gpin handling being commented out | ||
| 92 | // gpin0: Option<(u32, Gpin<'static, AnyPin>)>, | 220 | // gpin0: Option<(u32, Gpin<'static, AnyPin>)>, |
| 93 | // gpin1: Option<(u32, Gpin<'static, AnyPin>)>, | 221 | // gpin1: Option<(u32, Gpin<'static, AnyPin>)>, |
| 94 | } | 222 | } |
| 95 | 223 | ||
| 224 | impl Default for ClockConfig { | ||
| 225 | /// Creates a minimal default configuration with safe values. | ||
| 226 | /// | ||
| 227 | /// This configuration uses the ring oscillator (ROSC) as the clock source | ||
| 228 | /// and sets minimal defaults that guarantee a working system. It's intended | ||
| 229 | /// as a starting point for manual configuration. | ||
| 230 | /// | ||
| 231 | /// Most users should use one of the more specific configuration functions: | ||
| 232 | /// - `ClockConfig::crystal()` - Standard configuration with external crystal | ||
| 233 | /// - `ClockConfig::rosc()` - Configuration using only the internal oscillator | ||
| 234 | /// - `ClockConfig::system_freq()` - Configuration for a specific system frequency | ||
| 235 | fn default() -> Self { | ||
| 236 | Self { | ||
| 237 | rosc: None, | ||
| 238 | xosc: None, | ||
| 239 | ref_clk: RefClkConfig { | ||
| 240 | src: RefClkSrc::Rosc, | ||
| 241 | div: 1, | ||
| 242 | }, | ||
| 243 | sys_clk: SysClkConfig { | ||
| 244 | src: SysClkSrc::Rosc, | ||
| 245 | div_int: 1, | ||
| 246 | div_frac: 0, | ||
| 247 | }, | ||
| 248 | peri_clk_src: None, | ||
| 249 | usb_clk: None, | ||
| 250 | adc_clk: None, | ||
| 251 | #[cfg(feature = "rp2040")] | ||
| 252 | rtc_clk: None, | ||
| 253 | #[cfg(feature = "rp2040")] | ||
| 254 | core_voltage: CoreVoltage::V1_10, | ||
| 255 | #[cfg(feature = "rp2040")] | ||
| 256 | voltage_stabilization_delay_us: None, | ||
| 257 | // See above re gpin handling being commented out | ||
| 258 | // gpin0: None, | ||
| 259 | // gpin1: None, | ||
| 260 | } | ||
| 261 | } | ||
| 262 | } | ||
| 263 | |||
| 96 | impl ClockConfig { | 264 | impl ClockConfig { |
| 97 | /// Clock configuration derived from external crystal. | 265 | /// Clock configuration derived from external crystal. |
| 266 | /// | ||
| 267 | /// This uses default settings for most parameters, suitable for typical use cases. | ||
| 268 | /// For manual control of PLL parameters, use `new_manual()` or modify the struct fields directly. | ||
| 98 | pub fn crystal(crystal_hz: u32) -> Self { | 269 | pub fn crystal(crystal_hz: u32) -> Self { |
| 99 | Self { | 270 | Self { |
| 100 | rosc: Some(RoscConfig { | 271 | rosc: Some(RoscConfig { |
| @@ -152,6 +323,11 @@ impl ClockConfig { | |||
| 152 | div_frac: 0, | 323 | div_frac: 0, |
| 153 | phase: 0, | 324 | phase: 0, |
| 154 | }), | 325 | }), |
| 326 | #[cfg(feature = "rp2040")] | ||
| 327 | core_voltage: CoreVoltage::V1_10, // Use hardware default (1.10V) | ||
| 328 | #[cfg(feature = "rp2040")] | ||
| 329 | voltage_stabilization_delay_us: None, | ||
| 330 | // See above re gpin handling being commented out | ||
| 155 | // gpin0: None, | 331 | // gpin0: None, |
| 156 | // gpin1: None, | 332 | // gpin1: None, |
| 157 | } | 333 | } |
| @@ -192,20 +368,157 @@ impl ClockConfig { | |||
| 192 | div_frac: 171, | 368 | div_frac: 171, |
| 193 | phase: 0, | 369 | phase: 0, |
| 194 | }), | 370 | }), |
| 371 | #[cfg(feature = "rp2040")] | ||
| 372 | core_voltage: CoreVoltage::V1_10, // Use hardware default (1.10V) | ||
| 373 | #[cfg(feature = "rp2040")] | ||
| 374 | voltage_stabilization_delay_us: None, | ||
| 375 | // See above re gpin handling being commented out | ||
| 195 | // gpin0: None, | 376 | // gpin0: None, |
| 196 | // gpin1: None, | 377 | // gpin1: None, |
| 197 | } | 378 | } |
| 198 | } | 379 | } |
| 199 | 380 | ||
| 200 | // pub fn bind_gpin<P: GpinPin>(&mut self, gpin: Gpin<'static, P>, hz: u32) { | 381 | /// Configure clocks derived from an external crystal with specific system frequency. |
| 201 | // match P::NR { | 382 | /// |
| 202 | // 0 => self.gpin0 = Some((hz, gpin.into())), | 383 | /// This function calculates optimal PLL parameters to achieve the requested system |
| 203 | // 1 => self.gpin1 = Some((hz, gpin.into())), | 384 | /// frequency. This only works for the usual 12MHz crystal. In case a different crystal is used, |
| 204 | // _ => unreachable!(), | 385 | /// You will have to set the PLL parameters manually. |
| 205 | // } | 386 | /// |
| 206 | // // pin is now provisionally bound. if the config is applied it must be forgotten, | 387 | /// # Arguments |
| 207 | // // or Gpin::drop will deconfigure the clock input. | 388 | /// |
| 208 | // } | 389 | /// * `sys_freq_hz` - The desired system clock frequency in Hz |
| 390 | /// | ||
| 391 | /// # Returns | ||
| 392 | /// | ||
| 393 | /// A ClockConfig configured to achieve the requested system frequency using the | ||
| 394 | /// the usual 12Mhz crystal, or panic if no valid parameters can be found. | ||
| 395 | /// | ||
| 396 | /// # Note on core voltage: | ||
| 397 | /// To date the only officially documented core voltages (see Datasheet section 2.15.3.1. Instances) are: | ||
| 398 | /// - Up to 133MHz: V1_10 (default) | ||
| 399 | /// - Above 133MHz: V1_15, but in the context of the datasheet covering reaching up to 200Mhz | ||
| 400 | /// That way all other frequencies below 133MHz or above 200MHz are not explicitly documented and not covered here. | ||
| 401 | /// 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. | ||
| 402 | #[cfg(feature = "rp2040")] | ||
| 403 | pub fn system_freq(hz: u32) -> Self { | ||
| 404 | // Start with the standard configuration from crystal() | ||
| 405 | const DEFAULT_CRYSTAL_HZ: u32 = 12_000_000; | ||
| 406 | let mut config = Self::crystal(DEFAULT_CRYSTAL_HZ); | ||
| 407 | |||
| 408 | // No need to modify anything if target frequency is already 125MHz | ||
| 409 | // (which is what crystal() configures by default) | ||
| 410 | if hz == 125_000_000 { | ||
| 411 | return config; | ||
| 412 | } | ||
| 413 | |||
| 414 | // Find optimal PLL parameters for the requested frequency | ||
| 415 | let sys_pll_params = find_pll_params(DEFAULT_CRYSTAL_HZ, hz) | ||
| 416 | .unwrap_or_else(|| panic!("Could not find valid PLL parameters for system clock")); | ||
| 417 | |||
| 418 | // Replace the sys_pll configuration with our custom parameters | ||
| 419 | if let Some(xosc) = &mut config.xosc { | ||
| 420 | xosc.sys_pll = Some(sys_pll_params); | ||
| 421 | } | ||
| 422 | |||
| 423 | // Set the voltage scale based on the target frequency | ||
| 424 | // Higher frequencies require higher voltage | ||
| 425 | #[cfg(feature = "rp2040")] | ||
| 426 | { | ||
| 427 | config.core_voltage = match hz { | ||
| 428 | freq if freq > 133_000_000 => CoreVoltage::V1_15, | ||
| 429 | _ => CoreVoltage::V1_10, // Use default voltage (V1_10) | ||
| 430 | }; | ||
| 431 | } | ||
| 432 | |||
| 433 | config | ||
| 434 | } | ||
| 435 | |||
| 436 | /// Configure with manual PLL settings for full control over system clock | ||
| 437 | /// | ||
| 438 | /// This method provides a simple way to configure the system with custom PLL parameters | ||
| 439 | /// without needing to understand the full nested configuration structure. | ||
| 440 | /// | ||
| 441 | /// # Arguments | ||
| 442 | /// | ||
| 443 | /// * `xosc_hz` - The frequency of the external crystal in Hz | ||
| 444 | /// * `pll_config` - The PLL configuration parameters to achieve desired frequency | ||
| 445 | /// * `core_voltage` - Voltage scaling for overclocking (required for >133MHz) | ||
| 446 | /// | ||
| 447 | /// # Returns | ||
| 448 | /// | ||
| 449 | /// A ClockConfig configured with the specified PLL parameters | ||
| 450 | /// | ||
| 451 | /// # Example | ||
| 452 | /// | ||
| 453 | /// ```rust,ignore | ||
| 454 | /// // Configure for 200MHz operation | ||
| 455 | /// let config = Config::default(); | ||
| 456 | /// config.clocks = ClockConfig::manual_pll( | ||
| 457 | /// 12_000_000, | ||
| 458 | /// PllConfig { | ||
| 459 | /// refdiv: 1, // Reference divider (12 MHz / 1 = 12 MHz) | ||
| 460 | /// fbdiv: 100, // Feedback divider (12 MHz * 100 = 1200 MHz VCO) | ||
| 461 | /// post_div1: 3, // First post divider (1200 MHz / 3 = 400 MHz) | ||
| 462 | /// post_div2: 2, // Second post divider (400 MHz / 2 = 200 MHz) | ||
| 463 | /// }, | ||
| 464 | /// CoreVoltage::V1_15 | ||
| 465 | /// ); | ||
| 466 | /// ``` | ||
| 467 | #[cfg(feature = "rp2040")] | ||
| 468 | pub fn manual_pll(xosc_hz: u32, pll_config: PllConfig, core_voltage: CoreVoltage) -> Self { | ||
| 469 | // Validate PLL parameters | ||
| 470 | assert!(pll_config.is_valid(xosc_hz), "Invalid PLL parameters"); | ||
| 471 | |||
| 472 | let mut config = Self::default(); | ||
| 473 | |||
| 474 | config.xosc = Some(XoscConfig { | ||
| 475 | hz: xosc_hz, | ||
| 476 | sys_pll: Some(pll_config), | ||
| 477 | usb_pll: Some(PllConfig { | ||
| 478 | refdiv: 1, | ||
| 479 | fbdiv: 120, | ||
| 480 | post_div1: 6, | ||
| 481 | post_div2: 5, | ||
| 482 | }), | ||
| 483 | delay_multiplier: 128, | ||
| 484 | }); | ||
| 485 | |||
| 486 | config.ref_clk = RefClkConfig { | ||
| 487 | src: RefClkSrc::Xosc, | ||
| 488 | div: 1, | ||
| 489 | }; | ||
| 490 | |||
| 491 | config.sys_clk = SysClkConfig { | ||
| 492 | src: SysClkSrc::PllSys, | ||
| 493 | div_int: 1, | ||
| 494 | div_frac: 0, | ||
| 495 | }; | ||
| 496 | |||
| 497 | config.core_voltage = core_voltage; | ||
| 498 | config.peri_clk_src = Some(PeriClkSrc::Sys); | ||
| 499 | |||
| 500 | // Set reasonable defaults for other clocks | ||
| 501 | config.usb_clk = Some(UsbClkConfig { | ||
| 502 | src: UsbClkSrc::PllUsb, | ||
| 503 | div: 1, | ||
| 504 | phase: 0, | ||
| 505 | }); | ||
| 506 | |||
| 507 | config.adc_clk = Some(AdcClkConfig { | ||
| 508 | src: AdcClkSrc::PllUsb, | ||
| 509 | div: 1, | ||
| 510 | phase: 0, | ||
| 511 | }); | ||
| 512 | |||
| 513 | config.rtc_clk = Some(RtcClkConfig { | ||
| 514 | src: RtcClkSrc::PllUsb, | ||
| 515 | div_int: 1024, | ||
| 516 | div_frac: 0, | ||
| 517 | phase: 0, | ||
| 518 | }); | ||
| 519 | |||
| 520 | config | ||
| 521 | } | ||
| 209 | } | 522 | } |
| 210 | 523 | ||
| 211 | /// ROSC freq range. | 524 | /// ROSC freq range. |
| @@ -251,6 +564,7 @@ pub struct XoscConfig { | |||
| 251 | } | 564 | } |
| 252 | 565 | ||
| 253 | /// PLL configuration. | 566 | /// PLL configuration. |
| 567 | #[derive(Clone, Copy, Debug)] | ||
| 254 | pub struct PllConfig { | 568 | pub struct PllConfig { |
| 255 | /// Reference divisor. | 569 | /// Reference divisor. |
| 256 | pub refdiv: u8, | 570 | pub refdiv: u8, |
| @@ -262,6 +576,50 @@ pub struct PllConfig { | |||
| 262 | pub post_div2: u8, | 576 | pub post_div2: u8, |
| 263 | } | 577 | } |
| 264 | 578 | ||
| 579 | impl PllConfig { | ||
| 580 | /// Calculate the output frequency for this PLL configuration | ||
| 581 | /// given an input frequency. | ||
| 582 | pub fn output_frequency(&self, input_hz: u32) -> u32 { | ||
| 583 | let ref_freq = input_hz / self.refdiv as u32; | ||
| 584 | let vco_freq = ref_freq * self.fbdiv as u32; | ||
| 585 | vco_freq / ((self.post_div1 * self.post_div2) as u32) | ||
| 586 | } | ||
| 587 | |||
| 588 | /// Check if this PLL configuration is valid for the given input frequency. | ||
| 589 | pub fn is_valid(&self, input_hz: u32) -> bool { | ||
| 590 | // Check divisor constraints | ||
| 591 | if self.refdiv < 1 || self.refdiv > 63 { | ||
| 592 | return false; | ||
| 593 | } | ||
| 594 | if self.fbdiv < 16 || self.fbdiv > 320 { | ||
| 595 | return false; | ||
| 596 | } | ||
| 597 | if self.post_div1 < 1 || self.post_div1 > 7 { | ||
| 598 | return false; | ||
| 599 | } | ||
| 600 | if self.post_div2 < 1 || self.post_div2 > 7 { | ||
| 601 | return false; | ||
| 602 | } | ||
| 603 | if self.post_div2 > self.post_div1 { | ||
| 604 | return false; | ||
| 605 | } | ||
| 606 | |||
| 607 | // Calculate reference frequency | ||
| 608 | let ref_freq = input_hz / self.refdiv as u32; | ||
| 609 | |||
| 610 | // Check reference frequency range | ||
| 611 | if ref_freq < 5_000_000 || ref_freq > 800_000_000 { | ||
| 612 | return false; | ||
| 613 | } | ||
| 614 | |||
| 615 | // Calculate VCO frequency | ||
| 616 | let vco_freq = ref_freq * self.fbdiv as u32; | ||
| 617 | |||
| 618 | // Check VCO frequency range | ||
| 619 | vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000 | ||
| 620 | } | ||
| 621 | } | ||
| 622 | |||
| 265 | /// Reference clock config. | 623 | /// Reference clock config. |
| 266 | pub struct RefClkConfig { | 624 | pub struct RefClkConfig { |
| 267 | /// Reference clock source. | 625 | /// Reference clock source. |
| @@ -280,6 +638,7 @@ pub enum RefClkSrc { | |||
| 280 | Rosc, | 638 | Rosc, |
| 281 | /// PLL USB. | 639 | /// PLL USB. |
| 282 | PllUsb, | 640 | PllUsb, |
| 641 | // See above re gpin handling being commented out | ||
| 283 | // Gpin0, | 642 | // Gpin0, |
| 284 | // Gpin1, | 643 | // Gpin1, |
| 285 | } | 644 | } |
| @@ -298,6 +657,7 @@ pub enum SysClkSrc { | |||
| 298 | Rosc, | 657 | Rosc, |
| 299 | /// XOSC. | 658 | /// XOSC. |
| 300 | Xosc, | 659 | Xosc, |
| 660 | // See above re gpin handling being commented out | ||
| 301 | // Gpin0, | 661 | // Gpin0, |
| 302 | // Gpin1, | 662 | // Gpin1, |
| 303 | } | 663 | } |
| @@ -333,6 +693,7 @@ pub enum UsbClkSrc { | |||
| 333 | Rosc = ClkUsbCtrlAuxsrc::ROSC_CLKSRC_PH as _, | 693 | Rosc = ClkUsbCtrlAuxsrc::ROSC_CLKSRC_PH as _, |
| 334 | /// XOSC. | 694 | /// XOSC. |
| 335 | Xosc = ClkUsbCtrlAuxsrc::XOSC_CLKSRC as _, | 695 | Xosc = ClkUsbCtrlAuxsrc::XOSC_CLKSRC as _, |
| 696 | // See above re gpin handling being commented out | ||
| 336 | // Gpin0 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN0 as _ , | 697 | // Gpin0 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN0 as _ , |
| 337 | // Gpin1 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN1 as _ , | 698 | // Gpin1 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN1 as _ , |
| 338 | } | 699 | } |
| @@ -360,6 +721,7 @@ pub enum AdcClkSrc { | |||
| 360 | Rosc = ClkAdcCtrlAuxsrc::ROSC_CLKSRC_PH as _, | 721 | Rosc = ClkAdcCtrlAuxsrc::ROSC_CLKSRC_PH as _, |
| 361 | /// XOSC. | 722 | /// XOSC. |
| 362 | Xosc = ClkAdcCtrlAuxsrc::XOSC_CLKSRC as _, | 723 | Xosc = ClkAdcCtrlAuxsrc::XOSC_CLKSRC as _, |
| 724 | // See above re gpin handling being commented out | ||
| 363 | // Gpin0 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN0 as _ , | 725 | // Gpin0 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN0 as _ , |
| 364 | // Gpin1 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN1 as _ , | 726 | // Gpin1 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN1 as _ , |
| 365 | } | 727 | } |
| @@ -388,6 +750,7 @@ pub enum RtcClkSrc { | |||
| 388 | Rosc = ClkRtcCtrlAuxsrc::ROSC_CLKSRC_PH as _, | 750 | Rosc = ClkRtcCtrlAuxsrc::ROSC_CLKSRC_PH as _, |
| 389 | /// XOSC. | 751 | /// XOSC. |
| 390 | Xosc = ClkRtcCtrlAuxsrc::XOSC_CLKSRC as _, | 752 | Xosc = ClkRtcCtrlAuxsrc::XOSC_CLKSRC as _, |
| 753 | // See above re gpin handling being commented out | ||
| 391 | // Gpin0 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN0 as _ , | 754 | // Gpin0 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN0 as _ , |
| 392 | // Gpin1 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN1 as _ , | 755 | // Gpin1 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN1 as _ , |
| 393 | } | 756 | } |
| @@ -405,6 +768,103 @@ pub struct RtcClkConfig { | |||
| 405 | pub phase: u8, | 768 | pub phase: u8, |
| 406 | } | 769 | } |
| 407 | 770 | ||
| 771 | /// Find valid PLL parameters (refdiv, fbdiv, post_div1, post_div2) for a target output frequency | ||
| 772 | /// based on the input frequency. | ||
| 773 | /// | ||
| 774 | /// This function searches for the best PLL configuration to achieve the requested target frequency | ||
| 775 | /// while staying within the VCO frequency range of 750MHz to 1800MHz. It prioritizes stability | ||
| 776 | /// over exact frequency matching by using larger divisors where possible. | ||
| 777 | /// | ||
| 778 | /// # Parameters | ||
| 779 | /// | ||
| 780 | /// * `input_hz`: The input frequency in Hz (typically the crystal frequency, e.g. 12MHz for th most common one used on rp2040 boards) | ||
| 781 | /// * `target_hz`: The desired output frequency in Hz (e.g. 125MHz for standard RP2040 operation) | ||
| 782 | /// | ||
| 783 | /// # Returns | ||
| 784 | /// | ||
| 785 | /// * `Some(PllConfig)` if valid parameters were found | ||
| 786 | /// * `None` if no valid parameters could be found for the requested combination | ||
| 787 | /// | ||
| 788 | /// # Example | ||
| 789 | /// | ||
| 790 | /// ```rust,ignore | ||
| 791 | /// // Find parameters for 133MHz system clock from 12MHz crystal | ||
| 792 | /// let pll_params = find_pll_params(12_000_000, 133_000_000).unwrap(); | ||
| 793 | /// ``` | ||
| 794 | #[cfg(feature = "rp2040")] | ||
| 795 | fn find_pll_params(input_hz: u32, target_hz: u32) -> Option<PllConfig> { | ||
| 796 | // Fixed reference divider for system PLL | ||
| 797 | const PLL_SYS_REFDIV: u8 = 1; | ||
| 798 | |||
| 799 | // Calculate reference frequency | ||
| 800 | let reference_freq = input_hz as u64 / PLL_SYS_REFDIV as u64; | ||
| 801 | |||
| 802 | // Start from highest fbdiv for better stability (like SDK does) | ||
| 803 | for fbdiv in (16..=320).rev() { | ||
| 804 | let vco_freq = reference_freq * fbdiv; | ||
| 805 | |||
| 806 | // Check VCO frequency is within valid range | ||
| 807 | if vco_freq < 750_000_000 || vco_freq > 1_800_000_000 { | ||
| 808 | continue; | ||
| 809 | } | ||
| 810 | |||
| 811 | // Try all possible postdiv combinations starting from larger values | ||
| 812 | // (more conservative/stable approach) | ||
| 813 | for post_div1 in (1..=7).rev() { | ||
| 814 | for post_div2 in (1..=post_div1).rev() { | ||
| 815 | let out_freq = vco_freq / (post_div1 * post_div2); | ||
| 816 | |||
| 817 | // Check if we get the exact target frequency without remainder | ||
| 818 | if out_freq == target_hz as u64 && (vco_freq % (post_div1 * post_div2) == 0) { | ||
| 819 | return Some(PllConfig { | ||
| 820 | refdiv: PLL_SYS_REFDIV, | ||
| 821 | fbdiv: fbdiv as u16, | ||
| 822 | post_div1: post_div1 as u8, | ||
| 823 | post_div2: post_div2 as u8, | ||
| 824 | }); | ||
| 825 | } | ||
| 826 | } | ||
| 827 | } | ||
| 828 | } | ||
| 829 | |||
| 830 | // If we couldn't find an exact match, find the closest match | ||
| 831 | let mut best_config = None; | ||
| 832 | let mut min_diff = u32::MAX; | ||
| 833 | |||
| 834 | for fbdiv in (16..=320).rev() { | ||
| 835 | let vco_freq = reference_freq * fbdiv; | ||
| 836 | |||
| 837 | if vco_freq < 750_000_000 || vco_freq > 1_800_000_000 { | ||
| 838 | continue; | ||
| 839 | } | ||
| 840 | |||
| 841 | for post_div1 in (1..=7).rev() { | ||
| 842 | for post_div2 in (1..=post_div1).rev() { | ||
| 843 | let out_freq = (vco_freq / (post_div1 * post_div2) as u64) as u32; | ||
| 844 | let diff = if out_freq > target_hz { | ||
| 845 | out_freq - target_hz | ||
| 846 | } else { | ||
| 847 | target_hz - out_freq | ||
| 848 | }; | ||
| 849 | |||
| 850 | // If this is closer to the target, save it | ||
| 851 | if diff < min_diff { | ||
| 852 | min_diff = diff; | ||
| 853 | best_config = Some(PllConfig { | ||
| 854 | refdiv: PLL_SYS_REFDIV, | ||
| 855 | fbdiv: fbdiv as u16, | ||
| 856 | post_div1: post_div1 as u8, | ||
| 857 | post_div2: post_div2 as u8, | ||
| 858 | }); | ||
| 859 | } | ||
| 860 | } | ||
| 861 | } | ||
| 862 | } | ||
| 863 | |||
| 864 | // Return the closest match if we found one | ||
| 865 | best_config | ||
| 866 | } | ||
| 867 | |||
| 408 | /// safety: must be called exactly once at bootup | 868 | /// safety: must be called exactly once at bootup |
| 409 | pub(crate) unsafe fn init(config: ClockConfig) { | 869 | pub(crate) unsafe fn init(config: ClockConfig) { |
| 410 | // Reset everything except: | 870 | // Reset everything except: |
| @@ -447,6 +907,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { | |||
| 447 | reset::reset(peris); | 907 | reset::reset(peris); |
| 448 | reset::unreset_wait(peris); | 908 | reset::unreset_wait(peris); |
| 449 | 909 | ||
| 910 | // See above re gpin handling being commented out | ||
| 450 | // let gpin0_freq = config.gpin0.map_or(0, |p| { | 911 | // let gpin0_freq = config.gpin0.map_or(0, |p| { |
| 451 | // core::mem::forget(p.1); | 912 | // core::mem::forget(p.1); |
| 452 | // p.0 | 913 | // p.0 |
| @@ -464,19 +925,60 @@ pub(crate) unsafe fn init(config: ClockConfig) { | |||
| 464 | }; | 925 | }; |
| 465 | CLOCKS.rosc.store(rosc_freq, Ordering::Relaxed); | 926 | CLOCKS.rosc.store(rosc_freq, Ordering::Relaxed); |
| 466 | 927 | ||
| 928 | // Set Core Voltage (RP2040 only), if we have config for it and we're not using the default | ||
| 929 | #[cfg(feature = "rp2040")] | ||
| 930 | { | ||
| 931 | let voltage = config.core_voltage; | ||
| 932 | let vreg = pac::VREG_AND_CHIP_RESET; | ||
| 933 | let current_vsel = vreg.vreg().read().vsel(); | ||
| 934 | let target_vsel = voltage as u8; | ||
| 935 | |||
| 936 | // If the target voltage is different from the current one, we need to change it | ||
| 937 | if target_vsel != current_vsel { | ||
| 938 | // Use modify() to preserve the HIZ and EN bits - otherwise we will disable the regulator when changing voltage | ||
| 939 | vreg.vreg().modify(|w| w.set_vsel(target_vsel)); | ||
| 940 | |||
| 941 | // Wait for the voltage to stabilize. Use the provided delay or default based on voltage | ||
| 942 | let settling_time_us = config.voltage_stabilization_delay_us.unwrap_or_else(|| { | ||
| 943 | match voltage { | ||
| 944 | CoreVoltage::V1_15 => 1000, // 1ms for 1.15V | ||
| 945 | CoreVoltage::V1_20 | CoreVoltage::V1_25 | CoreVoltage::V1_30 => 2000, // 2ms for higher voltages | ||
| 946 | _ => 0, // no delay for all others | ||
| 947 | } | ||
| 948 | }); | ||
| 949 | |||
| 950 | if settling_time_us != 0 { | ||
| 951 | // Delay in microseconds, using the ROSC frequency to calculate cycles | ||
| 952 | let cycles_per_us = rosc_freq / 1_000_000; | ||
| 953 | let delay_cycles = settling_time_us * cycles_per_us; | ||
| 954 | cortex_m::asm::delay(delay_cycles); | ||
| 955 | } | ||
| 956 | |||
| 957 | // Only now set the BOD level. At this point the voltage is considered stable. | ||
| 958 | vreg.bod().write(|w| { | ||
| 959 | w.set_vsel(voltage.recommended_bod()); | ||
| 960 | w.set_en(true); // Enable brownout detection | ||
| 961 | }); | ||
| 962 | } | ||
| 963 | } | ||
| 964 | |||
| 467 | let (xosc_freq, pll_sys_freq, pll_usb_freq) = match config.xosc { | 965 | let (xosc_freq, pll_sys_freq, pll_usb_freq) = match config.xosc { |
| 468 | Some(config) => { | 966 | Some(config) => { |
| 469 | // start XOSC | 967 | // start XOSC |
| 470 | // datasheet mentions support for clock inputs into XIN, but doesn't go into | ||
| 471 | // how this is achieved. pico-sdk doesn't support this at all. | ||
| 472 | start_xosc(config.hz, config.delay_multiplier); | 968 | start_xosc(config.hz, config.delay_multiplier); |
| 473 | 969 | ||
| 474 | let pll_sys_freq = match config.sys_pll { | 970 | let pll_sys_freq = match config.sys_pll { |
| 475 | Some(sys_pll_config) => configure_pll(pac::PLL_SYS, config.hz, sys_pll_config), | 971 | Some(sys_pll_config) => match configure_pll(pac::PLL_SYS, config.hz, sys_pll_config) { |
| 972 | Ok(freq) => freq, | ||
| 973 | Err(e) => panic!("Failed to configure PLL_SYS: {}", e), | ||
| 974 | }, | ||
| 476 | None => 0, | 975 | None => 0, |
| 477 | }; | 976 | }; |
| 478 | let pll_usb_freq = match config.usb_pll { | 977 | let pll_usb_freq = match config.usb_pll { |
| 479 | Some(usb_pll_config) => configure_pll(pac::PLL_USB, config.hz, usb_pll_config), | 978 | Some(usb_pll_config) => match configure_pll(pac::PLL_USB, config.hz, usb_pll_config) { |
| 979 | Ok(freq) => freq, | ||
| 980 | Err(e) => panic!("Failed to configure PLL_USB: {}", e), | ||
| 981 | }, | ||
| 480 | None => 0, | 982 | None => 0, |
| 481 | }; | 983 | }; |
| 482 | 984 | ||
| @@ -484,6 +986,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { | |||
| 484 | } | 986 | } |
| 485 | None => (0, 0, 0), | 987 | None => (0, 0, 0), |
| 486 | }; | 988 | }; |
| 989 | |||
| 487 | CLOCKS.xosc.store(xosc_freq, Ordering::Relaxed); | 990 | CLOCKS.xosc.store(xosc_freq, Ordering::Relaxed); |
| 488 | CLOCKS.pll_sys.store(pll_sys_freq, Ordering::Relaxed); | 991 | CLOCKS.pll_sys.store(pll_sys_freq, Ordering::Relaxed); |
| 489 | CLOCKS.pll_usb.store(pll_usb_freq, Ordering::Relaxed); | 992 | CLOCKS.pll_usb.store(pll_usb_freq, Ordering::Relaxed); |
| @@ -496,6 +999,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { | |||
| 496 | RefClkSrc::Xosc => (Src::XOSC_CLKSRC, Aux::CLKSRC_PLL_USB, xosc_freq / div), | 999 | RefClkSrc::Xosc => (Src::XOSC_CLKSRC, Aux::CLKSRC_PLL_USB, xosc_freq / div), |
| 497 | RefClkSrc::Rosc => (Src::ROSC_CLKSRC_PH, Aux::CLKSRC_PLL_USB, rosc_freq / div), | 1000 | RefClkSrc::Rosc => (Src::ROSC_CLKSRC_PH, Aux::CLKSRC_PLL_USB, rosc_freq / div), |
| 498 | RefClkSrc::PllUsb => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq / div), | 1001 | RefClkSrc::PllUsb => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq / div), |
| 1002 | // See above re gpin handling being commented out | ||
| 499 | // RefClkSrc::Gpin0 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN0, gpin0_freq / div), | 1003 | // RefClkSrc::Gpin0 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN0, gpin0_freq / div), |
| 500 | // RefClkSrc::Gpin1 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN1, gpin1_freq / div), | 1004 | // RefClkSrc::Gpin1 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN1, gpin1_freq / div), |
| 501 | } | 1005 | } |
| @@ -540,6 +1044,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { | |||
| 540 | SysClkSrc::PllUsb => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq), | 1044 | SysClkSrc::PllUsb => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq), |
| 541 | SysClkSrc::Rosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::ROSC_CLKSRC, rosc_freq), | 1045 | SysClkSrc::Rosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::ROSC_CLKSRC, rosc_freq), |
| 542 | SysClkSrc::Xosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::XOSC_CLKSRC, xosc_freq), | 1046 | SysClkSrc::Xosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::XOSC_CLKSRC, xosc_freq), |
| 1047 | // See above re gpin handling being commented out | ||
| 543 | // SysClkSrc::Gpin0 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN0, gpin0_freq), | 1048 | // SysClkSrc::Gpin0 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN0, gpin0_freq), |
| 544 | // SysClkSrc::Gpin1 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN1, gpin1_freq), | 1049 | // SysClkSrc::Gpin1 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN1, gpin1_freq), |
| 545 | }; | 1050 | }; |
| @@ -583,6 +1088,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { | |||
| 583 | PeriClkSrc::PllUsb => pll_usb_freq, | 1088 | PeriClkSrc::PllUsb => pll_usb_freq, |
| 584 | PeriClkSrc::Rosc => rosc_freq, | 1089 | PeriClkSrc::Rosc => rosc_freq, |
| 585 | PeriClkSrc::Xosc => xosc_freq, | 1090 | PeriClkSrc::Xosc => xosc_freq, |
| 1091 | // See above re gpin handling being commented out | ||
| 586 | // PeriClkSrc::Gpin0 => gpin0_freq, | 1092 | // PeriClkSrc::Gpin0 => gpin0_freq, |
| 587 | // PeriClkSrc::Gpin1 => gpin1_freq, | 1093 | // PeriClkSrc::Gpin1 => gpin1_freq, |
| 588 | }; | 1094 | }; |
| @@ -608,6 +1114,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { | |||
| 608 | UsbClkSrc::PllSys => pll_sys_freq, | 1114 | UsbClkSrc::PllSys => pll_sys_freq, |
| 609 | UsbClkSrc::Rosc => rosc_freq, | 1115 | UsbClkSrc::Rosc => rosc_freq, |
| 610 | UsbClkSrc::Xosc => xosc_freq, | 1116 | UsbClkSrc::Xosc => xosc_freq, |
| 1117 | // See above re gpin handling being commented out | ||
| 611 | // UsbClkSrc::Gpin0 => gpin0_freq, | 1118 | // UsbClkSrc::Gpin0 => gpin0_freq, |
| 612 | // UsbClkSrc::Gpin1 => gpin1_freq, | 1119 | // UsbClkSrc::Gpin1 => gpin1_freq, |
| 613 | }; | 1120 | }; |
| @@ -631,6 +1138,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { | |||
| 631 | AdcClkSrc::PllSys => pll_sys_freq, | 1138 | AdcClkSrc::PllSys => pll_sys_freq, |
| 632 | AdcClkSrc::Rosc => rosc_freq, | 1139 | AdcClkSrc::Rosc => rosc_freq, |
| 633 | AdcClkSrc::Xosc => xosc_freq, | 1140 | AdcClkSrc::Xosc => xosc_freq, |
| 1141 | // See above re gpin handling being commented out | ||
| 634 | // AdcClkSrc::Gpin0 => gpin0_freq, | 1142 | // AdcClkSrc::Gpin0 => gpin0_freq, |
| 635 | // AdcClkSrc::Gpin1 => gpin1_freq, | 1143 | // AdcClkSrc::Gpin1 => gpin1_freq, |
| 636 | }; | 1144 | }; |
| @@ -659,6 +1167,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { | |||
| 659 | RtcClkSrc::PllSys => pll_sys_freq, | 1167 | RtcClkSrc::PllSys => pll_sys_freq, |
| 660 | RtcClkSrc::Rosc => rosc_freq, | 1168 | RtcClkSrc::Rosc => rosc_freq, |
| 661 | RtcClkSrc::Xosc => xosc_freq, | 1169 | RtcClkSrc::Xosc => xosc_freq, |
| 1170 | // See above re gpin handling being commented out | ||
| 662 | // RtcClkSrc::Gpin0 => gpin0_freq, | 1171 | // RtcClkSrc::Gpin0 => gpin0_freq, |
| 663 | // RtcClkSrc::Gpin1 => gpin1_freq, | 1172 | // RtcClkSrc::Gpin1 => gpin1_freq, |
| 664 | }; | 1173 | }; |
| @@ -725,6 +1234,7 @@ pub fn xosc_freq() -> u32 { | |||
| 725 | CLOCKS.xosc.load(Ordering::Relaxed) | 1234 | CLOCKS.xosc.load(Ordering::Relaxed) |
| 726 | } | 1235 | } |
| 727 | 1236 | ||
| 1237 | // See above re gpin handling being commented out | ||
| 728 | // pub fn gpin0_freq() -> u32 { | 1238 | // pub fn gpin0_freq() -> u32 { |
| 729 | // CLOCKS.gpin0.load(Ordering::Relaxed) | 1239 | // CLOCKS.gpin0.load(Ordering::Relaxed) |
| 730 | // } | 1240 | // } |
| @@ -783,46 +1293,100 @@ fn start_xosc(crystal_hz: u32, delay_multiplier: u32) { | |||
| 783 | while !pac::XOSC.status().read().stable() {} | 1293 | while !pac::XOSC.status().read().stable() {} |
| 784 | } | 1294 | } |
| 785 | 1295 | ||
| 1296 | /// PLL (Phase-Locked Loop) configuration | ||
| 786 | #[inline(always)] | 1297 | #[inline(always)] |
| 787 | fn configure_pll(p: pac::pll::Pll, input_freq: u32, config: PllConfig) -> u32 { | 1298 | fn configure_pll(p: pac::pll::Pll, input_freq: u32, config: PllConfig) -> Result<u32, &'static str> { |
| 1299 | // Calculate reference frequency | ||
| 788 | let ref_freq = input_freq / config.refdiv as u32; | 1300 | let ref_freq = input_freq / config.refdiv as u32; |
| 1301 | |||
| 1302 | // Validate PLL parameters | ||
| 1303 | // Feedback divider (FBDIV) must be between 16 and 320 | ||
| 789 | assert!(config.fbdiv >= 16 && config.fbdiv <= 320); | 1304 | assert!(config.fbdiv >= 16 && config.fbdiv <= 320); |
| 1305 | |||
| 1306 | // Post divider 1 (POSTDIV1) must be between 1 and 7 | ||
| 790 | assert!(config.post_div1 >= 1 && config.post_div1 <= 7); | 1307 | assert!(config.post_div1 >= 1 && config.post_div1 <= 7); |
| 1308 | |||
| 1309 | // Post divider 2 (POSTDIV2) must be between 1 and 7 | ||
| 791 | assert!(config.post_div2 >= 1 && config.post_div2 <= 7); | 1310 | assert!(config.post_div2 >= 1 && config.post_div2 <= 7); |
| 1311 | |||
| 1312 | // Post divider 2 (POSTDIV2) must be less than or equal to post divider 1 (POSTDIV1) | ||
| 1313 | assert!(config.post_div2 <= config.post_div1); | ||
| 1314 | |||
| 1315 | // Reference divider (REFDIV) must be between 1 and 63 | ||
| 792 | assert!(config.refdiv >= 1 && config.refdiv <= 63); | 1316 | assert!(config.refdiv >= 1 && config.refdiv <= 63); |
| 1317 | |||
| 1318 | // Reference frequency (REF_FREQ) must be between 5MHz and 800MHz | ||
| 793 | assert!(ref_freq >= 5_000_000 && ref_freq <= 800_000_000); | 1319 | assert!(ref_freq >= 5_000_000 && ref_freq <= 800_000_000); |
| 1320 | |||
| 1321 | // Calculate VCO frequency | ||
| 794 | let vco_freq = ref_freq.saturating_mul(config.fbdiv as u32); | 1322 | let vco_freq = ref_freq.saturating_mul(config.fbdiv as u32); |
| 1323 | |||
| 1324 | // VCO (Voltage Controlled Oscillator) frequency must be between 750MHz and 1800MHz | ||
| 795 | assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); | 1325 | assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); |
| 796 | 1326 | ||
| 797 | // Load VCO-related dividers before starting VCO | 1327 | // We follow the SDK's approach to PLL configuration which is: |
| 1328 | // 1. Power down PLL | ||
| 1329 | // 2. Configure the reference divider | ||
| 1330 | // 3. Configure the feedback divider | ||
| 1331 | // 4. Power up PLL and VCO | ||
| 1332 | // 5. Wait for PLL to lock | ||
| 1333 | // 6. Configure post-dividers | ||
| 1334 | // 7. Enable post-divider output | ||
| 1335 | |||
| 1336 | // 1. Power down PLL before configuration | ||
| 1337 | p.pwr().write(|w| { | ||
| 1338 | w.set_pd(true); // Power down the PLL | ||
| 1339 | w.set_vcopd(true); // Power down the VCO | ||
| 1340 | w.set_postdivpd(true); // Power down the post divider | ||
| 1341 | w.set_dsmpd(true); // Disable fractional mode | ||
| 1342 | *w | ||
| 1343 | }); | ||
| 1344 | |||
| 1345 | // Short delay after powering down | ||
| 1346 | cortex_m::asm::delay(10); | ||
| 1347 | |||
| 1348 | // 2. Configure reference divider first | ||
| 798 | p.cs().write(|w| w.set_refdiv(config.refdiv as _)); | 1349 | p.cs().write(|w| w.set_refdiv(config.refdiv as _)); |
| 1350 | |||
| 1351 | // 3. Configure feedback divider | ||
| 799 | p.fbdiv_int().write(|w| w.set_fbdiv_int(config.fbdiv)); | 1352 | p.fbdiv_int().write(|w| w.set_fbdiv_int(config.fbdiv)); |
| 800 | 1353 | ||
| 801 | // Turn on PLL | 1354 | // 4. Power up PLL and VCO, but keep post divider powered down during initial lock |
| 802 | let pwr = p.pwr().write(|w| { | 1355 | p.pwr().write(|w| { |
| 803 | w.set_dsmpd(true); // "nothing is achieved by setting this low" | 1356 | w.set_pd(false); // Power up the PLL |
| 804 | w.set_pd(false); | 1357 | w.set_vcopd(false); // Power up the VCO |
| 805 | w.set_vcopd(false); | 1358 | w.set_postdivpd(true); // Keep post divider powered down during initial lock |
| 806 | w.set_postdivpd(true); | 1359 | w.set_dsmpd(true); // Disable fractional mode (simpler configuration) |
| 807 | *w | 1360 | *w |
| 808 | }); | 1361 | }); |
| 809 | 1362 | ||
| 810 | // Wait for PLL to lock | 1363 | // 5. Wait for PLL to lock with a timeout |
| 811 | while !p.cs().read().lock() {} | 1364 | let mut timeout = 1_000_000; |
| 1365 | while !p.cs().read().lock() { | ||
| 1366 | timeout -= 1; | ||
| 1367 | if timeout == 0 { | ||
| 1368 | // PLL failed to lock, return 0 to indicate failure | ||
| 1369 | return Err("PLL failed to lock"); | ||
| 1370 | } | ||
| 1371 | } | ||
| 812 | 1372 | ||
| 813 | // Set post-dividers | 1373 | // 6. Configure post dividers after PLL is locked |
| 814 | p.prim().write(|w| { | 1374 | p.prim().write(|w| { |
| 815 | w.set_postdiv1(config.post_div1); | 1375 | w.set_postdiv1(config.post_div1); |
| 816 | w.set_postdiv2(config.post_div2); | 1376 | w.set_postdiv2(config.post_div2); |
| 817 | }); | 1377 | }); |
| 818 | 1378 | ||
| 819 | // Turn on post divider | 1379 | // 7. Enable the post divider output |
| 820 | p.pwr().write(|w| { | 1380 | p.pwr().modify(|w| { |
| 821 | *w = pwr; | 1381 | w.set_postdivpd(false); // Power up post divider |
| 822 | w.set_postdivpd(false); | 1382 | *w |
| 823 | }); | 1383 | }); |
| 824 | 1384 | ||
| 825 | vco_freq / ((config.post_div1 * config.post_div2) as u32) | 1385 | // Final delay to ensure everything is stable |
| 1386 | cortex_m::asm::delay(100); | ||
| 1387 | |||
| 1388 | // Calculate and return actual output frequency | ||
| 1389 | Ok(vco_freq / ((config.post_div1 * config.post_div2) as u32)) | ||
| 826 | } | 1390 | } |
| 827 | 1391 | ||
| 828 | /// General purpose input clock pin. | 1392 | /// General purpose input clock pin. |
| @@ -906,6 +1470,7 @@ impl_gpoutpin!(PIN_25, 3); | |||
| 906 | pub enum GpoutSrc { | 1470 | pub enum GpoutSrc { |
| 907 | /// Sys PLL. | 1471 | /// Sys PLL. |
| 908 | PllSys = ClkGpoutCtrlAuxsrc::CLKSRC_PLL_SYS as _, | 1472 | PllSys = ClkGpoutCtrlAuxsrc::CLKSRC_PLL_SYS as _, |
| 1473 | // See above re gpin handling being commented out | ||
| 909 | // Gpin0 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN0 as _ , | 1474 | // Gpin0 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN0 as _ , |
| 910 | // Gpin1 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN1 as _ , | 1475 | // Gpin1 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN1 as _ , |
| 911 | /// USB PLL. | 1476 | /// USB PLL. |
| @@ -1001,6 +1566,7 @@ impl<'d, T: GpoutPin> Gpout<'d, T> { | |||
| 1001 | 1566 | ||
| 1002 | let base = match src { | 1567 | let base = match src { |
| 1003 | ClkGpoutCtrlAuxsrc::CLKSRC_PLL_SYS => pll_sys_freq(), | 1568 | ClkGpoutCtrlAuxsrc::CLKSRC_PLL_SYS => pll_sys_freq(), |
| 1569 | // See above re gpin handling being commented out | ||
| 1004 | // ClkGpoutCtrlAuxsrc::CLKSRC_GPIN0 => gpin0_freq(), | 1570 | // ClkGpoutCtrlAuxsrc::CLKSRC_GPIN0 => gpin0_freq(), |
| 1005 | // ClkGpoutCtrlAuxsrc::CLKSRC_GPIN1 => gpin1_freq(), | 1571 | // ClkGpoutCtrlAuxsrc::CLKSRC_GPIN1 => gpin1_freq(), |
| 1006 | ClkGpoutCtrlAuxsrc::CLKSRC_PLL_USB => pll_usb_freq(), | 1572 | ClkGpoutCtrlAuxsrc::CLKSRC_PLL_USB => pll_usb_freq(), |
| @@ -1009,7 +1575,6 @@ impl<'d, T: GpoutPin> Gpout<'d, T> { | |||
| 1009 | ClkGpoutCtrlAuxsrc::CLK_SYS => clk_sys_freq(), | 1575 | ClkGpoutCtrlAuxsrc::CLK_SYS => clk_sys_freq(), |
| 1010 | ClkGpoutCtrlAuxsrc::CLK_USB => clk_usb_freq(), | 1576 | ClkGpoutCtrlAuxsrc::CLK_USB => clk_usb_freq(), |
| 1011 | ClkGpoutCtrlAuxsrc::CLK_ADC => clk_adc_freq(), | 1577 | ClkGpoutCtrlAuxsrc::CLK_ADC => clk_adc_freq(), |
| 1012 | //ClkGpoutCtrlAuxsrc::CLK_RTC => clk_rtc_freq() as _, | ||
| 1013 | ClkGpoutCtrlAuxsrc::CLK_REF => clk_ref_freq(), | 1578 | ClkGpoutCtrlAuxsrc::CLK_REF => clk_ref_freq(), |
| 1014 | _ => unreachable!(), | 1579 | _ => unreachable!(), |
| 1015 | }; | 1580 | }; |
| @@ -1069,6 +1634,7 @@ impl rand_core::RngCore for RoscRng { | |||
| 1069 | dest.fill_with(Self::next_u8) | 1634 | dest.fill_with(Self::next_u8) |
| 1070 | } | 1635 | } |
| 1071 | } | 1636 | } |
| 1637 | |||
| 1072 | /// Enter the `DORMANT` sleep state. This will stop *all* internal clocks | 1638 | /// Enter the `DORMANT` sleep state. This will stop *all* internal clocks |
| 1073 | /// and can only be exited through resets, dormant-wake GPIO interrupts, | 1639 | /// and can only be exited through resets, dormant-wake GPIO interrupts, |
| 1074 | /// and RTC interrupts. If RTC is clocked from an internal clock source | 1640 | /// and RTC interrupts. If RTC is clocked from an internal clock source |
| @@ -1197,3 +1763,196 @@ pub fn dormant_sleep() { | |||
| 1197 | } | 1763 | } |
| 1198 | } | 1764 | } |
| 1199 | } | 1765 | } |
| 1766 | |||
| 1767 | #[cfg(test)] | ||
| 1768 | mod tests { | ||
| 1769 | use super::*; | ||
| 1770 | |||
| 1771 | #[cfg(feature = "rp2040")] | ||
| 1772 | #[test] | ||
| 1773 | fn test_find_pll_params() { | ||
| 1774 | #[cfg(feature = "rp2040")] | ||
| 1775 | { | ||
| 1776 | // Test standard 125 MHz configuration with 12 MHz crystal | ||
| 1777 | let params = find_pll_params(12_000_000, 125_000_000).unwrap(); | ||
| 1778 | assert_eq!(params.refdiv, 1); | ||
| 1779 | assert_eq!(params.fbdiv, 125); | ||
| 1780 | |||
| 1781 | // Test USB PLL configuration for 48MHz | ||
| 1782 | // The algorithm may find different valid parameters than the SDK defaults | ||
| 1783 | // We'll check that it generates a valid configuration that produces 48MHz | ||
| 1784 | let params = find_pll_params(12_000_000, 48_000_000).unwrap(); | ||
| 1785 | assert_eq!(params.refdiv, 1); | ||
| 1786 | |||
| 1787 | // Calculate the actual output frequency | ||
| 1788 | let ref_freq = 12_000_000 / params.refdiv as u32; | ||
| 1789 | let vco_freq = ref_freq as u64 * params.fbdiv as u64; | ||
| 1790 | let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32; | ||
| 1791 | |||
| 1792 | // Verify the output frequency is correct | ||
| 1793 | assert_eq!(output_freq, 48_000_000); | ||
| 1794 | |||
| 1795 | // Verify VCO frequency is in valid range | ||
| 1796 | assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); | ||
| 1797 | |||
| 1798 | // Test overclocked configuration for 200 MHz | ||
| 1799 | let params = find_pll_params(12_000_000, 200_000_000).unwrap(); | ||
| 1800 | assert_eq!(params.refdiv, 1); | ||
| 1801 | let vco_freq = 12_000_000 as u64 * params.fbdiv as u64; | ||
| 1802 | let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32; | ||
| 1803 | assert_eq!(output_freq, 200_000_000); | ||
| 1804 | assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); // VCO in valid range | ||
| 1805 | |||
| 1806 | // Test non-standard crystal with 16 MHz | ||
| 1807 | let params = find_pll_params(16_000_000, 125_000_000).unwrap(); | ||
| 1808 | let vco_freq = (16_000_000 / params.refdiv as u32) as u64 * params.fbdiv as u64; | ||
| 1809 | let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32; | ||
| 1810 | |||
| 1811 | // Test non-standard crystal with 15 MHz | ||
| 1812 | let params = find_pll_params(15_000_000, 125_000_000).unwrap(); | ||
| 1813 | let vco_freq = (15_000_000 / params.refdiv as u32) as u64 * params.fbdiv as u64; | ||
| 1814 | let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32; | ||
| 1815 | |||
| 1816 | // With a 15 MHz crystal, we might not get exactly 125 MHz | ||
| 1817 | // Check that it's close enough (within 0.2% margin) | ||
| 1818 | let freq_diff = if output_freq > 125_000_000 { | ||
| 1819 | output_freq - 125_000_000 | ||
| 1820 | } else { | ||
| 1821 | 125_000_000 - output_freq | ||
| 1822 | }; | ||
| 1823 | let error_percentage = (freq_diff as f64 / 125_000_000.0) * 100.0; | ||
| 1824 | assert!( | ||
| 1825 | error_percentage < 0.2, | ||
| 1826 | "Output frequency {} is not close enough to target 125 MHz. Error: {:.2}%", | ||
| 1827 | output_freq, | ||
| 1828 | error_percentage | ||
| 1829 | ); | ||
| 1830 | |||
| 1831 | assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); | ||
| 1832 | } | ||
| 1833 | } | ||
| 1834 | |||
| 1835 | #[cfg(feature = "rp2040")] | ||
| 1836 | #[test] | ||
| 1837 | fn test_pll_config_validation() { | ||
| 1838 | // Test PLL configuration validation logic | ||
| 1839 | let valid_config = PllConfig { | ||
| 1840 | refdiv: 1, | ||
| 1841 | fbdiv: 125, | ||
| 1842 | post_div1: 6, | ||
| 1843 | post_div2: 2, | ||
| 1844 | }; | ||
| 1845 | |||
| 1846 | // Valid configuration should pass validation | ||
| 1847 | assert!(valid_config.is_valid(12_000_000)); | ||
| 1848 | |||
| 1849 | // Test fbdiv constraints | ||
| 1850 | let mut invalid_config = valid_config; | ||
| 1851 | invalid_config.fbdiv = 15; // Below minimum of 16 | ||
| 1852 | assert!(!invalid_config.is_valid(12_000_000)); | ||
| 1853 | |||
| 1854 | invalid_config.fbdiv = 321; // Above maximum of 320 | ||
| 1855 | assert!(!invalid_config.is_valid(12_000_000)); | ||
| 1856 | |||
| 1857 | // Test post_div constraints | ||
| 1858 | invalid_config = valid_config; | ||
| 1859 | invalid_config.post_div1 = 0; // Below minimum of 1 | ||
| 1860 | assert!(!invalid_config.is_valid(12_000_000)); | ||
| 1861 | |||
| 1862 | invalid_config = valid_config; | ||
| 1863 | invalid_config.post_div1 = 8; // Above maximum of 7 | ||
| 1864 | assert!(!invalid_config.is_valid(12_000_000)); | ||
| 1865 | |||
| 1866 | // Test post_div2 must be <= post_div1 | ||
| 1867 | invalid_config = valid_config; | ||
| 1868 | invalid_config.post_div2 = 7; | ||
| 1869 | invalid_config.post_div1 = 3; | ||
| 1870 | assert!(!invalid_config.is_valid(12_000_000)); | ||
| 1871 | |||
| 1872 | // Test reference frequency constraints | ||
| 1873 | invalid_config = valid_config; | ||
| 1874 | assert!(!invalid_config.is_valid(4_000_000)); // Below minimum of 5 MHz | ||
| 1875 | assert!(!invalid_config.is_valid(900_000_000)); // Above maximum of 800 MHz | ||
| 1876 | |||
| 1877 | // Test VCO frequency constraints | ||
| 1878 | invalid_config = valid_config; | ||
| 1879 | invalid_config.fbdiv = 16; | ||
| 1880 | assert!(!invalid_config.is_valid(12_000_000)); // VCO too low: 12MHz * 16 = 192MHz | ||
| 1881 | |||
| 1882 | // Test VCO frequency constraints - too high | ||
| 1883 | invalid_config = valid_config; | ||
| 1884 | invalid_config.fbdiv = 200; | ||
| 1885 | invalid_config.refdiv = 1; | ||
| 1886 | // This should be INVALID: 12MHz * 200 = 2400MHz exceeds max VCO of 1800MHz | ||
| 1887 | assert!(!invalid_config.is_valid(12_000_000)); | ||
| 1888 | |||
| 1889 | // Test a valid high VCO configuration | ||
| 1890 | invalid_config.fbdiv = 150; // 12MHz * 150 = 1800MHz, exactly at the limit | ||
| 1891 | assert!(invalid_config.is_valid(12_000_000)); | ||
| 1892 | } | ||
| 1893 | |||
| 1894 | #[cfg(feature = "rp2040")] | ||
| 1895 | #[test] | ||
| 1896 | fn test_manual_pll_helper() { | ||
| 1897 | { | ||
| 1898 | // Test the new manual_pll helper method | ||
| 1899 | let config = ClockConfig::manual_pll( | ||
| 1900 | 12_000_000, | ||
| 1901 | PllConfig { | ||
| 1902 | refdiv: 1, | ||
| 1903 | fbdiv: 100, | ||
| 1904 | post_div1: 3, | ||
| 1905 | post_div2: 2, | ||
| 1906 | }, | ||
| 1907 | CoreVoltage::V1_15, | ||
| 1908 | ); | ||
| 1909 | |||
| 1910 | // Check voltage scale was set correctly | ||
| 1911 | assert_eq!(config.core_voltage, CoreVoltage::V1_15); | ||
| 1912 | |||
| 1913 | // Check PLL config was set correctly | ||
| 1914 | assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().refdiv, 1); | ||
| 1915 | assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().fbdiv, 100); | ||
| 1916 | assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().post_div1, 3); | ||
| 1917 | assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().post_div2, 2); | ||
| 1918 | |||
| 1919 | // Check we get the expected frequency | ||
| 1920 | assert_eq!( | ||
| 1921 | config | ||
| 1922 | .xosc | ||
| 1923 | .as_ref() | ||
| 1924 | .unwrap() | ||
| 1925 | .sys_pll | ||
| 1926 | .as_ref() | ||
| 1927 | .unwrap() | ||
| 1928 | .output_frequency(12_000_000), | ||
| 1929 | 200_000_000 | ||
| 1930 | ); | ||
| 1931 | } | ||
| 1932 | } | ||
| 1933 | |||
| 1934 | #[cfg(feature = "rp2040")] | ||
| 1935 | #[test] | ||
| 1936 | fn test_auto_voltage_scaling() { | ||
| 1937 | { | ||
| 1938 | // Test automatic voltage scaling based on frequency | ||
| 1939 | // Under 133 MHz should use default voltage (V1_10) | ||
| 1940 | let config = ClockConfig::system_freq(125_000_000); | ||
| 1941 | assert_eq!(config.core_voltage, CoreVoltage::V1_10); | ||
| 1942 | |||
| 1943 | // 133-200 MHz should use V1_15 | ||
| 1944 | let config = ClockConfig::system_freq(150_000_000); | ||
| 1945 | assert_eq!(config.core_voltage, CoreVoltage::V1_15); | ||
| 1946 | let config = ClockConfig::system_freq(200_000_000); | ||
| 1947 | assert_eq!(config.core_voltage, CoreVoltage::V1_15); | ||
| 1948 | |||
| 1949 | // Above 200 MHz should use V1_25 | ||
| 1950 | let config = ClockConfig::system_freq(250_000_000); | ||
| 1951 | assert_eq!(config.core_voltage, CoreVoltage::V1_15); | ||
| 1952 | |||
| 1953 | // Below 125 MHz should use V1_10 | ||
| 1954 | let config = ClockConfig::system_freq(100_000_000); | ||
| 1955 | assert_eq!(config.core_voltage, CoreVoltage::V1_10); | ||
| 1956 | } | ||
| 1957 | } | ||
| 1958 | } | ||
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 @@ | |||
| 1 | //! Helper functions for calculating PIO clock dividers | ||
| 2 | |||
| 3 | use fixed::traits::ToFixed; | ||
| 4 | use fixed::types::extra::U8; | ||
| 5 | |||
| 6 | use crate::clocks::clk_sys_freq; | ||
| 7 | |||
| 8 | /// Calculate a PIO clock divider value based on the desired target frequency. | ||
| 9 | /// | ||
| 10 | /// # Arguments | ||
| 11 | /// | ||
| 12 | /// * `target_hz` - The desired PIO clock frequency in Hz | ||
| 13 | /// | ||
| 14 | /// # Returns | ||
| 15 | /// | ||
| 16 | /// A fixed-point divider value suitable for use in a PIO state machine configuration | ||
| 17 | #[inline] | ||
| 18 | pub fn calculate_pio_clock_divider(target_hz: u32) -> fixed::FixedU32<U8> { | ||
| 19 | // Requires a non-zero frequency | ||
| 20 | assert!(target_hz > 0, "PIO clock frequency cannot be zero"); | ||
| 21 | |||
| 22 | // Calculate the divider | ||
| 23 | let divider = (clk_sys_freq() + target_hz / 2) / target_hz; | ||
| 24 | divider.to_fixed() | ||
| 25 | } | ||
diff --git a/embassy-rp/src/pio_programs/hd44780.rs b/embassy-rp/src/pio_programs/hd44780.rs index 5846a8027..546c85a89 100644 --- a/embassy-rp/src/pio_programs/hd44780.rs +++ b/embassy-rp/src/pio_programs/hd44780.rs | |||
| @@ -5,6 +5,7 @@ use crate::pio::{ | |||
| 5 | Common, Config, Direction, FifoJoin, Instance, Irq, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, | 5 | Common, Config, Direction, FifoJoin, Instance, Irq, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, |
| 6 | StateMachine, | 6 | StateMachine, |
| 7 | }; | 7 | }; |
| 8 | use crate::pio_programs::clock_divider::calculate_pio_clock_divider; | ||
| 8 | use crate::Peri; | 9 | use crate::Peri; |
| 9 | 10 | ||
| 10 | /// This struct represents a HD44780 program that takes command words (<wait:24> <command:4> <0:4>) | 11 | /// This struct represents a HD44780 program that takes command words (<wait:24> <command:4> <0:4>) |
| @@ -134,7 +135,10 @@ impl<'l, P: Instance, const S: usize> PioHD44780<'l, P, S> { | |||
| 134 | 135 | ||
| 135 | let mut cfg = Config::default(); | 136 | let mut cfg = Config::default(); |
| 136 | cfg.use_program(&word_prg.prg, &[&e]); | 137 | cfg.use_program(&word_prg.prg, &[&e]); |
| 137 | cfg.clock_divider = 125u8.into(); | 138 | |
| 139 | // Target 1 MHz PIO clock (each cycle is 1µs) | ||
| 140 | cfg.clock_divider = calculate_pio_clock_divider(1_000_000); | ||
| 141 | |||
| 138 | cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); | 142 | cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); |
| 139 | cfg.shift_out = ShiftConfig { | 143 | cfg.shift_out = ShiftConfig { |
| 140 | auto_fill: true, | 144 | auto_fill: true, |
| @@ -160,7 +164,10 @@ impl<'l, P: Instance, const S: usize> PioHD44780<'l, P, S> { | |||
| 160 | 164 | ||
| 161 | let mut cfg = Config::default(); | 165 | let mut cfg = Config::default(); |
| 162 | cfg.use_program(&seq_prg.prg, &[&e]); | 166 | cfg.use_program(&seq_prg.prg, &[&e]); |
| 163 | cfg.clock_divider = 8u8.into(); // ~64ns/insn | 167 | |
| 168 | // Target ~15.6 MHz PIO clock (~64ns/insn) | ||
| 169 | cfg.clock_divider = calculate_pio_clock_divider(15_600_000); | ||
| 170 | |||
| 164 | cfg.set_jmp_pin(&db7); | 171 | cfg.set_jmp_pin(&db7); |
| 165 | cfg.set_set_pins(&[&rs, &rw]); | 172 | cfg.set_set_pins(&[&rs, &rw]); |
| 166 | cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); | 173 | cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); |
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 @@ | |||
| 1 | //! Pre-built pio programs for common interfaces | 1 | //! Pre-built pio programs for common interfaces |
| 2 | 2 | ||
| 3 | pub mod clock_divider; | ||
| 3 | pub mod hd44780; | 4 | pub mod hd44780; |
| 4 | pub mod i2s; | 5 | pub mod i2s; |
| 5 | pub mod onewire; | 6 | pub mod onewire; |
diff --git a/embassy-rp/src/pio_programs/rotary_encoder.rs b/embassy-rp/src/pio_programs/rotary_encoder.rs index e520da8a3..70b3795e9 100644 --- a/embassy-rp/src/pio_programs/rotary_encoder.rs +++ b/embassy-rp/src/pio_programs/rotary_encoder.rs | |||
| @@ -1,11 +1,10 @@ | |||
| 1 | //! PIO backed quadrature encoder | 1 | //! PIO backed quadrature encoder |
| 2 | 2 | ||
| 3 | use fixed::traits::ToFixed; | ||
| 4 | |||
| 5 | use crate::gpio::Pull; | 3 | use crate::gpio::Pull; |
| 6 | use crate::pio::{ | 4 | use crate::pio::{ |
| 7 | Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine, | 5 | Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine, |
| 8 | }; | 6 | }; |
| 7 | use crate::pio_programs::clock_divider::calculate_pio_clock_divider; | ||
| 9 | use crate::Peri; | 8 | use crate::Peri; |
| 10 | 9 | ||
| 11 | /// This struct represents an Encoder program loaded into pio instruction memory. | 10 | /// This struct represents an Encoder program loaded into pio instruction memory. |
| @@ -48,7 +47,10 @@ impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> { | |||
| 48 | cfg.set_in_pins(&[&pin_a, &pin_b]); | 47 | cfg.set_in_pins(&[&pin_a, &pin_b]); |
| 49 | cfg.fifo_join = FifoJoin::RxOnly; | 48 | cfg.fifo_join = FifoJoin::RxOnly; |
| 50 | cfg.shift_in.direction = ShiftDirection::Left; | 49 | cfg.shift_in.direction = ShiftDirection::Left; |
| 51 | cfg.clock_divider = 10_000.to_fixed(); | 50 | |
| 51 | // Target 12.5 KHz PIO clock | ||
| 52 | cfg.clock_divider = calculate_pio_clock_divider(12_500); | ||
| 53 | |||
| 52 | cfg.use_program(&program.prg, &[]); | 54 | cfg.use_program(&program.prg, &[]); |
| 53 | sm.set_config(&cfg); | 55 | sm.set_config(&cfg); |
| 54 | sm.set_enable(true); | 56 | sm.set_enable(true); |
diff --git a/embassy-rp/src/pio_programs/stepper.rs b/embassy-rp/src/pio_programs/stepper.rs index 495191659..0e9a8daf9 100644 --- a/embassy-rp/src/pio_programs/stepper.rs +++ b/embassy-rp/src/pio_programs/stepper.rs | |||
| @@ -2,11 +2,8 @@ | |||
| 2 | 2 | ||
| 3 | use core::mem::{self, MaybeUninit}; | 3 | use core::mem::{self, MaybeUninit}; |
| 4 | 4 | ||
| 5 | use fixed::traits::ToFixed; | ||
| 6 | use fixed::types::extra::U8; | ||
| 7 | use fixed::FixedU32; | ||
| 8 | |||
| 9 | use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, PioPin, StateMachine}; | 5 | use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, PioPin, StateMachine}; |
| 6 | use crate::pio_programs::clock_divider::calculate_pio_clock_divider; | ||
| 10 | use crate::Peri; | 7 | use crate::Peri; |
| 11 | 8 | ||
| 12 | /// This struct represents a Stepper driver program loaded into pio instruction memory. | 9 | /// This struct represents a Stepper driver program loaded into pio instruction memory. |
| @@ -64,7 +61,9 @@ impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> { | |||
| 64 | sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]); | 61 | sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]); |
| 65 | let mut cfg = Config::default(); | 62 | let mut cfg = Config::default(); |
| 66 | cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]); | 63 | cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]); |
| 67 | cfg.clock_divider = (125_000_000 / (100 * 136)).to_fixed(); | 64 | |
| 65 | cfg.clock_divider = calculate_pio_clock_divider(100 * 136); | ||
| 66 | |||
| 68 | cfg.use_program(&program.prg, &[]); | 67 | cfg.use_program(&program.prg, &[]); |
| 69 | sm.set_config(&cfg); | 68 | sm.set_config(&cfg); |
| 70 | sm.set_enable(true); | 69 | sm.set_enable(true); |
| @@ -73,9 +72,11 @@ impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> { | |||
| 73 | 72 | ||
| 74 | /// Set pulse frequency | 73 | /// Set pulse frequency |
| 75 | pub fn set_frequency(&mut self, freq: u32) { | 74 | pub fn set_frequency(&mut self, freq: u32) { |
| 76 | let clock_divider: FixedU32<U8> = (125_000_000 / (freq * 136)).to_fixed(); | 75 | let clock_divider = calculate_pio_clock_divider(freq * 136); |
| 77 | assert!(clock_divider <= 65536, "clkdiv must be <= 65536"); | 76 | let divider_f32 = clock_divider.to_num::<f32>(); |
| 78 | assert!(clock_divider >= 1, "clkdiv must be >= 1"); | 77 | assert!(divider_f32 <= 65536.0, "clkdiv must be <= 65536"); |
| 78 | assert!(divider_f32 >= 1.0, "clkdiv must be >= 1"); | ||
| 79 | |||
| 79 | self.sm.set_clock_divider(clock_divider); | 80 | self.sm.set_clock_divider(clock_divider); |
| 80 | self.sm.clkdiv_restart(); | 81 | self.sm.clkdiv_restart(); |
| 81 | } | 82 | } |
diff --git a/examples/rp/src/bin/overclock.rs b/examples/rp/src/bin/overclock.rs new file mode 100644 index 000000000..9c78e0c9d --- /dev/null +++ b/examples/rp/src/bin/overclock.rs | |||
| @@ -0,0 +1,64 @@ | |||
| 1 | //! # Overclocking the RP2040 to 200 MHz | ||
| 2 | //! | ||
| 3 | //! This example demonstrates how to configure the RP2040 to run at 200 MHz. | ||
| 4 | |||
| 5 | #![no_std] | ||
| 6 | #![no_main] | ||
| 7 | |||
| 8 | use defmt::*; | ||
| 9 | use embassy_executor::Spawner; | ||
| 10 | use embassy_rp::clocks::{clk_sys_freq, ClockConfig}; | ||
| 11 | use embassy_rp::config::Config; | ||
| 12 | use embassy_rp::gpio::{Level, Output}; | ||
| 13 | use embassy_time::{Duration, Instant, Timer}; | ||
| 14 | use {defmt_rtt as _, panic_probe as _}; | ||
| 15 | |||
| 16 | const COUNT_TO: i64 = 10_000_000; | ||
| 17 | |||
| 18 | #[embassy_executor::main] | ||
| 19 | async fn main(_spawner: Spawner) -> ! { | ||
| 20 | // Set up for clock frequency of 200 MHz, setting all necessary defaults. | ||
| 21 | let config = Config::new(ClockConfig::system_freq(200_000_000)); | ||
| 22 | |||
| 23 | // Show the voltage scale for verification | ||
| 24 | info!("System core voltage: {}", Debug2Format(&config.clocks.core_voltage)); | ||
| 25 | |||
| 26 | // Initialize the peripherals | ||
| 27 | let p = embassy_rp::init(config); | ||
| 28 | |||
| 29 | // Show CPU frequency for verification | ||
| 30 | let sys_freq = clk_sys_freq(); | ||
| 31 | info!("System clock frequency: {} MHz", sys_freq / 1_000_000); | ||
| 32 | |||
| 33 | // LED to indicate the system is running | ||
| 34 | let mut led = Output::new(p.PIN_25, Level::Low); | ||
| 35 | |||
| 36 | loop { | ||
| 37 | // Reset the counter at the start of measurement period | ||
| 38 | let mut counter = 0; | ||
| 39 | |||
| 40 | // Turn LED on while counting | ||
| 41 | led.set_high(); | ||
| 42 | |||
| 43 | let start = Instant::now(); | ||
| 44 | |||
| 45 | // This is a busy loop that will take some time to complete | ||
| 46 | while counter < COUNT_TO { | ||
| 47 | counter += 1; | ||
| 48 | } | ||
| 49 | |||
| 50 | let elapsed = Instant::now() - start; | ||
| 51 | |||
| 52 | // Report the elapsed time | ||
| 53 | led.set_low(); | ||
| 54 | info!( | ||
| 55 | "At {}Mhz: Elapsed time to count to {}: {}ms", | ||
| 56 | sys_freq / 1_000_000, | ||
| 57 | counter, | ||
| 58 | elapsed.as_millis() | ||
| 59 | ); | ||
| 60 | |||
| 61 | // Wait 2 seconds before starting the next measurement | ||
| 62 | Timer::after(Duration::from_secs(2)).await; | ||
| 63 | } | ||
| 64 | } | ||
diff --git a/examples/rp/src/bin/overclock_manual.rs b/examples/rp/src/bin/overclock_manual.rs new file mode 100644 index 000000000..35160b250 --- /dev/null +++ b/examples/rp/src/bin/overclock_manual.rs | |||
| @@ -0,0 +1,79 @@ | |||
| 1 | //! # Overclocking the RP2040 to 200 MHz manually | ||
| 2 | //! | ||
| 3 | //! This example demonstrates how to manually configure the RP2040 to run at 200 MHz. | ||
| 4 | |||
| 5 | #![no_std] | ||
| 6 | #![no_main] | ||
| 7 | |||
| 8 | use defmt::*; | ||
| 9 | use embassy_executor::Spawner; | ||
| 10 | use embassy_rp::clocks; | ||
| 11 | use embassy_rp::clocks::{ClockConfig, CoreVoltage, PllConfig}; | ||
| 12 | use embassy_rp::config::Config; | ||
| 13 | use embassy_rp::gpio::{Level, Output}; | ||
| 14 | use embassy_time::{Duration, Instant, Timer}; | ||
| 15 | use {defmt_rtt as _, panic_probe as _}; | ||
| 16 | |||
| 17 | const COUNT_TO: i64 = 10_000_000; | ||
| 18 | |||
| 19 | /// Configure the RP2040 for 200 MHz operation by manually specifying the PLL settings. | ||
| 20 | fn configure_manual_overclock() -> Config { | ||
| 21 | // Set the PLL configuration manually, starting from default values | ||
| 22 | let mut config = Config::default(); | ||
| 23 | |||
| 24 | // Set the system clock to 200 MHz | ||
| 25 | config.clocks = ClockConfig::manual_pll( | ||
| 26 | 12_000_000, // Crystal frequency, 12 MHz is common. If using custom, set to your value. | ||
| 27 | PllConfig { | ||
| 28 | refdiv: 1, // Reference divider | ||
| 29 | fbdiv: 100, // Feedback divider | ||
| 30 | post_div1: 3, // Post divider 1 | ||
| 31 | post_div2: 2, // Post divider 2 | ||
| 32 | }, | ||
| 33 | CoreVoltage::V1_15, // Core voltage, should be set to V1_15 for 200 MHz | ||
| 34 | ); | ||
| 35 | |||
| 36 | config | ||
| 37 | } | ||
| 38 | |||
| 39 | #[embassy_executor::main] | ||
| 40 | async fn main(_spawner: Spawner) -> ! { | ||
| 41 | // Initialize with our manual overclock configuration | ||
| 42 | let p = embassy_rp::init(configure_manual_overclock()); | ||
| 43 | |||
| 44 | // Verify the actual system clock frequency | ||
| 45 | let sys_freq = clocks::clk_sys_freq(); | ||
| 46 | info!("System clock frequency: {} MHz", sys_freq / 1_000_000); | ||
| 47 | |||
| 48 | // LED to indicate the system is running | ||
| 49 | let mut led = Output::new(p.PIN_25, Level::Low); | ||
| 50 | |||
| 51 | loop { | ||
| 52 | // Reset the counter at the start of measurement period | ||
| 53 | let mut counter = 0; | ||
| 54 | |||
| 55 | // Turn LED on while counting | ||
| 56 | led.set_high(); | ||
| 57 | |||
| 58 | let start = Instant::now(); | ||
| 59 | |||
| 60 | // This is a busy loop that will take some time to complete | ||
| 61 | while counter < COUNT_TO { | ||
| 62 | counter += 1; | ||
| 63 | } | ||
| 64 | |||
| 65 | let elapsed = Instant::now() - start; | ||
| 66 | |||
| 67 | // Report the elapsed time | ||
| 68 | led.set_low(); | ||
| 69 | info!( | ||
| 70 | "At {}Mhz: Elapsed time to count to {}: {}ms", | ||
| 71 | sys_freq / 1_000_000, | ||
| 72 | counter, | ||
| 73 | elapsed.as_millis() | ||
| 74 | ); | ||
| 75 | |||
| 76 | // Wait 2 seconds before starting the next measurement | ||
| 77 | Timer::after(Duration::from_secs(2)).await; | ||
| 78 | } | ||
| 79 | } | ||
diff --git a/tests/rp/src/bin/overclock.rs b/tests/rp/src/bin/overclock.rs new file mode 100644 index 000000000..be8e85a3f --- /dev/null +++ b/tests/rp/src/bin/overclock.rs | |||
| @@ -0,0 +1,70 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | #[cfg(feature = "rp2040")] | ||
| 5 | teleprobe_meta::target!(b"rpi-pico"); | ||
| 6 | #[cfg(feature = "rp235xb")] | ||
| 7 | teleprobe_meta::target!(b"pimoroni-pico-plus-2"); | ||
| 8 | |||
| 9 | use defmt::info; | ||
| 10 | #[cfg(feature = "rp2040")] | ||
| 11 | use defmt::{assert, assert_eq}; | ||
| 12 | use embassy_executor::Spawner; | ||
| 13 | use embassy_rp::clocks; | ||
| 14 | #[cfg(feature = "rp2040")] | ||
| 15 | use embassy_rp::clocks::ClockConfig; | ||
| 16 | #[cfg(feature = "rp2040")] | ||
| 17 | use embassy_rp::clocks::CoreVoltage; | ||
| 18 | use embassy_rp::config::Config; | ||
| 19 | use embassy_time::Instant; | ||
| 20 | use {defmt_rtt as _, panic_probe as _}; | ||
| 21 | |||
| 22 | const COUNT_TO: i64 = 10_000_000; | ||
| 23 | |||
| 24 | #[embassy_executor::main] | ||
| 25 | async fn main(_spawner: Spawner) { | ||
| 26 | #[cfg(feature = "rp2040")] | ||
| 27 | let mut config = Config::default(); | ||
| 28 | #[cfg(not(feature = "rp2040"))] | ||
| 29 | let config = Config::default(); | ||
| 30 | |||
| 31 | // Initialize with 200MHz clock configuration for RP2040, other chips will use default clock | ||
| 32 | #[cfg(feature = "rp2040")] | ||
| 33 | { | ||
| 34 | config.clocks = ClockConfig::system_freq(200_000_000); | ||
| 35 | let voltage = config.clocks.core_voltage; | ||
| 36 | assert!(matches!(voltage, CoreVoltage::V1_15), "Expected voltage scale V1_15"); | ||
| 37 | } | ||
| 38 | |||
| 39 | let _p = embassy_rp::init(config); | ||
| 40 | |||
| 41 | // Test the system speed | ||
| 42 | let (time_elapsed, clk_sys_freq) = { | ||
| 43 | let mut counter = 0; | ||
| 44 | let start = Instant::now(); | ||
| 45 | while counter < COUNT_TO { | ||
| 46 | counter += 1; | ||
| 47 | } | ||
| 48 | let elapsed = Instant::now() - start; | ||
| 49 | |||
| 50 | (elapsed.as_millis(), clocks::clk_sys_freq()) | ||
| 51 | }; | ||
| 52 | |||
| 53 | // Report the elapsed time, so that the compiler doesn't optimize it away for chips other than RP2040 | ||
| 54 | info!( | ||
| 55 | "At {}Mhz: Elapsed time to count to {}: {}ms", | ||
| 56 | clk_sys_freq / 1_000_000, | ||
| 57 | COUNT_TO, | ||
| 58 | time_elapsed | ||
| 59 | ); | ||
| 60 | |||
| 61 | #[cfg(feature = "rp2040")] | ||
| 62 | { | ||
| 63 | // we should be at 200MHz | ||
| 64 | assert_eq!(clk_sys_freq, 200_000_000, "System clock frequency is not 200MHz"); | ||
| 65 | // At 200MHz, the time to count to 10_000_000 should be at 600ms, testing with 1% margin | ||
| 66 | assert!(time_elapsed <= 606, "Elapsed time is too long"); | ||
| 67 | } | ||
| 68 | |||
| 69 | cortex_m::asm::bkpt(); | ||
| 70 | } | ||
