mirror of
https://github.com/embassy-rs/embassy.git
synced 2024-11-22 14:53:03 +00:00
add pio_i2s example for RP2040
This commit is contained in:
parent
43b6258a69
commit
69d4b42841
130
examples/rp/src/bin/pio_i2s.rs
Normal file
130
examples/rp/src/bin/pio_i2s.rs
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
//! 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 defmt_rtt as _;
|
||||||
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_rp::{
|
||||||
|
bind_interrupts,
|
||||||
|
peripherals::PIO0,
|
||||||
|
pio::{Config, FifoJoin, InterruptHandler, Pio, ShiftConfig, ShiftDirection},
|
||||||
|
Peripheral,
|
||||||
|
};
|
||||||
|
use fixed::traits::ToFixed;
|
||||||
|
use panic_probe as _;
|
||||||
|
use static_cell::StaticCell;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user