1#![no_std]
2
3use fugit::HertzU32;
4use rp2040_hal::{
5 gpio::{FunctionNull, Pin, PinId, PullDown, ValidFunction},
6 pio::{
7 InstallError, PIOExt, Rx, StateMachine, StateMachineIndex, Stopped, Tx, UninitStateMachine,
8 ValidStateMachine, PIO,
9 },
10};
11
12const PIO_CLOCK_HZ: u32 = 6_144_000;
16
17#[derive(Debug)]
18pub enum I2SError {
19 PioInstallationError(InstallError),
20}
21
22pub enum PioClockDivider {
23 Exact { integer: u16, fraction: u8 },
24 FromSystemClock(HertzU32),
25}
26
27impl PioClockDivider {
28 fn pio_divider(&self) -> (u16, u8) {
29 match self {
30 Self::Exact { integer, fraction } => (*integer, *fraction),
31 Self::FromSystemClock(system_clock_hz) => {
32 let hertz = system_clock_hz.to_Hz();
33 let (fraction, integer) = libm::modf(hertz as f64 / PIO_CLOCK_HZ as f64);
34
35 (integer as u16, (fraction * 256.0) as u8)
36 },
37 }
38 }
39}
40
41pub trait SampleReader {
42 fn read(&mut self) -> Option<u32>;
43}
44
45impl<SM: ValidStateMachine> SampleReader for Rx<SM> {
46 fn read(&mut self) -> Option<u32> {
47 self.read()
48 }
49}
50
51pub struct I2SOutput<P: rp2040_hal::pio::PIOExt, SM: rp2040_hal::pio::StateMachineIndex> {
52 state_machine: StateMachine<(P, SM), Stopped>,
53 fifo_rx: Rx<(P, SM)>,
54 fifo_tx: Tx<(P, SM)>,
55}
56
57impl<P: PIOExt, SM: StateMachineIndex> I2SOutput<P, SM> {
58 pub fn new<DataPin, BitClockPin, LeftRightClockPin>(
62 pio: &mut PIO<P>,
63 clock_divider: PioClockDivider,
64 state_machine: UninitStateMachine<(P, SM)>,
65 data_out_pin: Pin<DataPin, FunctionNull, PullDown>,
66 bit_clock_pin: Pin<BitClockPin, FunctionNull, PullDown>,
67 left_right_clock_pin: Pin<LeftRightClockPin, FunctionNull, PullDown>,
68 ) -> Result<Self, I2SError>
69 where
70 DataPin: PinId + ValidFunction<P::PinFunction>,
71 BitClockPin: PinId + ValidFunction<P::PinFunction>,
72 LeftRightClockPin: PinId + ValidFunction<P::PinFunction>,
73 {
74 let data_out_pin: Pin<_, P::PinFunction, _> = data_out_pin.into_function();
75 let bit_clock_pin: Pin<_, P::PinFunction, _> = bit_clock_pin.into_function();
76 let left_right_clock_pin: Pin<_, P::PinFunction, _> = left_right_clock_pin.into_function();
77
78 let data_pin_id = data_out_pin.id().num;
79 let bit_clock_pin_id = bit_clock_pin.id().num;
80 let left_right_clock_pin_id = left_right_clock_pin.id().num;
81
82 assert_eq!(
83 left_right_clock_pin_id - bit_clock_pin_id,
84 1,
85 "The word clock pin must consecutively follow the bit clock pin"
86 );
87
88 #[rustfmt::skip]
89 let dac_pio_program = pio_proc::pio_asm!(
90 ".side_set 2",
91 " set x, 30 side 0b01", "left_data:",
93 " out pins, 1 side 0b00",
94 " jmp x-- left_data side 0b01",
95 " out pins 1 side 0b10",
96 " set x, 30 side 0b11",
97 "right_data:",
98 " out pins 1 side 0b10",
99 " jmp x-- right_data side 0b11",
100 " out pins 1 side 0b00",
101 );
102
103 let installed =
104 pio.install(&dac_pio_program.program).map_err(I2SError::PioInstallationError)?;
105
106 let (divider_int, divider_fraction) = clock_divider.pio_divider();
107
108 let (mut dac_sm, fifo_rx, fifo_tx) =
109 rp2040_hal::pio::PIOBuilder::from_installed_program(installed)
110 .out_pins(data_pin_id, 1)
111 .side_set_pin_base(bit_clock_pin_id)
112 .out_shift_direction(rp2040_hal::pio::ShiftDirection::Left)
113 .clock_divisor_fixed_point(divider_int, divider_fraction)
114 .buffers(rp2040_hal::pio::Buffers::OnlyTx)
115 .autopull(true)
116 .pull_threshold(32)
117 .build(state_machine);
118
119 dac_sm.set_pindirs([
120 (data_pin_id, rp2040_hal::pio::PinDir::Output),
121 (bit_clock_pin_id, rp2040_hal::pio::PinDir::Output),
122 (left_right_clock_pin_id, rp2040_hal::pio::PinDir::Output),
123 ]);
124
125 Ok(Self { state_machine: dac_sm, fifo_rx, fifo_tx })
126 }
127
128 #[allow(clippy::type_complexity)]
129 pub fn split(self) -> (StateMachine<(P, SM), Stopped>, Rx<(P, SM)>, Tx<(P, SM)>) {
130 (self.state_machine, self.fifo_rx, self.fifo_tx)
131 }
132}
133
134pub struct I2SInput<P: rp2040_hal::pio::PIOExt, SM: rp2040_hal::pio::StateMachineIndex> {
135 state_machine: StateMachine<(P, SM), Stopped>,
136 fifo_rx: Rx<(P, SM)>,
137 fifo_tx: Tx<(P, SM)>,
138}
139
140impl<P: PIOExt, SM: StateMachineIndex> I2SInput<P, SM> {
141 pub fn new<DataPin, BitClockPin, LeftRightClockPin>(
145 pio: &mut PIO<P>,
146 clock_divider: PioClockDivider,
147 state_machine: UninitStateMachine<(P, SM)>,
148 data_in_pin: Pin<DataPin, FunctionNull, PullDown>,
149 bit_clock_pin: Pin<BitClockPin, FunctionNull, PullDown>,
150 left_right_clock_pin: Pin<LeftRightClockPin, FunctionNull, PullDown>,
151 ) -> Result<Self, I2SError>
152 where
153 DataPin: PinId + ValidFunction<P::PinFunction>,
154 BitClockPin: PinId + ValidFunction<P::PinFunction>,
155 LeftRightClockPin: PinId + ValidFunction<P::PinFunction>,
156 {
157 let data_in_pin: Pin<_, P::PinFunction, _> = data_in_pin.into_function();
158 let bit_clock_pin: Pin<_, P::PinFunction, _> = bit_clock_pin.into_function();
159 let left_right_clock_pin: Pin<_, P::PinFunction, _> = left_right_clock_pin.into_function();
160
161 let data_pin_id = data_in_pin.id().num;
162 let bit_clock_pin_id = bit_clock_pin.id().num;
163 let left_right_clock_pin_id = left_right_clock_pin.id().num;
164
165 assert_eq!(
166 left_right_clock_pin_id - bit_clock_pin_id,
167 1,
168 "The word clock pin must consecutively follow the bit clock pin"
169 );
170
171 #[rustfmt::skip]
172 let mic_pio_program = pio_proc::pio_asm!(
173 ".side_set 2",
174 " set x, 30 side 0b00", "left_data:",
176 " in pins, 1 side 0b01",
177 " jmp x-- left_data side 0b00",
178 " in pins, 1 side 0b11",
179 " set x, 30 side 0b10",
180 "right_data:",
181 " in pins, 1 side 0b11",
182 " jmp x-- right_data side 0b10",
183 " in pins, 1 side 0b01",
184 );
185
186 let installed = pio.install(&mic_pio_program.program).unwrap();
187
188 let (divider_int, divider_fraction) = clock_divider.pio_divider();
189
190 let (mut mic_sm, fifo_rx, fifo_tx) =
191 rp2040_hal::pio::PIOBuilder::from_installed_program(installed)
192 .in_pin_base(data_pin_id)
193 .side_set_pin_base(bit_clock_pin_id)
194 .in_shift_direction(rp2040_hal::pio::ShiftDirection::Left)
195 .clock_divisor_fixed_point(divider_int, divider_fraction)
196 .buffers(rp2040_hal::pio::Buffers::OnlyRx)
197 .autopush(true)
198 .push_threshold(32)
199 .build(state_machine);
200
201 mic_sm.set_pindirs([
202 (data_pin_id, rp2040_hal::pio::PinDir::Input),
203 (bit_clock_pin_id, rp2040_hal::pio::PinDir::Output),
204 (left_right_clock_pin_id, rp2040_hal::pio::PinDir::Output),
205 ]);
206
207 Ok(Self { state_machine: mic_sm, fifo_rx, fifo_tx })
208 }
209
210 pub fn new_data_only<DataPin>(
217 pio: &mut PIO<P>,
218 clock_divider: PioClockDivider,
219 state_machine: UninitStateMachine<(P, SM)>,
220 data_in_pin: Pin<DataPin, FunctionNull, PullDown>,
221 ) -> Result<Self, I2SError>
222 where
223 DataPin: PinId + ValidFunction<P::PinFunction>,
224 {
225 let data_in_pin: Pin<_, P::PinFunction, _> = data_in_pin.into_function();
226 let data_pin_id = data_in_pin.id().num;
227
228 #[rustfmt::skip]
229 let mic_pio_program = pio_proc::pio_asm!(
230 " set x, 30",
231 "left_data:",
232 " in pins, 1",
233 " jmp x-- left_data",
234 " in pins, 1",
235 " set x, 30",
236 "right_data:",
237 " in pins, 1",
238 " jmp x-- right_data",
239 " in pins, 1",
240 );
241
242 let installed = pio.install(&mic_pio_program.program).unwrap();
243
244 let (divider_int, divider_fraction) = clock_divider.pio_divider();
245
246 let (mut mic_sm, fifo_rx, fifo_tx) =
247 rp2040_hal::pio::PIOBuilder::from_installed_program(installed)
248 .in_pin_base(data_pin_id)
249 .in_shift_direction(rp2040_hal::pio::ShiftDirection::Left)
250 .clock_divisor_fixed_point(divider_int, divider_fraction)
251 .buffers(rp2040_hal::pio::Buffers::OnlyRx)
252 .autopush(true)
253 .push_threshold(32)
254 .build(state_machine);
255
256 mic_sm.set_pindirs([(data_pin_id, rp2040_hal::pio::PinDir::Input)]);
257
258 Ok(Self { state_machine: mic_sm, fifo_rx, fifo_tx })
259 }
260
261 #[allow(clippy::type_complexity)]
262 pub fn split(self) -> (StateMachine<(P, SM), Stopped>, Rx<(P, SM)>, Tx<(P, SM)>) {
263 (self.state_machine, self.fifo_rx, self.fifo_tx)
264 }
265}