mirror of
https://github.com/LegitCamper/picocalc-os-rs.git
synced 2025-12-27 07:45:28 +00:00
audio works
This commit is contained in:
@@ -82,6 +82,8 @@ embedded-graphics = { version = "0.8.1" }
|
||||
embedded-text = "0.7.2"
|
||||
embedded-layout = "0.4.2"
|
||||
|
||||
micromath = "2.1.0"
|
||||
fixed = "1.29.0"
|
||||
strum = { version = "0.27.2", default-features = false }
|
||||
rand = { version = "0.9.0", default-features = false }
|
||||
once_cell = { version = "1.21.3", default-features = false }
|
||||
@@ -98,4 +100,3 @@ embedded-alloc = { version = "0.6.0", features = [
|
||||
bumpalo = "3.19.0"
|
||||
|
||||
abi_sys = { path = "../abi_sys" }
|
||||
micromath = "2.1.0"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use abi_sys::{
|
||||
AUDIO_BUFFER_LEN, AllocAbi, AudioBufferReady, CLayout, CPixel, DeallocAbi, DrawIterAbi,
|
||||
AUDIO_BUFFER_SAMPLES, AllocAbi, AudioBufferReady, CLayout, CPixel, DeallocAbi, DrawIterAbi,
|
||||
FileLen, GenRand, GetMsAbi, ListDir, PrintAbi, ReadFile, RngRequest, SendAudioBuffer,
|
||||
SleepMsAbi, WriteFile, keyboard::*,
|
||||
};
|
||||
@@ -209,7 +209,6 @@ fn recurse_file<T>(
|
||||
dirs: &[&str],
|
||||
mut access: impl FnMut(&mut File) -> T,
|
||||
) -> Result<T, ()> {
|
||||
defmt::info!("dir: {}, dirs: {}", dir, dirs);
|
||||
if dirs.len() == 1 {
|
||||
let mut b = [0_u8; 50];
|
||||
let mut buf = LfnBuffer::new(&mut b);
|
||||
@@ -346,8 +345,15 @@ pub extern "C" fn send_audio_buffer(ptr: *const u8, len: usize) {
|
||||
|
||||
while !AUDIO_BUFFER_READY.load(Ordering::Acquire) {}
|
||||
|
||||
if buf.len() == AUDIO_BUFFER_LEN {
|
||||
if buf.len() == AUDIO_BUFFER_SAMPLES * 2 {
|
||||
AUDIO_BUFFER_READY.store(false, Ordering::Release);
|
||||
unsafe { AUDIO_BUFFER.copy_from_slice(buf) };
|
||||
AUDIO_BUFFER_READY.store(false, Ordering::Release)
|
||||
} else {
|
||||
#[cfg(feature = "defmt")]
|
||||
defmt::warn!(
|
||||
"user audio stream was wrong size: {} should be {}",
|
||||
buf.len(),
|
||||
AUDIO_BUFFER_SAMPLES * 2
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +1,92 @@
|
||||
use crate::Audio;
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
use embassy_futures::join::join;
|
||||
use embassy_rp::{
|
||||
Peri,
|
||||
clocks::clk_sys_freq,
|
||||
dma::{AnyChannel, Channel},
|
||||
gpio::Level,
|
||||
pio::{
|
||||
Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig,
|
||||
ShiftDirection, StateMachine, program::pio_asm,
|
||||
StateMachine, program::pio_asm,
|
||||
},
|
||||
pio_programs::clock_divider::calculate_pio_clock_divider,
|
||||
};
|
||||
use embassy_time::Timer;
|
||||
use fixed::traits::ToFixed;
|
||||
|
||||
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];
|
||||
static mut AUDIO_BUFFER_1: [u8; AUDIO_BUFFER_LEN] = [0; AUDIO_BUFFER_LEN];
|
||||
pub const SAMPLE_RATE_HZ: u32 = 22_050;
|
||||
const AUDIO_BUFFER_SAMPLES: usize = 1024;
|
||||
const _: () = assert!(AUDIO_BUFFER_SAMPLES == abi_sys::AUDIO_BUFFER_SAMPLES);
|
||||
|
||||
// 8bit stereo interleaved PCM audio buffers
|
||||
pub static mut AUDIO_BUFFER: [u8; AUDIO_BUFFER_SAMPLES * 2] = [0; AUDIO_BUFFER_SAMPLES * 2];
|
||||
static mut AUDIO_BUFFER_1: [u8; AUDIO_BUFFER_SAMPLES * 2] = [0; AUDIO_BUFFER_SAMPLES * 2];
|
||||
|
||||
pub static AUDIO_BUFFER_READY: AtomicBool = AtomicBool::new(true);
|
||||
|
||||
pub const SAMPLE_RATE_HZ: u32 = 8_000;
|
||||
|
||||
#[embassy_executor::task]
|
||||
pub async fn audio_handler(mut audio: Audio) {
|
||||
const SILENCE_VALUE: u8 = u8::MAX / 2;
|
||||
|
||||
let prg = PioPwmAudioProgram8Bit::new(&mut audio.pio);
|
||||
defmt::info!("loaded prg");
|
||||
let mut pwm_pio_left =
|
||||
PioPwmAudio::new(audio.dma0, &mut audio.pio, audio.sm0, audio.left, &prg);
|
||||
let mut pwm_pio_right =
|
||||
PioPwmAudio::new(audio.dma1, &mut audio.pio, audio.sm1, audio.right, &prg);
|
||||
|
||||
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();
|
||||
loop {
|
||||
pwm_pio.write(unsafe { &AUDIO_BUFFER_1 }).await;
|
||||
write_samples(&mut pwm_pio_left, &mut pwm_pio_right, unsafe {
|
||||
&AUDIO_BUFFER_1
|
||||
})
|
||||
.await;
|
||||
unsafe { &mut AUDIO_BUFFER_1 }.fill(SILENCE_VALUE);
|
||||
|
||||
unsafe { core::mem::swap(&mut AUDIO_BUFFER, &mut AUDIO_BUFFER_1) };
|
||||
AUDIO_BUFFER_READY.store(true, Ordering::Release)
|
||||
}
|
||||
}
|
||||
|
||||
async fn write_samples<PIO: Instance>(
|
||||
left: &mut PioPwmAudio<'static, PIO, 0>,
|
||||
right: &mut PioPwmAudio<'static, PIO, 1>,
|
||||
buf: &[u8],
|
||||
) {
|
||||
// pack two samples per word
|
||||
let mut packed_buf_left: [u32; AUDIO_BUFFER_SAMPLES / 2] = [0; AUDIO_BUFFER_SAMPLES / 2];
|
||||
let mut packed_buf_right: [u32; AUDIO_BUFFER_SAMPLES / 2] = [0; AUDIO_BUFFER_SAMPLES / 2];
|
||||
|
||||
for ((pl, pr), sample) in packed_buf_left
|
||||
.iter_mut()
|
||||
.zip(packed_buf_right.iter_mut())
|
||||
.zip(buf.chunks(4))
|
||||
{
|
||||
*pl = pack_u8_samples(sample[0], sample[2]);
|
||||
*pr = pack_u8_samples(sample[1], sample[3]);
|
||||
}
|
||||
|
||||
let left_fut = left
|
||||
.sm
|
||||
.tx()
|
||||
.dma_push(left.dma.reborrow(), &packed_buf_left, false);
|
||||
|
||||
let right_fut = right
|
||||
.sm
|
||||
.tx()
|
||||
.dma_push(right.dma.reborrow(), &packed_buf_right, false);
|
||||
|
||||
join(left_fut, right_fut).await;
|
||||
}
|
||||
|
||||
struct PioPwmAudioProgram8Bit<'d, PIO: Instance>(LoadedProgram<'d, PIO>);
|
||||
|
||||
/// Writes one sample to pwm as high and low time
|
||||
impl<'d, PIO: Instance> PioPwmAudioProgram8Bit<'d, PIO> {
|
||||
fn new(common: &mut Common<'d, PIO>) -> Self {
|
||||
// only uses 16 bits top for high, bottom for low
|
||||
// allows two samples per word
|
||||
let prg = pio_asm!(
|
||||
"out x, 8", // samples <<
|
||||
"out y, 8", // samples max >>
|
||||
"out x, 8", // pwm high time
|
||||
"out y, 8", // pwm low time
|
||||
"loop_high:",
|
||||
"set pins, 1", // keep pin high
|
||||
"jmp x-- loop_high", // decrement X until 0
|
||||
@@ -62,7 +103,6 @@ impl<'d, PIO: Instance> PioPwmAudioProgram8Bit<'d, PIO> {
|
||||
|
||||
struct PioPwmAudio<'d, PIO: Instance, const SM: usize> {
|
||||
dma: Peri<'d, AnyChannel>,
|
||||
cfg: Config<'d, PIO>,
|
||||
sm: StateMachine<'d, PIO, SM>,
|
||||
}
|
||||
|
||||
@@ -87,51 +127,24 @@ impl<'d, PIO: Instance, const SM: usize> PioPwmAudio<'d, PIO, SM> {
|
||||
cfg.use_program(&prg.0, &[]);
|
||||
sm.set_config(&cfg);
|
||||
|
||||
let target_clock = (u8::MAX as u32 + 1) * SAMPLE_RATE_HZ;
|
||||
let divider = (clk_sys_freq() / (target_clock * 2)).to_fixed();
|
||||
sm.set_clock_divider(divider);
|
||||
|
||||
sm.set_enable(true);
|
||||
|
||||
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
|
||||
/// packs two u8 samples into 32bit word
|
||||
fn pack_u8_samples(sample1: u8, sample2: u8) -> u32 {
|
||||
(u8_pcm_to_pwm(sample1) as u32) << 16 | u8_pcm_to_pwm(sample2) as u32
|
||||
}
|
||||
|
||||
fn u8_pcm_to_pwm(sample: u8) -> u16 {
|
||||
((sample as u16) << 8) | ((u8::MAX - sample) as u16)
|
||||
}
|
||||
|
||||
@@ -121,10 +121,12 @@ async fn watchdog_task(mut watchdog: Watchdog) {
|
||||
static ENABLE_UI: AtomicBool = AtomicBool::new(true);
|
||||
static UI_CHANGE: Signal<CriticalSectionRawMutex, ()> = Signal::new();
|
||||
|
||||
const OVERCLOCK: u32 = 300_000_000;
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(_spawner: Spawner) {
|
||||
let p = if cfg!(feature = "overclock") {
|
||||
let clocks = ClockConfig::system_freq(300_000_000).unwrap();
|
||||
let clocks = ClockConfig::system_freq(OVERCLOCK).unwrap();
|
||||
let config = Config::new(clocks);
|
||||
embassy_rp::init(config)
|
||||
} else {
|
||||
@@ -156,11 +158,12 @@ async fn main(_spawner: Spawner) {
|
||||
} = Pio::new(p.PIO0, Irqs);
|
||||
|
||||
let audio = Audio {
|
||||
dma: p.DMA_CH3,
|
||||
pio: common,
|
||||
sm0,
|
||||
sm1,
|
||||
dma0: p.DMA_CH3,
|
||||
left: p.PIN_26,
|
||||
sm1,
|
||||
dma1: p.DMA_CH4,
|
||||
right: p.PIN_27,
|
||||
};
|
||||
let sd = Sd {
|
||||
@@ -244,11 +247,12 @@ struct Display {
|
||||
reset: Peri<'static, PIN_15>,
|
||||
}
|
||||
struct Audio {
|
||||
dma: Peri<'static, DMA_CH3>,
|
||||
pio: Common<'static, PIO0>,
|
||||
dma0: Peri<'static, DMA_CH3>,
|
||||
sm0: StateMachine<'static, PIO0, 0>,
|
||||
sm1: StateMachine<'static, PIO0, 1>,
|
||||
left: Peri<'static, PIN_26>,
|
||||
dma1: Peri<'static, DMA_CH4>,
|
||||
sm1: StateMachine<'static, PIO0, 1>,
|
||||
right: Peri<'static, PIN_27>,
|
||||
}
|
||||
struct Sd {
|
||||
|
||||
Reference in New Issue
Block a user