This commit is contained in:
2025-10-01 13:36:25 -06:00
parent d8a5ab465e
commit 4c63f77c24
3 changed files with 139 additions and 39 deletions

View File

@@ -1,17 +1,17 @@
use core::{
sync::atomic::{AtomicBool, Ordering},
time::Duration,
};
use crate::Audio;
use core::sync::atomic::{AtomicBool, Ordering};
use embassy_rp::{
Peri,
pio::Pio,
pio_programs::pwm::{PioPwm, PioPwmProgram},
pwm::{Config, Pwm, SetDutyCycle},
dma::{AnyChannel, Channel},
gpio::Level,
pio::{
Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig,
ShiftDirection, StateMachine, program::pio_asm,
},
pio_programs::clock_divider::calculate_pio_clock_divider,
};
use embassy_time::Timer;
use crate::{Audio, Irqs};
const AUDIO_BUFFER_LEN: usize = 1024;
const _: () = assert!(AUDIO_BUFFER_LEN == abi_sys::AUDIO_BUFFER_LEN);
pub static mut AUDIO_BUFFER: [u8; AUDIO_BUFFER_LEN] = [0; AUDIO_BUFFER_LEN];
@@ -19,34 +19,119 @@ static mut AUDIO_BUFFER_1: [u8; AUDIO_BUFFER_LEN] = [0; AUDIO_BUFFER_LEN];
pub static AUDIO_BUFFER_READY: AtomicBool = AtomicBool::new(true);
pub const SAMPLE_RATE_HZ: u32 = 22_050;
pub const SAMPLE_RATE_HZ: u32 = 8_000;
#[embassy_executor::task]
pub async fn audio_handler(audio: Audio) {
let var_name = Pio::new(audio.pio_left, Irqs);
let Pio {
mut common, sm0, ..
} = var_name;
pub async fn audio_handler(mut audio: Audio) {
let prg = PioPwmAudioProgram8Bit::new(&mut audio.pio);
defmt::info!("loaded prg");
let prg = PioPwmProgram::new(&mut common);
let mut pwm_pio = PioPwm::new(&mut common, sm0, audio.left, &prg);
let period = Duration::from_nanos(1_000_000_000 / SAMPLE_RATE_HZ as u64);
pwm_pio.set_period(period);
let mut pwm_pio = PioPwmAudio::new(audio.dma, &mut audio.pio, audio.sm0, audio.left, &prg);
defmt::info!("cfgd sm");
pwm_pio.configure(SAMPLE_RATE_HZ);
pwm_pio.start();
let sample_interval = 1_000_000 / SAMPLE_RATE_HZ as u64; // in µs ≈ 45 µs
loop {
for &sample in unsafe { &AUDIO_BUFFER }.iter() {
let period_ns = period.as_nanos() as u32;
let duty_ns = period_ns * (sample as u32) / 255;
pwm_pio.write(Duration::from_nanos(duty_ns as u64));
Timer::after_micros(sample_interval).await; // sample interval = 1 / sample rate
}
pwm_pio.write(unsafe { &AUDIO_BUFFER_1 }).await;
unsafe { core::mem::swap(&mut AUDIO_BUFFER, &mut AUDIO_BUFFER_1) };
AUDIO_BUFFER_READY.store(true, Ordering::Release)
}
}
struct PioPwmAudioProgram8Bit<'d, PIO: Instance>(LoadedProgram<'d, PIO>);
impl<'d, PIO: Instance> PioPwmAudioProgram8Bit<'d, PIO> {
fn new(common: &mut Common<'d, PIO>) -> Self {
let prg = pio_asm!(
"out x, 8", // samples <<
"out y, 8", // samples max >>
"loop_high:",
"set pins, 1", // keep pin high
"jmp x-- loop_high", // decrement X until 0
"loop_low:",
"set pins, 0", // keep pin low
"jmp y-- loop_low", // decrement Y until 0
);
let prg = common.load_program(&prg.program);
Self(prg)
}
}
struct PioPwmAudio<'d, PIO: Instance, const SM: usize> {
dma: Peri<'d, AnyChannel>,
cfg: Config<'d, PIO>,
sm: StateMachine<'d, PIO, SM>,
}
impl<'d, PIO: Instance, const SM: usize> PioPwmAudio<'d, PIO, SM> {
fn new(
dma: Peri<'d, impl Channel>,
pio: &mut Common<'d, PIO>,
mut sm: StateMachine<'d, PIO, SM>,
pin: Peri<'d, impl PioPin>,
prg: &PioPwmAudioProgram8Bit<'d, PIO>,
) -> Self {
let pin = pio.make_pio_pin(pin);
sm.set_pins(Level::High, &[&pin]);
sm.set_pin_dirs(Direction::Out, &[&pin]);
let mut cfg = Config::default();
cfg.set_set_pins(&[&pin]);
cfg.fifo_join = FifoJoin::TxOnly;
let mut shift_cfg = ShiftConfig::default();
shift_cfg.auto_fill = true;
cfg.shift_out = shift_cfg;
cfg.use_program(&prg.0, &[]);
sm.set_config(&cfg);
Self {
dma: dma.into(),
cfg,
sm,
}
}
fn configure(&mut self, sample_rate: u32) {
let cycles_per_sample = u8::MAX as u32 + 2; // X_max + Y_max + movs
let target_pio_hz = cycles_per_sample * sample_rate; // ~11.3 MHz
let divider = calculate_pio_clock_divider(target_pio_hz);
self.cfg.clock_divider = divider;
self.sm.set_clock_divider(divider);
}
async fn write(&mut self, buf: &[u8]) {
let mut packed_buf = [0_u32; AUDIO_BUFFER_LEN / 4];
for (packed_sample, sample) in packed_buf.iter_mut().zip(buf.chunks(2)) {
*packed_sample = pack_two_samples(sample[0], sample[1]);
}
self.sm
.tx()
.dma_push(self.dma.reborrow(), &packed_buf, false)
.await
}
fn start(&mut self) {
self.sm.set_enable(true);
}
fn stop(&mut self) {
self.sm.set_enable(false);
}
}
fn pack_two_samples(s1: u8, s2: u8) -> u32 {
let x = ((s1 as u16) << 8 | (255 - s1) as u16); // original
let y = ((s2 as u16) << 8 | (255 - s2) as u16);
// Scale to full 16-bit for higher volume:
let x_scaled = ((x as u32) << 8) | (x as u32 >> 8);
let y_scaled = ((y as u32) << 8) | (y as u32 >> 8);
(x_scaled << 16) | y_scaled
}

