diff options
| author | Ulf Lilleengen <[email protected]> | 2025-10-29 07:49:52 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-10-29 07:49:52 +0000 |
| commit | a0d7c087a2bd4a370d24097e022a812f7119dd78 (patch) | |
| tree | 2b6ba801e2077e9cab6c8ce7ca037604f4bbf257 | |
| parent | 0b780871a31c7cb933bcbe11963c693001e84946 (diff) | |
| parent | a863aadc643d84707815e5c0f4564f2195809fec (diff) | |
Merge pull request #4792 from usedhondacivic/cyw43-pio-clock-divider
cyw43-pio: core clock speed based pio program selection
| -rw-r--r-- | cyw43-pio/CHANGELOG.md | 2 | ||||
| -rw-r--r-- | cyw43-pio/src/lib.rs | 108 |
2 files changed, 93 insertions, 17 deletions
diff --git a/cyw43-pio/CHANGELOG.md b/cyw43-pio/CHANGELOG.md index ec38989e3..c2d18919c 100644 --- a/cyw43-pio/CHANGELOG.md +++ b/cyw43-pio/CHANGELOG.md | |||
| @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
| 8 | <!-- next-header --> | 8 | <!-- next-header --> |
| 9 | ## Unreleased - ReleaseDate | 9 | ## Unreleased - ReleaseDate |
| 10 | 10 | ||
| 11 | - Select pio program based on core clock speed #4792 | ||
| 12 | |||
| 11 | ## 0.8.0 - 2025-08-28 | 13 | ## 0.8.0 - 2025-08-28 |
| 12 | 14 | ||
| 13 | - Bump cyw43 version | 15 | - Bump cyw43 version |
diff --git a/cyw43-pio/src/lib.rs b/cyw43-pio/src/lib.rs index 41ac6816d..c8715e662 100644 --- a/cyw43-pio/src/lib.rs +++ b/cyw43-pio/src/lib.rs | |||
| @@ -7,6 +7,7 @@ use core::slice; | |||
| 7 | 7 | ||
| 8 | use cyw43::SpiBusCyw43; | 8 | use cyw43::SpiBusCyw43; |
| 9 | use embassy_rp::Peri; | 9 | use embassy_rp::Peri; |
| 10 | use embassy_rp::clocks::clk_sys_freq; | ||
| 10 | use embassy_rp::dma::Channel; | 11 | use embassy_rp::dma::Channel; |
| 11 | use embassy_rp::gpio::{Drive, Level, Output, Pull, SlewRate}; | 12 | use embassy_rp::gpio::{Drive, Level, Output, Pull, SlewRate}; |
| 12 | use embassy_rp::pio::program::pio_asm; | 13 | use embassy_rp::pio::program::pio_asm; |
| @@ -23,23 +24,24 @@ pub struct PioSpi<'d, PIO: Instance, const SM: usize, DMA: Channel> { | |||
| 23 | wrap_target: u8, | 24 | wrap_target: u8, |
| 24 | } | 25 | } |
| 25 | 26 | ||
| 26 | /// The default clock divider that works for Pico 1 and 2 W. As well as the RM2 on rp2040 devices. | 27 | /// Clock divider used for most applications |
| 27 | /// same speed as pico-sdk, 62.5Mhz | 28 | /// With default core clock configuration: |
| 28 | /// This is actually the fastest we can go without overclocking. | 29 | /// RP2350: 150Mhz / 2 = 75Mhz pio clock -> 37.5Mhz GSPI clock |
| 29 | /// According to data sheet, the theoretical maximum is 100Mhz Pio => 50Mhz SPI Freq. | 30 | /// RP2040: 133Mhz / 2 = 66.5Mhz pio clock -> 33.25Mhz GSPI clock |
| 30 | /// However, the PIO uses a fractional divider, which works by introducing jitter when | ||
| 31 | /// the divider is not an integer. It does some clocks at 125mhz and others at 62.5mhz | ||
| 32 | /// so that it averages out to the desired frequency of 100mhz. The 125mhz clock cycles | ||
| 33 | /// violate the maximum from the data sheet. | ||
| 34 | pub const DEFAULT_CLOCK_DIVIDER: FixedU32<U8> = FixedU32::from_bits(0x0200); | 31 | pub const DEFAULT_CLOCK_DIVIDER: FixedU32<U8> = FixedU32::from_bits(0x0200); |
| 35 | 32 | ||
| 36 | /// The overclock clock divider for the Pico 1 W. Does not work on any known RM2 devices. | 33 | /// Clock divider used to overclock the cyw43 |
| 37 | /// 125mhz Pio => 62.5Mhz SPI Freq. 25% higher than theoretical maximum according to | 34 | /// With default core clock configuration: |
| 38 | /// data sheet, but seems to work fine. | 35 | /// RP2350: 150Mhz / 1 = 150Mhz pio clock -> 75Mhz GSPI clock (50% greater that manufacturer |
| 36 | /// recommended 50Mhz) | ||
| 37 | /// RP2040: 133Mhz / 1 = 133Mhz pio clock -> 66.5Mhz GSPI clock (33% greater that manufacturer | ||
| 38 | /// recommended 50Mhz) | ||
| 39 | pub const OVERCLOCK_CLOCK_DIVIDER: FixedU32<U8> = FixedU32::from_bits(0x0100); | 39 | pub const OVERCLOCK_CLOCK_DIVIDER: FixedU32<U8> = FixedU32::from_bits(0x0100); |
| 40 | 40 | ||
| 41 | /// The clock divider for the RM2 module. Found to be needed for the Pimoroni Pico Plus 2 W, | 41 | /// Clock divider used with the RM2 |
| 42 | /// Pico Plus 2 Non w with the RM2 breakout module, and the Pico 2 with the RM2 breakout module. | 42 | /// With default core clock configuration: |
| 43 | /// RP2350: 150Mhz / 3 = 50Mhz pio clock -> 25Mhz GSPI clock | ||
| 44 | /// RP2040: 133Mhz / 3 = 44.33Mhz pio clock -> 22.16Mhz GSPI clock | ||
| 43 | pub const RM2_CLOCK_DIVIDER: FixedU32<U8> = FixedU32::from_bits(0x0300); | 45 | pub const RM2_CLOCK_DIVIDER: FixedU32<U8> = FixedU32::from_bits(0x0300); |
| 44 | 46 | ||
| 45 | impl<'d, PIO, const SM: usize, DMA> PioSpi<'d, PIO, SM, DMA> | 47 | impl<'d, PIO, const SM: usize, DMA> PioSpi<'d, PIO, SM, DMA> |
| @@ -58,7 +60,40 @@ where | |||
| 58 | clk: Peri<'d, impl PioPin>, | 60 | clk: Peri<'d, impl PioPin>, |
| 59 | dma: Peri<'d, DMA>, | 61 | dma: Peri<'d, DMA>, |
| 60 | ) -> Self { | 62 | ) -> Self { |
| 61 | let loaded_program = if clock_divider < DEFAULT_CLOCK_DIVIDER { | 63 | let effective_pio_frequency = (clk_sys_freq() as f32 / clock_divider.to_num::<f32>()) as u32; |
| 64 | |||
| 65 | #[cfg(feature = "defmt")] | ||
| 66 | defmt::trace!("Effective pio frequency: {}Hz", effective_pio_frequency); | ||
| 67 | |||
| 68 | // Non-integer pio clock dividers are achieved by introducing clock jitter resulting in a | ||
| 69 | // combination of long and short cycles. The long and short cycles average to achieve the | ||
| 70 | // requested clock speed. | ||
| 71 | // This can be a problem for peripherals that expect a consistent clock / have a clock | ||
| 72 | // speed upper bound that is violated by the short cycles. The cyw43 seems to handle the | ||
| 73 | // jitter well, but we emit a warning to recommend an integer divider anyway. | ||
| 74 | if clock_divider.frac() != FixedU32::<U8>::ZERO { | ||
| 75 | #[cfg(feature = "defmt")] | ||
| 76 | defmt::trace!( | ||
| 77 | "Configured clock divider is not a whole number. Some clock cycles may violate the maximum recommended GSPI speed. Use at your own risk." | ||
| 78 | ); | ||
| 79 | } | ||
| 80 | |||
| 81 | // Different pio programs must be used for different pio clock speeds. | ||
| 82 | // The programs used below are based on the pico SDK: https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/pico_cyw43_driver/cyw43_bus_pio_spi.pio | ||
| 83 | // The clock speed cutoff for each program has been determined experimentally: | ||
| 84 | // > 100Mhz -> Overclock program | ||
| 85 | // [75Mhz, 100Mhz] -> High speed program | ||
| 86 | // [0, 75Mhz) -> Low speed program | ||
| 87 | let loaded_program = if effective_pio_frequency > 100_000_000 { | ||
| 88 | // Any frequency > 100Mhz is overclocking the chip (manufacturer recommends max 50Mhz GSPI | ||
| 89 | // clock) | ||
| 90 | // Example: | ||
| 91 | // * RP2040 @ 133Mhz (stock) with OVERCLOCK_CLOCK_DIVIDER (133MHz) | ||
| 92 | #[cfg(feature = "defmt")] | ||
| 93 | defmt::trace!( | ||
| 94 | "Configured clock divider results in a GSPI frequency greater than the manufacturer recommendation (50Mhz). Use at your own risk." | ||
| 95 | ); | ||
| 96 | |||
| 62 | let overclock_program = pio_asm!( | 97 | let overclock_program = pio_asm!( |
| 63 | ".side_set 1" | 98 | ".side_set 1" |
| 64 | 99 | ||
| @@ -69,7 +104,7 @@ where | |||
| 69 | "jmp x-- lp side 1" | 104 | "jmp x-- lp side 1" |
| 70 | // switch directions | 105 | // switch directions |
| 71 | "set pindirs, 0 side 0" | 106 | "set pindirs, 0 side 0" |
| 72 | "nop side 1" // necessary for clkdiv=1. | 107 | "nop side 1" |
| 73 | "nop side 0" | 108 | "nop side 0" |
| 74 | // read in y-1 bits | 109 | // read in y-1 bits |
| 75 | "lp2:" | 110 | "lp2:" |
| @@ -83,8 +118,47 @@ where | |||
| 83 | ".wrap" | 118 | ".wrap" |
| 84 | ); | 119 | ); |
| 85 | common.load_program(&overclock_program.program) | 120 | common.load_program(&overclock_program.program) |
| 121 | } else if effective_pio_frequency >= 75_000_000 { | ||
| 122 | // Experimentally determined cutoff. | ||
| 123 | // Notably includes the stock RP2350 configured with clk_div of 2 (150Mhz base clock / 2 = 75Mhz) | ||
| 124 | // but does not include stock RP2040 configured with clk_div of 2 (133Mhz base clock / 2 = 66.5Mhz) | ||
| 125 | // Example: | ||
| 126 | // * RP2350 @ 150Mhz (stock) with DEFAULT_CLOCK_DIVIDER (75Mhz) | ||
| 127 | // * RP2XXX @ 200Mhz with DEFAULT_CLOCK_DIVIDER (100Mhz) | ||
| 128 | #[cfg(feature = "defmt")] | ||
| 129 | defmt::trace!("Using high speed pio program."); | ||
| 130 | let high_speed_program = pio_asm!( | ||
| 131 | ".side_set 1" | ||
| 132 | |||
| 133 | ".wrap_target" | ||
| 134 | // write out x-1 bits | ||
| 135 | "lp:" | ||
| 136 | "out pins, 1 side 0" | ||
| 137 | "jmp x-- lp side 1" | ||
| 138 | // switch directions | ||
| 139 | "set pindirs, 0 side 0" | ||
| 140 | "nop side 1" | ||
| 141 | // read in y-1 bits | ||
| 142 | "lp2:" | ||
| 143 | "in pins, 1 side 0" | ||
| 144 | "jmp y-- lp2 side 1" | ||
| 145 | |||
| 146 | // wait for event and irq host | ||
| 147 | "wait 1 pin 0 side 0" | ||
| 148 | "irq 0 side 0" | ||
| 149 | |||
| 150 | ".wrap" | ||
| 151 | ); | ||
| 152 | common.load_program(&high_speed_program.program) | ||
| 86 | } else { | 153 | } else { |
| 87 | let default_program = pio_asm!( | 154 | // Low speed |
| 155 | // Examples: | ||
| 156 | // * RP2040 @ 133Mhz (stock) with DEFAULT_CLOCK_DIVIDER (66.5Mhz) | ||
| 157 | // * RP2040 @ 133Mhz (stock) with RM2_CLOCK_DIVIDER (44.3Mhz) | ||
| 158 | // * RP2350 @ 150Mhz (stock) with RM2_CLOCK_DIVIDER (50Mhz) | ||
| 159 | #[cfg(feature = "defmt")] | ||
| 160 | defmt::trace!("Using low speed pio program."); | ||
| 161 | let low_speed_program = pio_asm!( | ||
| 88 | ".side_set 1" | 162 | ".side_set 1" |
| 89 | 163 | ||
| 90 | ".wrap_target" | 164 | ".wrap_target" |
| @@ -106,7 +180,7 @@ where | |||
| 106 | 180 | ||
| 107 | ".wrap" | 181 | ".wrap" |
| 108 | ); | 182 | ); |
| 109 | common.load_program(&default_program.program) | 183 | common.load_program(&low_speed_program.program) |
| 110 | }; | 184 | }; |
| 111 | 185 | ||
| 112 | let mut pin_io: embassy_rp::pio::Pin<PIO> = common.make_pio_pin(dio); | 186 | let mut pin_io: embassy_rp::pio::Pin<PIO> = common.make_pio_pin(dio); |
