use triple buffer to decouple tasks
This commit is contained in:
parent
c93cdd7e1e
commit
4f29e7515f
6 changed files with 87 additions and 76 deletions
|
@ -1,45 +1,49 @@
|
||||||
use bytemuck::bytes_of_mut;
|
use bytemuck::bytes_of_mut;
|
||||||
|
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||||
use embassy_time::Duration;
|
use embassy_time::Duration;
|
||||||
use esp_idf_svc::hal::{
|
use esp_idf_svc::hal::{
|
||||||
gpio::{AnyIOPin, Gpio4, Gpio5, Gpio6},
|
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]
|
#[embassy_executor::task]
|
||||||
pub async fn mic_input_task(i2s_per: I2S0, sd: Gpio5, sclk: Gpio4, ws: Gpio6) {
|
pub async fn mic_input_task(
|
||||||
let std_cfg = i2s::config::StdConfig::new(
|
i2s_per: I2S0,
|
||||||
i2s::config::Config::new().role(i2s::config::Role::Controller),
|
sd: Gpio5,
|
||||||
i2s::config::StdClkConfig::new(
|
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,
|
24000,
|
||||||
i2s::config::ClockSource::default(),
|
config::ClockSource::default(),
|
||||||
i2s::config::MclkMultiple::M256,
|
config::MclkMultiple::M256,
|
||||||
),
|
),
|
||||||
i2s::config::StdSlotConfig::msb_slot_default(
|
config::StdSlotConfig::msb_slot_default(
|
||||||
i2s::config::DataBitWidth::Bits32,
|
config::DataBitWidth::Bits32,
|
||||||
i2s::config::SlotMode::Stereo,
|
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,
|
|
||||||
)
|
)
|
||||||
|
.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_width(32)
|
||||||
.ws_polarity(false)
|
.ws_polarity(false)
|
||||||
.bit_shift(true)
|
.bit_shift(true)
|
||||||
.left_align(true)
|
.left_align(true)
|
||||||
.big_endian(false)
|
.big_endian(false)
|
||||||
.bit_order_lsb(false),
|
.bit_order_lsb(false),
|
||||||
i2s::config::StdGpioConfig::new(false, false, false),
|
config::StdGpioConfig::new(false, false, false),
|
||||||
);
|
);
|
||||||
let mut mic_drv =
|
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");
|
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];
|
let mut tmp_buf: [u8; 128] = [0; 128];
|
||||||
loop {
|
loop {
|
||||||
mic.read_async(tmp_buf.as_mut_slice()).await.unwrap();
|
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");
|
.expect_err("ignore died early");
|
||||||
|
|
||||||
loop {
|
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 buffer = bytes_of_mut(audio_in_buf);
|
||||||
let mut total_bytes_read: usize = 0;
|
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);
|
log::warn!("buffer underflow: {}", total_bytes_read);
|
||||||
}
|
}
|
||||||
|
|
||||||
audio_sender.send_done();
|
sender.send_done();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||||
|
|
||||||
use embassy_sync::zerocopy_channel::{Receiver, Sender};
|
|
||||||
use esp_idf_svc::sys::{esp_dsp, esp_nofail};
|
use esp_idf_svc::sys::{esp_dsp, esp_nofail};
|
||||||
use static_cell::StaticCell;
|
use static_cell::StaticCell;
|
||||||
|
|
||||||
use crate::config::{AUDIO_BANDS, AUDIO_SAMPLES_PER_BUF};
|
use crate::config::{AUDIO_BANDS, AUDIO_SAMPLES_PER_BUF};
|
||||||
use crate::helpers::{falloff, falloff_f};
|
use crate::helpers::{falloff, falloff_f};
|
||||||
|
use crate::triple_buffer::{Receiver, Sender};
|
||||||
|
|
||||||
pub type AudioBuffer = [i32; AUDIO_SAMPLES_PER_BUF];
|
pub type AudioBuffer = [i32; AUDIO_SAMPLES_PER_BUF];
|
||||||
pub type DspBuffer = [f32; 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();
|
let mut processor = AudioProcessor::default();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let in_buf = input.receive().await;
|
let in_buf = input.receive_new().await;
|
||||||
|
|
||||||
if output.is_full() {
|
|
||||||
output.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
let (out_buf, stats) = output.send().await;
|
let (out_buf, stats) = output.send().await;
|
||||||
|
|
||||||
|
@ -141,6 +137,5 @@ pub async fn process_audio(
|
||||||
*stats = processor.stats;
|
*stats = processor.stats;
|
||||||
|
|
||||||
output.send_done();
|
output.send_done();
|
||||||
input.receive_done();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use embassy_sync::zerocopy_channel::Sender;
|
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||||
|
|
||||||
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, zerocopy_channel::Receiver};
|
|
||||||
use embassy_time::{Duration, Ticker};
|
use embassy_time::{Duration, Ticker};
|
||||||
|
|
||||||
use crate::audio_process::{AudioStats, DspBuffer};
|
use crate::audio_process::{AudioStats, DspBuffer};
|
||||||
use crate::effects::led_effect::{LedColors, LedData, LedEffect, Rgbv};
|
use crate::effects::led_effect::{LedColors, LedData, LedEffect, Rgbv};
|
||||||
use crate::helpers::random_at_most;
|
use crate::helpers::random_at_most;
|
||||||
|
use crate::triple_buffer::{Receiver, Sender};
|
||||||
use crate::LED_COUNT;
|
use crate::LED_COUNT;
|
||||||
|
|
||||||
pub struct LedEffectBassSparks {
|
pub struct LedEffectBassSparks {
|
||||||
|
@ -19,7 +18,13 @@ impl Default for LedEffectBassSparks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl LedEffect 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]) {
|
if stats.floating_max > 10100000 && (stats.current_powers[0] > 1.25 * stats.avg_powers[0]) {
|
||||||
self.bass_color = Rgbv::new(127, 0, 255, 4)
|
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(
|
async fn process_led_effect(
|
||||||
|
@ -49,12 +54,10 @@ impl LedEffect for LedEffectBassSparks {
|
||||||
let mut set_interval = Duration::from_hz(100);
|
let mut set_interval = Duration::from_hz(100);
|
||||||
let mut ticker = Ticker::every(set_interval);
|
let mut ticker = Ticker::every(set_interval);
|
||||||
loop {
|
loop {
|
||||||
let (fft, stats) = input.receive().await;
|
let ((fft, stats), was_new) = input.receive_cached().await;
|
||||||
let leds = output.send().await;
|
let leds = output.send().await;
|
||||||
|
|
||||||
let interval = self.render(fft, stats, &mut leds.leds);
|
let interval = self.render(was_new, fft, stats, &mut leds.leds);
|
||||||
|
|
||||||
input.receive_done();
|
|
||||||
output.send_done();
|
output.send_done();
|
||||||
|
|
||||||
if set_interval != interval {
|
if set_interval != interval {
|
||||||
|
|
|
@ -1,15 +1,23 @@
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
use embassy_sync::{
|
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||||
blocking_mutex::raw::NoopRawMutex,
|
|
||||||
zerocopy_channel::{Receiver, Sender},
|
|
||||||
};
|
|
||||||
use embassy_time::Duration;
|
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 {
|
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(
|
fn process_led_effect(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, zerocopy_channel::Receiver};
|
use bytemuck::bytes_of;
|
||||||
use esp_idf_svc::hal::{gpio::{Gpio11, Gpio12}, spi::SPI2};
|
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::effects::led_effect::LedData;
|
||||||
|
use crate::triple_buffer::Receiver;
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
pub async fn output_leds(
|
pub async fn output_leds(
|
||||||
|
@ -11,27 +16,26 @@ pub async fn output_leds(
|
||||||
sck: Gpio12,
|
sck: Gpio12,
|
||||||
mut receiver: Receiver<'static, NoopRawMutex, LedData>,
|
mut receiver: Receiver<'static, NoopRawMutex, LedData>,
|
||||||
) {
|
) {
|
||||||
let mut led_drv = spi::SpiDeviceDriver::new_single(
|
let mut led_drv = SpiDeviceDriver::new_single(
|
||||||
spi_per,
|
spi_per,
|
||||||
sck,
|
sck,
|
||||||
sdo,
|
sdo,
|
||||||
AnyIOPin::none(),
|
AnyIOPin::none(),
|
||||||
AnyIOPin::none(),
|
AnyIOPin::none(),
|
||||||
&spi::config::DriverConfig::new(),
|
&config::DriverConfig::new(),
|
||||||
&spi::config::Config::new()
|
&config::Config::new()
|
||||||
.baudrate(1.MHz().into())
|
.baudrate(1.MHz().into())
|
||||||
.data_mode(spi::config::MODE_3),
|
.data_mode(config::MODE_3),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let led_data = receiver.receive().await;
|
let led_data = receiver.receive_new().await;
|
||||||
|
|
||||||
let output_buffer = bytes_of(led_data);
|
let output_buffer = bytes_of(led_data);
|
||||||
led_drv
|
led_drv
|
||||||
.write_async(output_buffer)
|
.write_async(output_buffer)
|
||||||
.await
|
.await
|
||||||
.expect("spi write failed");
|
.expect("spi write failed");
|
||||||
receiver.receive_done();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
41
src/main.rs
41
src/main.rs
|
@ -12,20 +12,18 @@ use esp_idf_svc::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_sync::{
|
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||||
blocking_mutex::raw::NoopRawMutex,
|
|
||||||
zerocopy_channel::{Channel, Receiver, Sender},
|
|
||||||
};
|
|
||||||
use static_cell::StaticCell;
|
use static_cell::StaticCell;
|
||||||
|
|
||||||
use audio_input::mic_input_task;
|
use audio_input::mic_input_task;
|
||||||
use audio_process::{process_audio, AudioBuffer, AudioStats, DspBuffer};
|
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::{
|
use effects::{
|
||||||
bass_sparks::LedEffectBassSparks,
|
bass_sparks::LedEffectBassSparks,
|
||||||
led_effect::{LedData, LedEffect},
|
led_effect::{LedData, LedEffect},
|
||||||
};
|
};
|
||||||
use led_output::output_leds;
|
use led_output::output_leds;
|
||||||
|
use triple_buffer::{Receiver, Sender, TripleBuffer};
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
async fn effect_task(
|
async fn effect_task(
|
||||||
|
@ -45,35 +43,33 @@ async fn main(spawner: Spawner) {
|
||||||
// Bind the log crate to the ESP Logging facilities
|
// Bind the log crate to the ESP Logging facilities
|
||||||
esp_idf_svc::log::EspLogger::initialize_default();
|
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();
|
let peripherals = Peripherals::take().unwrap();
|
||||||
|
|
||||||
// mic buffers
|
// mic buffers
|
||||||
static AUDIO_BUFFER_CELL: StaticCell<[AudioBuffer; AUDIO_BUFFERS]> = StaticCell::new();
|
static AUDIO_BUFFER_CELL: StaticCell<[AudioBuffer; 3]> = StaticCell::new();
|
||||||
let audio_buffer = AUDIO_BUFFER_CELL.init([[0; AUDIO_SAMPLES_PER_BUF]; AUDIO_BUFFERS]);
|
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();
|
StaticCell::new();
|
||||||
let audio_channel = AUDIO_CHANNEL_CELL.init(Channel::new(audio_buffer));
|
let audio_channel = AUDIO_CHANNEL_CELL.init(TripleBuffer::new(audio_buffer));
|
||||||
let (mut audio_sender, audio_receiver) = audio_channel.split();
|
let (audio_sender, audio_receiver) = audio_channel.split();
|
||||||
|
|
||||||
// fft buffers
|
// fft buffers
|
||||||
static FFT_BUFFER_CELL: StaticCell<[(DspBuffer, AudioStats); AUDIO_BUFFERS]> =
|
static FFT_BUFFER_CELL: StaticCell<[(DspBuffer, AudioStats); 3]> = StaticCell::new();
|
||||||
StaticCell::new();
|
let fft_buffer =
|
||||||
let fft_buffer = FFT_BUFFER_CELL
|
FFT_BUFFER_CELL.init([([0f32; AUDIO_SAMPLES_PER_BUF], AudioStats::default()); 3]);
|
||||||
.init([([0f32; AUDIO_SAMPLES_PER_BUF], AudioStats::default()); AUDIO_BUFFERS]);
|
|
||||||
|
|
||||||
static FFT_CHANNEL_CELL: StaticCell<Channel<'_, NoopRawMutex, (DspBuffer, AudioStats)>> =
|
static FFT_CHANNEL_CELL: StaticCell<TripleBuffer<'_, NoopRawMutex, (DspBuffer, AudioStats)>> =
|
||||||
StaticCell::new();
|
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();
|
let (fft_sender, fft_receiver) = fft_channel.split();
|
||||||
|
|
||||||
// leds
|
// leds
|
||||||
static LED_DATA: StaticCell<[LedData; 1]> = StaticCell::new();
|
static LED_DATA: StaticCell<[LedData; 3]> = StaticCell::new();
|
||||||
let leds = LED_DATA.init([LedData::default(); 1]);
|
let leds = LED_DATA.init([LedData::default(); 3]);
|
||||||
static LED_CHANNEL: StaticCell<Channel<'_, NoopRawMutex, LedData>> = StaticCell::new();
|
|
||||||
let led_channel = LED_CHANNEL.init(Channel::new(leds));
|
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();
|
let (led_sender, led_receiver) = led_channel.split();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -93,6 +89,7 @@ async fn main(spawner: Spawner) {
|
||||||
peripherals.pins.gpio5,
|
peripherals.pins.gpio5,
|
||||||
peripherals.pins.gpio4,
|
peripherals.pins.gpio4,
|
||||||
peripherals.pins.gpio6,
|
peripherals.pins.gpio6,
|
||||||
|
audio_sender,
|
||||||
))
|
))
|
||||||
.expect("spawn failed");
|
.expect("spawn failed");
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue