-
Notifications
You must be signed in to change notification settings - Fork 935
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2468 from exoticorn/pio_i2s-example
add pio_i2s example for RP2040
- Loading branch information
Showing
1 changed file
with
125 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
//! This example shows generating audio and sending it to a connected i2s DAC using the PIO | ||
//! module of the RP2040. | ||
//! | ||
//! Connect the i2s DAC as follows: | ||
//! bclk : GPIO 18 | ||
//! lrc : GPIO 19 | ||
//! din : GPIO 20 | ||
//! Then hold down the boot select button to trigger a rising triangle waveform. | ||
#![no_std] | ||
#![no_main] | ||
|
||
use core::mem; | ||
|
||
use embassy_executor::Spawner; | ||
use embassy_rp::peripherals::PIO0; | ||
use embassy_rp::pio::{Config, FifoJoin, InterruptHandler, Pio, ShiftConfig, ShiftDirection}; | ||
use embassy_rp::{bind_interrupts, Peripheral}; | ||
use fixed::traits::ToFixed; | ||
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; | ||
|
||
#[embassy_executor::main] | ||
async fn main(_spawner: Spawner) { | ||
let mut p = embassy_rp::init(Default::default()); | ||
|
||
// Setup pio state machine for i2s output | ||
let mut pio = Pio::new(p.PIO0, Irqs); | ||
|
||
#[rustfmt::skip] | ||
let pio_program = pio_proc::pio_asm!( | ||
".side_set 2", | ||
" set x, 14 side 0b01", // side 0bWB - W = Word Clock, B = Bit Clock | ||
"left_data:", | ||
" out pins, 1 side 0b00", | ||
" jmp x-- left_data side 0b01", | ||
" out pins 1 side 0b10", | ||
" set x, 14 side 0b11", | ||
"right_data:", | ||
" out pins 1 side 0b10", | ||
" jmp x-- right_data side 0b11", | ||
" out pins 1 side 0b00", | ||
); | ||
|
||
let bit_clock_pin = p.PIN_18; | ||
let left_right_clock_pin = p.PIN_19; | ||
let data_pin = p.PIN_20; | ||
|
||
let data_pin = pio.common.make_pio_pin(data_pin); | ||
let bit_clock_pin = pio.common.make_pio_pin(bit_clock_pin); | ||
let left_right_clock_pin = pio.common.make_pio_pin(left_right_clock_pin); | ||
|
||
let cfg = { | ||
let mut cfg = Config::default(); | ||
cfg.use_program( | ||
&pio.common.load_program(&pio_program.program), | ||
&[&bit_clock_pin, &left_right_clock_pin], | ||
); | ||
cfg.set_out_pins(&[&data_pin]); | ||
const BIT_DEPTH: u32 = 16; | ||
const CHANNELS: u32 = 2; | ||
let clock_frequency = SAMPLE_RATE * BIT_DEPTH * CHANNELS; | ||
cfg.clock_divider = (125_000_000. / clock_frequency as f64 / 2.).to_fixed(); | ||
cfg.shift_out = 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::TxOnly; | ||
cfg | ||
}; | ||
pio.sm0.set_config(&cfg); | ||
pio.sm0.set_pin_dirs( | ||
embassy_rp::pio::Direction::Out, | ||
&[&data_pin, &left_right_clock_pin, &bit_clock_pin], | ||
); | ||
|
||
// create two audio buffers (back and front) which will take turns being | ||
// filled with new audio data and being sent to 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); | ||
|
||
// start pio state machine | ||
pio.sm0.set_enable(true); | ||
let tx = pio.sm0.tx(); | ||
let mut dma_ref = p.DMA_CH0.into_ref(); | ||
|
||
let mut fade_value: i32 = 0; | ||
let mut phase: i32 = 0; | ||
|
||
loop { | ||
// trigger transfer of front buffer data to the pio fifo | ||
// but don't await the returned future, yet | ||
let dma_future = tx.dma_push(dma_ref.reborrow(), front_buffer); | ||
|
||
// fade in audio when bootsel is pressed | ||
let fade_target = if p.BOOTSEL.is_pressed() { i32::MAX } else { 0 }; | ||
|
||
// fill back buffer with fresh audio samples before awaiting the dma future | ||
for s in back_buffer.iter_mut() { | ||
// exponential approach of fade_value => fade_target | ||
fade_value += (fade_target - fade_value) >> 14; | ||
// generate triangle wave with amplitude and frequency based on fade value | ||
phase = (phase + (fade_value >> 22)) & 0xffff; | ||
let triangle_sample = (phase as i16 as i32).abs() - 16384; | ||
let sample = (triangle_sample * (fade_value >> 15)) >> 16; | ||
// duplicate mono sample into lower and upper half of dma word | ||
*s = (sample as u16 as u32) * 0x10001; | ||
} | ||
|
||
// 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; | ||
mem::swap(&mut back_buffer, &mut front_buffer); | ||
} | ||
} |