Files
DigiRadio/Software
micheleandClaude Sonnet 5 f610121fd3 Add FM ANTCAP antenna calibration: HTTP sweep control + EEPROM persistence
Confirmed live tonight that the Si4684's automatic front-end tuning
(FE_VARM/VARB properties) is measurably suboptimal on this board: a
129-value ANTCAP sweep (0-128, AN851 Appendix A) at four different FM
frequencies found antcap=102 beats auto-tune by +6 to +11 dB RSSI and
+3 to +11 dB SNR everywhere tested, consistent with the front-end
network component mismatch already logged in
docs/si4684-rf-investigation-report.md — the board's actual matching
network differs from the AN851 reference network those auto-tune
constants were derived from, so a fixed empirical override
compensates for a gap the chip's own algorithm can't see.

Wiring, bottom to top:
- Si4684Driver::tuneFm() already took an antCap byte; threaded it
  through core::ITuner::tuneFm() and si4684::Si4684Tuner::tuneFm() as
  a new parameter (default 0 = auto, unchanged behaviour for every
  existing caller).
- TunerService::tuneFm() takes an optional override instead: omitted,
  it falls back to a new defaultFmAntCap_ member so every ordinary FM
  tune (seek, scan, station recall, live UI) benefits automatically
  once calibrated, not just calls that pass antcap explicitly.
- POST /api/tuner/tune gained an optional "antcap" field for sweeping
  live without touching the saved calibration.
- POST /api/tuner/calibrate-antenna commits a sweep result: writes it
  to the 24AA025E48 EEPROM's user-writable region (word address 0x00,
  separate from the factory-locked EUI-48 at 0xFA-0xFF) via a new
  Eeprom24aa::writeFmAntCap()/readFmAntCap() pair, and immediately
  updates the live TunerService default — no reboot needed to take
  effect, though HardwareBootstrap::boot() also loads it at every
  boot so it survives power cycles. Same net::AntennaCalibration
  function-pointer bridge pattern as PhoneStreamSink/BleProvisioning,
  so components/net stays free of eeprom24aa headers.

Verified end to end on hardware: swept and found 102, saved it via
the new endpoint, confirmed the live default changed immediately,
then power-cycled and confirmed the boot log reports "FM ANTCAP
calibration loaded: 102" and a subsequent default tune reflects it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0178rASQ6ZETPMUamvpoR2KR
2026-08-19 02:01:09 +02:00
..
2026-07-06 01:12:37 +02:00
2026-07-06 01:12:37 +02:00

DigiRadio Firmware

Open-source Hi-Fi DAB+/FM receiver firmware for the ESP32-S3.

Status: fw 0.8.5 on main — BT1035 I2S slave boot init, dual OTA + DSP blob updates, EEPROM identity, NVS + flash encryption (development mode), tabbed Web UI with System uploads, 15 host tests, 4 CI jobs. Agent tasks T1T12 complete; device HIL pending first PCB.

Area Shipped in 0.8.5
Boot Si4684 HOST_LOAD, ADAU1701 RAM program, BT1035 I2S slave init (AUXCFG=3, I2SCFG=67)
Tuner FM/DAB tune, seek up/down, RSQ, RDS, DAB services + DLS
Audio 6-band EQ, mixer, stereo/bass enhance, NVS profile
Updates ESP32 dual-OTA, ADAU1701 dsp partition blob replay
Identity 24AA025E48 EUI-48 → serial, SoftAP/BT name suffix
Presets CRUD, reorder, integrated recall + last-preset at boot
Network SoftAP/STA, tabbed gzipped SPA, typed JSON REST API
Security initEncryptedStorage() — see docs/security-flash-nvs.md
Quality Doxygen, manual sync, Si4684 blob policy check

Architecture: functional core (components/core, host-tested, no ESP-IDF) + imperative shell (drivers, services, net, secure_store). Rules: AGENTS.md · backlog: docs/TODO.md

Prerequisites

Si4684 blobs (proprietary, local only — not in git):

python3 tools/fetch_si4684_firmware.py --dab-only
python3 tools/fetch_si4684_firmware.py --si46xx-dir /path/to/si46xx_firmware
python3 tools/check_si4684_blobs.py

