Files

129 lines
4.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# synth-embedded
Real-time audio synthesizer firmware for the **Raspberry Pi Pico 2** (RP2350), built on the [Embassy](https://embassy.dev) async runtime. Outputs 48 kHz stereo I2S audio to a PCM5102A DAC and accepts MIDI input over UART.
## Hardware
| GPIO | Signal | Connected to |
|------|--------|-------------|
| 9 | BCK (bit clock) | PCM5102A BCK |
| 10 | LRCK (word select) | PCM5102A LRCK |
| 11 | DATA | PCM5102A DIN |
| 1 | UART0 RX | MIDI IN (optocoupler) |
Any PCM5102A-compatible I2S DAC (e.g., the CJMCU-5102 module) works. Standard MIDI current-loop interface: 5 mA optocoupler on GPIO 1, no UART TX required.
## Architecture
Two Embassy tasks run concurrently on core 0:
```
┌──────────────┐ Mutex<SynthParams> ┌──────────────┐
│ midi_task │ ──────────────────────────▶│ audio_task │
│ (UART RX) │ NoteOn/Off, CC, PB, PC │ (PIO I2S) │
└──────────────┘ └──────────────┘
│ │
GPIO 1 GPIO 9/10/11
31 250 baud 48 kHz I2S
```
### `audio_task`
1. Initialises the `PioI2sOut` driver (PIO0 state machine 0, 24-bit, 48 kHz).
2. Creates the DSP chain: **VCO → SVF → VCA ← ADSR**.
3. Runs a double-buffered DMA loop — while one 256-frame block transfers to the PIO FIFO, the next block is rendered on the CPU.
**Block size:** 256 frames × 2 channels × 4 bytes = 2 KB per buffer, 5.33 ms per block. At 150 MHz the Cortex-M33 has roughly 10× headroom for the DSP render.
**Sample format:** 24-bit signed PCM, left-justified in bits 31..8 of a `u32` word, interleaved L/R: `[L0, R0, L1, R1, …]`.
### `midi_task`
Reads raw bytes from UART0 at 31 250 baud (standard MIDI) and feeds them to `synth_core::MidiParser`. Decoded events update the shared `PARAMS` mutex:
| Event | Action |
|-------|--------|
| Note On | Set note, velocity, gate = true |
| Note Off | Clear gate if note matches |
| CC 1 (mod wheel) | Filter resonance |
| CC 7 | Master volume |
| CC 72 | Release time (08 s) |
| CC 73 | Attack time (04 s) |
| CC 74 | Filter cutoff (80 Hz18 kHz) |
| CC 75 | Decay time (04 s) |
| Pitch Bend | ±2 semitones |
| Program Change | Waveform (0=Sine 1=Saw 2=Square 3=Triangle 4=Pulse) |
## Building
### Prerequisites
```bash
# Rust target for RP2350 Cortex-M33
rustup target add thumbv8m.main-none-eabihf
# probe-rs for flashing
cargo install probe-rs-tools
```
### Build
```bash
cargo build -p synth-embedded --release
```
### Flash
Connect a debug probe (Raspberry Pi Debug Probe, J-Link, etc.) and run:
```bash
cargo run -p synth-embedded --release
# or equivalently:
probe-rs run --chip RP2350 target/thumbv8m.main-none-eabihf/release/synth-embedded
```
The `runner` in `.cargo/config.toml` calls `probe-rs run` automatically.
### Debug logging
`defmt` logs are streamed over RTT. View them with:
```bash
probe-rs attach --chip RP2350 --log-format '{t} {L} {s}'
```
The `DEFMT_LOG` environment variable (set to `debug` by default in `.cargo/config.toml`) controls verbosity.
## Linker Setup
The crate uses a custom `memory.x` to describe the RP2350's 2 MB flash and 520 KB SRAM. `build.rs` copies it to the build output directory so `cortex-m-rt`'s `link.x` can find it via `INCLUDE memory.x`.
`.cargo/config.toml` passes two linker scripts:
- `-Tdefmt.x` — places the defmt log sections
- `-Tlink.x` — cortex-m-rt startup, vector table, and `INCLUDE memory.x`
## Dependencies
| Crate | Purpose |
|-------|---------|
| `synth-core` | DSP modules (VCO, SVF, ADSR, VCA, MIDI parser) |
| `embassy-executor` | Async task executor (Cortex-M) |
| `embassy-rp` | RP2350 HAL + PIO I2S driver |
| `embassy-sync` | `Mutex` for shared parameter state |
| `embassy-time` | Timekeeping |
| `cortex-m` / `cortex-m-rt` | ARM Cortex-M runtime |
| `defmt` + `defmt-rtt` | Structured logging over RTT |
| `panic-probe` | Panic messages sent to debug probe |
| `fixed` | Fixed-point clock divisor math |
| `libm` | `no_std` math (`powf` for pitch bend) |
## Customisation
**Change the oscillator waveform default** — edit `SynthParams::new()` in `src/params.rs`.
**Change block size** — edit `BLOCK_FRAMES` in `src/audio.rs`. Larger blocks reduce CPU wake-up overhead; smaller blocks reduce latency.
**Add a second voice** — instantiate a second `(Vco, Adsr, Svf, Vca)` tuple in `audio_task`, mix both outputs before the DMA write.
**Change GPIO pins** — update the constants in `src/main.rs` and re-wire accordingly.