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>
4.2 KiB
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 hostsynthrenderrenders topm-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 thedaisycrate'sexamples/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:
- Install the bootloader once at https://flash.daisy.audio/ (Bootloader tab, v6.x).
- In
memory.x, setFLASH : ORIGIN = 0x90040000, LENGTH = 8M - 0x40000(commented there). - 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 5–300) |
| 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).