This commit is contained in:
Nathan Rashleigh committed 2026-07-23 19:15:56 +10:00
1 parent c523f504cc
commit b2b4fc34a1
1 file changed
+24 -42
+24 -42
View File
@@ -5,10 +5,11 @@
//! with MCLK (256×fs) enabled; the main loop streams a 400 Hz sawtooth //! with MCLK (256×fs) enabled; the main loop streams a 400 Hz sawtooth
//! (48 000 / 400 = 120 samples per cycle) into both channels. //! (48 000 / 400 = 120 samples per cycle) into both channels.
//! //!
//! The USER button (B1, PA0, high = pressed) toggles the tone via EXTI0: the //! The USER button (B1, PA0, high = pressed) toggles the tone: the audio
//! interrupt only sets a flag, the audio loop applies it (with a debounce //! loop polls it once per frame (a press edge outside the debounce hold-off
//! hold-off), so no GPIO polling or I2C traffic happens in the sample path. //! flips the tone). While off, silence is streamed so the codec keeps its
//! While off, silence is streamed so the codec keeps its clocks. //! clocks. Green LED (LD4) mirrors the tone state; blue (LD6) is a 1 Hz
//! firmware heartbeat.
#![no_std] #![no_std]
#![no_main] #![no_main]
@@ -17,23 +18,19 @@
use defmt_rtt as _; use defmt_rtt as _;
use panic_probe as _; use panic_probe as _;
use core::cell::RefCell;
use core::sync::atomic::{AtomicBool, Ordering};
use cortex_m::interrupt::Mutex;
use cortex_m_rt::entry; use cortex_m_rt::entry;
// the HAL is re-exported by the f411 board-support crate as `f411::hal` // the HAL is re-exported by the f411 board-support crate as `f411::hal`
use f411::{ use f411::{
button::Buttons,
codec::Cs43l22, codec::Cs43l22,
led::LedCompass, led::LedCompass,
hal::{ hal::{
gpio::{gpioa, gpioa::PA0, gpioc, Edge, Input}, gpio::{gpioa, gpioc},
i2c::{I2c, Mode}, i2c::{I2c, Mode},
i2s::stm32_i2s_v12x::transfer::*, i2s::stm32_i2s_v12x::transfer::*,
i2s::I2s, i2s::I2s,
nb::block, nb::block,
pac, pac,
pac::interrupt,
prelude::*, prelude::*,
rcc::{Config, Rcc}, rcc::{Config, Rcc},
}, },
@@ -50,11 +47,6 @@ const AMPLITUDE: i32 = i16::MAX as i32 / 8;
// After a toggle, ignore further presses for ~20 ms of samples (bounce). // After a toggle, ignore further presses for ~20 ms of samples (bounce).
const DEBOUNCE_SAMPLES: u32 = SAMPLE_RATE_HZ / 50; const DEBOUNCE_SAMPLES: u32 = SAMPLE_RATE_HZ / 50;
/// Button pin, parked here so the EXTI0 handler can clear its pending bit.
static BUTTON: Mutex<RefCell<Option<PA0<Input>>>> = Mutex::new(RefCell::new(None));
/// Set by EXTI0 on a press edge, consumed by the audio loop.
static TOGGLE_REQ: AtomicBool = AtomicBool::new(false);
type AudioOut = I2sTransfer<I2s<pac::SPI3>, Master, Transmit, Philips, Data16Channel16>; type AudioOut = I2sTransfer<I2s<pac::SPI3>, Master, Transmit, Philips, Data16Channel16>;
/// I2S3 master transmit, 16-bit stereo, MCLK out for the codec. /// I2S3 master transmit, 16-bit stereo, MCLK out for the codec.
@@ -82,7 +74,7 @@ fn saw_sample(i: u32) -> i16 {
#[entry] #[entry]
fn main() -> ! { fn main() -> ! {
let cp = cortex_m::Peripherals::take().unwrap(); let cp = cortex_m::Peripherals::take().unwrap();
let mut p = pac::Peripherals::take().unwrap(); let p = pac::Peripherals::take().unwrap();
// PLLI2S tuned to 61.44 MHz so 48 kHz divides exactly (61.44 MHz / 256fs / 5). // PLLI2S tuned to 61.44 MHz so 48 kHz divides exactly (61.44 MHz / 256fs / 5).
let mut rcc = p let mut rcc = p
@@ -95,14 +87,8 @@ fn main() -> ! {
let gpioc = p.GPIOC.split(&mut rcc); let gpioc = p.GPIOC.split(&mut rcc);
let gpiod = p.GPIOD.split(&mut rcc); let gpiod = p.GPIOD.split(&mut rcc);
// USER button (B1): rising-edge interrupt on EXTI0. // USER button (B1), polled by the audio loop.
let mut syscfg = p.SYSCFG.constrain(&mut rcc); let mut button = Buttons::new(gpioa.pa0).user;
let mut button = gpioa.pa0.into_pull_down_input();
button.make_interrupt_source(&mut syscfg);
button.trigger_on_edge(&mut p.EXTI, Edge::Rising);
button.enable_interrupt(&mut p.EXTI);
cortex_m::interrupt::free(|cs| BUTTON.borrow(cs).replace(Some(button)));
unsafe { pac::NVIC::unmask(pac::Interrupt::EXTI0) };
// CS43L22 codec: control port + reset via the board-support driver. // CS43L22 codec: control port + reset via the board-support driver.
let i2c = I2c::new( let i2c = I2c::new(
@@ -141,14 +127,20 @@ fn main() -> ! {
let mut on = false; // silent at boot; USER button toggles the saw on let mut on = false; // silent at boot; USER button toggles the saw on
let mut holdoff: u32 = 0; let mut holdoff: u32 = 0;
loop { loop {
// Consume any press flag every frame (so bounce during the hold-off // Poll the button only every 64th frame (~1.3 ms). The gap between
// window is discarded), apply it only outside the window. // the L and R half-frame writes is ~10 µs; in an unoptimized build a
holdoff = holdoff.saturating_sub(1); // per-frame poll costs enough to miss that slot every frame, which
if TOGGLE_REQ.swap(false, Ordering::AcqRel) && holdoff == 0 { // starves the I2S shift register and garbles the output. Every 64th
on = !on; // frame the cost amortizes to nothing and 1.3 ms is still instant
holdoff = DEBOUNCE_SAMPLES; // for a human press.
saw_led.set(on); if frames & 63 == 0 {
defmt::info!("saw {}", if on { "ON" } else { "OFF" }); holdoff = holdoff.saturating_sub(1);
if button.just_pressed() && holdoff == 0 {
on = !on;
holdoff = DEBOUNCE_SAMPLES / 64;
saw_led.set(on);
defmt::info!("saw {}", if on { "ON" } else { "OFF" });
}
} }
let sample = match on { let sample = match on {
@@ -165,13 +157,3 @@ fn main() -> ! {
} }
} }
} }
#[interrupt]
fn EXTI0() {
cortex_m::interrupt::free(|cs| {
if let Some(pin) = BUTTON.borrow(cs).borrow_mut().as_mut() {
pin.clear_interrupt_pending_bit();
}
});
TOGGLE_REQ.store(true, Ordering::Release);
}