Your EEG headset streams 500 samples per second per channel. Sending that raw to the cloud costs battery, bandwidth, and privacy. The fix? Compute local field potentials (LFPs) on the device, in Rust, before a single byte leaves the board.
Why LFPs Belong on Silicon
LFPs capture synchronized synaptic activity in a volume of tissue. They sit between single-unit spikes and scalp EEG in spatial scale. For brain-computer interfaces, sleep staging, or seizure detection, LFP bands (delta, theta, alpha, beta, gamma) are the feature set. Running bandpower, coherence, or phase-amplitude coupling on a Cortex-M4 or RISC-V core means real-time feedback without a radio link.
Rust Toolchain for Embedded DSP
Start with embassy for async hardware abstraction, dsp for filter primitives, and heapless for static allocation. No allocator, no panic runtime bloat. Cross-compile with cargo build --target thumbv7em-none-eabihf for STM32 or riscv32imac-unknown-none-elf for ESP32-C3.
Signal Chain: Anti-Alias to Bandpower
1. Sample at 1 kHz. 2. Apply a 4th-order IIR high-pass at 0.5 Hz (DC drift). 3. Decimate by 2 with a half-band FIR to 500 Hz. 4. Compute Welch PSD: 256-point FFT, 50% overlap, Hann window. 5. Integrate power in canonical bands. All fixed-point Q15 to avoid FPU latency.
Memory Budget Table
| Buffer | Size (bytes) | Location |
|---|---|---|
| ADC raw | 512 | DMA |
| Q15 work | 512 | Stack |
| Decimated | 256 | Stack |
| FFT complex | 1024 | Static |
| PSD bins | 512 | Static |
| Bandpower out | 20 | Stack |
On-Device Inference Loop
Feed the 5-band vector into a tiny logistic regression or decision tree (≤20 nodes) stored in flash. No tensor runtime needed. Update thresholds via OTA without reflashing the DSP core. Latency: ~2 ms on Cortex-M4 @ 120 MHz. Power: <5 mA average.
"The best neural interface is the one that never leaves the skull.
— Embedded BCI engineer, 2024
Ship It This Week
Clone embassy-stm32 examples. Add dsp and micromath to Cargo.toml. Port the biquad and FIR coeffs from Python's SciPy. Verify PSD against a known LFP dataset on host. Flash. Measure cycle count with DWT_CYCCNT. Iterate until 95th percentile < 10 ms. That's your v1.










