A polished, multi-sensor instrument app for the FreeWili 2 (Raspberry Pi RP2350B), built in C with the Pico SDK and LVGL 9. SensorView turns the FreeWili 2's 480×320 touchscreen, 9-axis motion stack, environmental sensors, and 4-microphone array into a set of live instruments: environment dashboards, an artificial horizon, a tilt-compensated compass, a spectrum analyzer, and an acoustic direction finder.
Target: RP2350B · Pico SDK 2.2.0 · LVGL 9.2.3
- 🌡️ Environment — temperature, humidity, and ambient light with gauges, history charts, and a hero readout; the backlight auto-dims to the room.
- 📐 Motion — a 2-axis bubble level (mirrored on the LED strip), live accel/gyro bars, and a sensor-fused artificial horizon + 3D wireframe attitude view.
- 🧭 Field — a tilt-compensated compass with hard-iron auto-calibration, a magnetic-field meter, and an auto-zeroing metal detector.
- 🔊 Audio — a real-time FFT spectrum + waterfall with an LED VU meter, plus an acoustic direction-of-arrival (DOA) radar that points an arrow at the dominant sound source.
- 💡 16 addressable LEDs used as instruments: a tilt arm, a north hint, a VU bar, and a "point at the sound" direction arm.
- 🧪 Host-tested DSP/math — the pure signal-processing and geometry (CIC, FFT, DOA, fusion render, value→pixel mappings) compile and run on the host with assertions, independent of hardware.
| Home | Environment | Motion (level + LED tilt arm) |
|---|---|---|
![]() |
![]() |
![]() |
| Field (detector) | Audio — spectrum + DOA radar |
|---|---|
![]() |
![]() |
Photos are taken of the live panel — it's a real 480×320 display, not a simulator.
| Part | Role | Bus |
|---|---|---|
| RP2350B | Dual Cortex-M33 MCU | — |
| ST7796 (480×320) | LCD, RGB565 over SPI1 + DMA | SPI1 |
| FT6336U | Capacitive touch (polled) | I²C1 @ 0x38 |
| PCAL6524 | IO expander (gates display reset, mic power) | I²C1 @ 0x23 |
| SHT40 | Temperature + humidity | I²C1 @ 0x44 |
| OPT4001 | Ambient light (lux) | I²C1 @ 0x45 |
| BMI323 | 3-axis accel + gyro (IMU) | I²C1 @ 0x68 |
| BMM350 | 3-axis magnetometer | I²C1 @ 0x14 |
| 4× PDM MEMS mics | 19 mm linear array (DOA) | PIO @ 1.024 MHz |
| 16× WS2812 | Addressable RGB LEDs | PIO (GPIO21) |
The board runs at a stable 153.6 MHz (vreg 1.15 V) with clk_peri re-sourced from clk_sys — the proven-clean recipe for the display SPI/PIO bring-up on this hardware.
SHT40 + OPT4001 polled at 2 Hz. Three views — analog-style Gauges, a scrolling Chart of recent history, and a large Hero readout. A PWM backlight follows ambient light (with a comfortable floor) so the panel is readable in any room.
BMI323 IMU at 20 Hz. Level shows an on-screen bubble and drives a 2-axis tilt indicator on the LED strip (a horizontal arm + a vertical arm). Accel and Gyro are live bar meters. Attitude runs the IMU + magnetometer through the vendored x-io Fusion AHRS to draw an artificial horizon and a mini 3D wireframe cube, projected on an lv_canvas.
BMM350 magnetometer with full OTP / temperature / cross-axis compensation. Compass is tilt-compensated (heading from the fused yaw) and auto-calibrates hard-iron offsets on the fly to fight the board's own speaker/haptic magnets. Field is a µT bar meter; Detector is an auto-zeroing deviation meter with an LED bar — wave it near metal.
The 4-mic PDM array is decimated on-chip (CIC + DC-block) to 16 kHz PCM.
- Spectrum — a 256-point FFT drawn as log-spaced bars over a scrolling heat waterfall, with an LED VU meter.
- Direction (DOA) — all four mics are transformed (
fft_forward) and fed to a dominant-bin phase-difference estimator that recovers the azimuth (±90°) and a confidence. A radar-gauge panel — arc rim, graduated fan, tick labels, and a confidence-colored arrow with a real arrowhead — points at the source, and the LED strip lights the arm LED nearest the bearing. Mics are remapped to physical line order{D, B, A, C}before estimation (the array's phase is monotonic only in that order).
Tooling is Windows PowerShell + the Raspberry Pi Pico VS Code extension toolchain (
~/.pico-sdk, SDK 2.2.0). Diagnostics are over SEGGER RTT (the board is USB host-mode, no serial stdio).
# Build (always pass -Clean before a hardware verify; see note below)
powershell -File tools/build.ps1 -Clean
# Flash + reset over the CMSIS-DAP debug probe (OpenOCD, no BOOTSEL dance)
Get-Process openocd -ErrorAction SilentlyContinue | Stop-Process -Force
powershell -File tools/flash.ps1
# Live RTT console (scaled-integer logs; RTT printf has no float support)
powershell -File tools/rtt.ps1 -Seconds 10
# Host unit tests (pure DSP/math, system gcc — no SDK required)
powershell -File tests/host/run_host_tests.ps1The host suite covers the signal-processing and geometry that can be reasoned about off-hardware: CIC decimation, the FFT, the DOA estimator (synthetic plane-wave recovery within tolerance), the AHRS render math, sensor conversions, and the value→pixel/colour mappings.
src/
├── main.c # boot: clocks, drivers, LVGL, app shell
├── platform/ # board bring-up, IO expander, backlight, RTT diag
├── display/ st7796.c # 480x320 RGB565 SPI+DMA panel driver
├── touch/ ft6336.c # capacitive touch (rotated transform)
├── leds/ ws2812_driver # 16 addressable LEDs (PIO, hardware-inverted)
├── sensors/ # SHT40, OPT4001, BMI323, BMM350 + sensor_hub poller
├── fusion/ # x-io Fusion AHRS wrapper, mag cal, 3D render
├── pdm/ pdm_capture # 4-mic PDM capture (PIO + on-chip CIC)
├── dsp/ # cic, dcblock, radix-2 fft
├── doa/ # delay/phase DOA estimator + per-mic calibration
├── audio/ audio_engine # mics -> FFT -> spectrum + DOA
└── ui/ # app shell + one screen per tile, views, LVGL canvas
docs/superpowers/ # design specs + implementation plans per phase
tests/host/ # self-checking host unit tests
third_party/ # LVGL, x-io Fusion, SEGGER RTT (own licenses)
Each sensor tile is an LVGL screen built from small, independently-testable view
modules. A shared sensor_hub polls the I²C sensors and advances the fusion
filter; the heavy attitude/audio canvases share a single RGB565 buffer to fit
the ~520 KB of SRAM. The pure math lives behind hardware-free interfaces so it
can be unit-tested on the host.
Note on rebuilds: the project lives in a Dropbox folder, and Dropbox can rewrite file timestamps such that Ninja skips a recompile. Always pass
-Clean(or delete the specific.obj) before flashing for a hardware check.
SensorView was built in phases, each with its own design spec and implementation
plan (under docs/superpowers/): foundation/app-shell → environment/motion/field
dashboards → sensor fusion instruments → audio spectrum → acoustic direction
finder. Every phase was verified end-to-end on real hardware.
MIT © 2026 Dave Robins.
Vendored components under third_party/ retain their own licenses: LVGL (MIT),
x-io Technologies Fusion (MIT), and SEGGER RTT (SEGGER's BSD-style license).
Built for the FreeWili 2. Display, audio, and magnetometer bring-up drew on the FreeWili reference firmware and community projects for this board.




