aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2024-01-21 21:30:11 +0000
committerGitHub <[email protected]>2024-01-21 21:30:11 +0000
commit20fd03a14f1261e7b2264dcbca8e164393e66b94 (patch)
tree400a1beedda794ffd350555b8944784db12e1bf4
parent43b6258a69b12e6b01f8d1f69626174579420c81 (diff)
parent7931fcfb3d3211b9c7e46b43cebe48c433893b15 (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.rs125
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
13use core::mem;
14
15use embassy_executor::Spawner;
16use embassy_rp::peripherals::PIO0;
17use embassy_rp::pio::{Config, FifoJoin, InterruptHandler, Pio, ShiftConfig, ShiftDirection};
18use embassy_rp::{bind_interrupts, Peripheral};
19use fixed::traits::ToFixed;
20use static_cell::StaticCell;
21use {defmt_rtt as _, panic_probe as _};
22
23bind_interrupts!(struct Irqs {
24 PIO0_IRQ_0 => InterruptHandler<PIO0>;
25});
26
27const SAMPLE_RATE: u32 = 48_000;
28
29#[embassy_executor::main]
30async 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}