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>
83 lines
4.2 KiB
Markdown
83 lines
4.2 KiB
Markdown
# pm-daisy — PolyMeter click engine on the Daisy Pod
|
||
|
||
A time-boxed spike (see [`docs/daisy-spike.md`](../../docs/daisy-spike.md)): play the PolyMeter
|
||
groove engine on real Cortex-M7 hardware to judge whether the [Electrosmith Daisy
|
||
Pod](https://electro-smith.com/products/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
|
||
```sh
|
||
./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-flash.md)).
|
||
```sh
|
||
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:
|
||
```sh
|
||
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`](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:
|
||
```sh
|
||
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).
|