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
//! (48 000 / 400 = 120 samples per cycle) into both channels.
//!
//! The USER button (B1, PA0, high = pressed) toggles the tone via EXTI0: the
//! interrupt only sets a flag, the audio loop applies it (with a debounce
//! hold-off), so no GPIO polling or I2C traffic happens in the sample path.
//! While off, silence is streamed so the codec keeps its clocks.
//! The USER button (B1, PA0, high = pressed) toggles the tone: the audio
//! loop polls it once per frame (a press edge outside the debounce hold-off
//! flips the tone). While off, silence is streamed so the codec keeps its
//! clocks. Green LED (LD4) mirrors the tone state; blue (LD6) is a 1 Hz
//! firmware heartbeat.
#![no_std]
#![no_main]
@@ -17,23 +18,19 @@
use defmt_rtt 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;
// the HAL is re-exported by the f411 board-support crate as `f411::hal`
use f411::{
button::Buttons,
codec::Cs43l22,
led::LedCompass,
hal::{
gpio::{gpioa, gpioa::PA0, gpioc, Edge, Input},
gpio::{gpioa, gpioc},
i2c::{I2c, Mode},
i2s::stm32_i2s_v12x::transfer::*,
i2s::I2s,
nb::block,
pac,
pac::interrupt,
prelude::*,
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).
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>;
/// I2S3 master transmit, 16-bit stereo, MCLK out for the codec.
@@ -82,7 +74,7 @@ fn saw_sample(i: u32) -> i16 {
#[entry]
fn main() -> ! {
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).
let mut rcc = p
@@ -95,14 +87,8 @@ fn main() -> ! {
let gpioc = p.GPIOC.split(&mut rcc);
let gpiod = p.GPIOD.split(&mut rcc);
// USER button (B1): rising-edge interrupt on EXTI0.
let mut syscfg = p.SYSCFG.constrain(&mut rcc);
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) };
// USER button (B1), polled by the audio loop.
let mut button = Buttons::new(gpioa.pa0).user;
// CS43L22 codec: control port + reset via the board-support driver.
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 holdoff: u32 = 0;
loop {
// Consume any press flag every frame (so bounce during the hold-off
// window is discarded), apply it only outside the window.
holdoff = holdoff.saturating_sub(1);
if TOGGLE_REQ.swap(false, Ordering::AcqRel) && holdoff == 0 {
on = !on;
holdoff = DEBOUNCE_SAMPLES;
saw_led.set(on);
defmt::info!("saw {}", if on { "ON" } else { "OFF" });
// Poll the button only every 64th frame (~1.3 ms). The gap between
// the L and R half-frame writes is ~10 µs; in an unoptimized build a
// per-frame poll costs enough to miss that slot every frame, which
// starves the I2S shift register and garbles the output. Every 64th
// frame the cost amortizes to nothing and 1.3 ms is still instant
// for a human press.
if frames & 63 == 0 {
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 {
@@ -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);
}