Add internet radio streaming with runtime API, modernize web UI, remove auto-tune/beep at boot

Streaming (main feature this session):
- New WebRadioConfig/WebRadioJson core types, ISecureStore-backed persistence
- New webradio::WebRadioService (thread-safe live config) + GET/POST /api/streaming
- web_radio_stream task now runtime-toggleable (no reboot), no hardcoded URL
- Content-Type diagnostic: warns clearly when a URL is a webpage, not an audio stream

Boot cleanup:
- Removed boot-time auto FM/DAB tune, auto-beep, and the (now-concluded) Si4684
  crystal IBIAS/CTUN empirical sweep from main.cpp — tuning/beep are on-demand
  via the existing REST API only

Web UI:
- Modernized styling (cards, gradients, toggle switches, light/dark theme)
- New Stream tab wired to /api/streaming

Fixes found via real idf.py build (not just clangd):
- Restored wrongly-removed si4684/Si4684Tuner.hpp include in main.cpp
- Fixed MP3Decode() argument types in web_radio_stream.cpp (unsigned char**/int*)

Quality-gate fixes:
- Host-test stub headers (esp_log.h, freertos/*) so TunerService.cpp's
  scanForStation logging/pacing compiles for station_service_test /
  integration_service_test instead of running stale binaries
- Added WifiScanner and WebRadioService manual sections; filled in missing
  Doxygen docs on BluetoothService, i2s_sdata_probe, test_firmware, Bt1035At
- Ignore clangd's .cache/ index directory

Also includes prior uncommitted work carried in the tree: Wi-Fi/Bluetooth
device scan REST API and UI (WifiScanner, BT scan), SigmaStudio TCP bridge,
and the current ADAU1701 SigmaStudio DSP program export.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-08 21:15:22 +02:00
co-authored by Claude Sonnet 5
parent 8a8523515d
commit 6f7b6dd12c
131 changed files with 20309 additions and 1702 deletions
@@ -0,0 +1,10 @@
# Documentazione Integrazione FSC-BT1035 - Sistema DigiRadio
## 1. Introduzione
Il modulo FSC-BT1035 (basato su chipset Qualcomm QCC3056) è un modulo Bluetooth Dual-Mode. Nell'ecosistema DigiRadio, agisce come bridge wireless per l'audio e i dati seriali (SPP/GATT).
## 2. Architettura di Sistema
Il modulo si collega al controller principale DigiRadio tramite:
- **UART (Baudrate 115200 8N1)**: Comando e controllo tramite AT Commands.
- **Audio Interface**: Uscita differenziale (SPK_P/N) per il sistema di amplificazione o I2S per processori DSP esterni (come l'ADAU1701 presente nel vostro sistema).
- **Controllo Hardware**: Pin `SYS_CTRL` per il Power-On e `RESET` per il riavvio hardware.
@@ -0,0 +1,16 @@
# Configurazione e Setup del Modulo
## 1. Parametri di Default
- **Nome BR/EDR**: FSC-BT1035
- **Nome LE**: FSC-BT1035-LE
- **Baudrate**: 115200
## 2. Inizializzazione via UART
Al boot, inviare i comandi per impostare l'ambiente:
1. `AT+RESTORE`: (Opzionale) Ripristina impostazioni di fabbrica.
2. `AT+PROFILE=BITMASK`: Selezionare i profili necessari (Somma i valori binari: SPP=1, GATT_S=2, A2DP_S=32). Esempio: `AT+PROFILE=33` (SPP+A2DP).
3. `AT+NAME=DigiRadio_V1,0`: Imposta nome dispositivo senza suffisso MAC.
4. `AT+REBOOT`: Applicare i cambiamenti.
## 3. Verifica stato
Monitorare il pin `LEDO` (Pin 17) che indica lo stato di pairing (onda quadra durante scanning, alto a connessione stabilita).
@@ -0,0 +1,16 @@
# Riferimento Rapido Comandi AT
## Comandi Generali
- `AT+VER`: Query versione firmware (es: `+VER=BT1035,V2.6.1...`).
- `AT+ADDR`: Lettura MAC Address (necessario per pairing forzato).
- `AT+STAT`: Query globale stati profili (DEV, SPP, GATT, HFP, A2DP, AVRCP).
## Audio e Connessione
- `AT+A2DPCONN=[MAC]`: Connessione a specifica sorgente.
- `AT+A2DPAUDIO=1`: Apre flusso audio.
- `AT+SPPSEND=[LEN],[DATA]`: Invio dati via SPP (max payload 236 byte).
## Eventi Asincroni
Il modulo risponde con eventi preceduti da `+`. Esempi:
- `+SPPDATA=[LEN],[PAYLOAD]`: Ricezione dati seriali.
- `+A2DPSTAT=4`: Streaming A2DP attivo.
@@ -0,0 +1,10 @@
# Note Hardware per DigiRadio
## 1. Gestione Alimentazione
- **VCHG/VBAT**: Il modulo supporta ingressi da 3.0V a 4.2V.
- **SYS_CTRL**: Richiede un impulso di >20ms per avviare il power-up da stato DORMANT/OFF.
## 2. Integrazione ADAU1701 (DSP)
Se il DigiRadio utilizza l'ADAU1701 come processore audio:
- Collegare le uscite `SPK_P/N` del modulo agli ingressi analogici dell'ADAU1701 (tramite filtro passivo come descritto nel datasheet ADAU1701).
- In alternativa, utilizzare l'interfaccia I2S (Pin 4-7 del BT1035) per un collegamento digitale diretto, garantendo che i clock (BCLK, LRCLK) siano configurati in modalità coerente (BT1035 come Master/Slave a seconda del setup dell'ADAU).
@@ -0,0 +1,6 @@
# Diagnostica e Risoluzione Problemi
1. **Modulo non risponde**: Verificare i livelli logici RX/TX. Il modulo BT1035 richiede TX e RX stabili. Controllare se il Pin `SYS_CTRL` è alto.
2. **Audio distorto**: Verificare che l'impedenza di carico sui pin `SPK_P/N` sia conforme e che il volume digitale (`AT+SPKVOL`) non sia in clipping.
3. **Mancato Pairing**: Verificare `AT+PAIR=1`. Assicurarsi che il modulo non sia già connesso a un altro dispositivo.
4. **Terminazione Comandi**: Ogni comando deve terminare con `0x0D` (CR) e `0x0A` (LF).
@@ -0,0 +1,13 @@
# Manuale di Integrazione Hardware FSC-BT1035
## 1. Architettura di Connessione
Il modulo FSC-BT1035 deve essere integrato seguendo rigorosamente le indicazioni di Feasycom per evitare degrado del segnale RF:
- **Pin 34 (SYS_CTRL)**: Richiede un segnale di accensione (>20ms). È consigliato un circuito di controllo tramite il controller DigiRadio.
- **Pin 16 (BT_RTS/PIO2)**: Utilizzato nativamente per il MUTE degli stadi di potenza. Integrare questa funzione nel sistema di amplificazione del DigiRadio.
- **Audio I/O**: Le uscite `SPK_P/N` sono differenziali. Per il collegamento al DSP ADAU1701:
- Utilizzare uno stadio di ingresso con op-amp (es. AD8608 come da datasheet ADAU1701) per convertire il segnale differenziale in singolo (o mantenere il bilanciamento se l'ingresso DSP lo permette).
- Impedenza: Assicurarsi di rispettare i filtri RC per il passaggio basso (corner 50kHz) per eliminare il noise del codec Bluetooth.
## 2. Note di Layout (Critiche)
- **Clearance Antenna**: Rispettare rigorosamente l'area di "Keep Out" (5mm clearance) attorno all'antenna PCB integrata, come indicato nel documento `FSC-BT1035 Datasheet` (sezione 9).
- **Grounding**: Utilizzare una via stitching densa attorno al modulo per connettere i piani di massa e prevenire leakage RF nel PCB del DigiRadio.
@@ -0,0 +1,12 @@
# Guida alla Configurazione Firmware
## 1. Analisi file DigiRadio.params
Il file `DigiRadio.params` analizzato suggerisce parametri di inizializzazione specifici per l'interfacciamento del modulo. Durante il boot, il controller DigiRadio deve:
1. Inviare `AT+VER` per identificare la versione del modulo (es. V2.6.1).
2. Caricare i profili: `AT+PROFILE=341` (combinazione SPP, GATT Server, GATT Client, A2DP Source, ecc.).
3. Configurare la modalità I2S se il flusso audio è digitale (`AT+I2SCFG`):
- Impostare `Param=67` per I2S Slave, 48kHz, 32-bit (o come richiesto dall'ADAU1701).
## 2. Gestione Profili
Il modulo supporta le topologie miste. È possibile mantenere simultaneamente una connessione A2DP (Audio Source) e una GATT (Data).
- Nota: Per il passaggio tra le modalità, interrogare sempre lo stato tramite `AT+STAT` prima di tentare nuove connessioni.
@@ -0,0 +1,13 @@
# Deep Dive Comandi AT e Gestione Eventi
## 1. Protocollo Comunicazione
Il modulo risponde in formato ASCII. Ogni stringa inizia con `
` (0x0D 0x0A).
- Esempio Parsing:
Se il controller riceve `+SPPDATA=10,ABCDEFGHIJ`, il parser deve estrarre il valore `10` come lunghezza e gestire il buffer dei successivi 10 caratteri.
## 2. Throughput Mode (TPMODE)
Per applicazioni ad alto volume dati (es. aggiornamento firmware del DigiRadio via Bluetooth):
1. Inviare `AT+TPMODE=1`.
2. A questo punto, il modulo cessa di interpretare i comandi AT e inoltra ogni byte ricevuto sulla UART direttamente nel link radio (SPP/GATT).
3. Per tornare in modalità comando, è necessario inviare una sequenza di escape definita nel firmware o resettare il modulo.
@@ -0,0 +1,11 @@
# Integrazione DSP ADAU1701
Il collegamento BT1035 -> ADAU1701 può avvenire in due modi:
1. **Digitale (I2S)**:
- BT1035 (Master/Slave) -> SDATA_IN0 (ADAU1701).
- Configurazione ADAU: Impostare `MP4/MP5` come ingressi I2S (LRCLK/BCLK).
- Assicurarsi che il sample rate del modulo Bluetooth (`AT+I2SCFG`) corrisponda al clock configurato nell'ADAU tramite il software SigmaStudio.
2. **Analogico**:
- Uscite differenziali BT1035 (`SPK_P/N`) -> ingressi ADC `ADC0/1`.
- Utilizzare il filtro passivo di ricostruzione consigliato dal datasheet dell'ADAU1701 (50kHz corner) per evitare aliasing dovuto al campionamento del DAC interno del Bluetooth.
@@ -0,0 +1,8 @@
# Mapping Risorse DigiRadio
Analisi dei file del progetto DigiRadio:
- `DigiRadio_IC_1.h`: Mappatura dei registri del controller per il modulo IC.
- `DigiRadio_IC_1_REG.h`: Definizione dei registri di configurazione (probabilmente relativi alla configurazione I2C del modulo Bluetooth).
- `DigiRadio.xml`: Descriptor di sistema utilizzato dal software di configurazione/test del progetto DigiRadio.
Il modulo BT1035 deve essere mappato nell'indirizzo di registro 0x68 (default I2C) se il controller agisce come master I2C sul bus del modulo. Verificare che il pin `ADDRO` sia configurato correttamente in base alla netlist fornita.
+298
View File
@@ -0,0 +1,298 @@
# Audio bring-up — handoff per Claude Code
> **RISOLTO (2026-08-07):** audio confermato funzionante end-to-end su Bose
> (Si4684/ESP32 → ADAU1701 → BT1035 → A2DP). Causa finale del silenzio:
> la cella DSP "Beep - variable gain" (toolbox ADI Sound Generation) usata
> come tono di test richiede sia `ENABLE` **sia** `KICK` (il parametro
> trigger) per avviare davvero la generazione — scrivere solo `ENABLE`
> lasciava il generatore silenzioso nonostante tutto il resto della catena
> (clock I2S, `AT+I2SCFG=35` a 24-bit, `AT+A2DPAUDIO`, encoder SBC attivo)
> fosse già corretto. Vedi `Adau1701Driver::setBeepEnabled()`.
>
> Il tono di test 440 Hz ora è generato dall'oscillatore interno del DSP
> ADAU1701 (programma SigmaStudio), non più dall'ESP32. Il codice
> `esp32_i2s_tone` e `CONFIG_ESP32_I2S_TONE_TEST` descritti in questo
> documento sono stati rimossi dal firmware; il resto del documento resta
> come riferimento storico del bring-up.
**Data:** 2026-08-05
**Contesto:** bring-up hardware DigiRadio su scheda reale (ESP32-S3 + Si4684 + ADAU1701 + FSC-BT1035 → Bose Solo II).
**Problema utente:** *non si sente* nulla sul Bose nonostante connessione Bluetooth.
**Stato:** fix A2DP streaming applicati e verificati parzialmente; test tono ESP32 ancora da confermare a orecchio; FM debole senza antenna.
---
## Setup laboratorio
| Parametro | Valore |
|-----------|--------|
| Serial | `/dev/cu.usbmodem1101` |
| WiFi STA | `MikiLab` |
| IP / mDNS | ~`192.168.1.56`, `digiradio-CC4DB4.local` |
| Speaker BT | Bose Solo II, MAC `BC87FAE69D6E` |
| Area FM test | Massa (MS) — RTL **100.9 MHz**, RAI1 **95.5**, Club FM **97.3** |
| Working dir build | `Software/` |
---
## Catena audio (SigmaStudio / hardware)
```
Si4684 I2S (slave) → ADAU1701 MP0 (fader Si4674) ─┐
ESP32 I2S (slave) → ADAU1701 MP1 (fader ESP32) ─┤→ St Mixer1 → EQ → master → MP6 out
└→ BT1035 I2S slave (AUXCFG=3) → A2DP → Bose
```
- **ADAU1701** = I2S **master** @ 48 kHz
- **ESP32 / BT1035 / Si4684** = I2S **slave**
- GPIO I2S ESP32: `BCLK=6`, `LRCLK/WS=7`, `DOUT=16` → ADAU MP1
---
## Modifiche effettuate (non committate)
Tutte le modifiche sono su branch `main`, **working tree dirty** (~48 file toccati in totale, molti pre-esistenti + sessione audio).
### 1. Bluetooth — A2DP streaming (causa principale del silenzio)
**Problema:** il BT1035 restava su `+A2DPSTAT=3` (Connected) senza passare a `4` (Streaming). Senza streaming il modulo non invia audio I2S→A2DP al Bose.
**Fix Feasycom §5.3.6:** dopo la connessione serve `AT+A2DPAUDIO=1`.
| File | Modifica |
|------|----------|
| `components/core/include/core/Bt1035At.hpp` | Doc + `buildBt1035A2dpAudioLine()` |
| `components/core/src/Bt1035At.cpp` | `AT+A2DPAUDIO=1/0` |
| `components/core/test/bt1035_at_test.cpp` | Test linee A2DPAUDIO |
| `components/drivers/bt1035/include/bt1035/Bt1035Driver.hpp` | `startA2dpAudio()`, `waitForA2dpStreaming()` |
| `components/drivers/bt1035/src/Bt1035Driver.cpp` | Poll A2DPSTAT; invia A2DPAUDIO=1 se bloccato su Connected; **non uscire subito** se query fallisce (retry fino a timeout) |
| `components/services/bluetooth/src/BluetoothService.cpp` | `ensureA2dpStreaming()` dopo connect/reconnect; boot reconnect attende STAT=4 |
**Verifica log (OK):**
```
I (Bt1035) A2DP audio start (AT+A2DPAUDIO=1)
I (Bt1035) stream wait: A2DPSTAT=4
I (BluetoothSvc) A2DP streaming OK
```
### 2. ADAU1701 — routing mixer boot
| File | Modifica |
|------|----------|
| `components/core/src/MixerState.cpp` | `radioFirst()` / `esp32First()` — selezione sorgente ST0/ST1 |
| `components/services/audio/src/AudioService.cpp` | `applyRadioFirstMix()` / `applyEsp32FirstMix()` |
| `main/hardware_bootstrap.cpp` | Dopo `loadAndApply()`: mix in base a Kconfig |
Con `CONFIG_ESP32_I2S_TONE_TEST=y`:
```
I (hw_boot) ADAU1701 ESP32 I2S input routed (esp32-first mix)
```
Senza tone test (FM):
```
I (hw_boot) ADAU1701 Si4684 input routed (radio-first mix)
```
### 3. Si4684 FM — I2S out + tune/scan
| File | Modifica |
|------|----------|
| `components/drivers/si4684/src/Si4684Driver.cpp` | `AUDIO_OUTPUT_CONFIG` bit I2SOUTEN @ boot; unmute; volume max; offset RSQ AN649; tune con fallback STC; band FM 87500107900 kHz |
| `components/drivers/si4684/src/Si4684Tuner.cpp` | Volume default 63; `readStatus()` non sovrascrive freq con READFREQ stale |
| `components/services/tuner/src/TunerService.cpp` | Scan FM step +100 kHz; filtro SNR; `fmChipReadFrequency` in status |
| `components/core/include/core/TunerStatus.hpp` | Campo `fmChipReadFrequency` |
**Nota:** con filo antenna corto, `rssi=0` su 100.9 MHz è normale — non indica per forza bug software.
### 4. Test tono ESP32 → ADAU → BT (isolamento path)
| File | Modifica |
|------|----------|
| `main/Kconfig.projbuild` | `ESP32_I2S_TONE_TEST` (default y in Kconfig, disabilitare in produzione) |
| `main/esp32_i2s_tone.cpp` | Sinusoide 440 Hz, 48 kHz, sample 24-bit MSB in slot 32-bit, amp 0.5 |
| `main/esp32_i2s_tone.hpp` | API `start()` |
| `main/main.cpp` | Task `esp32_tone`: delay 15 s (lascia finire boot reconnect), poi `waitForA2DPStreaming`, poi I2S |
| `main/CMakeLists.txt` | Compila `esp32_i2s_tone.cpp` se Kconfig attivo |
| `sdkconfig.defaults` | `CONFIG_ESP32_I2S_TONE_TEST=y` |
| `sdkconfig` | `CONFIG_ESP32_I2S_TONE_TEST=y` (abilitato manualmente — prima era `# is not set` e il tone test **non veniva compilato**) |
### 5. FM probe boot (alternativa al tone test)
| File | Modifica |
|------|----------|
| `main/main.cpp` | `fm_probe` task stack **16384** (era 4096 → stack overflow); tune 100.9 MHz, volume 63 |
Attivo solo se `CONFIG_ESP32_I2S_TONE_TEST` è **disabilitato**.
---
## Build e flash
```bash
cd Software
# Tone test ON (bring-up attuale)
idf.py build
idf.py -p /dev/cu.usbmodem1101 flash monitor
# FM probe al posto del tone test
idf.py -DCONFIG_ESP32_I2S_TONE_TEST=n fullclean reconfigure build flash
```
**Attenzione:** `-DCONFIG_ESP32_I2S_TONE_TEST=y` da riga di comando **non basta** se `sdkconfig` esistente ha `# CONFIG_ESP32_I2S_TONE_TEST is not set`. Verificare:
```bash
rg CONFIG_ESP32_I2S_TONE_TEST build/config/sdkconfig.h
# deve mostrare: #define CONFIG_ESP32_I2S_TONE_TEST 1
```
Ultimo build riuscito: `digiradio.bin` ~`0x237fe0` bytes.
---
## Log osservati (sessione 2026-08-05 sera)
### A2DP — OK dopo fix
```
I (BluetoothSvc) reconnect saved speaker BC87FAE69D6E (BOSE SOLO 2)
I (Bt1035) A2DP audio start (AT+A2DPAUDIO=1)
I (Bt1035) stream wait: A2DPSTAT=4
I (BluetoothSvc) A2DP streaming OK
```
A volte compare brevemente `A2DPSTAT=5` prima del `4`.
### Tone test — problemi I2S (in corso)
**Primo flash (tone test non compilato):** partiva `fm_probe`, mix radio-first, nessun log `esp32_i2s`.
**Secondo flash (tone test compilato):**
1. Race: tone task partiva insieme al reconnect → `waitForA2dpStreaming` usciva subito (query fallita) → tono partiva **prima** dello streaming.
2. I2S init falliva:
```
E (i2s_std) i2s_std_calculate_clock(68): sample rate is too large
E (esp32_i2s) i2s_channel_init_std_mode failed
```
Causa: `I2S_CLK_SRC_EXTERNAL` senza `ext_clk_freq_hz` coerente con `bclk_div`.
**Fix applicati (post [Fix no audio path](8719ea5e-ee61-48fe-ba57-7298b5638cf9)):**
- Rimosso `I2S_CLK_SRC_EXTERNAL`; slave usa BCLK/LRCLK da ADAU con `clk_src` default.
- `waitForA2dpStreaming`: continua a pollare se query fallisce (no early exit).
- Tone task: **delay 15 s** prima di attendere A2DP (evita race con boot reconnect).
- **Re-apply `applyEsp32FirstMix()`** + `startA2dpAudio()` subito prima del tono (NVS/integration sovrascriveva il mix al boot).
- I2S **24-bit** Philips + `mclk_multiple=384`; ampiezza tono 0.85.
- `sdkconfig`: `CONFIG_ESP32_I2S_TONE_TEST=y` obbligatorio (altrimenti tone test non compilato).
**Log verificati su serial (2026-08-05 ~20:15, I2S OK ma Bose silenzioso):**
```
I (Bt1035) stream wait: A2DPSTAT=4
I (BluetoothSvc) A2DP streaming OK
I (digiradio) A2DP streaming — starting I2S tone
I (digiradio) ADAU esp32-first mix re-applied before tone
I (esp32_i2s) I2S slave TX started (BCLK=6 WS=7 DOUT=16)
I (esp32_i2s) tone task: 440 Hz sine, 48000 Hz, ADAU I2S slave TX
I (esp32_i2s) i2s streaming 2048 bytes/frame to ADAU MP1
```
### FM (senza tone test)
```
I (digiradio) FM audio probe OK: 100900 kHz rssi=0 dBuV snr=0 dB locked=0
```
Segnale assente/ debole — serve antenna.
---
## Dove siamo arrivati
| Componente | Stato |
|------------|-------|
| Boot ADAU + profilo NVS | OK |
| BT1035 I2S slave (`AUXCFG=3`, `I2SCFG=67`) | OK |
| Reconnect Bose saved speaker | OK |
| **A2DP Streaming (STAT=4)** | **OK** — fix `AT+A2DPAUDIO=1` ([Fix no audio path](8719ea5e-ee61-48fe-ba57-7298b5638cf9)) |
| Mix ADAU esp32-first / radio-first | OK; **re-apply prima del tono** necessario (NVS) |
| **Test tono 440 Hz — firmware** | **OK** (I2S init + write verificati in log) |
| **Test tono 440 Hz — Bose** | **Silenzio** — probabile hardware ADAU MP6 → BT1035 o sorgente BT Bose |
| FM audio su Bose | Non testabile finché tono non si sente o RSSI > 0 |
| Utente | *«no nulla»* — silenzio persistente nonostante log software OK |
---
## Prossimi passi consigliati (priorità)
1. **Se log mostrano `A2DPSTAT=4` + `i2s streaming … bytes/frame` ma Bose muto** → debug **hardware**:
- Oscilloscopio: BCLK/LRCLK GPIO 6/7; dati MP6 ADAU → BT1035; dati GPIO 16 → ADAU MP1.
- Bose: sorgente BT = DigiRadio, telefono scollegato, volume alto.
- Verificare saldature/cavi I2S tra ADAU e BT1035 (path dopo DSP).
2. **Se I2S init fallisce ancora**
- Provare `I2S_DATA_BIT_WIDTH_24BIT` + slot 32-bit (allineamento ADAU MP1).
- Provare `bclk_div` 4 o 12 (vedi test ESP-IDF `i2s_multi_dev` slave).
- Verificare con oscilloscopio BCLK/LRCLK su GPIO 6/7 quando ADAU è bootato (ADAU deve essere master attivo prima di `i2s_channel_enable`).
3. **Se tono OK ma FM no**
- Tornare a build senza tone test (`CONFIG_ESP32_I2S_TONE_TEST=n`).
- Antenna FM; verificare `I2SOUTEN` Si4684 e `applyRadioFirstMix`.
- Cercare `rssi>15`, `locked=1`.
4. **Se A2DPSTAT=4 ma silenzio totale**
- Hardware: MP6 ADAU → pin I2S BT1035; alimentazione Bose.
- Verificare master volume / mute ADAU (safeload profilo — non dovrebbe essere a −∞).
5. **Commit** — non richiesto dall'utente finora; quando pronto, separare almeno:
- BT A2DPAUDIO / streaming
- Si4684 I2SOUTEN + scan/tune
- Tone test Kconfig (dev-only)
---
## File chiave (quick reference)
```
main/main.cpp — fm_probe vs esp32_tone task
main/hardware_bootstrap.cpp — mix ADAU al boot
main/esp32_i2s_tone.cpp — generatore tono I2S slave
main/Kconfig.projbuild — ESP32_I2S_TONE_TEST
sdkconfig / sdkconfig.defaults — Kconfig effettivo
components/drivers/bt1035/ — A2DPAUDIO, waitForA2dpStreaming
components/services/bluetooth/ — ensureA2dpStreaming al reconnect
components/services/audio/ — applyRadioFirstMix / applyEsp32FirstMix
components/core/MixerState.cpp — preset mixer ST0/ST1
components/drivers/si4684/ — I2SOUTEN, tune, RSQ
components/services/tuner/ — FM scan step tune
```
---
## Note per Claude Code
- Leggere `AGENTS.md` e `.cursor/rules/` prima di modifiche.
- **Non committare** salvo richiesta esplicita utente.
- Il tone test è **solo bring-up** — disabilitare per produzione (`sdkconfig.defaults.production` o `CONFIG_ESP32_I2S_TONE_TEST=n`).
- Due task competono sulla UART BT1035 (reconnect + tone wait): il delay 15 s mitiga ma non elimina del tutto la contesa; alternativa migliore: avviare il tone **dal callback** post-`boot reconnect OK` in `BluetoothService`.
- Transcript conversazione Cursor: thread audio/FM, ID `d10654e5-d27a-4999-812e-f371dc48567a`.
---
## Comandi utili debug
```bash
# Monitor filtrato
idf.py -p /dev/cu.usbmodem1101 monitor | rg -i 'A2DP|esp32_i2s|hw_boot|tone|streaming|error'
# Verifica config tone test nel binario
rg CONFIG_ESP32_I2S_TONE_TEST build/config/sdkconfig.h
# Reset + cattura serial (python)
python3 -c "
import serial, time
s=serial.Serial('/dev/cu.usbmodem1101',115200,timeout=0.2)
s.dtr=False; s.rts=True; time.sleep(0.1); s.rts=False
t=time.time()
while time.time()-t<90:
l=s.readline()
if l: print(l.decode(errors='replace'), end='')
"
```
+371
View File
@@ -0,0 +1,371 @@
# Claude Code — brief completo: audio bring-up DigiRadio
> **RISOLTO (2026-08-07):** audio confermato funzionante end-to-end su Bose.
> Causa finale: la cella DSP "Beep - variable gain" richiede sia `ENABLE`
> sia `KICK` per avviare la generazione — vedi
> `Adau1701Driver::setBeepEnabled()`. Il tono di test ora è generato
> dall'oscillatore interno del DSP ADAU1701, non più dall'ESP32; il codice
> `esp32_i2s_tone` e `CONFIG_ESP32_I2S_TONE_TEST` descritti in questo
> documento sono stati rimossi dal firmware. Resto del documento come
> riferimento storico del bring-up.
**Data:** 2026-08-05
**Repo:** `/Users/michelebigi/Documents/Develop/DigiRadio`
**Working dir firmware:** `Software/`
**Priorità:** far sentire audio sul **Bose Solo II** (test tono 440 Hz, poi FM)
---
## Prompt iniziale (copia-incolla in Claude Code)
```
Stai lavorando su DigiRadio (ESP32-S3 firmware in Software/).
CONTESTO
- Bring-up audio su scheda reale: Si4684 → ADAU1701 → FSC-BT1035 → Bose Solo II.
- Serial: /dev/cu.usbmodem1101
- Speaker BT salvato: Bose Solo II MAC BC87FAE69D6E
- WiFi: MikiLab, mDNS digiradio-CC4DB4.local
PROBLEMA
L'utente non sente NULLA sul Bose. I log seriali mostrano però:
- A2DPSTAT=4 (streaming OK) dopo AT+A2DPAUDIO=1
- esp32_i2s: I2S slave TX started
- esp32_i2s: i2s streaming 2048 bytes/frame to ADAU MP1
Quindi il firmware BT + I2S sembra OK; il silenzio potrebbe essere:
1) routing/mute ADAU (NVS profile sovrascrive mix),
2) formato I2S ESP32↔ADAU (24 vs 32 bit),
3) cablaggio I2S ADAU MP6 → BT1035,
4) Bose (sorgente BT errata, telefono in competizione).
LEGGI PRIMA
- Software/AGENTS.md
- Software/docs/claude-code-audio-bringup.md (questo file)
- Software/docs/audio-bringup-handoff.md
OBIETTIVO
1. Far sentire un tono 440 Hz sul Bose (CONFIG_ESP32_I2S_TONE_TEST=y).
2. Poi FM su 100.9 MHz (Massa) con applyRadioFirstMix.
VINCOLI
- NON committare senza richiesta esplicita.
- Modifiche minime, seguire convenzioni esistenti.
- Build: idf.py build da Software/
- Flash: idf.py -p /dev/cu.usbmodem1101 flash monitor
TASK SUGGERITI (in ordine)
A) Verificare log post-boot (~30s): cercare mix re-applied, i2s streaming, errori write.
B) Se software OK ma muto: aggiungere test alternativo — safeload beep interno ADAU o DC test su master volume (se possibile via parametri esistenti).
C) Avviare tone task SOLO da BluetoothService dopo "boot reconnect OK" (elimina race UART).
D) Provare entrambi i formati I2S (24-bit Philips vs 32-bit MSB<<8) con Kconfig o retry.
E) Verificare che IntegrationService::startup() non muti il mixer durante tone test (skip applyProfile se tone test attivo).
F) Documentare pinout I2S ADAU↔BT1035 da docs/manual/ e Hardware/ per debug utente.
Quando finisci: riassumi cosa hai cambiato, comando flash, log attesi.
```
---
## Setup laboratorio
| Parametro | Valore |
|-----------|--------|
| MCU | ESP32-S3 (USB-JTAG `/dev/cu.usbmodem1101`) |
| Serial monitor | 115200 baud |
| WiFi STA | `MikiLab` |
| IP / mDNS | ~`192.168.1.56`, `digiradio-CC4DB4.local` |
| Unit serial | `D8478FCC4DB4` |
| Speaker BT | **Bose Solo II**, nome AT `BOSE SOLO 2`, MAC **`BC87FAE69D6E`** |
| Area FM (Massa MS) | RTL **100900** kHz, RAI1 **95500**, Club FM **97300** |
| Build | ESP-IDF 5.5, C++23, target `esp32s3` |
---
## Catena audio
```
Si4684 I2S (slave) → ADAU1701 MP0 / Si4674 fader ─┐
ESP32 I2S (slave) → ADAU1701 MP1 / ESP32 fader ─┤→ St Mixer1 (ST0=Si4684, ST1=ESP32)
│ → Param EQ → master → MP6 SDATA_OUT0
└→ BT1035 I2S slave (AUXCFG=3, I2SCFG=67)
→ A2DP encode → Bose Solo II
```
| Ruolo | Chip | Note |
|-------|------|------|
| I2S master 48 kHz | ADAU1701 | BCLK/LRCLK su MP10/MP11 |
| I2S slave TX test | ESP32 | BCLK=GPIO6, WS=GPIO7, DOUT=GPIO16 → ADAU MP1 |
| I2S slave RX | BT1035 | Riceve da ADAU MP6 |
| FM tuner | Si4684 | I2S out verso ADAU MP0; richiede `I2SOUTEN` |
Documentazione SigmaStudio: `Software/docs/manual/ch-adau1701.tex`, export in `Software/Firmware/ADAU1701-Firmware/`.
---
## Stato attuale (2026-08-05 sera)
| Layer | Stato | Evidenza log |
|-------|-------|--------------|
| ADAU boot + program load | OK | `hw_boot: companion chips ready` |
| BT1035 I2S slave | OK | `AT+AUXCFG=3, AT+I2SCFG=67` |
| Reconnect Bose | OK | `reconnect saved speaker BC87FAE69D6E` |
| A2DP Connected (3) | OK | `A2DPSTAT=3` |
| **A2DP Streaming (4)** | **OK** | fix `AT+A2DPAUDIO=1` |
| Mix esp32-first | OK (con re-apply) | `ADAU esp32-first mix re-applied before tone` |
| ESP32 I2S tone 440 Hz | **Firmware OK** | `I2S slave TX started`, `i2s streaming 2048 bytes/frame` |
| **Audio al Bose** | **FALLITO** | Utente: *«no nulla»* |
| FM 100.9 MHz | Segnale assente | `rssi=0` (antenna wire) |
**Conclusione:** il collo di bottiglia non è più A2DP Connected-vs-Streaming. Prossimo focus: path **ADAU MP6 → BT1035** oppure formato/mix, o Bose lato utente.
---
## Modifiche firmware (non committate)
### A. Bluetooth — A2DP streaming (FIX PRINCIPALE, verificato)
**Root cause:** BT1035 restava su `A2DPSTAT=3` (Connected). Serve `AT+A2DPAUDIO=1` (Feasycom §5.3.6) per `A2DPSTAT=4` (Streaming).
| File | Modifica |
|------|----------|
| `components/core/include/core/Bt1035At.hpp` | `buildBt1035A2dpAudioLine(bool establish)` |
| `components/core/src/Bt1035At.cpp` | `AT+A2DPAUDIO=1\r\n` / `=0` |
| `components/core/test/bt1035_at_test.cpp` | Unit test linee AT |
| `components/drivers/bt1035/include/bt1035/Bt1035Driver.hpp` | `startA2dpAudio()`, `waitForA2dpStreaming(int timeoutMs)` |
| `components/drivers/bt1035/src/Bt1035Driver.cpp` | Poll `A2DPSTAT`; retry `A2DPAUDIO=1` ogni 3 s su Connected; **no early return** se query fallisce |
| `components/services/bluetooth/src/BluetoothService.cpp` | `ensureA2dpStreaming()` dopo ogni connect/reconnect |
### B. ADAU1701 — mixer routing
| File | Modifica |
|------|----------|
| `components/core/include/core/MixerState.hpp` | `radioFirst()`, `esp32First()` |
| `components/core/src/MixerState.cpp` | ST0=Si4684, ST1=ESP32; mute path non usato a 96 dB |
| `components/services/audio/include/audio/AudioService.hpp` | `applyRadioFirstMix()`, `applyEsp32FirstMix()` |
| `components/services/audio/src/AudioService.cpp` | Safeload mixer + master unity |
| `main/hardware_bootstrap.cpp` | Dopo `loadAndApply()`: mix per Kconfig |
**Mapping SigmaStudio (St Mixer1):**
- `mixLeft` → ST0 (Si4684)
- `mixRight` → ST1 (ESP32)
**Bug scoperto:** `IntegrationService::startup()``recallPreset()``applyStoredAudioProfile()` può **sovrascrivere il mix** dopo `hw_boot`. Fix attuale: re-apply `applyEsp32FirstMix()` in `esp32I2sToneTask` prima di I2S. Fix migliore: skip apply profile durante tone test, o avviare tone da `BluetoothService` post-reconnect.
### C. Si4684 FM
| File | Modifica |
|------|----------|
| `components/drivers/si4684/src/Si4684Driver.cpp` | `AUDIO_OUTPUT_CONFIG` **I2SOUTEN** (0x0302=0x0002); unmute; vol 63; RSQ offsets AN649; tune STC fallback 150 ms |
| `components/drivers/si4684/src/Si4684Tuner.cpp` | Volume 63; READFREQ stale guard |
| `components/services/tuner/src/TunerService.cpp` | FM scan step +100 kHz; SNR≥10; chip freq check |
| `components/core/include/core/TunerStatus.hpp` | `fmChipReadFrequency` |
### D. Test tono ESP32 (bring-up dev)
| File | Modifica |
|------|----------|
| `main/Kconfig.projbuild` | `CONFIG_ESP32_I2S_TONE_TEST` (default y) |
| `main/esp32_i2s_tone.cpp` | 440 Hz sine, I2S slave, 24-bit Philips, mclk_multiple=384, amp 0.85 |
| `main/esp32_i2s_tone.hpp` | `bool start()` |
| `main/main.cpp` | Task `esp32_tone`: delay 15 s → wait A2DP → re-apply mix → `startA2dpAudio()` → I2S |
| `main/CMakeLists.txt` | Condizionale su Kconfig |
| `main/board_pins.hpp` | BCLK=6, WS=7, DOUT=16 |
| `sdkconfig.defaults` | `CONFIG_ESP32_I2S_TONE_TEST=y` |
| `sdkconfig` | `CONFIG_ESP32_I2S_TONE_TEST=y` (**critico** — se `# is not set` il tone non compila) |
### E. FM probe (alternativa, tone test OFF)
| File | Modifica |
|------|----------|
| `main/main.cpp` | `fm_probe` stack 16384; tune 100900 kHz; `applyRadioFirstMix` |
### F. Altri file dirty (sessioni precedenti, non audio-core)
Net/UI, secure store, BluetoothJson, SetupWebServer, station/tuner tests — vedi `git diff --name-only`.
---
## Boot sequence (ordine temporale)
```
app_main
→ HardwareBootstrap::boot()
→ ADAU program load
→ loadAndApply() ← profilo NVS
→ applyEsp32FirstMix() ← se tone test
→ BT1035 boot (AUXCFG=3)
→ Si4684 boot
→ integration.startup() ← può recall preset + applyProfile (SOVRASCRIVE MIX)
→ NetBootstrap
→ bluetoothService.startupReconnect() ← task async, A2DPSTAT→4
→ esp32I2sToneTask ← delay 15s, wait stream, re-apply mix, I2S start
```
---
## Build, flash, verifica
```bash
cd /Users/michelebigi/Documents/Develop/DigiRadio/Software
# Verifica tone test compilato
rg CONFIG_ESP32_I2S_TONE_TEST build/config/sdkconfig.h
# Atteso: #define CONFIG_ESP32_I2S_TONE_TEST 1
idf.py build
idf.py -p /dev/cu.usbmodem1101 flash monitor
```
**Disabilitare tone test (FM probe):**
```bash
# In sdkconfig: # CONFIG_ESP32_I2S_TONE_TEST is not set
idf.py fullclean reconfigure build flash
```
---
## Log attesi (tone test OK, ~30 s dopo reset)
```
I (hw_boot) ADAU1701 ESP32 I2S input routed (esp32-first mix)
I (Bt1035) I2S slave mode enabled (AT+AUXCFG=3, AT+I2SCFG=67)
I (BluetoothSvc) boot reconnect task started
I (BluetoothSvc) reconnect saved speaker BC87FAE69D6E (BOSE SOLO 2)
I (Bt1035) A2DP audio start (AT+A2DPAUDIO=1)
I (Bt1035) stream wait: A2DPSTAT=4
I (BluetoothSvc) A2DP streaming OK
I (digiradio) ==== ESP32 I2S tone test (440 Hz) — waiting A2DP streaming ====
I (digiradio) A2DP streaming — starting I2S tone
I (digiradio) ADAU esp32-first mix re-applied before tone
I (esp32_i2s) I2S slave TX started (BCLK=6 WS=7 DOUT=16)
I (esp32_i2s) tone task: 440 Hz sine, 48000 Hz, ADAU I2S slave TX
I (esp32_i2s) i2s streaming 2048 bytes/frame to ADAU MP1
```
**Errori già risolti (non devono riapparire):**
```
E (i2s_std) i2s_std_calculate_clock(68): sample rate is too large ← I2S_CLK_SRC_EXTERNAL errato
W (digiradio) A2DP not streaming after 90000 ms (a t=10s) ← race waitForA2dpStreaming
```
---
## Ipotesi da investigare (Claude Code)
### H1 — Hardware I2S ADAU → BT1035
Firmware invia PCM a BT1035 solo se ADAU MP6 ha clock+dati. Verificare:
- MP6 configurato come SDATA_OUT0 (SigmaStudio `MFSELECT6=0x4` in export)
- `REG_SERIALOUTREGISTER1 = 0x800` (output enabled)
- Cavo dati MP6 → pin I2S DIN del BT1035
### H2 — Formato I2S ESP32 → ADAU MP1
ADAU accetta 24-bit I2S. Firmware usa 24-bit Philips; provare anche 32-bit con sample `<< 8` se tono ancora muto.
### H3 — NVS profile muta mixer/enhancements
`IntegrationService` richiama `applyProfile(currentProfile())` che riapplica mixer da NVS. Durante tone test:
- Disabilitare recall preset audio, oppure
- `#if CONFIG_ESP32_I2S_TONE_TEST` skip in `IntegrationService::applyStoredAudioProfile`
### H4 — Bose non in playout
- Telefono connesso al Bose in parallelo
- Bose su input AUX invece di BT
- Volume Bose al minimo
### H5 — BT1035 I2S non sincronizzato
Con `A2DPSTAT=4` e I2S da ADAU: verificare se BT1035 richiede comandi aggiuntivi oltre `A2DPAUDIO=1` (leggere `docs/manual/ch-bt1035.tex`, manuale Feasycom).
### H6 — Master volume / enhancements
`applyProfile` applica EQ con enhancements overlay. Verificare che master gain non sia 96 dB in profilo NVS salvato.
---
## Task concreti per Claude Code (checklist)
- [ ] **T1** Leggere log fresh dopo flash; confermare sequenza sopra.
- [ ] **T2** Spostare avvio tone in callback `BluetoothService` post `boot reconnect OK` (elimina delay 15s + race UART).
- [ ] **T3** Guard tone test: skip `IntegrationService::applyStoredAudioProfile` quando `CONFIG_ESP32_I2S_TONE_TEST`.
- [ ] **T4** Aggiungere Kconfig `ESP32_I2S_TONE_FORMAT_32BIT` per A/B test formato senza rebuild manuale.
- [ ] **T5** Log `queryA2dpState` + `A2DPENC` / volume BT1035 se esiste comando AT.
- [ ] **T6** Test senza ESP32: `applyRadioFirstMix` + Si4684 tone/noise (se RSSI>0 con antenna) per isolare BT path.
- [ ] **T7** Aggiornare `docs/audio-bringup-handoff.md` con esito.
---
## File chiave (aprire per primi)
```
Software/main/main.cpp
Software/main/esp32_i2s_tone.cpp
Software/main/hardware_bootstrap.cpp
Software/main/board_pins.hpp
Software/main/Kconfig.projbuild
Software/components/services/bluetooth/src/BluetoothService.cpp
Software/components/drivers/bt1035/src/Bt1035Driver.cpp
Software/components/core/src/Bt1035At.cpp
Software/components/services/audio/src/AudioService.cpp
Software/components/core/src/MixerState.cpp
Software/components/drivers/adau1701/src/Adau1701Driver.cpp
Software/components/services/integration/src/IntegrationService.cpp
Software/components/drivers/si4684/src/Si4684Driver.cpp
Software/Firmware/ADAU1701-Firmware/DigiRadio_IC_1_PARAM.h
Software/sdkconfig
Software/sdkconfig.defaults
```
---
## Regole progetto
- Leggere `Software/AGENTS.md` e `Software/.cursor/rules/` prima di ogni modifica.
- **Non committare** senza richiesta utente.
- Tone test = solo dev; produzione usa `sdkconfig.defaults.production` senza tone test.
- Host tests: `cmake --build build-host && ctest` (se tocchi core/bt1035).
---
## Comandi debug rapidi
```bash
# Monitor filtrato
idf.py -p /dev/cu.usbmodem1101 monitor 2>&1 | rg -i 'A2DP|esp32_i2s|hw_boot|tone|streaming|error|mix'
# Reset + 90s capture
python3 <<'PY'
import serial, time
s = serial.Serial("/dev/cu.usbmodem1101", 115200, timeout=0.2)
s.dtr = False; s.rts = True; time.sleep(0.1); s.rts = False
t = time.time()
while time.time() - t < 90:
line = s.readline()
if line: print(line.decode(errors="replace"), end="")
PY
# Host unit test BT1035 AT
cd Software && cmake -B build-host -DBUILD_HOST_TESTS=ON && cmake --build build-host && ctest --test-dir build-host -R bt1035
```
---
## Documenti correlati
| File | Contenuto |
|------|-----------|
| `Software/docs/claude-code-audio-bringup.md` | **Questo file** — brief operativo Claude Code |
| `Software/docs/audio-bringup-handoff.md` | Handoff tecnico sessione Cursor |
| `Software/docs/manual/ch-adau1701.tex` | ADAU boot, safeload, signal chain |
| `Software/docs/manual/ch-bt1035.tex` | BT1035 I2S, AUXCFG, pairing |
| `Software/AGENTS.md` | Regole agenti, build, DoD |
---
## Storia sessione Cursor
- Thread: audio/FM bring-up, ID `d10654e5-d27a-4999-812e-f371dc48567a`
- Subagent [Fix no audio path](8719ea5e-ee61-48fe-ba57-7298b5638cf9): fix `AT+A2DPAUDIO=1` + tone test scaffold
- Follow-up Cursor: sdkconfig tone test, fix I2S slave clock, mix re-apply, flash multipli
- Utente conferma silenzio persistente nonostante log software OK → sospetto hardware o mix/NVS
+93 -6
View File
@@ -99,10 +99,14 @@ renamed cells):
signal chain changed.
\end{enumerate}
\begin{drnote}[No USBi download on DigiRadio]
Do \emph{not} rely on SigmaStudio \emph{Link Compile Download} on this
hardware path. Export-only workflow is sufficient: the ESP32 is the programmer
at every power-up.
\begin{drnote}[Export-only workflow still recommended]
The export-only workflow above remains the simplest path: the ESP32 replays
the committed export at every power-up, so \emph{Link Compile Download} is
not required for day-to-day firmware builds. Since firmware~0.9.0, however,
DigiRadio does expose SigmaStudio's Remote Connection over TCP for live DSP
work and bench debugging --- see Section~\ref{sec:adau1701-sigmastudio-tcp}.
A completed Download over that path is automatically persisted to flash and
becomes the boot program, exactly like re-exporting and rebuilding.
\end{drnote}
\section{Boot sequence (I\textsuperscript{2}C RAM load)}
@@ -134,14 +138,97 @@ the DSP program and writes it into RAM after each reset.
\label{fig:adau1701-boot}
\end{figure}
\texttt{SIGMA\_WRITE\_REGISTER\_BLOCK} streams each write in 64-byte I\textsuperscript{2}C
chunks (payloads may exceed 255 bytes). The active script comes from the
\texttt{SIGMA\_WRITE\_REGISTER\_BLOCK} streams each write in $\leq$64-byte
I\textsuperscript{2}C chunks, rounded down to a whole number of words for the
target region (Parameter RAM = 4~bytes/word, Program RAM = 5, control
registers = 2) so chunk boundaries never split a word or desynchronise the
running address (payloads may exceed 255 bytes). The active script comes from the
\texttt{dsp} data partition when a valid \texttt{DRAD} blob is present; otherwise
the embedded SigmaStudio export is used (Section~\ref{sec:api-dsp-program}).
After download, \texttt{loadAndApply()} restores the saved
\texttt{core::AudioProfile} from NVS (or factory defaults) so user settings
survive power cycles without re-writing the whole program.
\section{SigmaStudio Remote Connection (TCP bridge)}
\label{sec:adau1701-sigmastudio-tcp}
\texttt{net::SigmaStudioTcpServer}\label{cls:SigmaStudioTcpServer} exposes the
ADAU1701 to SigmaStudio running on a PC over TCP/IP, so SigmaStudio's own
\emph{Connect} and \emph{Link Compile Download} work against DigiRadio exactly
as they would against a USBi/ICP dongle. It listens on port \textbf{8086} in
both SoftAP setup mode and STA mode, started from \texttt{NetBootstrap}
alongside \texttt{SetupWebServer}. The socket is unauthenticated, consistent
with the existing LAN-exposed \texttt{POST /api/system/ota} and
\texttt{POST /api/dsp/program} endpoints.
\begin{drref}[Protocol source]
This implements the subset of SigmaStudio's TCPi wire protocol needed for
Connect, Download, register read/write, and runtime safeload, ported from the
\href{https://github.com/rarranzb/ADAU1701-TCPi-ESP32}{ADAU1701-TCPi-ESP32}
reference project (MIT licence). Analog Devices' TCPi protocol itself is
closed/undocumented; this is a single third-party reverse-engineering source,
cross-checked against DigiRadio's own generated \texttt{DigiRadio\_IC\_1.h}
memory-map constants (Program RAM at word-address \texttt{0x0400}, Parameter
RAM at \texttt{0x0000}, core control register at \texttt{0x081C}) which match
exactly. Verify with a real SigmaStudio Connect + Download session before
relying on it for production DSP work.
\end{drref}
\begin{table}[htbp]
\centering
\small
\begin{tabular}{@{}L{2.2cm}L{9.8cm}@{}}
\drhead Frame & Layout \\
\midrule
\texttt{0x09} WRITE &
\texttt{[op][safeload][rsvd][totalLen:2][chipAddr][dataLen:2][addr:2][payload]} \\
\texttt{0x0A} READ\_REQ &
\texttt{[op][totalLen:2][chipAddr][dataLen:2][addr:2]} \\
\texttt{0x0B} READ\_RESP &
\texttt{[op][size:2][chipAddr][dataLen:2][addr:2][status][data]} \\
\bottomrule
\end{tabular}
\caption{TCPi frame layout (all multi-byte fields big-endian).}
\label{tab:adau1701-tcpi-frames}
\end{table}
Direct (non-safeload) writes reuse the region-aware
\texttt{SIGMA\_WRITE\_REGISTER\_BLOCK} (Section~\ref{sec:adau1701-boot} boot
path). Safeload writes forward SigmaStudio's already-encoded 4-byte words
verbatim via \texttt{sigma\_safeload\_raw\_block()} --- unlike
\texttt{sigma\_safeload\_block()} (Section~\ref{sec:adau1701-safeload}), which
reconstructs the payload from a host \texttt{int}, this path never
reinterprets network bytes. A new \texttt{sigma\_studio\_lock()} /
\texttt{sigma\_studio\_unlock()} pair in \texttt{SigmaStudioFW.c} serialises
whole safeload/write sequences against \texttt{Adau1701Driver}'s own
REST-triggered safeload calls, so a live SigmaStudio session and
\texttt{/api/audio/*} traffic cannot interleave mid-transaction.
\subsection{Persisting a Download as the boot program}
A completed \emph{Link Compile Download} is captured and made the program
DigiRadio boots with next time --- the same outcome as re-exporting and
rebuilding. \texttt{SigmaStudioTcpServer} coalesces contiguous direct writes
into a handful of regions per connection (mirroring the five-block shape
of \texttt{EmbeddedDspProgramSource}), completion is detected the same way
the reference project detects it: a direct write to \texttt{0x081C} that sets
the DSPRUN bit. On completion the coalesced regions are serialised
(\texttt{core::serializeDspProgramBlob()}) and written to the \texttt{dsp}
flash partition via \texttt{adau1701::FlashDspProgramSource::storeBlob()} ---
the same function \texttt{POST /api/dsp/program} already uses
(Section~\ref{sec:api-dsp-program}). If a session is too fragmented to fit the
blob's own caps (32 regions / 16\,KiB per region / 200\,KiB total), the
persist step is skipped for that session only; the DSP still runs from what
was written live over I\textsuperscript{2}C.
\begin{drcaution}[Not ported from the reference project]
DigiRadio does not implement the reference project's EEPROM self-boot capture
(binary format for an external self-boot EEPROM) or its Wi-Fi/GPIO
configuration web UI --- DigiRadio has no self-boot EEPROM on this bus (the
24AA025E48 at \texttt{0x52} is an EUI-48 identity chip only), and Wi-Fi
provisioning already exists via \texttt{POST /api/wifi}.
\end{drcaution}
\section{Software architecture}
\label{sec:adau1701-stack}
+46 -16
View File
@@ -8,9 +8,9 @@
The Feasycom FSC-BT1035 (Qualcomm QCC3056) is the wireless output stage of
DigiRadio: it receives PCM from the ADAU1701 over I\textsuperscript{2}S and
streams Bluetooth audio with aptX, aptX~HD, and aptX~Adaptive. This chapter
documents how the ESP32-S3 controls the module over UART (AT commands with
RTS/CTS), why I\textsuperscript{2}S slave mode is mandatory, and how
\texttt{bt1035::Bt1035Driver} implements the bring-up sequence.
documents how the ESP32-S3 controls the module over UART (AT commands, no
hardware flow control), why I\textsuperscript{2}S slave mode is mandatory,
and how \texttt{bt1035::Bt1035Driver} implements the bring-up sequence.
\begin{drref}[Hardware context]
Board wiring (UART pins, I\textsuperscript{2}S to the module, flow control)
@@ -30,9 +30,9 @@ accepted and encoded for transmission.
Without firmware init the module may stay in a default mode that ignores the
I\textsuperscript{2}S bus from the ADAU1701. The mandatory
\texttt{AT+AUXCFG=3} and \texttt{AT+I2SCFG=67} commands select I\textsuperscript{2}S
slave input at 48\,kHz --- omitting them silently breaks the entire wireless
output (Section~\ref{sec:bt1035-i2s}).
\texttt{AT+AUXCFG=3} and \texttt{AT+I2SCFG=35} commands select I\textsuperscript{2}S
slave input at 48\,kHz, 24-bit --- omitting them silently breaks the entire
wireless output (Section~\ref{sec:bt1035-i2s}).
\section{Control interface}
\label{sec:bt1035-uart}
@@ -48,7 +48,7 @@ output (Section~\ref{sec:bt1035-i2s}).
Port & UART2 (not the console UART) \\
Baud rate & 115200 \\
Data format & 8N1 \\
Flow control & Hardware RTS/CTS (required) \\
Flow control & Disabled on the ESP32 UART (\texttt{UART\_HW\_FLOWCTRL\_DISABLE}) \\
Reset & GPIO active-low pulse at boot \\
SYS\_CTL & Held active to enable the module \\
\bottomrule
@@ -57,6 +57,18 @@ output (Section~\ref{sec:bt1035-i2s}).
\label{tab:bt1035-uart}
\end{table}
\begin{drnote}[Flow control disabled, not just unused]
Earlier revisions wired \texttt{RTS}/\texttt{CTS} through
\texttt{Bt1035Pins} and configured \texttt{UART\_HW\_FLOWCTRL\_CTS\_RTS}.
That was reverted: \texttt{Bt1035Driver.cpp} now configures
\texttt{UART\_HW\_FLOWCTRL\_DISABLE} and passes
\texttt{UART\_PIN\_NO\_CHANGE} for both lines --- GPIO14/21 are not driven by
this driver. No AT command in \texttt{core::Bt1035AtCommand} configures flow
control on the module side either; this has not caused observed boot
failures, but has not been independently verified against the module's own
flow-control expectation.
\end{drnote}
\subsection{Response handling}
Every command expects a module reply containing \texttt{OK} or
@@ -69,7 +81,7 @@ Every command expects a module reply containing \texttt{OK} or
\end{itemize}
Host tests in \drpath{components/core/test/bt1035_at_test.cpp} lock the
init sequence (\texttt{AT+AUXCFG=3}, \texttt{AT+I2SCFG=67}) and the parser.
init sequence (\texttt{AT+AUXCFG=3}, \texttt{AT+I2SCFG=35}) and the parser.
\section{Mandatory I\textsuperscript{2}S slave mode}
\label{sec:bt1035-i2s}
@@ -80,7 +92,9 @@ with shared BCLK/LRCLK (see Chapter~\ref{ch:hardware}). The init sequence
must use:
\begin{itemize}
\item \texttt{AT+AUXCFG=3} --- I\textsuperscript{2}S mode (§5.1.25)
\item \texttt{AT+I2SCFG=67} --- I\textsuperscript{2}S slave, 48\,kHz, 32-bit (§5.1.4)
\item \texttt{AT+I2SCFG=35} --- I\textsuperscript{2}S slave, 48\,kHz, 24-bit (§5.1.4),
matching the ADAU1701 serial output word length configured in the
SigmaStudio Hardware Configuration
\end{itemize}
\texttt{AT+AUXCFG=1} (Line-In) does \textbf{not} match the schematic.
AGENTS.md treats skipping I\textsuperscript{2}S init as a production bug.
@@ -101,16 +115,18 @@ I\textsuperscript{2}S path is ready before Wi-Fi starts.
>={Latex}, node distance=3mm]
\node[drstep, fill=black!6] (sys) {SYS\_CTL high, RESET\# pulse};
\node[drstep, fill=black!6, below=of sys] (uart)
{Install UART2 @ 115200, RTS/CTS};
\node[drstep, fill=black!8, below=of uart] (at)
{Install UART2 @ 115200, flow control disabled};
\node[drstep, fill=black!7, below=of uart] (swrst)
{Send \texttt{AT+RESET} (best-effort), settle 500\,ms, flush RX};
\node[drstep, fill=black!8, below=of swrst] (at)
{Send \texttt{AT} --- expect OK};
\node[drstep, fill=black!10, below=of at] (aux)
{Send \texttt{AT+AUXCFG=3} --- expect OK (I\textsuperscript{2}S)};
\node[drstep, fill=black!10, below=of aux] (i2s)
{Send \texttt{AT+I2SCFG=67} --- expect OK (slave 48\,kHz)};
{Send \texttt{AT+I2SCFG=35} --- expect OK (slave 48\,kHz, 24-bit)};
\node[drstep, fill=black!6, below=of i2s] (done)
{\texttt{Bt1035Driver::isBooted()} = true};
\foreach \a/\b in {sys/uart, uart/at, at/aux, aux/i2s, i2s/done} {
\foreach \a/\b in {sys/uart, uart/swrst, swrst/at, at/aux, aux/i2s, i2s/done} {
\draw[->] (\a) -- (\b);
}
\end{tikzpicture}
@@ -118,6 +134,17 @@ I\textsuperscript{2}S path is ready before Wi-Fi starts.
\label{fig:bt1035-boot}
\end{figure}
\begin{drnote}[AT+RESET is best-effort, not gated]
Unlike \texttt{AT+AUXCFG=3}/\texttt{AT+I2SCFG=35} (mandatory,
\texttt{core::bootInitSequence()}, boot fails if either is refused),
\texttt{AT+RESET} is sent separately and its result is discarded: it is
unverified whether this Feasycom firmware acknowledges the command before
rebooting, resets silently without a reply, or rejects it outright. Gating
boot on an unconfirmed ack would risk breaking an otherwise-working sequence.
The 500\,ms settle delay and RX flush after sending it mirror the delay
already used after the hardware GPIO reset pulse.
\end{drnote}
Pairing, codec selection, and volume over Bluetooth are handled by the
module's own firmware and NVS; DigiRadio firmware currently implements
only the I\textsuperscript{2}S bring-up required for the wired audio path.
@@ -158,9 +185,10 @@ The firmware enumerates every command it sends. Wire formats follow
\begin{tabular}{@{}L{2.4cm}L{3.4cm}L{5.5cm}@{}}
\drhead Enum & Line sent & Programming guide \\
\midrule
\texttt{Reset} & \texttt{AT+RESET} & software reset, best-effort \\
\texttt{Ping} & \texttt{AT} & link check \\
\texttt{I2sMode} & \texttt{AT+AUXCFG=3} & §5.1.25 Param=3 I2S \\
\texttt{I2sSlave48k32} & \texttt{AT+I2SCFG=67} & §5.1.4 slave 48\,kHz 32-bit \\
\texttt{I2sSlave48k24} & \texttt{AT+I2SCFG=35} & §5.1.4 slave 48\,kHz 24-bit \\
\texttt{PairDiscoverable} & \texttt{AT+PAIR=1} & §5.1.20 enter discoverable \\
\texttt{PairHidden} & \texttt{AT+PAIR=0} & §5.1.20 leave discoverable \\
\texttt{A2dpStat} & \texttt{AT+A2DPSTAT} & §5.3.1; states 1--5 \\
@@ -171,7 +199,9 @@ The firmware enumerates every command it sends. Wire formats follow
\bottomrule
\end{tabular}
\caption{Enumerated AT commands (\texttt{core::Bt1035AtCommand}). Boot
uses Ping + I2sMode + I2sSlave48k32; pairing commands are runtime.}
sends Reset (best-effort) then the mandatory Ping + I2sMode +
I2sSlave48k24 (\texttt{core::bootInitSequence()}); pairing commands are
runtime.}
\label{tab:bt1035-at}
\end{table}
@@ -258,7 +288,7 @@ bt1035::Bt1035Driver& bt = ...;
if (auto r = bt.sendCommand(core::Bt1035AtCommand::I2sMode); !r) {
// handle Bt1035Error
}
if (auto r = bt.sendCommand(core::Bt1035AtCommand::I2sSlave48k32); !r) {
if (auto r = bt.sendCommand(core::Bt1035AtCommand::I2sSlave48k24); !r) {
// handle Bt1035Error
}
\end{verbatim}
+19 -2
View File
@@ -79,11 +79,20 @@ Minimal HTTP server: gzipped setup UI and typed JSON REST handlers:
\item Audio: \texttt{/api/audio/*}
\item Bluetooth: \texttt{/api/bluetooth/*}
\item Presets: \texttt{/api/stations/*}
\item Streaming: \texttt{/api/streaming}
\end{itemize}
JSON parsing and serialisation delegate to the pure core; credentials persist via \texttt{ISecureStore};
tuner via \texttt{tuner::TunerService}; audio via
\texttt{audio::AudioService}; Bluetooth via \texttt{bluetooth::BluetoothService};
presets via \texttt{station::StationService}.
presets via \texttt{station::StationService}; streaming via
\texttt{webradio::WebRadioService}.
\section{WifiScanner}\label{cls:WifiScanner}
Stateless shell helper wrapping \texttt{esp\_wifi} scan for the setup UI's
Wi-Fi tab. \texttt{scanNearby()} temporarily switches an AP-only radio to
APSTA mode when needed, runs a blocking scan, dedupes access points by
SSID (keeping the strongest RSSI), and maps results to
\texttt{core::WifiScannedNetwork}. Called from \texttt{POST /api/wifi/scan}.
\section{NetBootstrap}\label{cls:NetBootstrap}
Owns network resources for setup or STA mode. Initialises encrypted NVS via
@@ -168,7 +177,7 @@ index (1--8), 12-digit MAC, and optional friendly name. Parsed by
UART driver for the FSC-BT1035 (Chapter~\ref{ch:bt1035}). \texttt{boot()}
pulses RESET\#, opens UART2 with RTS/CTS, and runs
\texttt{core::bootInitSequence()} (Ping + \texttt{AT+AUXCFG=3} +
\texttt{AT+I2SCFG=67}). Returns
\texttt{AT+I2SCFG=35}). Returns
\texttt{Bt1035Error} on timeout, ERROR response, or UART failure.
\section{Adau1701Driver}\label{cls:Adau1701Driver}
@@ -341,4 +350,12 @@ the inactive OTA slot, validates the app descriptor via
\texttt{core::validateOtaAppDescriptor()}, and exposes
\texttt{confirmBoot()} for rollback cancellation after a healthy network boot.
\section{WebRadioService}\label{cls:WebRadioService}
Thread-safe holder for the internet radio streaming config (enabled flag +
HTTP MP3 URL), backed by \texttt{core::ISecureStore}. A FreeRTOS mutex
guards an in-RAM copy so \texttt{setConfig()} from an HTTP handler and
\texttt{config()} polled by the streaming task in
\texttt{main/web\_radio\_stream.cpp} never race. Exposed on
\texttt{/api/streaming}.
% ------------------------------------------------------------------
+2 -2
View File
@@ -151,7 +151,7 @@ safeload registers.
\paragraph{FSC-BT1035 (Bluetooth).}
The module is controlled by AT commands over UART with hardware flow control.
The initialisation sequence includes enabling I\textsuperscript{2}S slave mode
(\texttt{AT+AUXCFG=3} and \texttt{AT+I2SCFG=67}), which is required for the
(\texttt{AT+AUXCFG=3} and \texttt{AT+I2SCFG=35}), which is required for the
wired digital audio path from the ADAU1701 to the module.
Command responses are parsed explicitly, with timeouts treated as errors.
Full driver API, boot flow, and error codes are in Chapter~\ref{ch:bt1035}.
@@ -218,7 +218,7 @@ bring-up:
defaults) via ADAU1701 safeload before network bring-up.
\item \textbf{FSC-BT1035} (UART): \texttt{bt1035::Bt1035Driver::boot()}
enables I\textsuperscript{2}S slave mode (\texttt{AT+AUXCFG=3},
\texttt{AT+I2SCFG=67}) after the DSP path is configured
\texttt{AT+I2SCFG=35}) after the DSP path is configured
(Chapter~\ref{ch:bt1035}).
\end{enumerate}
+2 -2
View File
@@ -132,7 +132,7 @@ in the module's own non-volatile memory.
\begin{drcaution}[I\textsuperscript{2}S slave mode]
The PCB routes ADAU1701 \texttt{SDATA\_OUT0} to the module over
I\textsuperscript{2}S (BCLK, LRCLK, PCM data). Firmware must initialise
\texttt{AT+AUXCFG=3} and \texttt{AT+I2SCFG=67}, not Line-In
\texttt{AT+AUXCFG=3} and \texttt{AT+I2SCFG=35}, not Line-In
(\texttt{AT+AUXCFG=1}). See Chapter~\ref{ch:bt1035}, Section~\ref{sec:bt1035-i2s}.
\end{drcaution}
@@ -525,7 +525,7 @@ ever used.}
\midrule
UART with flow control & TX/RX + RTS/CTS wired & verified \\
I\textsuperscript{2}S init (firmware) &
\texttt{AT+AUXCFG=3}, \texttt{AT+I2SCFG=67} & driver rule \\
\texttt{AT+AUXCFG=3}, \texttt{AT+I2SCFG=35} & driver rule \\
I\textsuperscript{2}S input & receives clocks from ADAU master (slave) & verified \\
Reset line & RESET (GPIO17) driven by ESP32 & verified \\
\bottomrule
+17 -6
View File
@@ -21,9 +21,18 @@ the program, not to drive the chip in operation.
The whole program can be built and exported offline, without a physical
ADAU1701 or a USBi programmer connected. A USBi block is placed in the
Hardware Configuration only because SigmaStudio requires a communication
channel to compile; it is never used for a real download in this project.
channel to compile.
\end{drkey}
\begin{drref}[Live connection is also possible]
Since firmware~0.9.0, DigiRadio's \texttt{net::SigmaStudioTcpServer}
(Chapter~\ref{ch:adau1701}, Section~\ref{sec:adau1701-sigmastudio-tcp})
exposes a TCP:8086 bridge so SigmaStudio can \emph{Connect} and
\emph{Link Compile Download} directly against a running board, for live DSP
tuning and bench debugging. The export-only workflow below remains the
simplest path for ordinary firmware builds.
\end{drref}
\section{Hardware Configuration}
\label{sec:ss-hwcfg}
@@ -270,9 +279,11 @@ The firmware's ADAU1701 driver replays the program data over
I\textsuperscript{2}C at boot, and uses the parameter addresses for
safeload updates.
\begin{drnote}[No download needed]
Since there is no ADAU1701 attached during development, do \emph{not} use
\emph{Link Compile Download} (it would fail trying to reach the chip).
\emph{Export System Files} compiles the schematic and writes the files
directly.
\begin{drnote}[Export System Files vs. Link Compile Download]
Without a physical ADAU1701/USBi attached, \emph{Export System Files} is what
you want: it compiles the schematic and writes the files directly, with no
chip connection required. \emph{Link Compile Download} instead needs a live
communication channel to a chip --- either the classic USBi/ICP dongle, or
DigiRadio's own TCP:8086 SigmaStudio bridge (Section~\ref{sec:adau1701-sigmastudio-tcp})
when connecting live to a running board.
\end{drnote}