From 241129c569023dc71d0025cdc41bcfe40418e7b8 Mon Sep 17 00:00:00 2001 From: Thor McAvenia Date: Fri, 16 May 2025 00:09:10 -0700 Subject: Add PioI2sIn, PioI2sInProgram, and example binary --- embassy-rp/CHANGELOG.md | 3 ++ embassy-rp/src/pio_programs/i2s.rs | 96 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 95 insertions(+), 4 deletions(-) (limited to 'embassy-rp') 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 ## Unreleased - ReleaseDate +- Add PIO SPI +- Add PIO I2S input + ## 0.8.0 - 2025-08-26 ## 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 @@ -//! Pio backed I2s output +//! Pio backed I2s output and output drivers use fixed::traits::ToFixed; use crate::dma::{AnyChannel, Channel, Transfer}; +use crate::gpio::Pull; use crate::pio::{ Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine, }; use crate::Peri; +/// This struct represents an i2s receiver & controller driver program +pub struct PioI2sInProgram<'d, PIO: Instance> { + prg: LoadedProgram<'d, PIO>, +} + +impl<'d, PIO: Instance> PioI2sInProgram<'d, PIO> { + /// Load the input program into the given pio + pub fn new(common: &mut Common<'d, PIO>) -> Self { + let prg = pio::pio_asm! { + ".side_set 2", + " set x, 14 side 0b01", + "left_data:", + " in pins, 1 side 0b00", // read one left-channel bit from SD + " jmp x-- left_data side 0b01", + " in pins, 1 side 0b10", // ws changes 1 clock before MSB + " set x, 14 side 0b11", + "right_data:", + " in pins, 1 side 0b10", + " jmp x-- right_data side 0b11", + " in pins, 1 side 0b00" // ws changes 1 clock before ms + }; + let prg = common.load_program(&prg.program); + Self { prg } + } +} + +/// Pio backed I2s input driver +pub struct PioI2sIn<'d, P: Instance, const S: usize> { + dma: Peri<'d, AnyChannel>, + sm: StateMachine<'d, P, S>, +} + +impl<'d, P: Instance, const S: usize> PioI2sIn<'d, P, S> { + /// Configure a state machine to act as both the controller (provider of SCK and WS) and receiver (of SD) for an I2S signal + pub fn new( + common: &mut Common<'d, P>, + mut sm: StateMachine<'d, P, S>, + dma: Peri<'d, impl Channel>, + // Whether or not to use the MCU's internal pull-down resistor, as the + // Pico 2 is known to have problems with the inbuilt pulldowns, many + // opt to just use an external pull down resistor to meet requirements of common + // i2s microphones such as the INMP441 + data_pulldown: bool, + data_pin: Peri<'d, impl PioPin>, + bit_clock_pin: Peri<'d, impl PioPin>, + lr_clock_pin: Peri<'d, impl PioPin>, + sample_rate: u32, + bit_depth: u32, + channels: u32, + program: &PioI2sInProgram<'d, P>, + ) -> Self { + let mut data_pin = common.make_pio_pin(data_pin); + if data_pulldown { + data_pin.set_pull(Pull::Down); + } + let bit_clock_pin = common.make_pio_pin(bit_clock_pin); + let left_right_clock_pin = common.make_pio_pin(lr_clock_pin); + + let cfg = { + let mut cfg = Config::default(); + cfg.use_program(&program.prg, &[&bit_clock_pin, &left_right_clock_pin]); + cfg.set_in_pins(&[&data_pin]); + let clock_frequency = sample_rate * bit_depth * channels; + cfg.clock_divider = (crate::clocks::clk_sys_freq() as f64 / clock_frequency as f64 / 2.).to_fixed(); + cfg.shift_in = ShiftConfig { + threshold: 32, + direction: ShiftDirection::Left, + auto_fill: true, + }; + // join fifos to have twice the time to start the next dma transfer + cfg.fifo_join = FifoJoin::RxOnly; // both control signals are sent via side-setting + cfg + }; + sm.set_config(&cfg); + sm.set_pin_dirs(Direction::In, &[&data_pin]); + sm.set_pin_dirs(Direction::Out, &[&left_right_clock_pin, &bit_clock_pin]); + sm.set_enable(true); + + Self { dma: dma.into(), sm } + } + + /// Return an in-prograss dma transfer future. Awaiting it will guarentee a complete transfer. + pub fn read<'b>(&'b mut self, buff: &'b mut [u32]) -> Transfer<'b, AnyChannel> { + self.sm.rx().dma_pull(self.dma.reborrow(), buff, false) + } +} + /// This struct represents an i2s output driver program /// /// The sample bit-depth is set through scratch register `Y`. @@ -26,12 +114,12 @@ impl<'d, PIO: Instance> PioI2sOutProgram<'d, PIO> { "left_data:", " out pins, 1 side 0b00", " jmp x-- left_data side 0b01", - " out pins 1 side 0b10", + " out pins, 1 side 0b10", " mov x, y side 0b11", "right_data:", - " out pins 1 side 0b10", + " out pins, 1 side 0b10", " jmp x-- right_data side 0b11", - " out pins 1 side 0b00", + " out pins, 1 side 0b00", ); let prg = common.load_program(&prg.program); -- cgit