From 4f29e7515f42966d07332e0701f2d3976d477bd7 Mon Sep 17 00:00:00 2001 From: Asaril <pub@asaril.de> Date: Fri, 21 Mar 2025 22:27:53 +0100 Subject: [PATCH] use triple buffer to decouple tasks --- src/audio_input.rs | 48 +++++++++++++++++++++----------------- src/audio_process.rs | 9 ++----- src/effects/bass_sparks.rs | 21 ++++++++++------- src/effects/led_effect.rs | 20 +++++++++++----- src/led_output.rs | 24 +++++++++++-------- src/main.rs | 41 +++++++++++++++----------------- 6 files changed, 87 insertions(+), 76 deletions(-) diff --git a/src/audio_input.rs b/src/audio_input.rs index f006f78..a7ac0bc 100644 --- a/src/audio_input.rs +++ b/src/audio_input.rs @@ -1,45 +1,49 @@ use bytemuck::bytes_of_mut; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_time::Duration; use esp_idf_svc::hal::{ gpio::{AnyIOPin, Gpio4, Gpio5, Gpio6}, - i2s::{self, I2sRx, I2S0}, + i2s::{config, I2sDriver, I2sRx, I2S0}, }; -use crate::{audio_process::AudioBuffer, config::AUDIO_SAMPLES_PER_BUF}; +use crate::{audio_process::AudioBuffer, config::AUDIO_SAMPLES_PER_BUF, triple_buffer::Sender}; #[embassy_executor::task] -pub async fn mic_input_task(i2s_per: I2S0, sd: Gpio5, sclk: Gpio4, ws: Gpio6) { - let std_cfg = i2s::config::StdConfig::new( - i2s::config::Config::new().role(i2s::config::Role::Controller), - i2s::config::StdClkConfig::new( +pub async fn mic_input_task( + i2s_per: I2S0, + sd: Gpio5, + sclk: Gpio4, + ws: Gpio6, + mut sender: Sender<'static, NoopRawMutex, AudioBuffer>, +) { + let std_cfg = config::StdConfig::new( + config::Config::new().role(config::Role::Controller), + config::StdClkConfig::new( 24000, - i2s::config::ClockSource::default(), - i2s::config::MclkMultiple::M256, + config::ClockSource::default(), + config::MclkMultiple::M256, ), - i2s::config::StdSlotConfig::msb_slot_default( - i2s::config::DataBitWidth::Bits32, - i2s::config::SlotMode::Stereo, - ) - .data_bit_width(i2s::config::DataBitWidth::Bits32) - .slot_bit_width(i2s::config::SlotBitWidth::Bits32) - .slot_mode_mask( - i2s::config::SlotMode::Stereo, - i2s::config::StdSlotMask::Both, + config::StdSlotConfig::msb_slot_default( + config::DataBitWidth::Bits32, + config::SlotMode::Stereo, ) + .data_bit_width(config::DataBitWidth::Bits32) + .slot_bit_width(config::SlotBitWidth::Bits32) + .slot_mode_mask(config::SlotMode::Stereo, config::StdSlotMask::Both) .ws_width(32) .ws_polarity(false) .bit_shift(true) .left_align(true) .big_endian(false) .bit_order_lsb(false), - i2s::config::StdGpioConfig::new(false, false, false), + config::StdGpioConfig::new(false, false, false), ); let mut mic_drv = - i2s::I2sDriver::new_std_rx(i2s_per, &std_cfg, sclk, sd, AnyIOPin::none(), ws).unwrap(); + I2sDriver::new_std_rx(i2s_per, &std_cfg, sclk, sd, AnyIOPin::none(), ws).unwrap(); mic_drv.rx_enable().expect("rx not enabled"); - async fn ignore_mic_startup(mic: &mut i2s::I2sDriver<'_, I2sRx>) { + async fn ignore_mic_startup(mic: &mut I2sDriver<'_, I2sRx>) { let mut tmp_buf: [u8; 128] = [0; 128]; loop { mic.read_async(tmp_buf.as_mut_slice()).await.unwrap(); @@ -51,7 +55,7 @@ pub async fn mic_input_task(i2s_per: I2S0, sd: Gpio5, sclk: Gpio4, ws: Gpio6) { .expect_err("ignore died early"); loop { - let audio_in_buf: &mut AudioBuffer = audio_sender.send().await; + let audio_in_buf: &mut AudioBuffer = sender.send().await; let buffer = bytes_of_mut(audio_in_buf); let mut total_bytes_read: usize = 0; @@ -68,6 +72,6 @@ pub async fn mic_input_task(i2s_per: I2S0, sd: Gpio5, sclk: Gpio4, ws: Gpio6) { log::warn!("buffer underflow: {}", total_bytes_read); } - audio_sender.send_done(); + sender.send_done(); } } diff --git a/src/audio_process.rs b/src/audio_process.rs index c7da8a3..aafe6be 100644 --- a/src/audio_process.rs +++ b/src/audio_process.rs @@ -1,11 +1,11 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embassy_sync::zerocopy_channel::{Receiver, Sender}; use esp_idf_svc::sys::{esp_dsp, esp_nofail}; use static_cell::StaticCell; use crate::config::{AUDIO_BANDS, AUDIO_SAMPLES_PER_BUF}; use crate::helpers::{falloff, falloff_f}; +use crate::triple_buffer::{Receiver, Sender}; pub type AudioBuffer = [i32; AUDIO_SAMPLES_PER_BUF]; pub type DspBuffer = [f32; AUDIO_SAMPLES_PER_BUF]; @@ -129,11 +129,7 @@ pub async fn process_audio( let mut processor = AudioProcessor::default(); loop { - let in_buf = input.receive().await; - - if output.is_full() { - output.clear(); - } + let in_buf = input.receive_new().await; let (out_buf, stats) = output.send().await; @@ -141,6 +137,5 @@ pub async fn process_audio( *stats = processor.stats; output.send_done(); - input.receive_done(); } } diff --git a/src/effects/bass_sparks.rs b/src/effects/bass_sparks.rs index c6e2374..598745b 100644 --- a/src/effects/bass_sparks.rs +++ b/src/effects/bass_sparks.rs @@ -1,11 +1,10 @@ -use embassy_sync::zerocopy_channel::Sender; - -use embassy_sync::{blocking_mutex::raw::NoopRawMutex, zerocopy_channel::Receiver}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_time::{Duration, Ticker}; use crate::audio_process::{AudioStats, DspBuffer}; use crate::effects::led_effect::{LedColors, LedData, LedEffect, Rgbv}; use crate::helpers::random_at_most; +use crate::triple_buffer::{Receiver, Sender}; use crate::LED_COUNT; pub struct LedEffectBassSparks { @@ -19,7 +18,13 @@ impl Default for LedEffectBassSparks { } } impl LedEffect for LedEffectBassSparks { - fn render(&mut self, _: &DspBuffer, stats: &AudioStats, leds: &mut LedColors) -> Duration { + fn render( + &mut self, + _: bool, + _: &DspBuffer, + stats: &AudioStats, + leds: &mut LedColors, + ) -> Duration { if stats.floating_max > 10100000 && (stats.current_powers[0] > 1.25 * stats.avg_powers[0]) { self.bass_color = Rgbv::new(127, 0, 255, 4) } @@ -38,7 +43,7 @@ impl LedEffect for LedEffectBassSparks { } } - Duration::from_hz(20) + Duration::from_hz(50) } async fn process_led_effect( @@ -49,12 +54,10 @@ impl LedEffect for LedEffectBassSparks { let mut set_interval = Duration::from_hz(100); let mut ticker = Ticker::every(set_interval); loop { - let (fft, stats) = input.receive().await; + let ((fft, stats), was_new) = input.receive_cached().await; let leds = output.send().await; - let interval = self.render(fft, stats, &mut leds.leds); - - input.receive_done(); + let interval = self.render(was_new, fft, stats, &mut leds.leds); output.send_done(); if set_interval != interval { diff --git a/src/effects/led_effect.rs b/src/effects/led_effect.rs index 03204cf..a359386 100644 --- a/src/effects/led_effect.rs +++ b/src/effects/led_effect.rs @@ -1,15 +1,23 @@ use anyhow::{bail, Result}; use bytemuck::{Pod, Zeroable}; -use embassy_sync::{ - blocking_mutex::raw::NoopRawMutex, - zerocopy_channel::{Receiver, Sender}, -}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_time::Duration; -use crate::{audio_process::DspBuffer, config::LED_COUNT, AudioStats}; +use crate::{ + audio_process::DspBuffer, + config::LED_COUNT, + triple_buffer::{Receiver, Sender}, + AudioStats, +}; pub trait LedEffect { - fn render(&mut self, fft: &DspBuffer, stats: &AudioStats, leds: &mut LedColors) -> Duration; + fn render( + &mut self, + new_data: bool, + fft: &DspBuffer, + stats: &AudioStats, + leds: &mut LedColors, + ) -> Duration; fn process_led_effect( &mut self, diff --git a/src/led_output.rs b/src/led_output.rs index 6406fbf..b35fff0 100644 --- a/src/led_output.rs +++ b/src/led_output.rs @@ -1,8 +1,13 @@ -use embassy_sync::{blocking_mutex::raw::NoopRawMutex, zerocopy_channel::Receiver}; -use esp_idf_svc::hal::{gpio::{Gpio11, Gpio12}, spi::SPI2}; +use bytemuck::bytes_of; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use esp_idf_svc::hal::units::FromValueType; +use esp_idf_svc::hal::{ + gpio::{AnyIOPin, Gpio11, Gpio12}, + spi::{config, SpiDeviceDriver, SPI2}, +}; use crate::effects::led_effect::LedData; - +use crate::triple_buffer::Receiver; #[embassy_executor::task] pub async fn output_leds( @@ -11,27 +16,26 @@ pub async fn output_leds( sck: Gpio12, mut receiver: Receiver<'static, NoopRawMutex, LedData>, ) { - let mut led_drv = spi::SpiDeviceDriver::new_single( + let mut led_drv = SpiDeviceDriver::new_single( spi_per, sck, sdo, AnyIOPin::none(), AnyIOPin::none(), - &spi::config::DriverConfig::new(), - &spi::config::Config::new() + &config::DriverConfig::new(), + &config::Config::new() .baudrate(1.MHz().into()) - .data_mode(spi::config::MODE_3), + .data_mode(config::MODE_3), ) .unwrap(); loop { - let led_data = receiver.receive().await; + let led_data = receiver.receive_new().await; let output_buffer = bytes_of(led_data); led_drv .write_async(output_buffer) .await .expect("spi write failed"); - receiver.receive_done(); } -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index 0a47f88..eaa45bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,20 +12,18 @@ use esp_idf_svc::{ }; use embassy_executor::Spawner; -use embassy_sync::{ - blocking_mutex::raw::NoopRawMutex, - zerocopy_channel::{Channel, Receiver, Sender}, -}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; use static_cell::StaticCell; use audio_input::mic_input_task; use audio_process::{process_audio, AudioBuffer, AudioStats, DspBuffer}; -use config::{AUDIO_BUFFERS, AUDIO_SAMPLES_PER_BUF, LED_COUNT}; +use config::{AUDIO_SAMPLES_PER_BUF, LED_COUNT}; use effects::{ bass_sparks::LedEffectBassSparks, led_effect::{LedData, LedEffect}, }; use led_output::output_leds; +use triple_buffer::{Receiver, Sender, TripleBuffer}; #[embassy_executor::task] async fn effect_task( @@ -45,35 +43,33 @@ async fn main(spawner: Spawner) { // Bind the log crate to the ESP Logging facilities esp_idf_svc::log::EspLogger::initialize_default(); - // static PERIPHERALS: StaticCell<Peripherals> = StaticCell::new(); - // let peripherals = PERIPHERALS.init(Peripherals::take().unwrap()); let peripherals = Peripherals::take().unwrap(); // mic buffers - static AUDIO_BUFFER_CELL: StaticCell<[AudioBuffer; AUDIO_BUFFERS]> = StaticCell::new(); - let audio_buffer = AUDIO_BUFFER_CELL.init([[0; AUDIO_SAMPLES_PER_BUF]; AUDIO_BUFFERS]); + static AUDIO_BUFFER_CELL: StaticCell<[AudioBuffer; 3]> = StaticCell::new(); + let audio_buffer = AUDIO_BUFFER_CELL.init([[0; AUDIO_SAMPLES_PER_BUF]; 3]); - static AUDIO_CHANNEL_CELL: StaticCell<Channel<'_, NoopRawMutex, AudioBuffer>> = + static AUDIO_CHANNEL_CELL: StaticCell<TripleBuffer<'_, NoopRawMutex, AudioBuffer>> = StaticCell::new(); - let audio_channel = AUDIO_CHANNEL_CELL.init(Channel::new(audio_buffer)); - let (mut audio_sender, audio_receiver) = audio_channel.split(); + let audio_channel = AUDIO_CHANNEL_CELL.init(TripleBuffer::new(audio_buffer)); + let (audio_sender, audio_receiver) = audio_channel.split(); // fft buffers - static FFT_BUFFER_CELL: StaticCell<[(DspBuffer, AudioStats); AUDIO_BUFFERS]> = - StaticCell::new(); - let fft_buffer = FFT_BUFFER_CELL - .init([([0f32; AUDIO_SAMPLES_PER_BUF], AudioStats::default()); AUDIO_BUFFERS]); + static FFT_BUFFER_CELL: StaticCell<[(DspBuffer, AudioStats); 3]> = StaticCell::new(); + let fft_buffer = + FFT_BUFFER_CELL.init([([0f32; AUDIO_SAMPLES_PER_BUF], AudioStats::default()); 3]); - static FFT_CHANNEL_CELL: StaticCell<Channel<'_, NoopRawMutex, (DspBuffer, AudioStats)>> = + static FFT_CHANNEL_CELL: StaticCell<TripleBuffer<'_, NoopRawMutex, (DspBuffer, AudioStats)>> = StaticCell::new(); - let fft_channel = FFT_CHANNEL_CELL.init(Channel::new(fft_buffer)); + let fft_channel = FFT_CHANNEL_CELL.init(TripleBuffer::new(fft_buffer)); let (fft_sender, fft_receiver) = fft_channel.split(); // leds - static LED_DATA: StaticCell<[LedData; 1]> = StaticCell::new(); - let leds = LED_DATA.init([LedData::default(); 1]); - static LED_CHANNEL: StaticCell<Channel<'_, NoopRawMutex, LedData>> = StaticCell::new(); - let led_channel = LED_CHANNEL.init(Channel::new(leds)); + static LED_DATA: StaticCell<[LedData; 3]> = StaticCell::new(); + let leds = LED_DATA.init([LedData::default(); 3]); + + static LED_CHANNEL: StaticCell<TripleBuffer<'_, NoopRawMutex, LedData>> = StaticCell::new(); + let led_channel = LED_CHANNEL.init(TripleBuffer::new(leds)); let (led_sender, led_receiver) = led_channel.split(); unsafe { @@ -93,6 +89,7 @@ async fn main(spawner: Spawner) { peripherals.pins.gpio5, peripherals.pins.gpio4, peripherals.pins.gpio6, + audio_sender, )) .expect("spawn failed");