View File

@@ -49,11 +49,10 @@ use embassy_rp::{
i2c::{self, I2c},
multicore::{Stack, spawn_core1},
peripherals::{
DMA_CH0, DMA_CH1, I2C1, PIN_6, PIN_7, PIN_10, PIN_11, PIN_12, PIN_13, PIN_14, PIN_15,
PIN_16, PIN_17, PIN_18, PIN_19, PIN_22, PIN_26, PIN_27, PIO0, PIO1, PWM_SLICE5, SPI0, SPI1,
USB,
DMA_CH0, DMA_CH1, DMA_CH3, I2C1, PIN_6, PIN_7, PIN_10, PIN_11, PIN_12, PIN_13, PIN_14,
PIN_15, PIN_16, PIN_17, PIN_18, PIN_19, PIN_22, PIN_26, PIN_27, PIO0, SPI0, SPI1, USB,
},
pio,
pio::{self, Common, Pio, StateMachine},
spi::{self, Spi},
usb as embassy_rp_usb,
};
@@ -110,9 +109,15 @@ async fn main(_spawner: Spawner) {
data: p.PIN_14,
reset: p.PIN_15,
};
let Pio {
common, sm0, sm1, ..
} = Pio::new(p.PIO0, Irqs);
let audio = Audio {
pio_left: p.PIO0,
pio_right: p.PIO1,
dma: p.DMA_CH3,
pio: common,
sm0,
sm1,
left: p.PIN_26,
right: p.PIN_27,
};
@@ -182,8 +187,10 @@ struct Display {
reset: Peri<'static, PIN_15>,
}
struct Audio {
pio_left: Peri<'static, PIO0>,
pio_right: Peri<'static, PIO1>,
dma: Peri<'static, DMA_CH3>,
pio: Common<'static, PIO0>,
sm0: StateMachine<'static, PIO0, 0>,
sm1: StateMachine<'static, PIO0, 1>,
left: Peri<'static, PIN_26>,
right: Peri<'static, PIN_27>,
}
@@ -250,7 +257,6 @@ async fn kernel_task(
usb: Peri<'static, USB>,
) {
setup_mcu(mcu).await;
Timer::after_millis(250).await;
setup_display(display, spawner).await;
setup_sd(sd).await;