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