diff options
| author | 1-rafael-1 <[email protected]> | 2025-09-15 20:07:18 +0200 |
|---|---|---|
| committer | 1-rafael-1 <[email protected]> | 2025-09-15 20:07:18 +0200 |
| commit | 6bb3d2c0720fa082f27d3cdb70f516058497ec87 (patch) | |
| tree | 5a1e255cff999b00800f203b91a759c720c973e5 /embassy-rp/src/pio_programs/i2s.rs | |
| parent | eb685574601d98c44faed9a3534d056199b46e20 (diff) | |
| parent | 92a6fd2946f2cbb15359290f68aa360953da2ff7 (diff) | |
Merge branch 'main' into rp2040-rtc-alarm
Diffstat (limited to 'embassy-rp/src/pio_programs/i2s.rs')
| -rw-r--r-- | embassy-rp/src/pio_programs/i2s.rs | 114 |
1 files changed, 105 insertions, 9 deletions
diff --git a/embassy-rp/src/pio_programs/i2s.rs b/embassy-rp/src/pio_programs/i2s.rs index b967f0160..2382a3f9f 100644 --- a/embassy-rp/src/pio_programs/i2s.rs +++ b/embassy-rp/src/pio_programs/i2s.rs | |||
| @@ -1,14 +1,106 @@ | |||
| 1 | //! Pio backed I2s output | 1 | //! Pio backed I2s output and output drivers |
| 2 | 2 | ||
| 3 | use fixed::traits::ToFixed; | 3 | use fixed::traits::ToFixed; |
| 4 | 4 | ||
| 5 | use crate::dma::{AnyChannel, Channel, Transfer}; | 5 | use crate::dma::{AnyChannel, Channel, Transfer}; |
| 6 | use crate::gpio::Pull; | ||
| 6 | use crate::pio::{ | 7 | use crate::pio::{ |
| 7 | Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine, | 8 | Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine, |
| 8 | }; | 9 | }; |
| 9 | use crate::Peri; | 10 | use crate::Peri; |
| 10 | 11 | ||
| 12 | /// This struct represents an i2s receiver & controller driver program | ||
| 13 | pub struct PioI2sInProgram<'d, PIO: Instance> { | ||
| 14 | prg: LoadedProgram<'d, PIO>, | ||
| 15 | } | ||
| 16 | |||
| 17 | impl<'d, PIO: Instance> PioI2sInProgram<'d, PIO> { | ||
| 18 | /// Load the input program into the given pio | ||
| 19 | pub fn new(common: &mut Common<'d, PIO>) -> Self { | ||
| 20 | let prg = pio::pio_asm! { | ||
| 21 | ".side_set 2", | ||
| 22 | " set x, 14 side 0b01", | ||
| 23 | "left_data:", | ||
| 24 | " in pins, 1 side 0b00", // read one left-channel bit from SD | ||
| 25 | " jmp x-- left_data side 0b01", | ||
| 26 | " in pins, 1 side 0b10", // ws changes 1 clock before MSB | ||
| 27 | " set x, 14 side 0b11", | ||
| 28 | "right_data:", | ||
| 29 | " in pins, 1 side 0b10", | ||
| 30 | " jmp x-- right_data side 0b11", | ||
| 31 | " in pins, 1 side 0b00" // ws changes 1 clock before ms | ||
| 32 | }; | ||
| 33 | let prg = common.load_program(&prg.program); | ||
| 34 | Self { prg } | ||
| 35 | } | ||
| 36 | } | ||
| 37 | |||
| 38 | /// Pio backed I2s input driver | ||
| 39 | pub struct PioI2sIn<'d, P: Instance, const S: usize> { | ||
| 40 | dma: Peri<'d, AnyChannel>, | ||
| 41 | sm: StateMachine<'d, P, S>, | ||
| 42 | } | ||
| 43 | |||
| 44 | impl<'d, P: Instance, const S: usize> PioI2sIn<'d, P, S> { | ||
| 45 | /// Configure a state machine to act as both the controller (provider of SCK and WS) and receiver (of SD) for an I2S signal | ||
| 46 | pub fn new( | ||
| 47 | common: &mut Common<'d, P>, | ||
| 48 | mut sm: StateMachine<'d, P, S>, | ||
| 49 | dma: Peri<'d, impl Channel>, | ||
| 50 | // Whether or not to use the MCU's internal pull-down resistor, as the | ||
| 51 | // Pico 2 is known to have problems with the inbuilt pulldowns, many | ||
| 52 | // opt to just use an external pull down resistor to meet requirements of common | ||
| 53 | // i2s microphones such as the INMP441 | ||
| 54 | data_pulldown: bool, | ||
| 55 | data_pin: Peri<'d, impl PioPin>, | ||
| 56 | bit_clock_pin: Peri<'d, impl PioPin>, | ||
| 57 | lr_clock_pin: Peri<'d, impl PioPin>, | ||
| 58 | sample_rate: u32, | ||
| 59 | bit_depth: u32, | ||
| 60 | channels: u32, | ||
| 61 | program: &PioI2sInProgram<'d, P>, | ||
| 62 | ) -> Self { | ||
| 63 | let mut data_pin = common.make_pio_pin(data_pin); | ||
| 64 | if data_pulldown { | ||
| 65 | data_pin.set_pull(Pull::Down); | ||
| 66 | } | ||
| 67 | let bit_clock_pin = common.make_pio_pin(bit_clock_pin); | ||
| 68 | let left_right_clock_pin = common.make_pio_pin(lr_clock_pin); | ||
| 69 | |||
| 70 | let cfg = { | ||
| 71 | let mut cfg = Config::default(); | ||
| 72 | cfg.use_program(&program.prg, &[&bit_clock_pin, &left_right_clock_pin]); | ||
| 73 | cfg.set_in_pins(&[&data_pin]); | ||
| 74 | let clock_frequency = sample_rate * bit_depth * channels; | ||
| 75 | cfg.clock_divider = (crate::clocks::clk_sys_freq() as f64 / clock_frequency as f64 / 2.).to_fixed(); | ||
| 76 | cfg.shift_in = ShiftConfig { | ||
| 77 | threshold: 32, | ||
| 78 | direction: ShiftDirection::Left, | ||
| 79 | auto_fill: true, | ||
| 80 | }; | ||
| 81 | // join fifos to have twice the time to start the next dma transfer | ||
| 82 | cfg.fifo_join = FifoJoin::RxOnly; // both control signals are sent via side-setting | ||
| 83 | cfg | ||
| 84 | }; | ||
| 85 | sm.set_config(&cfg); | ||
| 86 | sm.set_pin_dirs(Direction::In, &[&data_pin]); | ||
| 87 | sm.set_pin_dirs(Direction::Out, &[&left_right_clock_pin, &bit_clock_pin]); | ||
| 88 | sm.set_enable(true); | ||
| 89 | |||
| 90 | Self { dma: dma.into(), sm } | ||
| 91 | } | ||
| 92 | |||
| 93 | /// Return an in-prograss dma transfer future. Awaiting it will guarentee a complete transfer. | ||
| 94 | pub fn read<'b>(&'b mut self, buff: &'b mut [u32]) -> Transfer<'b, AnyChannel> { | ||
| 95 | self.sm.rx().dma_pull(self.dma.reborrow(), buff, false) | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 11 | /// This struct represents an i2s output driver program | 99 | /// This struct represents an i2s output driver program |
| 100 | /// | ||
| 101 | /// The sample bit-depth is set through scratch register `Y`. | ||
| 102 | /// `Y` has to be set to sample bit-depth - 2. | ||
| 103 | /// (14 = 16bit, 22 = 24bit, 30 = 32bit) | ||
| 12 | pub struct PioI2sOutProgram<'d, PIO: Instance> { | 104 | pub struct PioI2sOutProgram<'d, PIO: Instance> { |
| 13 | prg: LoadedProgram<'d, PIO>, | 105 | prg: LoadedProgram<'d, PIO>, |
| 14 | } | 106 | } |
| @@ -17,17 +109,17 @@ impl<'d, PIO: Instance> PioI2sOutProgram<'d, PIO> { | |||
| 17 | /// Load the program into the given pio | 109 | /// Load the program into the given pio |
| 18 | pub fn new(common: &mut Common<'d, PIO>) -> Self { | 110 | pub fn new(common: &mut Common<'d, PIO>) -> Self { |
| 19 | let prg = pio::pio_asm!( | 111 | let prg = pio::pio_asm!( |
| 20 | ".side_set 2", | 112 | ".side_set 2", // side 0bWB - W = Word Clock, B = Bit Clock |
| 21 | " set x, 14 side 0b01", // side 0bWB - W = Word Clock, B = Bit Clock | 113 | " mov x, y side 0b01", // y stores sample depth - 2 (14 = 16bit, 22 = 24bit, 30 = 32bit) |
| 22 | "left_data:", | 114 | "left_data:", |
| 23 | " out pins, 1 side 0b00", | 115 | " out pins, 1 side 0b00", |
| 24 | " jmp x-- left_data side 0b01", | 116 | " jmp x-- left_data side 0b01", |
| 25 | " out pins 1 side 0b10", | 117 | " out pins, 1 side 0b10", |
| 26 | " set x, 14 side 0b11", | 118 | " mov x, y side 0b11", |
| 27 | "right_data:", | 119 | "right_data:", |
| 28 | " out pins 1 side 0b10", | 120 | " out pins, 1 side 0b10", |
| 29 | " jmp x-- right_data side 0b11", | 121 | " jmp x-- right_data side 0b11", |
| 30 | " out pins 1 side 0b00", | 122 | " out pins, 1 side 0b00", |
| 31 | ); | 123 | ); |
| 32 | 124 | ||
| 33 | let prg = common.load_program(&prg.program); | 125 | let prg = common.load_program(&prg.program); |
| @@ -53,7 +145,6 @@ impl<'d, P: Instance, const S: usize> PioI2sOut<'d, P, S> { | |||
| 53 | lr_clock_pin: Peri<'d, impl PioPin>, | 145 | lr_clock_pin: Peri<'d, impl PioPin>, |
| 54 | sample_rate: u32, | 146 | sample_rate: u32, |
| 55 | bit_depth: u32, | 147 | bit_depth: u32, |
| 56 | channels: u32, | ||
| 57 | program: &PioI2sOutProgram<'d, P>, | 148 | program: &PioI2sOutProgram<'d, P>, |
| 58 | ) -> Self { | 149 | ) -> Self { |
| 59 | let data_pin = common.make_pio_pin(data_pin); | 150 | let data_pin = common.make_pio_pin(data_pin); |
| @@ -64,7 +155,7 @@ impl<'d, P: Instance, const S: usize> PioI2sOut<'d, P, S> { | |||
| 64 | let mut cfg = Config::default(); | 155 | let mut cfg = Config::default(); |
| 65 | cfg.use_program(&program.prg, &[&bit_clock_pin, &left_right_clock_pin]); | 156 | cfg.use_program(&program.prg, &[&bit_clock_pin, &left_right_clock_pin]); |
| 66 | cfg.set_out_pins(&[&data_pin]); | 157 | cfg.set_out_pins(&[&data_pin]); |
| 67 | let clock_frequency = sample_rate * bit_depth * channels; | 158 | let clock_frequency = sample_rate * bit_depth * 2; |
| 68 | cfg.clock_divider = (crate::clocks::clk_sys_freq() as f64 / clock_frequency as f64 / 2.).to_fixed(); | 159 | cfg.clock_divider = (crate::clocks::clk_sys_freq() as f64 / clock_frequency as f64 / 2.).to_fixed(); |
| 69 | cfg.shift_out = ShiftConfig { | 160 | cfg.shift_out = ShiftConfig { |
| 70 | threshold: 32, | 161 | threshold: 32, |
| @@ -78,6 +169,11 @@ impl<'d, P: Instance, const S: usize> PioI2sOut<'d, P, S> { | |||
| 78 | sm.set_config(&cfg); | 169 | sm.set_config(&cfg); |
| 79 | sm.set_pin_dirs(Direction::Out, &[&data_pin, &left_right_clock_pin, &bit_clock_pin]); | 170 | sm.set_pin_dirs(Direction::Out, &[&data_pin, &left_right_clock_pin, &bit_clock_pin]); |
| 80 | 171 | ||
| 172 | // Set the `y` register up to configure the sample depth | ||
| 173 | // The SM counts down to 0 and uses one clock cycle to set up the counter, | ||
| 174 | // which results in bit_depth - 2 as register value. | ||
| 175 | unsafe { sm.set_y(bit_depth - 2) }; | ||
| 176 | |||
| 81 | sm.set_enable(true); | 177 | sm.set_enable(true); |
| 82 | 178 | ||
| 83 | Self { dma: dma.into(), sm } | 179 | Self { dma: dma.into(), sm } |
