Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add pio_i2s example for RP2040 #2468

Merged
merged 2 commits into from
Jan 21, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions examples/rp/src/bin/pio_i2s.rs
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);
}
}