From b2b4fc34a1ae2fbb17c1e5e2300170e3fe39e4b8 Mon Sep 17 00:00:00 2001 From: Nathan Rashleigh Date: Thu, 23 Jul 2026 19:15:56 +1000 Subject: [PATCH] fbbl --- src/main.rs | 66 +++++++++++++++++++---------------------------------- 1 file changed, 24 insertions(+), 42 deletions(-) diff --git a/src/main.rs b/src/main.rs index f600c55..d4332e5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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>>> = 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, 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); -}