Files
analogue_synth/crates/synth-embedded/README.md

4.7 KiB
Raw Blame History

synth-embedded

Real-time audio synthesizer firmware for the Raspberry Pi Pico 2 (RP2350), built on the Embassy 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

# Rust target for RP2350 Cortex-M33
rustup target add thumbv8m.main-none-eabihf

# probe-rs for flashing
cargo install probe-rs-tools

Build

cargo build -p synth-embedded --release

Flash

Connect a debug probe (Raspberry Pi Debug Probe, J-Link, etc.) and run:

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:

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.