diff options
| author | Dario Nieuwenhuis <[email protected]> | 2024-01-21 21:30:11 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-01-21 21:30:11 +0000 |
| commit | 20fd03a14f1261e7b2264dcbca8e164393e66b94 (patch) | |
| tree | 400a1beedda794ffd350555b8944784db12e1bf4 | |
| parent | 43b6258a69b12e6b01f8d1f69626174579420c81 (diff) | |
| parent | 7931fcfb3d3211b9c7e46b43cebe48c433893b15 (diff) | |
Merge pull request #2468 from exoticorn/pio_i2s-example
add pio_i2s example for RP2040
| -rw-r--r-- | examples/rp/src/bin/pio_i2s.rs | 125 |
1 files changed, 125 insertions, 0 deletions
diff --git a/examples/rp/src/bin/pio_i2s.rs b/examples/rp/src/bin/pio_i2s.rs new file mode 100644 index 000000000..cf60e5b30 --- /dev/null +++ b/examples/rp/src/bin/pio_i2s.rs | |||
| @@ -0,0 +1,125 @@ | |||
| 1 | //! This example shows generating audio and sending it to a connected i2s DAC using the PIO | ||
| 2 | //! module of the RP2040. | ||
| 3 | //! | ||
| 4 | //! Connect the i2s DAC as follows: | ||
| 5 | //! bclk : GPIO 18 | ||
| 6 | //! lrc : GPIO 19 | ||
| 7 | //! din : GPIO 20 | ||
| 8 | //! Then hold down the boot select button to trigger a rising triangle waveform. | ||
| 9 | |||
| 10 | #![no_std] | ||
| 11 | #![no_main] | ||
| 12 | |||
| 13 | use core::mem; | ||
| 14 | |||
| 15 | use embassy_executor::Spawner; | ||
| 16 | use embassy_rp::peripherals::PIO0; | ||
| 17 | use embassy_rp::pio::{Config, FifoJoin, InterruptHandler, Pio, ShiftConfig, ShiftDirection}; | ||
| 18 | use embassy_rp::{bind_interrupts, Peripheral}; | ||
| 19 | use fixed::traits::ToFixed; | ||
| 20 | use static_cell::StaticCell; | ||
| 21 | use {defmt_rtt as _, panic_probe as _}; | ||
| 22 | |||
| 23 | bind_interrupts!(struct Irqs { | ||
| 24 | PIO0_IRQ_0 => InterruptHandler<PIO0>; | ||
| 25 | }); | ||
| 26 | |||
| 27 | const SAMPLE_RATE: u32 = 48_000; | ||
| 28 | |||
| 29 | #[embassy_executor::main] | ||
| 30 | async fn main(_spawner: Spawner) { | ||
| 31 | let mut p = embassy_rp::init(Default::default()); | ||
| 32 | |||
| 33 | // Setup pio state machine for i2s output | ||
| 34 | let mut pio = Pio::new(p.PIO0, Irqs); | ||
| 35 | |||
| 36 | #[rustfmt::skip] | ||
| 37 | let pio_program = pio_proc::pio_asm!( | ||
| 38 | ".side_set 2", | ||
| 39 | " set x, 14 side 0b01", // side 0bWB - W = Word Clock, B = Bit Clock | ||
| 40 | "left_data:", | ||
| 41 | " out pins, 1 side 0b00", | ||
| 42 | " jmp x-- left_data side 0b01", | ||
| 43 | " out pins 1 side 0b10", | ||
| 44 | " set x, 14 side 0b11", | ||
| 45 | "right_data:", | ||
| 46 | " out pins 1 side 0b10", | ||
| 47 | " jmp x-- right_data side 0b11", | ||
| 48 | " out pins 1 side 0b00", | ||
| 49 | ); | ||
| 50 | |||
| 51 | let bit_clock_pin = p.PIN_18; | ||
| 52 | let left_right_clock_pin = p.PIN_19; | ||
| 53 | let data_pin = p.PIN_20; | ||
| 54 | |||
| 55 | let data_pin = pio.common.make_pio_pin(data_pin); | ||
| 56 | let bit_clock_pin = pio.common.make_pio_pin(bit_clock_pin); | ||
| 57 | let left_right_clock_pin = pio.common.make_pio_pin(left_right_clock_pin); | ||
| 58 | |||
| 59 | let cfg = { | ||
| 60 | let mut cfg = Config::default(); | ||
| 61 | cfg.use_program( | ||
| 62 | &pio.common.load_program(&pio_program.program), | ||
| 63 | &[&bit_clock_pin, &left_right_clock_pin], | ||
| 64 | ); | ||
| 65 | cfg.set_out_pins(&[&data_pin]); | ||
| 66 | const BIT_DEPTH: u32 = 16; | ||
| 67 | const CHANNELS: u32 = 2; | ||
| 68 | let clock_frequency = SAMPLE_RATE * BIT_DEPTH * CHANNELS; | ||
| 69 | cfg.clock_divider = (125_000_000. / clock_frequency as f64 / 2.).to_fixed(); | ||
| 70 | cfg.shift_out = ShiftConfig { | ||
| 71 | threshold: 32, | ||
| 72 | direction: ShiftDirection::Left, | ||
| 73 | auto_fill: true, | ||
| 74 | }; | ||
| 75 | // join fifos to have twice the time to start the next dma transfer | ||
| 76 | cfg.fifo_join = FifoJoin::TxOnly; | ||
| 77 | cfg | ||
| 78 | }; | ||
| 79 | pio.sm0.set_config(&cfg); | ||
| 80 | pio.sm0.set_pin_dirs( | ||
| 81 | embassy_rp::pio::Direction::Out, | ||
| 82 | &[&data_pin, &left_right_clock_pin, &bit_clock_pin], | ||
| 83 | ); | ||
| 84 | |||
| 85 | // create two audio buffers (back and front) which will take turns being | ||
| 86 | // filled with new audio data and being sent to the pio fifo using dma | ||
| 87 | const BUFFER_SIZE: usize = 960; | ||
| 88 | static DMA_BUFFER: StaticCell<[u32; BUFFER_SIZE * 2]> = StaticCell::new(); | ||
| 89 | let dma_buffer = DMA_BUFFER.init_with(|| [0u32; BUFFER_SIZE * 2]); | ||
| 90 | let (mut back_buffer, mut front_buffer) = dma_buffer.split_at_mut(BUFFER_SIZE); | ||
| 91 | |||
| 92 | // start pio state machine | ||
| 93 | pio.sm0.set_enable(true); | ||
| 94 | let tx = pio.sm0.tx(); | ||
| 95 | let mut dma_ref = p.DMA_CH0.into_ref(); | ||
| 96 | |||
| 97 | let mut fade_value: i32 = 0; | ||
| 98 | let mut phase: i32 = 0; | ||
| 99 | |||
| 100 | loop { | ||
| 101 | // trigger transfer of front buffer data to the pio fifo | ||
| 102 | // but don't await the returned future, yet | ||
| 103 | let dma_future = tx.dma_push(dma_ref.reborrow(), front_buffer); | ||
| 104 | |||
| 105 | // fade in audio when bootsel is pressed | ||
| 106 | let fade_target = if p.BOOTSEL.is_pressed() { i32::MAX } else { 0 }; | ||
| 107 | |||
| 108 | // fill back buffer with fresh audio samples before awaiting the dma future | ||
| 109 | for s in back_buffer.iter_mut() { | ||
| 110 | // exponential approach of fade_value => fade_target | ||
| 111 | fade_value += (fade_target - fade_value) >> 14; | ||
| 112 | // generate triangle wave with amplitude and frequency based on fade value | ||
| 113 | phase = (phase + (fade_value >> 22)) & 0xffff; | ||
| 114 | let triangle_sample = (phase as i16 as i32).abs() - 16384; | ||
| 115 | let sample = (triangle_sample * (fade_value >> 15)) >> 16; | ||
| 116 | // duplicate mono sample into lower and upper half of dma word | ||
| 117 | *s = (sample as u16 as u32) * 0x10001; | ||
| 118 | } | ||
| 119 | |||
| 120 | // now await the dma future. once the dma finishes, the next buffer needs to be queued | ||
| 121 | // within DMA_DEPTH / SAMPLE_RATE = 8 / 48000 seconds = 166us | ||
| 122 | dma_future.await; | ||
| 123 | mem::swap(&mut back_buffer, &mut front_buffer); | ||
| 124 | } | ||
| 125 | } | ||
