aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdrian Wowk <[email protected]>2025-07-18 19:19:27 -0500
committerDario Nieuwenhuis <[email protected]>2025-09-05 20:35:48 +0200
commit62ff0194f4b7413b17dbc69813ec205638248aa7 (patch)
tree7f954609bf7e3eaad08f05284f9e8f49a8483168
parent83b42e0db620b7fc2364763b452b346b711e8d9f (diff)
rp: add pio spi runtime reconfiguration
-rw-r--r--embassy-rp/src/pio_programs/spi.rs119
-rw-r--r--examples/rp/src/bin/pio_spi.rs20
-rw-r--r--examples/rp/src/bin/pio_spi_async.rs10
3 files changed, 87 insertions, 62 deletions
diff --git a/embassy-rp/src/pio_programs/spi.rs b/embassy-rp/src/pio_programs/spi.rs
index a1b36c1c8..6b97cd0f3 100644
--- a/embassy-rp/src/pio_programs/spi.rs
+++ b/embassy-rp/src/pio_programs/spi.rs
@@ -11,12 +11,13 @@ use fixed::types::extra::U8;
11use crate::clocks::clk_sys_freq; 11use crate::clocks::clk_sys_freq;
12use crate::dma::{AnyChannel, Channel}; 12use crate::dma::{AnyChannel, Channel};
13use crate::gpio::Level; 13use crate::gpio::Level;
14use crate::pio::{Common, Direction, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine}; 14use crate::pio::{Common, Direction, Instance, LoadedProgram, Pin, PioPin, ShiftDirection, StateMachine};
15use crate::spi::{Async, Blocking, Mode}; 15use crate::spi::{Async, Blocking, Config, Mode};
16 16
17/// This struct represents a uart tx program loaded into pio instruction memory. 17/// This struct represents an SPI program loaded into pio instruction memory.
18pub struct PioSpiProgram<'d, PIO: Instance> { 18struct PioSpiProgram<'d, PIO: Instance> {
19 prg: LoadedProgram<'d, PIO>, 19 prg: LoadedProgram<'d, PIO>,
20 phase: Phase,
20} 21}
21 22
22impl<'d, PIO: Instance> PioSpiProgram<'d, PIO> { 23impl<'d, PIO: Instance> PioSpiProgram<'d, PIO> {
@@ -30,11 +31,11 @@ impl<'d, PIO: Instance> PioSpiProgram<'d, PIO> {
30 // - MOSI is OUT pin 0 31 // - MOSI is OUT pin 0
31 // - MISO is IN pin 0 32 // - MISO is IN pin 0
32 // 33 //
33 // Autopush and autopull must be enabled, and the serial frame size is set by 34 // Auto-push and auto-pull must be enabled, and the serial frame size is set by
34 // configuring the push/pull threshold. Shift left/right is fine, but you must 35 // configuring the push/pull threshold. Shift left/right is fine, but you must
35 // justify the data yourself. This is done most conveniently for frame sizes of 36 // justify the data yourself. This is done most conveniently for frame sizes of
36 // 8 or 16 bits by using the narrow store replication and narrow load byte 37 // 8 or 16 bits by using the narrow store replication and narrow load byte
37 // picking behaviour of RP2040's IO fabric. 38 // picking behavior of RP2040's IO fabric.
38 39
39 let prg = match phase { 40 let prg = match phase {
40 Phase::CaptureOnFirstTransition => { 41 Phase::CaptureOnFirstTransition => {
@@ -60,9 +61,9 @@ impl<'d, PIO: Instance> PioSpiProgram<'d, PIO> {
60 ; Clock phase = 1: data transitions on the leading edge of each SCK pulse, and 61 ; Clock phase = 1: data transitions on the leading edge of each SCK pulse, and
61 ; is captured on the trailing edge. 62 ; is captured on the trailing edge.
62 63
63 out x, 1 side 0 ; Stall here on empty (keep SCK deasserted) 64 out x, 1 side 0 ; Stall here on empty (keep SCK de-asserted)
64 mov pins, x side 1 [1] ; Output data, assert SCK (mov pins uses OUT mapping) 65 mov pins, x side 1 [1] ; Output data, assert SCK (mov pins uses OUT mapping)
65 in pins, 1 side 0 ; Input data, deassert SCK 66 in pins, 1 side 0 ; Input data, de-assert SCK
66 "# 67 "#
67 ); 68 );
68 69
@@ -70,7 +71,7 @@ impl<'d, PIO: Instance> PioSpiProgram<'d, PIO> {
70 } 71 }
71 }; 72 };
72 73
73 Self { prg } 74 Self { prg, phase }
74 } 75 }
75} 76}
76 77
@@ -83,35 +84,19 @@ pub enum Error {
83} 84}
84 85
85/// PIO based Spi driver. 86/// PIO based Spi driver.
86/// 87/// Unlike other PIO programs, the PIO SPI driver owns and holds a reference to
87/// This driver is less flexible than the hardware backed one. Configuration can 88/// the PIO memory it uses. This is so that it can be reconfigured at runtime if
88/// not be changed at runtime. 89/// desired.
89pub struct Spi<'d, PIO: Instance, const SM: usize, M: Mode> { 90pub struct Spi<'d, PIO: Instance, const SM: usize, M: Mode> {
90 sm: StateMachine<'d, PIO, SM>, 91 sm: StateMachine<'d, PIO, SM>,
92 cfg: crate::pio::Config<'d, PIO>,
93 program: Option<PioSpiProgram<'d, PIO>>,
94 clk_pin: Pin<'d, PIO>,
91 tx_dma: Option<Peri<'d, AnyChannel>>, 95 tx_dma: Option<Peri<'d, AnyChannel>>,
92 rx_dma: Option<Peri<'d, AnyChannel>>, 96 rx_dma: Option<Peri<'d, AnyChannel>>,
93 phantom: PhantomData<M>, 97 phantom: PhantomData<M>,
94} 98}
95 99
96/// PIO SPI configuration.
97#[non_exhaustive]
98#[derive(Clone)]
99pub struct Config {
100 /// Frequency (Hz).
101 pub frequency: u32,
102 /// Polarity.
103 pub polarity: Polarity,
104}
105
106impl Default for Config {
107 fn default() -> Self {
108 Self {
109 frequency: 1_000_000,
110 polarity: Polarity::IdleLow,
111 }
112 }
113}
114
115impl<'d, PIO: Instance, const SM: usize, M: Mode> Spi<'d, PIO, SM, M> { 100impl<'d, PIO: Instance, const SM: usize, M: Mode> Spi<'d, PIO, SM, M> {
116 #[allow(clippy::too_many_arguments)] 101 #[allow(clippy::too_many_arguments)]
117 fn new_inner( 102 fn new_inner(
@@ -122,9 +107,10 @@ impl<'d, PIO: Instance, const SM: usize, M: Mode> Spi<'d, PIO, SM, M> {
122 miso_pin: Peri<'d, impl PioPin>, 107 miso_pin: Peri<'d, impl PioPin>,
123 tx_dma: Option<Peri<'d, AnyChannel>>, 108 tx_dma: Option<Peri<'d, AnyChannel>>,
124 rx_dma: Option<Peri<'d, AnyChannel>>, 109 rx_dma: Option<Peri<'d, AnyChannel>>,
125 program: &PioSpiProgram<'d, PIO>,
126 config: Config, 110 config: Config,
127 ) -> Self { 111 ) -> Self {
112 let program = PioSpiProgram::new(pio, config.phase);
113
128 let mut clk_pin = pio.make_pio_pin(clk_pin); 114 let mut clk_pin = pio.make_pio_pin(clk_pin);
129 let mosi_pin = pio.make_pio_pin(mosi_pin); 115 let mosi_pin = pio.make_pio_pin(mosi_pin);
130 let miso_pin = pio.make_pio_pin(miso_pin); 116 let miso_pin = pio.make_pio_pin(miso_pin);
@@ -153,15 +139,16 @@ impl<'d, PIO: Instance, const SM: usize, M: Mode> Spi<'d, PIO, SM, M> {
153 cfg.shift_out.direction = ShiftDirection::Left; 139 cfg.shift_out.direction = ShiftDirection::Left;
154 cfg.shift_out.threshold = 8; 140 cfg.shift_out.threshold = 8;
155 141
156 let sys_freq = clk_sys_freq().to_fixed::<fixed::FixedU64<U8>>(); 142 cfg.clock_divider = calculate_clock_divider(config.frequency);
157 let target_freq = (config.frequency * 4).to_fixed::<fixed::FixedU64<U8>>();
158 cfg.clock_divider = (sys_freq / target_freq).to_fixed();
159 143
160 sm.set_config(&cfg); 144 sm.set_config(&cfg);
161 sm.set_enable(true); 145 sm.set_enable(true);
162 146
163 Self { 147 Self {
164 sm, 148 sm,
149 program: Some(program),
150 cfg,
151 clk_pin,
165 tx_dma, 152 tx_dma,
166 rx_dma, 153 rx_dma,
167 phantom: PhantomData, 154 phantom: PhantomData,
@@ -242,6 +229,63 @@ impl<'d, PIO: Instance, const SM: usize, M: Mode> Spi<'d, PIO, SM, M> {
242 229
243 Ok(()) 230 Ok(())
244 } 231 }
232
233 /// Set SPI frequency.
234 pub fn set_frequency(&mut self, freq: u32) {
235 self.sm.set_enable(false);
236
237 let divider = calculate_clock_divider(freq);
238
239 // save into the config for later but dont use sm.set_config() since
240 // that operation is relatively more expensive than just setting the
241 // clock divider
242 self.cfg.clock_divider = divider;
243 self.sm.set_clock_divider(divider);
244
245 self.sm.set_enable(true);
246 }
247
248 /// Set SPI config.
249 ///
250 /// This operation will panic if the PIO program needs to be reloaded and
251 /// there is insufficient room. This is unlikely since the programs for each
252 /// phase only differ in size by a single instruction.
253 pub fn set_config(&mut self, pio: &mut Common<'d, PIO>, config: &Config) {
254 self.sm.set_enable(false);
255
256 self.cfg.clock_divider = calculate_clock_divider(config.frequency);
257
258 if let Polarity::IdleHigh = config.polarity {
259 self.clk_pin.set_output_inversion(true);
260 } else {
261 self.clk_pin.set_output_inversion(false);
262 }
263
264 if self.program.as_ref().unwrap().phase != config.phase {
265 let old_program = self.program.take().unwrap();
266
267 // SAFETY: the state machine is disabled while this happens
268 unsafe { pio.free_instr(old_program.prg.used_memory) };
269
270 let new_program = PioSpiProgram::new(pio, config.phase);
271
272 self.cfg.use_program(&new_program.prg, &[&self.clk_pin]);
273 self.program = Some(new_program);
274 }
275
276 self.sm.set_config(&self.cfg);
277 self.sm.restart();
278
279 self.sm.set_enable(true);
280 }
281}
282
283fn calculate_clock_divider(frequency_hz: u32) -> fixed::FixedU32<U8> {
284 // we multiply by 4 since each clock period is equal to 4 instructions
285
286 let sys_freq = clk_sys_freq().to_fixed::<fixed::FixedU64<U8>>();
287 let target_freq = (frequency_hz * 4).to_fixed::<fixed::FixedU64<U8>>();
288 (sys_freq / target_freq).to_fixed()
245} 289}
246 290
247impl<'d, PIO: Instance, const SM: usize> Spi<'d, PIO, SM, Blocking> { 291impl<'d, PIO: Instance, const SM: usize> Spi<'d, PIO, SM, Blocking> {
@@ -252,10 +296,9 @@ impl<'d, PIO: Instance, const SM: usize> Spi<'d, PIO, SM, Blocking> {
252 clk: Peri<'d, impl PioPin>, 296 clk: Peri<'d, impl PioPin>,
253 mosi: Peri<'d, impl PioPin>, 297 mosi: Peri<'d, impl PioPin>,
254 miso: Peri<'d, impl PioPin>, 298 miso: Peri<'d, impl PioPin>,
255 program: &PioSpiProgram<'d, PIO>,
256 config: Config, 299 config: Config,
257 ) -> Self { 300 ) -> Self {
258 Self::new_inner(pio, sm, clk, mosi, miso, None, None, program, config) 301 Self::new_inner(pio, sm, clk, mosi, miso, None, None, config)
259 } 302 }
260} 303}
261 304
@@ -270,7 +313,6 @@ impl<'d, PIO: Instance, const SM: usize> Spi<'d, PIO, SM, Async> {
270 miso: Peri<'d, impl PioPin>, 313 miso: Peri<'d, impl PioPin>,
271 tx_dma: Peri<'d, impl Channel>, 314 tx_dma: Peri<'d, impl Channel>,
272 rx_dma: Peri<'d, impl Channel>, 315 rx_dma: Peri<'d, impl Channel>,
273 program: &PioSpiProgram<'d, PIO>,
274 config: Config, 316 config: Config,
275 ) -> Self { 317 ) -> Self {
276 Self::new_inner( 318 Self::new_inner(
@@ -281,7 +323,6 @@ impl<'d, PIO: Instance, const SM: usize> Spi<'d, PIO, SM, Async> {
281 miso, 323 miso,
282 Some(tx_dma.into()), 324 Some(tx_dma.into()),
283 Some(rx_dma.into()), 325 Some(rx_dma.into()),
284 program,
285 config, 326 config,
286 ) 327 )
287 } 328 }
diff --git a/examples/rp/src/bin/pio_spi.rs b/examples/rp/src/bin/pio_spi.rs
index 0164e4c81..4218327ec 100644
--- a/examples/rp/src/bin/pio_spi.rs
+++ b/examples/rp/src/bin/pio_spi.rs
@@ -10,8 +10,8 @@
10use defmt::*; 10use defmt::*;
11use embassy_executor::Spawner; 11use embassy_executor::Spawner;
12use embassy_rp::peripherals::PIO0; 12use embassy_rp::peripherals::PIO0;
13use embassy_rp::pio_programs::spi::{Config, PioSpiProgram, Spi}; 13use embassy_rp::pio_programs::spi::Spi;
14use embassy_rp::spi::Phase; 14use embassy_rp::spi::Config;
15use embassy_rp::{bind_interrupts, pio}; 15use embassy_rp::{bind_interrupts, pio};
16use embassy_time::Timer; 16use embassy_time::Timer;
17use {defmt_rtt as _, panic_probe as _}; 17use {defmt_rtt as _, panic_probe as _};
@@ -25,7 +25,7 @@ async fn main(_spawner: Spawner) {
25 let p = embassy_rp::init(Default::default()); 25 let p = embassy_rp::init(Default::default());
26 info!("Hello World!"); 26 info!("Hello World!");
27 27
28 // These pins are routed to differnet hardware SPI peripherals, but we can 28 // These pins are routed to different hardware SPI peripherals, but we can
29 // use them together regardless 29 // use them together regardless
30 let mosi = p.PIN_6; // SPI0 SCLK 30 let mosi = p.PIN_6; // SPI0 SCLK
31 let miso = p.PIN_7; // SPI0 MOSI 31 let miso = p.PIN_7; // SPI0 MOSI
@@ -33,20 +33,8 @@ async fn main(_spawner: Spawner) {
33 33
34 let pio::Pio { mut common, sm0, .. } = pio::Pio::new(p.PIO0, Irqs); 34 let pio::Pio { mut common, sm0, .. } = pio::Pio::new(p.PIO0, Irqs);
35 35
36 // The PIO program must be configured with the clock phase
37 let program = PioSpiProgram::new(&mut common, Phase::CaptureOnFirstTransition);
38
39 // Construct an SPI driver backed by a PIO state machine 36 // Construct an SPI driver backed by a PIO state machine
40 let mut spi = Spi::new_blocking( 37 let mut spi = Spi::new_blocking(&mut common, sm0, clk, mosi, miso, Config::default());
41 &mut common,
42 sm0,
43 clk,
44 mosi,
45 miso,
46 &program,
47 // Only the frequency and polarity are set here
48 Config::default(),
49 );
50 38
51 loop { 39 loop {
52 let tx_buf = [1_u8, 2, 3, 4, 5, 6]; 40 let tx_buf = [1_u8, 2, 3, 4, 5, 6];
diff --git a/examples/rp/src/bin/pio_spi_async.rs b/examples/rp/src/bin/pio_spi_async.rs
index 1dbdff609..74a2dd11b 100644
--- a/examples/rp/src/bin/pio_spi_async.rs
+++ b/examples/rp/src/bin/pio_spi_async.rs
@@ -10,8 +10,8 @@
10use defmt::*; 10use defmt::*;
11use embassy_executor::Spawner; 11use embassy_executor::Spawner;
12use embassy_rp::peripherals::PIO0; 12use embassy_rp::peripherals::PIO0;
13use embassy_rp::pio_programs::spi::{Config, PioSpiProgram, Spi}; 13use embassy_rp::pio_programs::spi::Spi;
14use embassy_rp::spi::Phase; 14use embassy_rp::spi::Config;
15use embassy_rp::{bind_interrupts, pio}; 15use embassy_rp::{bind_interrupts, pio};
16use embassy_time::Timer; 16use embassy_time::Timer;
17use {defmt_rtt as _, panic_probe as _}; 17use {defmt_rtt as _, panic_probe as _};
@@ -25,7 +25,7 @@ async fn main(_spawner: Spawner) {
25 let p = embassy_rp::init(Default::default()); 25 let p = embassy_rp::init(Default::default());
26 info!("Hello World!"); 26 info!("Hello World!");
27 27
28 // These pins are routed to differnet hardware SPI peripherals, but we can 28 // These pins are routed to different hardware SPI peripherals, but we can
29 // use them together regardless 29 // use them together regardless
30 let mosi = p.PIN_6; // SPI0 SCLK 30 let mosi = p.PIN_6; // SPI0 SCLK
31 let miso = p.PIN_7; // SPI0 MOSI 31 let miso = p.PIN_7; // SPI0 MOSI
@@ -33,9 +33,6 @@ async fn main(_spawner: Spawner) {
33 33
34 let pio::Pio { mut common, sm0, .. } = pio::Pio::new(p.PIO0, Irqs); 34 let pio::Pio { mut common, sm0, .. } = pio::Pio::new(p.PIO0, Irqs);
35 35
36 // The PIO program must be configured with the clock phase
37 let program = PioSpiProgram::new(&mut common, Phase::CaptureOnFirstTransition);
38
39 // Construct an SPI driver backed by a PIO state machine 36 // Construct an SPI driver backed by a PIO state machine
40 let mut spi = Spi::new( 37 let mut spi = Spi::new(
41 &mut common, 38 &mut common,
@@ -46,7 +43,6 @@ async fn main(_spawner: Spawner) {
46 p.DMA_CH0, 43 p.DMA_CH0,
47 p.DMA_CH1, 44 p.DMA_CH1,
48 &program, 45 &program,
49 // Only the frequency and polarity are set here
50 Config::default(), 46 Config::default(),
51 ); 47 );
52 48