aboutsummaryrefslogtreecommitdiff
path: root/embassy-rp/src/pio_programs
diff options
context:
space:
mode:
author1-rafael-1 <[email protected]>2025-09-15 20:07:18 +0200
committer1-rafael-1 <[email protected]>2025-09-15 20:07:18 +0200
commit6bb3d2c0720fa082f27d3cdb70f516058497ec87 (patch)
tree5a1e255cff999b00800f203b91a759c720c973e5 /embassy-rp/src/pio_programs
parenteb685574601d98c44faed9a3534d056199b46e20 (diff)
parent92a6fd2946f2cbb15359290f68aa360953da2ff7 (diff)
Merge branch 'main' into rp2040-rtc-alarm
Diffstat (limited to 'embassy-rp/src/pio_programs')
-rw-r--r--embassy-rp/src/pio_programs/i2s.rs114
-rw-r--r--embassy-rp/src/pio_programs/mod.rs1
-rw-r--r--embassy-rp/src/pio_programs/onewire.rs45
-rw-r--r--embassy-rp/src/pio_programs/spi.rs474
-rw-r--r--embassy-rp/src/pio_programs/ws2812.rs70
5 files changed, 690 insertions, 14 deletions
diff --git a/embassy-rp/src/pio_programs/i2s.rs b/embassy-rp/src/pio_programs/i2s.rs
index b967f0160..2382a3f9f 100644
--- a/embassy-rp/src/pio_programs/i2s.rs
+++ b/embassy-rp/src/pio_programs/i2s.rs
@@ -1,14 +1,106 @@
1//! Pio backed I2s output 1//! Pio backed I2s output and output drivers
2 2
3use fixed::traits::ToFixed; 3use fixed::traits::ToFixed;
4 4
5use crate::dma::{AnyChannel, Channel, Transfer}; 5use crate::dma::{AnyChannel, Channel, Transfer};
6use crate::gpio::Pull;
6use crate::pio::{ 7use crate::pio::{
7 Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine, 8 Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine,
8}; 9};
9use crate::Peri; 10use crate::Peri;
10 11
12/// This struct represents an i2s receiver & controller driver program
13pub struct PioI2sInProgram<'d, PIO: Instance> {
14 prg: LoadedProgram<'d, PIO>,
15}
16
17impl<'d, PIO: Instance> PioI2sInProgram<'d, PIO> {
18 /// Load the input program into the given pio
19 pub fn new(common: &mut Common<'d, PIO>) -> Self {
20 let prg = pio::pio_asm! {
21 ".side_set 2",
22 " set x, 14 side 0b01",
23 "left_data:",
24 " in pins, 1 side 0b00", // read one left-channel bit from SD
25 " jmp x-- left_data side 0b01",
26 " in pins, 1 side 0b10", // ws changes 1 clock before MSB
27 " set x, 14 side 0b11",
28 "right_data:",
29 " in pins, 1 side 0b10",
30 " jmp x-- right_data side 0b11",
31 " in pins, 1 side 0b00" // ws changes 1 clock before ms
32 };
33 let prg = common.load_program(&prg.program);
34 Self { prg }
35 }
36}
37
38/// Pio backed I2s input driver
39pub struct PioI2sIn<'d, P: Instance, const S: usize> {
40 dma: Peri<'d, AnyChannel>,
41 sm: StateMachine<'d, P, S>,
42}
43
44impl<'d, P: Instance, const S: usize> PioI2sIn<'d, P, S> {
45 /// Configure a state machine to act as both the controller (provider of SCK and WS) and receiver (of SD) for an I2S signal
46 pub fn new(
47 common: &mut Common<'d, P>,
48 mut sm: StateMachine<'d, P, S>,
49 dma: Peri<'d, impl Channel>,
50 // Whether or not to use the MCU's internal pull-down resistor, as the
51 // Pico 2 is known to have problems with the inbuilt pulldowns, many
52 // opt to just use an external pull down resistor to meet requirements of common
53 // i2s microphones such as the INMP441
54 data_pulldown: bool,
55 data_pin: Peri<'d, impl PioPin>,
56 bit_clock_pin: Peri<'d, impl PioPin>,
57 lr_clock_pin: Peri<'d, impl PioPin>,
58 sample_rate: u32,
59 bit_depth: u32,
60 channels: u32,
61 program: &PioI2sInProgram<'d, P>,
62 ) -> Self {
63 let mut data_pin = common.make_pio_pin(data_pin);
64 if data_pulldown {
65 data_pin.set_pull(Pull::Down);
66 }
67 let bit_clock_pin = common.make_pio_pin(bit_clock_pin);
68 let left_right_clock_pin = common.make_pio_pin(lr_clock_pin);
69
70 let cfg = {
71 let mut cfg = Config::default();
72 cfg.use_program(&program.prg, &[&bit_clock_pin, &left_right_clock_pin]);
73 cfg.set_in_pins(&[&data_pin]);
74 let clock_frequency = sample_rate * bit_depth * channels;
75 cfg.clock_divider = (crate::clocks::clk_sys_freq() as f64 / clock_frequency as f64 / 2.).to_fixed();
76 cfg.shift_in = ShiftConfig {
77 threshold: 32,
78 direction: ShiftDirection::Left,
79 auto_fill: true,
80 };
81 // join fifos to have twice the time to start the next dma transfer
82 cfg.fifo_join = FifoJoin::RxOnly; // both control signals are sent via side-setting
83 cfg
84 };
85 sm.set_config(&cfg);
86 sm.set_pin_dirs(Direction::In, &[&data_pin]);
87 sm.set_pin_dirs(Direction::Out, &[&left_right_clock_pin, &bit_clock_pin]);
88 sm.set_enable(true);
89
90 Self { dma: dma.into(), sm }
91 }
92
93 /// Return an in-prograss dma transfer future. Awaiting it will guarentee a complete transfer.
94 pub fn read<'b>(&'b mut self, buff: &'b mut [u32]) -> Transfer<'b, AnyChannel> {
95 self.sm.rx().dma_pull(self.dma.reborrow(), buff, false)
96 }
97}
98
11/// This struct represents an i2s output driver program 99/// This struct represents an i2s output driver program
100///
101/// The sample bit-depth is set through scratch register `Y`.
102/// `Y` has to be set to sample bit-depth - 2.
103/// (14 = 16bit, 22 = 24bit, 30 = 32bit)
12pub struct PioI2sOutProgram<'d, PIO: Instance> { 104pub struct PioI2sOutProgram<'d, PIO: Instance> {
13 prg: LoadedProgram<'d, PIO>, 105 prg: LoadedProgram<'d, PIO>,
14} 106}
@@ -17,17 +109,17 @@ impl<'d, PIO: Instance> PioI2sOutProgram<'d, PIO> {
17 /// Load the program into the given pio 109 /// Load the program into the given pio
18 pub fn new(common: &mut Common<'d, PIO>) -> Self { 110 pub fn new(common: &mut Common<'d, PIO>) -> Self {
19 let prg = pio::pio_asm!( 111 let prg = pio::pio_asm!(
20 ".side_set 2", 112 ".side_set 2", // side 0bWB - W = Word Clock, B = Bit Clock
21 " set x, 14 side 0b01", // side 0bWB - W = Word Clock, B = Bit Clock 113 " mov x, y side 0b01", // y stores sample depth - 2 (14 = 16bit, 22 = 24bit, 30 = 32bit)
22 "left_data:", 114 "left_data:",
23 " out pins, 1 side 0b00", 115 " out pins, 1 side 0b00",
24 " jmp x-- left_data side 0b01", 116 " jmp x-- left_data side 0b01",
25 " out pins 1 side 0b10", 117 " out pins, 1 side 0b10",
26 " set x, 14 side 0b11", 118 " mov x, y side 0b11",
27 "right_data:", 119 "right_data:",
28 " out pins 1 side 0b10", 120 " out pins, 1 side 0b10",
29 " jmp x-- right_data side 0b11", 121 " jmp x-- right_data side 0b11",
30 " out pins 1 side 0b00", 122 " out pins, 1 side 0b00",
31 ); 123 );
32 124
33 let prg = common.load_program(&prg.program); 125 let prg = common.load_program(&prg.program);
@@ -53,7 +145,6 @@ impl<'d, P: Instance, const S: usize> PioI2sOut<'d, P, S> {
53 lr_clock_pin: Peri<'d, impl PioPin>, 145 lr_clock_pin: Peri<'d, impl PioPin>,
54 sample_rate: u32, 146 sample_rate: u32,
55 bit_depth: u32, 147 bit_depth: u32,
56 channels: u32,
57 program: &PioI2sOutProgram<'d, P>, 148 program: &PioI2sOutProgram<'d, P>,
58 ) -> Self { 149 ) -> Self {
59 let data_pin = common.make_pio_pin(data_pin); 150 let data_pin = common.make_pio_pin(data_pin);
@@ -64,7 +155,7 @@ impl<'d, P: Instance, const S: usize> PioI2sOut<'d, P, S> {
64 let mut cfg = Config::default(); 155 let mut cfg = Config::default();
65 cfg.use_program(&program.prg, &[&bit_clock_pin, &left_right_clock_pin]); 156 cfg.use_program(&program.prg, &[&bit_clock_pin, &left_right_clock_pin]);
66 cfg.set_out_pins(&[&data_pin]); 157 cfg.set_out_pins(&[&data_pin]);
67 let clock_frequency = sample_rate * bit_depth * channels; 158 let clock_frequency = sample_rate * bit_depth * 2;
68 cfg.clock_divider = (crate::clocks::clk_sys_freq() as f64 / clock_frequency as f64 / 2.).to_fixed(); 159 cfg.clock_divider = (crate::clocks::clk_sys_freq() as f64 / clock_frequency as f64 / 2.).to_fixed();
69 cfg.shift_out = ShiftConfig { 160 cfg.shift_out = ShiftConfig {
70 threshold: 32, 161 threshold: 32,
@@ -78,6 +169,11 @@ impl<'d, P: Instance, const S: usize> PioI2sOut<'d, P, S> {
78 sm.set_config(&cfg); 169 sm.set_config(&cfg);
79 sm.set_pin_dirs(Direction::Out, &[&data_pin, &left_right_clock_pin, &bit_clock_pin]); 170 sm.set_pin_dirs(Direction::Out, &[&data_pin, &left_right_clock_pin, &bit_clock_pin]);
80 171
172 // Set the `y` register up to configure the sample depth
173 // The SM counts down to 0 and uses one clock cycle to set up the counter,
174 // which results in bit_depth - 2 as register value.
175 unsafe { sm.set_y(bit_depth - 2) };
176
81 sm.set_enable(true); 177 sm.set_enable(true);
82 178
83 Self { dma: dma.into(), sm } 179 Self { dma: dma.into(), sm }
diff --git a/embassy-rp/src/pio_programs/mod.rs b/embassy-rp/src/pio_programs/mod.rs
index 8eac328b3..d05ba3884 100644
--- a/embassy-rp/src/pio_programs/mod.rs
+++ b/embassy-rp/src/pio_programs/mod.rs
@@ -6,6 +6,7 @@ pub mod i2s;
6pub mod onewire; 6pub mod onewire;
7pub mod pwm; 7pub mod pwm;
8pub mod rotary_encoder; 8pub mod rotary_encoder;
9pub mod spi;
9pub mod stepper; 10pub mod stepper;
10pub mod uart; 11pub mod uart;
11pub mod ws2812; 12pub mod ws2812;
diff --git a/embassy-rp/src/pio_programs/onewire.rs b/embassy-rp/src/pio_programs/onewire.rs
index 287ddab41..980d0fe5f 100644
--- a/embassy-rp/src/pio_programs/onewire.rs
+++ b/embassy-rp/src/pio_programs/onewire.rs
@@ -52,7 +52,8 @@ impl<'a, PIO: Instance> PioOneWireProgram<'a, PIO> {
52 52
53 ; The low pulse was already done, we only need to delay and poll the bit in case we are reading 53 ; The low pulse was already done, we only need to delay and poll the bit in case we are reading
54 write_1: 54 write_1:
55 nop side 0 [( 6 / CLK) - 1] ; Delay before sampling the input pin 55 jmp y--, continue_1 side 0 [( 6 / CLK) - 1] ; Delay before sampling input. Always decrement y
56 continue_1:
56 in pins, 1 side 0 [(48 / CLK) - 1] ; This writes the state of the pin into the ISR 57 in pins, 1 side 0 [(48 / CLK) - 1] ; This writes the state of the pin into the ISR
57 ; Fallthrough 58 ; Fallthrough
58 59
@@ -61,9 +62,24 @@ impl<'a, PIO: Instance> PioOneWireProgram<'a, PIO> {
61 .wrap_target 62 .wrap_target
62 out x, 1 side 0 [(12 / CLK) - 1] ; Stalls if no data available in TX FIFO and OSR 63 out x, 1 side 0 [(12 / CLK) - 1] ; Stalls if no data available in TX FIFO and OSR
63 jmp x--, write_1 side 1 [( 6 / CLK) - 1] ; Do the always low part of a bit, jump to write_1 if we want to write a 1 bit 64 jmp x--, write_1 side 1 [( 6 / CLK) - 1] ; Do the always low part of a bit, jump to write_1 if we want to write a 1 bit
64 in null, 1 side 1 [(54 / CLK) - 1] ; Do the remainder of the low part of a 0 bit 65 jmp y--, continue_0 side 1 [(48 / CLK) - 1] ; Do the remainder of the low part of a 0 bit
65 ; This writes 0 into the ISR so that the shift count stays in sync 66 jmp pullup side 1 [( 6 / CLK) - 1] ; Remain low while jumping
67 continue_0:
68 in null, 1 side 1 [( 6 / CLK) - 1] ; This writes 0 into the ISR so that the shift count stays in sync
66 .wrap 69 .wrap
70
71 ; Assume that strong pullup commands always have MSB (the last bit) = 0,
72 ; since the rising edge can be used to start the operation.
73 ; That's the case for DS18B20 (44h and 48h).
74 pullup:
75 set pins, 1 side 1[( 6 / CLK) - 1] ; Drive pin high output immediately.
76 ; Strong pullup must be within 10us of rise.
77 in null, 1 side 1[( 6 / CLK) - 1] ; Keep ISR in sync. Must occur after the y--.
78 out null, 8 side 1[( 6 / CLK) - 1] ; Wait for write_bytes_pullup() delay to complete.
79 ; The delay is hundreds of ms, so done externally.
80 set pins, 0 side 0[( 6 / CLK) - 1] ; Back to open drain, pin low when driven
81 in null, 8 side 1[( 6 / CLK) - 1] ; Inform write_bytes_pullup() it's ready
82 jmp next_bit side 0[( 6 / CLK) - 1] ; Continue
67 "# 83 "#
68 ); 84 );
69 85
@@ -98,6 +114,7 @@ impl<'d, PIO: Instance, const SM: usize> PioOneWire<'d, PIO, SM> {
98 let mut cfg = Config::default(); 114 let mut cfg = Config::default();
99 cfg.use_program(&program.prg, &[&pin]); 115 cfg.use_program(&program.prg, &[&pin]);
100 cfg.set_in_pins(&[&pin]); 116 cfg.set_in_pins(&[&pin]);
117 cfg.set_set_pins(&[&pin]);
101 118
102 let shift_cfg = ShiftConfig { 119 let shift_cfg = ShiftConfig {
103 auto_fill: true, 120 auto_fill: true,
@@ -146,6 +163,19 @@ impl<'d, PIO: Instance, const SM: usize> PioOneWire<'d, PIO, SM> {
146 163
147 /// Write bytes to the onewire bus 164 /// Write bytes to the onewire bus
148 pub async fn write_bytes(&mut self, data: &[u8]) { 165 pub async fn write_bytes(&mut self, data: &[u8]) {
166 unsafe { self.sm.set_y(u32::MAX as u32) };
167 let (rx, tx) = self.sm.rx_tx();
168 for b in data {
169 tx.wait_push(*b as u32).await;
170
171 // Empty the buffer that is being filled with every write
172 let _ = rx.wait_pull().await;
173 }
174 }
175
176 /// Write bytes to the onewire bus, then apply a strong pullup
177 pub async fn write_bytes_pullup(&mut self, data: &[u8], pullup_time: embassy_time::Duration) {
178 unsafe { self.sm.set_y(data.len() as u32 * 8 - 1) };
149 let (rx, tx) = self.sm.rx_tx(); 179 let (rx, tx) = self.sm.rx_tx();
150 for b in data { 180 for b in data {
151 tx.wait_push(*b as u32).await; 181 tx.wait_push(*b as u32).await;
@@ -153,10 +183,19 @@ impl<'d, PIO: Instance, const SM: usize> PioOneWire<'d, PIO, SM> {
153 // Empty the buffer that is being filled with every write 183 // Empty the buffer that is being filled with every write
154 let _ = rx.wait_pull().await; 184 let _ = rx.wait_pull().await;
155 } 185 }
186
187 // Perform the delay, usually hundreds of ms.
188 embassy_time::Timer::after(pullup_time).await;
189
190 // Signal that delay has completed
191 tx.wait_push(0 as u32).await;
192 // Wait until it's back at 0 low, open drain
193 let _ = rx.wait_pull().await;
156 } 194 }
157 195
158 /// Read bytes from the onewire bus 196 /// Read bytes from the onewire bus
159 pub async fn read_bytes(&mut self, data: &mut [u8]) { 197 pub async fn read_bytes(&mut self, data: &mut [u8]) {
198 unsafe { self.sm.set_y(u32::MAX as u32) };
160 let (rx, tx) = self.sm.rx_tx(); 199 let (rx, tx) = self.sm.rx_tx();
161 for b in data { 200 for b in data {
162 // Write all 1's so that we can read what the device responds 201 // Write all 1's so that we can read what the device responds
diff --git a/embassy-rp/src/pio_programs/spi.rs b/embassy-rp/src/pio_programs/spi.rs
new file mode 100644
index 000000000..b10fc6628
--- /dev/null
+++ b/embassy-rp/src/pio_programs/spi.rs
@@ -0,0 +1,474 @@
1//! PIO backed SPi drivers
2
3use core::marker::PhantomData;
4
5use embassy_futures::join::join;
6use embassy_hal_internal::Peri;
7use embedded_hal_02::spi::{Phase, Polarity};
8use fixed::traits::ToFixed;
9use fixed::types::extra::U8;
10
11use crate::clocks::clk_sys_freq;
12use crate::dma::{AnyChannel, Channel};
13use crate::gpio::Level;
14use crate::pio::{Common, Direction, Instance, LoadedProgram, Pin, PioPin, ShiftDirection, StateMachine};
15use crate::spi::{Async, Blocking, Config, Mode};
16
17/// This struct represents an SPI program loaded into pio instruction memory.
18struct PioSpiProgram<'d, PIO: Instance> {
19 prg: LoadedProgram<'d, PIO>,
20 phase: Phase,
21}
22
23impl<'d, PIO: Instance> PioSpiProgram<'d, PIO> {
24 /// Load the spi program into the given pio
25 pub fn new(common: &mut Common<'d, PIO>, phase: Phase) -> Self {
26 // These PIO programs are taken straight from the datasheet (3.6.1 in
27 // RP2040 datasheet, 11.6.1 in RP2350 datasheet)
28
29 // Pin assignments:
30 // - SCK is side-set pin 0
31 // - MOSI is OUT pin 0
32 // - MISO is IN pin 0
33 //
34 // Auto-push and auto-pull must be enabled, and the serial frame size is set by
35 // configuring the push/pull threshold. Shift left/right is fine, but you must
36 // justify the data yourself. This is done most conveniently for frame sizes of
37 // 8 or 16 bits by using the narrow store replication and narrow load byte
38 // picking behavior of RP2040's IO fabric.
39
40 let prg = match phase {
41 Phase::CaptureOnFirstTransition => {
42 let prg = pio::pio_asm!(
43 r#"
44 .side_set 1
45
46 ; Clock phase = 0: data is captured on the leading edge of each SCK pulse, and
47 ; transitions on the trailing edge, or some time before the first leading edge.
48
49 out pins, 1 side 0 [1] ; Stall here on empty (sideset proceeds even if
50 in pins, 1 side 1 [1] ; instruction stalls, so we stall with SCK low)
51 "#
52 );
53
54 common.load_program(&prg.program)
55 }
56 Phase::CaptureOnSecondTransition => {
57 let prg = pio::pio_asm!(
58 r#"
59 .side_set 1
60
61 ; Clock phase = 1: data transitions on the leading edge of each SCK pulse, and
62 ; is captured on the trailing edge.
63
64 out x, 1 side 0 ; Stall here on empty (keep SCK de-asserted)
65 mov pins, x side 1 [1] ; Output data, assert SCK (mov pins uses OUT mapping)
66 in pins, 1 side 0 ; Input data, de-assert SCK
67 "#
68 );
69
70 common.load_program(&prg.program)
71 }
72 };
73
74 Self { prg, phase }
75 }
76}
77
78/// PIO SPI errors.
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80#[cfg_attr(feature = "defmt", derive(defmt::Format))]
81#[non_exhaustive]
82pub enum Error {
83 // No errors for now
84}
85
86/// PIO based Spi driver.
87/// Unlike other PIO programs, the PIO SPI driver owns and holds a reference to
88/// the PIO memory it uses. This is so that it can be reconfigured at runtime if
89/// desired.
90pub struct Spi<'d, PIO: Instance, const SM: usize, M: Mode> {
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>,
95 tx_dma: Option<Peri<'d, AnyChannel>>,
96 rx_dma: Option<Peri<'d, AnyChannel>>,
97 phantom: PhantomData<M>,
98}
99
100impl<'d, PIO: Instance, const SM: usize, M: Mode> Spi<'d, PIO, SM, M> {
101 #[allow(clippy::too_many_arguments)]
102 fn new_inner(
103 pio: &mut Common<'d, PIO>,
104 mut sm: StateMachine<'d, PIO, SM>,
105 clk_pin: Peri<'d, impl PioPin>,
106 mosi_pin: Peri<'d, impl PioPin>,
107 miso_pin: Peri<'d, impl PioPin>,
108 tx_dma: Option<Peri<'d, AnyChannel>>,
109 rx_dma: Option<Peri<'d, AnyChannel>>,
110 config: Config,
111 ) -> Self {
112 let program = PioSpiProgram::new(pio, config.phase);
113
114 let mut clk_pin = pio.make_pio_pin(clk_pin);
115 let mosi_pin = pio.make_pio_pin(mosi_pin);
116 let miso_pin = pio.make_pio_pin(miso_pin);
117
118 if let Polarity::IdleHigh = config.polarity {
119 clk_pin.set_output_inversion(true);
120 } else {
121 clk_pin.set_output_inversion(false);
122 }
123
124 let mut cfg = crate::pio::Config::default();
125
126 cfg.use_program(&program.prg, &[&clk_pin]);
127 cfg.set_out_pins(&[&mosi_pin]);
128 cfg.set_in_pins(&[&miso_pin]);
129
130 cfg.shift_in.auto_fill = true;
131 cfg.shift_in.direction = ShiftDirection::Left;
132 cfg.shift_in.threshold = 8;
133
134 cfg.shift_out.auto_fill = true;
135 cfg.shift_out.direction = ShiftDirection::Left;
136 cfg.shift_out.threshold = 8;
137
138 cfg.clock_divider = calculate_clock_divider(config.frequency);
139
140 sm.set_config(&cfg);
141
142 sm.set_pins(Level::Low, &[&clk_pin, &mosi_pin]);
143 sm.set_pin_dirs(Direction::Out, &[&clk_pin, &mosi_pin]);
144 sm.set_pin_dirs(Direction::In, &[&miso_pin]);
145
146 sm.set_enable(true);
147
148 Self {
149 sm,
150 program: Some(program),
151 cfg,
152 clk_pin,
153 tx_dma,
154 rx_dma,
155 phantom: PhantomData,
156 }
157 }
158
159 fn blocking_read_u8(&mut self) -> Result<u8, Error> {
160 while self.sm.rx().empty() {}
161 let value = self.sm.rx().pull() as u8;
162
163 Ok(value)
164 }
165
166 fn blocking_write_u8(&mut self, v: u8) -> Result<(), Error> {
167 let value = u32::from_be_bytes([v, 0, 0, 0]);
168
169 while !self.sm.tx().try_push(value) {}
170
171 // need to clear here for flush to work correctly
172 self.sm.tx().stalled();
173
174 Ok(())
175 }
176
177 /// Read data from SPI blocking execution until done.
178 pub fn blocking_read(&mut self, data: &mut [u8]) -> Result<(), Error> {
179 for v in data {
180 self.blocking_write_u8(0)?;
181 *v = self.blocking_read_u8()?;
182 }
183 self.flush()?;
184 Ok(())
185 }
186
187 /// Write data to SPI blocking execution until done.
188 pub fn blocking_write(&mut self, data: &[u8]) -> Result<(), Error> {
189 for v in data {
190 self.blocking_write_u8(*v)?;
191 let _ = self.blocking_read_u8()?;
192 }
193 self.flush()?;
194 Ok(())
195 }
196
197 /// Transfer data to SPI blocking execution until done.
198 pub fn blocking_transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> {
199 let len = read.len().max(write.len());
200 for i in 0..len {
201 let wb = write.get(i).copied().unwrap_or(0);
202 self.blocking_write_u8(wb)?;
203
204 let rb = self.blocking_read_u8()?;
205 if let Some(r) = read.get_mut(i) {
206 *r = rb;
207 }
208 }
209 self.flush()?;
210 Ok(())
211 }
212
213 /// Transfer data in place to SPI blocking execution until done.
214 pub fn blocking_transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Error> {
215 for v in data {
216 self.blocking_write_u8(*v)?;
217 *v = self.blocking_read_u8()?;
218 }
219 self.flush()?;
220 Ok(())
221 }
222
223 /// Block execution until SPI is done.
224 pub fn flush(&mut self) -> Result<(), Error> {
225 // Wait for all words in the FIFO to have been pulled by the SM
226 while !self.sm.tx().empty() {}
227
228 // Wait for last value to be written out to the wire
229 while !self.sm.tx().stalled() {}
230
231 Ok(())
232 }
233
234 /// Set SPI frequency.
235 pub fn set_frequency(&mut self, freq: u32) {
236 self.sm.set_enable(false);
237
238 let divider = calculate_clock_divider(freq);
239
240 // save into the config for later but dont use sm.set_config() since
241 // that operation is relatively more expensive than just setting the
242 // clock divider
243 self.cfg.clock_divider = divider;
244 self.sm.set_clock_divider(divider);
245
246 self.sm.set_enable(true);
247 }
248
249 /// Set SPI config.
250 ///
251 /// This operation will panic if the PIO program needs to be reloaded and
252 /// there is insufficient room. This is unlikely since the programs for each
253 /// phase only differ in size by a single instruction.
254 pub fn set_config(&mut self, pio: &mut Common<'d, PIO>, config: &Config) {
255 self.sm.set_enable(false);
256
257 self.cfg.clock_divider = calculate_clock_divider(config.frequency);
258
259 if let Polarity::IdleHigh = config.polarity {
260 self.clk_pin.set_output_inversion(true);
261 } else {
262 self.clk_pin.set_output_inversion(false);
263 }
264
265 if self.program.as_ref().unwrap().phase != config.phase {
266 let old_program = self.program.take().unwrap();
267
268 // SAFETY: the state machine is disabled while this happens
269 unsafe { pio.free_instr(old_program.prg.used_memory) };
270
271 let new_program = PioSpiProgram::new(pio, config.phase);
272
273 self.cfg.use_program(&new_program.prg, &[&self.clk_pin]);
274 self.program = Some(new_program);
275 }
276
277 self.sm.set_config(&self.cfg);
278 self.sm.restart();
279
280 self.sm.set_enable(true);
281 }
282}
283
284fn calculate_clock_divider(frequency_hz: u32) -> fixed::FixedU32<U8> {
285 // we multiply by 4 since each clock period is equal to 4 instructions
286
287 let sys_freq = clk_sys_freq().to_fixed::<fixed::FixedU64<U8>>();
288 let target_freq = (frequency_hz * 4).to_fixed::<fixed::FixedU64<U8>>();
289 (sys_freq / target_freq).to_fixed()
290}
291
292impl<'d, PIO: Instance, const SM: usize> Spi<'d, PIO, SM, Blocking> {
293 /// Create an SPI driver in blocking mode.
294 pub fn new_blocking(
295 pio: &mut Common<'d, PIO>,
296 sm: StateMachine<'d, PIO, SM>,
297 clk: Peri<'d, impl PioPin>,
298 mosi: Peri<'d, impl PioPin>,
299 miso: Peri<'d, impl PioPin>,
300 config: Config,
301 ) -> Self {
302 Self::new_inner(pio, sm, clk, mosi, miso, None, None, config)
303 }
304}
305
306impl<'d, PIO: Instance, const SM: usize> Spi<'d, PIO, SM, Async> {
307 /// Create an SPI driver in async mode supporting DMA operations.
308 #[allow(clippy::too_many_arguments)]
309 pub fn new(
310 pio: &mut Common<'d, PIO>,
311 sm: StateMachine<'d, PIO, SM>,
312 clk: Peri<'d, impl PioPin>,
313 mosi: Peri<'d, impl PioPin>,
314 miso: Peri<'d, impl PioPin>,
315 tx_dma: Peri<'d, impl Channel>,
316 rx_dma: Peri<'d, impl Channel>,
317 config: Config,
318 ) -> Self {
319 Self::new_inner(
320 pio,
321 sm,
322 clk,
323 mosi,
324 miso,
325 Some(tx_dma.into()),
326 Some(rx_dma.into()),
327 config,
328 )
329 }
330
331 /// Read data from SPI using DMA.
332 pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
333 let (rx, tx) = self.sm.rx_tx();
334
335 let len = buffer.len();
336
337 let rx_ch = self.rx_dma.as_mut().unwrap().reborrow();
338 let rx_transfer = rx.dma_pull(rx_ch, buffer, false);
339
340 let tx_ch = self.tx_dma.as_mut().unwrap().reborrow();
341 let tx_transfer = tx.dma_push_repeated::<_, u8>(tx_ch, len);
342
343 join(tx_transfer, rx_transfer).await;
344
345 Ok(())
346 }
347
348 /// Write data to SPI using DMA.
349 pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> {
350 let (rx, tx) = self.sm.rx_tx();
351
352 let rx_ch = self.rx_dma.as_mut().unwrap().reborrow();
353 let rx_transfer = rx.dma_pull_repeated::<_, u8>(rx_ch, buffer.len());
354
355 let tx_ch = self.tx_dma.as_mut().unwrap().reborrow();
356 let tx_transfer = tx.dma_push(tx_ch, buffer, false);
357
358 join(tx_transfer, rx_transfer).await;
359
360 Ok(())
361 }
362
363 /// Transfer data to SPI using DMA.
364 pub async fn transfer(&mut self, rx_buffer: &mut [u8], tx_buffer: &[u8]) -> Result<(), Error> {
365 self.transfer_inner(rx_buffer, tx_buffer).await
366 }
367
368 /// Transfer data in place to SPI using DMA.
369 pub async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Error> {
370 self.transfer_inner(words, words).await
371 }
372
373 async fn transfer_inner(&mut self, rx_buffer: *mut [u8], tx_buffer: *const [u8]) -> Result<(), Error> {
374 let (rx, tx) = self.sm.rx_tx();
375
376 let mut rx_ch = self.rx_dma.as_mut().unwrap().reborrow();
377 let rx_transfer = async {
378 rx.dma_pull(rx_ch.reborrow(), unsafe { &mut *rx_buffer }, false).await;
379
380 if tx_buffer.len() > rx_buffer.len() {
381 let read_bytes_len = tx_buffer.len() - rx_buffer.len();
382
383 rx.dma_pull_repeated::<_, u8>(rx_ch, read_bytes_len).await;
384 }
385 };
386
387 let mut tx_ch = self.tx_dma.as_mut().unwrap().reborrow();
388 let tx_transfer = async {
389 tx.dma_push(tx_ch.reborrow(), unsafe { &*tx_buffer }, false).await;
390
391 if rx_buffer.len() > tx_buffer.len() {
392 let write_bytes_len = rx_buffer.len() - tx_buffer.len();
393
394 tx.dma_push_repeated::<_, u8>(tx_ch, write_bytes_len).await;
395 }
396 };
397
398 join(tx_transfer, rx_transfer).await;
399
400 Ok(())
401 }
402}
403
404// ====================
405
406impl<'d, PIO: Instance, const SM: usize, M: Mode> embedded_hal_02::blocking::spi::Transfer<u8> for Spi<'d, PIO, SM, M> {
407 type Error = Error;
408 fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> {
409 self.blocking_transfer_in_place(words)?;
410 Ok(words)
411 }
412}
413
414impl<'d, PIO: Instance, const SM: usize, M: Mode> embedded_hal_02::blocking::spi::Write<u8> for Spi<'d, PIO, SM, M> {
415 type Error = Error;
416
417 fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
418 self.blocking_write(words)
419 }
420}
421
422impl embedded_hal_1::spi::Error for Error {
423 fn kind(&self) -> embedded_hal_1::spi::ErrorKind {
424 match *self {}
425 }
426}
427
428impl<'d, PIO: Instance, const SM: usize, M: Mode> embedded_hal_1::spi::ErrorType for Spi<'d, PIO, SM, M> {
429 type Error = Error;
430}
431
432impl<'d, PIO: Instance, const SM: usize, M: Mode> embedded_hal_1::spi::SpiBus<u8> for Spi<'d, PIO, SM, M> {
433 fn flush(&mut self) -> Result<(), Self::Error> {
434 Ok(())
435 }
436
437 fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
438 self.blocking_transfer(words, &[])
439 }
440
441 fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
442 self.blocking_write(words)
443 }
444
445 fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
446 self.blocking_transfer(read, write)
447 }
448
449 fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
450 self.blocking_transfer_in_place(words)
451 }
452}
453
454impl<'d, PIO: Instance, const SM: usize> embedded_hal_async::spi::SpiBus<u8> for Spi<'d, PIO, SM, Async> {
455 async fn flush(&mut self) -> Result<(), Self::Error> {
456 Ok(())
457 }
458
459 async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
460 self.write(words).await
461 }
462
463 async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
464 self.read(words).await
465 }
466
467 async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
468 self.transfer(read, write).await
469 }
470
471 async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
472 self.transfer_in_place(words).await
473 }
474}
diff --git a/embassy-rp/src/pio_programs/ws2812.rs b/embassy-rp/src/pio_programs/ws2812.rs
index 578937e11..37dd1c4e0 100644
--- a/embassy-rp/src/pio_programs/ws2812.rs
+++ b/embassy-rp/src/pio_programs/ws2812.rs
@@ -2,7 +2,7 @@
2 2
3use embassy_time::Timer; 3use embassy_time::Timer;
4use fixed::types::U24F8; 4use fixed::types::U24F8;
5use smart_leds::RGB8; 5use smart_leds::{RGB8, RGBW};
6 6
7use crate::clocks::clk_sys_freq; 7use crate::clocks::clk_sys_freq;
8use crate::dma::{AnyChannel, Channel}; 8use crate::dma::{AnyChannel, Channel};
@@ -50,7 +50,7 @@ impl<'a, PIO: Instance> PioWs2812Program<'a, PIO> {
50 } 50 }
51} 51}
52 52
53/// Pio backed ws2812 driver 53/// Pio backed RGB ws2812 driver
54/// Const N is the number of ws2812 leds attached to this pin 54/// Const N is the number of ws2812 leds attached to this pin
55pub struct PioWs2812<'d, P: Instance, const S: usize, const N: usize> { 55pub struct PioWs2812<'d, P: Instance, const S: usize, const N: usize> {
56 dma: Peri<'d, AnyChannel>, 56 dma: Peri<'d, AnyChannel>,
@@ -111,3 +111,69 @@ impl<'d, P: Instance, const S: usize, const N: usize> PioWs2812<'d, P, S, N> {
111 Timer::after_micros(55).await; 111 Timer::after_micros(55).await;
112 } 112 }
113} 113}
114
115/// Pio backed RGBW ws2812 driver
116/// This version is intended for ws2812 leds with 4 addressable lights
117/// Const N is the number of ws2812 leds attached to this pin
118pub struct RgbwPioWs2812<'d, P: Instance, const S: usize, const N: usize> {
119 dma: Peri<'d, AnyChannel>,
120 sm: StateMachine<'d, P, S>,
121}
122
123impl<'d, P: Instance, const S: usize, const N: usize> RgbwPioWs2812<'d, P, S, N> {
124 /// Configure a pio state machine to use the loaded ws2812 program.
125 pub fn new(
126 pio: &mut Common<'d, P>,
127 mut sm: StateMachine<'d, P, S>,
128 dma: Peri<'d, impl Channel>,
129 pin: Peri<'d, impl PioPin>,
130 program: &PioWs2812Program<'d, P>,
131 ) -> Self {
132 // Setup sm0
133 let mut cfg = Config::default();
134
135 // Pin config
136 let out_pin = pio.make_pio_pin(pin);
137 cfg.set_out_pins(&[&out_pin]);
138 cfg.set_set_pins(&[&out_pin]);
139
140 cfg.use_program(&program.prg, &[&out_pin]);
141
142 // Clock config, measured in kHz to avoid overflows
143 let clock_freq = U24F8::from_num(clk_sys_freq() / 1000);
144 let ws2812_freq = U24F8::from_num(800);
145 let bit_freq = ws2812_freq * CYCLES_PER_BIT;
146 cfg.clock_divider = clock_freq / bit_freq;
147
148 // FIFO config
149 cfg.fifo_join = FifoJoin::TxOnly;
150 cfg.shift_out = ShiftConfig {
151 auto_fill: true,
152 threshold: 32,
153 direction: ShiftDirection::Left,
154 };
155
156 sm.set_config(&cfg);
157 sm.set_enable(true);
158
159 Self { dma: dma.into(), sm }
160 }
161
162 /// Write a buffer of [smart_leds::RGBW] to the ws2812 string
163 pub async fn write(&mut self, colors: &[RGBW<u8>; N]) {
164 // Precompute the word bytes from the colors
165 let mut words = [0u32; N];
166 for i in 0..N {
167 let word = (u32::from(colors[i].g) << 24)
168 | (u32::from(colors[i].r) << 16)
169 | (u32::from(colors[i].b) << 8)
170 | u32::from(colors[i].a.0);
171 words[i] = word;
172 }
173
174 // DMA transfer
175 self.sm.tx().dma_push(self.dma.reborrow(), &words, false).await;
176
177 Timer::after_micros(55).await;
178 }
179}