metronome/rust/pm-daisy/README.md
Me Here 802e46f5bb pm-daisy: wire Pod controls (encoder/buttons/knobs/RGB LEDs)
The Daisy Pod hardware arrived (Seed 1.2 / PCM3060). Build the controls
phase on top of the audio spike: encoder turn = tempo, push = play/pause,
buttons step the program list, knob 1 = volume, RGB LED 1 = beat colored
by dynamic, RGB LED 2 = transport. Boot still plays the spike groove so
the spike's decision criteria stay observable.

- pm-synth: add Player::{position,seek,set_volume,last_level} + Synth::
  set_master, with host tests (tests/player.rs). Live tempo/program
  changes rebuild the Player in thread context and swap it in under a
  short critical section, preserving loop phase (seek) on tempo changes.
- pm-daisy: SysTick 1 kHz millis tick, non-blocking beat flash, and
  PLAYING/VOLUME/LAST_LEVEL atomics the audio IRQ honors/publishes.
- New modules controls.rs (buttons/encoder/pots, libDaisy debounce +
  quadrature decode, 1 kHz poll), leds.rs (active-low RGB + boot
  self-test), programs.rs (spike groove at index 0 + pm-grid grooves).
- Pin map verified against libDaisy daisy_pod.cpp + the daisy crate's
  pins.rs. Builds clean for all three Seed revisions; 91 KB/128 KB flash.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 20:04:59 -05:00

4.2 KiB
Raw Blame History

pm-daisy — PolyMeter click engine on the Daisy Pod

A time-boxed spike (see docs/daisy-spike.md): play the PolyMeter groove engine on real Cortex-M7 hardware to judge whether the Electrosmith Daisy Pod (STM32H750, onboard audio codec) is a credible home for the audio engine if PolyMeter ever grows into a real-time audio workstation.

It boots playing a hardcoded 124-BPM 909 pattern (pm_synth::SPIKE_PROGRAM) out the Pod's audio jack, flashing the Daisy Seed's USER LED on each click.

What runs here

  • Shared engine, verified on host. The audio is produced by pm_synth::Player — the exact same code the host synthrender renders to pm-daisy-preview.wav. Listen to that WAV to hear what the hardware should play before you flash anything.
  • Transport only is new. This crate is a thin board-support binary: heap + board bring-up + the SAI audio-DMA interrupt feeding Player::next_sample() into stereo frames. Structure follows the daisy crate's examples/audio.rs.

⚠️ Set the board revision (or you get silence)

The Daisy Seed comes in revisions with different audio codecs. Pick the one on your Seed (check the sticker / silkscreen) — it's a Cargo feature:

Your Seed Codec Build with
Daisy Seed (original) AK4556 ./build.sh seed
Daisy Seed 1.1 (default) WM8731 ./build.sh (or seed_1_1)
Daisy Seed 1.2 / Seed2 DFM PCM3060 ./build.sh seed_1_2

Build

./build.sh [revision]     # containerized (pm-rust:2); produces pm-daisy.bin + pm-daisy.elf

Flash — two options

A. Debug probe (recommended — gives you defmt logs over RTT): A probe wired to the Seed's SWD pins (e.g. the Raspberry Pi Debug Probe you already use for pm-grid/pm-kit — see rust/probe-flash.md).

probe-rs run --chip STM32H750VBTx pm-daisy.elf    # or `cargo run --release` from this dir

B. USB DFU (no probe needed): Hold the Daisy's BOOT button, tap RESET, release BOOT — the Seed enumerates as STM32 system DFU. Then:

dfu-util -a 0 -s 0x08000000:leave -D pm-daisy.bin -d ,0483:df11

Too big for 128 KB?

The STM32H750 has only 128 KB internal flash. If the linker reports a FLASH overflow, flash via the Daisy Bootloader to the 8 MB QSPI instead:

  1. Install the bootloader once at https://flash.daisy.audio/ (Bootloader tab, v6.x).
  2. In memory.x, set FLASH : ORIGIN = 0x90040000, LENGTH = 8M - 0x40000 (commented there).
  3. Rebuild, then enter the bootloader (tap BOOT within 2 s of reset; LED pulses) and:
    dfu-util -a 0 -s 0x90040000:leave -D pm-daisy.bin -d ,0483:df11
    

Pod controls

The Pod's buttons, encoder, knobs, and RGB LEDs are wired up (in src/controls.rs + src/leds.rs). Boot still plays the spike groove (program index 0), so the original spike criteria stay observable.

Control Action
Encoder turn tempo ±1 BPM per detent (clamped 5300)
Encoder push play / pause (ignored if you turned while holding it)
Button 1 / Button 2 previous / next program (src/programs.rs, wraps)
Knob 1 master volume (read at boot — no startup jump)
Knob 2 reserved (read but unused)
RGB LED 1 beat flash, colored by dynamic: accent = yellow, normal = cyan, ghost = magenta
RGB LED 2 transport: green = playing, red = paused
Seed USER LED unchanged spike beat flash

At boot both RGB LEDs cycle R → G → B (a ~0.45 s self-test) so a miswired or dead leg is obvious before audio starts. A live tempo or program change rebuilds the engine in the main loop (not the audio IRQ) and swaps it in under a short critical section, preserving loop phase on tempo changes.

Pin map (verified against libDaisy daisy_pod.cpp, cross-checked with the daisy crate's pins.rs): buttons D27/D28 (PG9/PA2), encoder A/B/click D26/D25/D13 (PD11/PA0/PB6), knobs D21/D15 (PC4/PC0 → ADC1), LED 1 D20/D19/D18 (PC1/PA6/PA7), LED 2 D17/D24/D23 (PB1/PA1/PA4). The RGB LEDs are common-anode (active-low). If the encoder feels reversed or double-steps, adjust the decode in Pod::poll (detents-per-quadrature-cycle varies by encoder).