aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2025-09-05 19:08:06 +0000
committerGitHub <[email protected]>2025-09-05 19:08:06 +0000
commit46bf0c71cceacc68fa739726d374e27f1a17bba0 (patch)
tree1a620a78feef49cd01eb42403f486cba7c068720
parentbbcf9af87eddbf9e1dba3281a3f1a9e5e419477f (diff)
parent241129c569023dc71d0025cdc41bcfe40418e7b8 (diff)
Merge pull request #4210 from mcaveniathor/pio_i2s_rx
Add PioI2sIn, PioI2sInProgram, and example binary
-rw-r--r--embassy-rp/CHANGELOG.md3
-rw-r--r--embassy-rp/src/pio_programs/i2s.rs96
-rw-r--r--examples/rp235x/src/bin/pio_i2s_rx.rs81
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
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
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]
18use core::mem;
19
20use defmt::*;
21use embassy_executor::Spawner;
22use embassy_rp::bind_interrupts;
23use embassy_rp::peripherals::PIO0;
24use embassy_rp::pio::{InterruptHandler, Pio};
25use embassy_rp::pio_programs::i2s::{PioI2sIn, PioI2sInProgram};
26use static_cell::StaticCell;
27use {defmt_rtt as _, panic_probe as _};
28
29bind_interrupts!(struct Irqs {
30 PIO0_IRQ_0 => InterruptHandler<PIO0>;
31});
32
33const SAMPLE_RATE: u32 = 48_000;
34const BIT_DEPTH: u32 = 16;
35const CHANNELS: u32 = 2;
36const 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]
39async 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}