diff options
| -rw-r--r-- | embassy-rp/CHANGELOG.md | 3 | ||||
| -rw-r--r-- | embassy-rp/src/pio_programs/i2s.rs | 96 | ||||
| -rw-r--r-- | examples/rp235x/src/bin/pio_i2s_rx.rs | 81 |
3 files changed, 176 insertions, 4 deletions
diff --git a/embassy-rp/CHANGELOG.md b/embassy-rp/CHANGELOG.md index ebdc3e1c8..b50d41dd1 100644 --- a/embassy-rp/CHANGELOG.md +++ b/embassy-rp/CHANGELOG.md | |||
| @@ -8,6 +8,9 @@ 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 | - Add PIO SPI | ||
| 12 | - Add PIO I2S input | ||
| 13 | |||
| 11 | ## 0.8.0 - 2025-08-26 | 14 | ## 0.8.0 - 2025-08-26 |
| 12 | 15 | ||
| 13 | ## 0.7.1 - 2025-08-26 | 16 | ## 0.7.1 - 2025-08-26 |
diff --git a/embassy-rp/src/pio_programs/i2s.rs b/embassy-rp/src/pio_programs/i2s.rs index 7ceed3fa6..2382a3f9f 100644 --- a/embassy-rp/src/pio_programs/i2s.rs +++ b/embassy-rp/src/pio_programs/i2s.rs | |||
| @@ -1,13 +1,101 @@ | |||
| 1 | //! Pio backed I2s output | 1 | //! Pio backed I2s output and output drivers |
| 2 | 2 | ||
| 3 | use fixed::traits::ToFixed; | 3 | use fixed::traits::ToFixed; |
| 4 | 4 | ||
| 5 | use crate::dma::{AnyChannel, Channel, Transfer}; | 5 | use crate::dma::{AnyChannel, Channel, Transfer}; |
| 6 | use crate::gpio::Pull; | ||
| 6 | use crate::pio::{ | 7 | use 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 | }; |
| 9 | use crate::Peri; | 10 | use crate::Peri; |
| 10 | 11 | ||
| 12 | /// This struct represents an i2s receiver & controller driver program | ||
| 13 | pub struct PioI2sInProgram<'d, PIO: Instance> { | ||
| 14 | prg: LoadedProgram<'d, PIO>, | ||
| 15 | } | ||
| 16 | |||
| 17 | impl<'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 | ||
| 39 | pub struct PioI2sIn<'d, P: Instance, const S: usize> { | ||
| 40 | dma: Peri<'d, AnyChannel>, | ||
| 41 | sm: StateMachine<'d, P, S>, | ||
| 42 | } | ||
| 43 | |||
| 44 | impl<'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 |
| 12 | /// | 100 | /// |
| 13 | /// The sample bit-depth is set through scratch register `Y`. | 101 | /// The sample bit-depth is set through scratch register `Y`. |
| @@ -26,12 +114,12 @@ impl<'d, PIO: Instance> PioI2sOutProgram<'d, PIO> { | |||
| 26 | "left_data:", | 114 | "left_data:", |
| 27 | " out pins, 1 side 0b00", | 115 | " out pins, 1 side 0b00", |
| 28 | " jmp x-- left_data side 0b01", | 116 | " jmp x-- left_data side 0b01", |
| 29 | " out pins 1 side 0b10", | 117 | " out pins, 1 side 0b10", |
| 30 | " mov x, y side 0b11", | 118 | " mov x, y side 0b11", |
| 31 | "right_data:", | 119 | "right_data:", |
| 32 | " out pins 1 side 0b10", | 120 | " out pins, 1 side 0b10", |
| 33 | " jmp x-- right_data side 0b11", | 121 | " jmp x-- right_data side 0b11", |
| 34 | " out pins 1 side 0b00", | 122 | " out pins, 1 side 0b00", |
| 35 | ); | 123 | ); |
| 36 | 124 | ||
| 37 | let prg = common.load_program(&prg.program); | 125 | let prg = common.load_program(&prg.program); |
diff --git a/examples/rp235x/src/bin/pio_i2s_rx.rs b/examples/rp235x/src/bin/pio_i2s_rx.rs new file mode 100644 index 000000000..c3f505b13 --- /dev/null +++ b/examples/rp235x/src/bin/pio_i2s_rx.rs | |||
| @@ -0,0 +1,81 @@ | |||
| 1 | //! This example shows receiving audio from a connected I2S microphone (or other audio source) | ||
| 2 | //! using the PIO module of the RP235x. | ||
| 3 | //! | ||
| 4 | //! | ||
| 5 | //! Connect the i2s microphone as follows: | ||
| 6 | //! bclk : GPIO 18 | ||
| 7 | //! lrc : GPIO 19 | ||
| 8 | //! din : GPIO 20 | ||
| 9 | //! Then hold down the boot select button to begin receiving audio. Received I2S words will be written to | ||
| 10 | //! buffers for the left and right channels for use in your application, whether that's storage or | ||
| 11 | //! further processing | ||
| 12 | //! | ||
| 13 | //! Note the const USE_ONBOARD_PULLDOWN is by default set to false, meaning an external | ||
| 14 | //! pull-down resistor is being used on the data pin if required by the mic being used. | ||
| 15 | |||
| 16 | #![no_std] | ||
| 17 | #![no_main] | ||
| 18 | use core::mem; | ||
| 19 | |||
| 20 | use defmt::*; | ||
| 21 | use embassy_executor::Spawner; | ||
| 22 | use embassy_rp::bind_interrupts; | ||
| 23 | use embassy_rp::peripherals::PIO0; | ||
| 24 | use embassy_rp::pio::{InterruptHandler, Pio}; | ||
| 25 | use embassy_rp::pio_programs::i2s::{PioI2sIn, PioI2sInProgram}; | ||
| 26 | use static_cell::StaticCell; | ||
| 27 | use {defmt_rtt as _, panic_probe as _}; | ||
| 28 | |||
| 29 | bind_interrupts!(struct Irqs { | ||
| 30 | PIO0_IRQ_0 => InterruptHandler<PIO0>; | ||
| 31 | }); | ||
| 32 | |||
| 33 | const SAMPLE_RATE: u32 = 48_000; | ||
| 34 | const BIT_DEPTH: u32 = 16; | ||
| 35 | const CHANNELS: u32 = 2; | ||
| 36 | const USE_ONBOARD_PULLDOWN: bool = false; // whether or not to use the onboard pull-down resistor, | ||
| 37 | // which has documented issues on many RP235x boards | ||
| 38 | #[embassy_executor::main] | ||
| 39 | async fn main(_spawner: Spawner) { | ||
| 40 | let p = embassy_rp::init(Default::default()); | ||
| 41 | |||
| 42 | // Setup pio state machine for i2s input | ||
| 43 | let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); | ||
| 44 | |||
| 45 | let bit_clock_pin = p.PIN_18; | ||
| 46 | let left_right_clock_pin = p.PIN_19; | ||
| 47 | let data_pin = p.PIN_20; | ||
| 48 | |||
| 49 | let program = PioI2sInProgram::new(&mut common); | ||
| 50 | let mut i2s = PioI2sIn::new( | ||
| 51 | &mut common, | ||
| 52 | sm0, | ||
| 53 | p.DMA_CH0, | ||
| 54 | USE_ONBOARD_PULLDOWN, | ||
| 55 | data_pin, | ||
| 56 | bit_clock_pin, | ||
| 57 | left_right_clock_pin, | ||
| 58 | SAMPLE_RATE, | ||
| 59 | BIT_DEPTH, | ||
| 60 | CHANNELS, | ||
| 61 | &program, | ||
| 62 | ); | ||
| 63 | |||
| 64 | // create two audio buffers (back and front) which will take turns being | ||
| 65 | // filled with new audio data from the PIO fifo using DMA | ||
| 66 | const BUFFER_SIZE: usize = 960; | ||
| 67 | static DMA_BUFFER: StaticCell<[u32; BUFFER_SIZE * 2]> = StaticCell::new(); | ||
| 68 | let dma_buffer = DMA_BUFFER.init_with(|| [0u32; BUFFER_SIZE * 2]); | ||
| 69 | let (mut back_buffer, mut front_buffer) = dma_buffer.split_at_mut(BUFFER_SIZE); | ||
| 70 | |||
| 71 | loop { | ||
| 72 | // trigger transfer of front buffer data to the pio fifo | ||
| 73 | // but don't await the returned future, yet | ||
| 74 | let dma_future = i2s.read(front_buffer); | ||
| 75 | // now await the dma future. once the dma finishes, the next buffer needs to be queued | ||
| 76 | // within DMA_DEPTH / SAMPLE_RATE = 8 / 48000 seconds = 166us | ||
| 77 | dma_future.await; | ||
| 78 | info!("Received I2S data word: {:?}", &front_buffer); | ||
| 79 | mem::swap(&mut back_buffer, &mut front_buffer); | ||
| 80 | } | ||
| 81 | } | ||