See Firmware/Si4684-Firmware/README.md.

ESP-IDF v5.5.x, target esp32s3.

Quick start

Open this directory (Software/) as the Cursor project so AGENTS.md and .cursor/rules/ load automatically.

idf.py set-target esp32s3
idf.py build
idf.py erase-flash flash   # once when first enabling encryption (0.8.3+)
idf.py -p <port> monitor

Production flash-encryption release mode: overlay sdkconfig.defaults.production (see security doc — irreversible on chip).

Host tests

Pure core, no hardware (needs C++23 compiler on macOS — Homebrew llvm or GCC 14):

cmake -S components/core/test -B build-host \
      -DCMAKE_CXX_COMPILER="$(brew --prefix llvm)/bin/clang++"
cmake --build build-host
ctest --test-dir build-host --output-on-failure

Quality gates (CI on every push to main)

Run from Software/:

doxygen Doxyfile
python3 tools/check-manual-sync.py
python3 tools/check_si4684_blobs.py
tools/gzip-www.sh   # after editing components/net/www/index.html

CI jobs: host-tests, doxygen, manual-sync, si4684-blobs (.github/workflows/ci.yml).

Web UI

Gzipped single-page app at / — tabs: Now (RDS/DLS), Radio, Presets, Audio (6-band EQ), BT, WiFi, System (firmware OTA + DSP upload). Header shows EEPROM serial from /api/health. Source: components/net/www/index.html · regenerate embed: tools/gzip-www.sh.

HTTP API (fw 0.8.5)

Method Path Purpose
GET /api/health Status, firmware version, serial, companion-chip flags
POST /api/wifi Provision STA credentials; reboot on success
GET /api/tuner/status Tuner snapshot (DAB/FM, RDS/DLS metadata)
GET /api/tuner/services DAB service list for current ensemble
POST /api/tuner/tune Tune DAB ensemble or FM frequency
POST /api/tuner/play Start DAB service playback
POST /api/tuner/seek FM seek up or down ({"direction":"up|down"})
GET/PUT /api/audio/profile Read/apply ADAU1701 mixer + 6-band EQ
POST /api/audio/reset Factory-flat audio profile
POST /api/audio/stereo-enhance Stereo depth overlay (0100)
POST /api/audio/bass-enhance Bass enhance overlay (0100)
POST /api/dsp/program Upload ADAU1701 DRAD blob (raw bytes, reboot)
POST /api/system/ota Upload ESP32 firmware image (raw bytes, reboot)
GET /api/bluetooth/status BT1035 boot, pairing, A2DP, name, auto-reconnect
GET /api/bluetooth/paired Paired-device list from module
POST /api/bluetooth/pair Enter discoverable mode
POST /api/bluetooth/pair/stop Leave discoverable mode
POST /api/bluetooth/disconnect Release A2DP session
POST /api/bluetooth/auto-reconnect Set auto-reconnect count (015)
GET /api/stations List saved presets
POST /api/stations Add preset
POST /api/stations/remove Remove preset by index
POST /api/stations/reorder Move preset (from/to indices)
POST /api/stations/tune Recall preset (tuner + audio profile + NVS)

Wire schemas: docs/manual/ch-api.tex · C++ API: doxygen Doxyfiledocs/api/html/index.html.

Layout

Path Role
Firmware/ Si4684 .bin (local) + ADAU1701 SigmaStudio export
components/core/ Pure domain (host-tested)
components/drivers/ Si4684, ADAU1701, BT1035
components/services/ Tuner, audio, Bluetooth, station, integration
components/secure_store/ Encrypted NVS: credentials, profiles, presets
components/net/ Wi-Fi, HTTP server, gzipped web UI
sdkconfig.defaults C++23, NVS + flash encryption (dev mode)
partitions.csv nvs, otadata, dual OTA (ota_0/ota_1), dsp blob, nvs_keys
docs/manual/ LaTeX technical manual
docs/security-flash-nvs.md Encryption + device HIL checklist
docs/TODO.md Completed tasks + HIL backlog
tools/ Blob fetch, CI policy checks, UI gzip

Manual PDF: cd docs/manual && latexmk -lualatex manual.tex

Licence

Apache-2.0 — see LICENSE.