aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorusedhondacivic <[email protected]>2025-10-26 18:00:51 -0700
committerusedhondacivic <[email protected]>2025-10-26 19:57:54 -0700
commite349ebb72c706c99dde34ba7b624aa9d1c25af39 (patch)
treebf7fbd862e7905041b6c05d591941f665a1a9afc
parentc8c4c6f40bd8a2e548f3c8e06b798d448f67b884 (diff)
cyw43-pio: core clock speed based pio program selection
-rw-r--r--cyw43-pio/src/lib.rs109
1 files changed, 92 insertions, 17 deletions
diff --git a/cyw43-pio/src/lib.rs b/cyw43-pio/src/lib.rs
index 41ac6816d..51d8ec3ae 100644
--- a/cyw43-pio/src/lib.rs
+++ b/cyw43-pio/src/lib.rs
@@ -7,11 +7,13 @@ use core::slice;
7 7
8use cyw43::SpiBusCyw43; 8use cyw43::SpiBusCyw43;
9use embassy_rp::Peri; 9use embassy_rp::Peri;
10use embassy_rp::clocks::clk_sys_freq;
10use embassy_rp::dma::Channel; 11use embassy_rp::dma::Channel;
11use embassy_rp::gpio::{Drive, Level, Output, Pull, SlewRate}; 12use embassy_rp::gpio::{Drive, Level, Output, Pull, SlewRate};
12use embassy_rp::pio::program::pio_asm; 13use embassy_rp::pio::program::pio_asm;
13use embassy_rp::pio::{Common, Config, Direction, Instance, Irq, PioPin, ShiftDirection, StateMachine}; 14use embassy_rp::pio::{Common, Config, Direction, Instance, Irq, PioPin, ShiftDirection, StateMachine};
14use fixed::FixedU32; 15use fixed::FixedU32;
16use fixed::traits::LosslessTryInto;
15use fixed::types::extra::U8; 17use fixed::types::extra::U8;
16 18
17/// SPI comms driven by PIO. 19/// SPI comms driven by PIO.
@@ -23,23 +25,24 @@ pub struct PioSpi<'d, PIO: Instance, const SM: usize, DMA: Channel> {
23 wrap_target: u8, 25 wrap_target: u8,
24} 26}
25 27
26/// The default clock divider that works for Pico 1 and 2 W. As well as the RM2 on rp2040 devices. 28/// Clock divider used for most applications
27/// same speed as pico-sdk, 62.5Mhz 29/// With default core clock configuration:
28/// This is actually the fastest we can go without overclocking. 30/// RP2350: 150Mhz / 2 = 75Mhz pio clock -> 37.5Mhz GSPI clock
29/// According to data sheet, the theoretical maximum is 100Mhz Pio => 50Mhz SPI Freq. 31/// 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.
34pub const DEFAULT_CLOCK_DIVIDER: FixedU32<U8> = FixedU32::from_bits(0x0200); 32pub const DEFAULT_CLOCK_DIVIDER: FixedU32<U8> = FixedU32::from_bits(0x0200);
35 33
36/// The overclock clock divider for the Pico 1 W. Does not work on any known RM2 devices. 34/// Clock divider used to overclock the cyw43
37/// 125mhz Pio => 62.5Mhz SPI Freq. 25% higher than theoretical maximum according to 35/// With default core clock configuration:
38/// data sheet, but seems to work fine. 36/// RP2350: 150Mhz / 1 = 150Mhz pio clock -> 75Mhz GSPI clock (50% greater that manufacturer
37/// recommended 50Mhz)
38/// RP2040: 133Mhz / 1 = 133Mhz pio clock -> 66.5Mhz GSPI clock (33% greater that manufacturer
39/// recommended 50Mhz)
39pub const OVERCLOCK_CLOCK_DIVIDER: FixedU32<U8> = FixedU32::from_bits(0x0100); 40pub const OVERCLOCK_CLOCK_DIVIDER: FixedU32<U8> = FixedU32::from_bits(0x0100);
40 41
41/// The clock divider for the RM2 module. Found to be needed for the Pimoroni Pico Plus 2 W, 42/// 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. 43/// With default core clock configuration:
44/// RP2350: 150Mhz / 3 = 50Mhz pio clock -> 25Mhz GSPI clock
45/// RP2040: 133Mhz / 3 = 44.33Mhz pio clock -> 22.16Mhz GSPI clock
43pub const RM2_CLOCK_DIVIDER: FixedU32<U8> = FixedU32::from_bits(0x0300); 46pub const RM2_CLOCK_DIVIDER: FixedU32<U8> = FixedU32::from_bits(0x0300);
44 47
45impl<'d, PIO, const SM: usize, DMA> PioSpi<'d, PIO, SM, DMA> 48impl<'d, PIO, const SM: usize, DMA> PioSpi<'d, PIO, SM, DMA>
@@ -58,7 +61,40 @@ where
58 clk: Peri<'d, impl PioPin>, 61 clk: Peri<'d, impl PioPin>,
59 dma: Peri<'d, DMA>, 62 dma: Peri<'d, DMA>,
60 ) -> Self { 63 ) -> Self {
61 let loaded_program = if clock_divider < DEFAULT_CLOCK_DIVIDER { 64 let effective_pio_frequency = (clk_sys_freq() as f32 / clock_divider.to_num::<f32>()) as u32;
65
66 #[cfg(feature = "defmt")]
67 defmt::trace!("Effective pio frequency: {}", effective_pio_frequency);
68
69 // Non-integer pio clock dividers are achieved by introducing clock jitter resulting in a
70 // combination of long and short cycles. The long and short cycles average to achieve the
71 // requested clock speed.
72 // This can be a problem for peripherals that expect a consistent clock / have a clock
73 // speed upper bound that is violated by the short cycles. The cyw43 seems to handle the
74 // jitter well, but we emit a warning to recommend an integer divider anyway.
75 if let None = clock_divider.lossless_try_into<u32>() {
76 #[cfg(feature = "defmt")]
77 defmt::trace!(
78 "Configured clock divider is not a whole number. Some clock cycles may violate the maximum recommended GSPI speed. Use at your own risk."
79 );
80 }
81
82 // Different pio programs must be used for different pio clock speeds.
83 // 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
84 // The clock speed cutoff for each program has been determined experimentally:
85 // > 100Mhz -> Overclock program
86 // [75Mhz, 100Mhz] -> High speed program
87 // [0, 75Mhz) -> Low speed program
88 let loaded_program = if effective_pio_frequency > 100_000_000 {
89 // Any frequency > 100Mhz is overclocking the chip (manufacturer recommends max 50Mhz GSPI
90 // clock)
91 // Example:
92 // * RP2040 @ 133Mhz (stock) with OVERCLOCK_CLOCK_DIVIDER (133MHz)
93 #[cfg(feature = "defmt")]
94 defmt::trace!(
95 "Configured clock divider results in a GSPI frequency greater than the manufacturer recommendation (50Mhz). Use at your own risk."
96 );
97
62 let overclock_program = pio_asm!( 98 let overclock_program = pio_asm!(
63 ".side_set 1" 99 ".side_set 1"
64 100
@@ -69,7 +105,7 @@ where
69 "jmp x-- lp side 1" 105 "jmp x-- lp side 1"
70 // switch directions 106 // switch directions
71 "set pindirs, 0 side 0" 107 "set pindirs, 0 side 0"
72 "nop side 1" // necessary for clkdiv=1. 108 "nop side 1"
73 "nop side 0" 109 "nop side 0"
74 // read in y-1 bits 110 // read in y-1 bits
75 "lp2:" 111 "lp2:"
@@ -83,8 +119,47 @@ where
83 ".wrap" 119 ".wrap"
84 ); 120 );
85 common.load_program(&overclock_program.program) 121 common.load_program(&overclock_program.program)
122 } else if effective_pio_frequency >= 75_000_000 {
123 // Experimentally determined cutoff.
124 // Notably includes the stock RP2350 configured with clk_div of 2 (150Mhz base clock / 2 = 75Mhz)
125 // but does not include stock RP2040 configured with clk_div of 2 (133Mhz base clock / 2 = 66.5Mhz)
126 // Example:
127 // * RP2350 @ 150Mhz (stock) with DEFAULT_CLOCK_DIVIDER (75Mhz)
128 // * RP2XXX @ 200Mhz with DEFAULT_CLOCK_DIVIDER (100Mhz)
129 #[cfg(feature = "defmt")]
130 defmt::trace!("Using high speed pio program.");
131 let high_speed_program = pio_asm!(
132 ".side_set 1"
133
134 ".wrap_target"
135 // write out x-1 bits
136 "lp:"
137 "out pins, 1 side 0"
138 "jmp x-- lp side 1"
139 // switch directions
140 "set pindirs, 0 side 0"
141 "nop side 1"
142 // read in y-1 bits
143 "lp2:"
144 "in pins, 1 side 0"
145 "jmp y-- lp2 side 1"
146
147 // wait for event and irq host
148 "wait 1 pin 0 side 0"
149 "irq 0 side 0"
150
151 ".wrap"
152 );
153 common.load_program(&high_speed_program.program)
86 } else { 154 } else {
87 let default_program = pio_asm!( 155 // Low speed
156 // Examples:
157 // * RP2040 @ 133Mhz (stock) with DEFAULT_CLOCK_DIVIDER (66.5Mhz)
158 // * RP2040 @ 133Mhz (stock) with RM2_CLOCK_DIVIDER (44.3Mhz)
159 // * RP2350 @ 150Mhz (stock) with RM2_CLOCK_DIVIDER (50Mhz)
160 #[cfg(feature = "defmt")]
161 defmt::trace!("Using low speed pio program.");
162 let low_speed_program = pio_asm!(
88 ".side_set 1" 163 ".side_set 1"
89 164
90 ".wrap_target" 165 ".wrap_target"
@@ -106,7 +181,7 @@ where
106 181
107 ".wrap" 182 ".wrap"
108 ); 183 );
109 common.load_program(&default_program.program) 184 common.load_program(&low_speed_program.program)
110 }; 185 };
111 186
112 let mut pin_io: embassy_rp::pio::Pin<PIO> = common.make_pio_pin(dio); 187 let mut pin_io: embassy_rp::pio::Pin<PIO> = common.make_pio_pin(dio);