mirror of
https://github.com/embassy-rs/embassy.git
synced 2025-09-27 12:20:37 +00:00
Merge pull request #4210 from mcaveniathor/pio_i2s_rx
Add PioI2sIn, PioI2sInProgram, and example binary
This commit is contained in:
commit
46bf0c71cc
@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
<!-- next-header -->
|
||||
## Unreleased - ReleaseDate
|
||||
|
||||
- Add PIO SPI
|
||||
- Add PIO I2S input
|
||||
|
||||
## 0.8.0 - 2025-08-26
|
||||
|
||||
## 0.7.1 - 2025-08-26
|
||||
|
@ -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);
|
||||
|
81
examples/rp235x/src/bin/pio_i2s_rx.rs
Normal file
81
examples/rp235x/src/bin/pio_i2s_rx.rs
Normal file
@ -0,0 +1,81 @@
|
||||
//! This example shows receiving audio from a connected I2S microphone (or other audio source)
|
||||
//! using the PIO module of the RP235x.
|
||||
//!
|
||||
//!
|
||||
//! Connect the i2s microphone as follows:
|
||||
//! bclk : GPIO 18
|
||||
//! lrc : GPIO 19
|
||||
//! din : GPIO 20
|
||||
//! Then hold down the boot select button to begin receiving audio. Received I2S words will be written to
|
||||
//! buffers for the left and right channels for use in your application, whether that's storage or
|
||||
//! further processing
|
||||
//!
|
||||
//! Note the const USE_ONBOARD_PULLDOWN is by default set to false, meaning an external
|
||||
//! pull-down resistor is being used on the data pin if required by the mic being used.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
use core::mem;
|
||||
|
||||
use defmt::*;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_rp::bind_interrupts;
|
||||
use embassy_rp::peripherals::PIO0;
|
||||
use embassy_rp::pio::{InterruptHandler, Pio};
|
||||
use embassy_rp::pio_programs::i2s::{PioI2sIn, PioI2sInProgram};
|
||||
use static_cell::StaticCell;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
bind_interrupts!(struct Irqs {
|
||||
PIO0_IRQ_0 => InterruptHandler<PIO0>;
|
||||
});
|
||||
|
||||
const SAMPLE_RATE: u32 = 48_000;
|
||||
const BIT_DEPTH: u32 = 16;
|
||||
const CHANNELS: u32 = 2;
|
||||
const USE_ONBOARD_PULLDOWN: bool = false; // whether or not to use the onboard pull-down resistor,
|
||||
// which has documented issues on many RP235x boards
|
||||
#[embassy_executor::main]
|
||||
async fn main(_spawner: Spawner) {
|
||||
let p = embassy_rp::init(Default::default());
|
||||
|
||||
// Setup pio state machine for i2s input
|
||||
let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs);
|
||||
|
||||
let bit_clock_pin = p.PIN_18;
|
||||
let left_right_clock_pin = p.PIN_19;
|
||||
let data_pin = p.PIN_20;
|
||||
|
||||
let program = PioI2sInProgram::new(&mut common);
|
||||
let mut i2s = PioI2sIn::new(
|
||||
&mut common,
|
||||
sm0,
|
||||
p.DMA_CH0,
|
||||
USE_ONBOARD_PULLDOWN,
|
||||
data_pin,
|
||||
bit_clock_pin,
|
||||
left_right_clock_pin,
|
||||
SAMPLE_RATE,
|
||||
BIT_DEPTH,
|
||||
CHANNELS,
|
||||
&program,
|
||||
);
|
||||
|
||||
// create two audio buffers (back and front) which will take turns being
|
||||
// filled with new audio data from the PIO fifo using DMA
|
||||
const BUFFER_SIZE: usize = 960;
|
||||
static DMA_BUFFER: StaticCell<[u32; BUFFER_SIZE * 2]> = StaticCell::new();
|
||||
let dma_buffer = DMA_BUFFER.init_with(|| [0u32; BUFFER_SIZE * 2]);
|
||||
let (mut back_buffer, mut front_buffer) = dma_buffer.split_at_mut(BUFFER_SIZE);
|
||||
|
||||
loop {
|
||||
// trigger transfer of front buffer data to the pio fifo
|
||||
// but don't await the returned future, yet
|
||||
let dma_future = i2s.read(front_buffer);
|
||||
// now await the dma future. once the dma finishes, the next buffer needs to be queued
|
||||
// within DMA_DEPTH / SAMPLE_RATE = 8 / 48000 seconds = 166us
|
||||
dma_future.await;
|
||||
info!("Received I2S data word: {:?}", &front_buffer);
|
||||
mem::swap(&mut back_buffer, &mut front_buffer);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user