|
|
@@ -0,0 +1,164 @@
|
|
|
+# CLAUDE.md — ModulosDSP_101
|
|
|
+
|
|
|
+## What This Is
|
|
|
+
|
|
|
+Arduino sketch for the **Waveshare ESP32-S3 Zero** that bridges SigmaStudio (Analog Devices' DSP programming GUI) to a physical **ADAU1401/1701 DSP** over WiFi → TCP → I²C. It also hosts an HTTP UI for writing DSP firmware to a 24C256 EEPROM, and an HTTP OTA page for reflashing the ESP32 itself. The sketch runs on the companion **MSD ADAU14-1701 Adapter** PCB (Altium files in the parent directory).
|
|
|
+
|
|
|
+## Build Environment
|
|
|
+
|
|
|
+- **Arduino IDE** 1.8.x or 2.x with the ESP32 Arduino core
|
|
|
+- Board: **ESP32S3 Dev Module** (Flash mode: QIO, 4 MB, Default 4MB with spiffs partition)
|
|
|
+- Both HTML UIs (`index_html.h`, `ota_html.h`) are PROGMEM string literals compiled into flash — no separate LittleFS image upload is required
|
|
|
+- Baud rate for Serial monitor: **115200**
|
|
|
+
|
|
|
+## Architecture
|
|
|
+
|
|
|
+Two servers run in parallel — one TCP, one HTTP:
|
|
|
+
|
|
|
+- `tcpServer` (port 8086): SigmaTCP protocol. `handleTcpBridgeClient()` blocks `loop()` for the duration of a session (single client at a time).
|
|
|
+- `httpServer` (port 80): EEPROM upload (`/`), OTA firmware update (`/ota`), and status endpoints. `httpServer.handleClient()` is called inside the TCP client loop so HTTP stays responsive during a TCP session.
|
|
|
+
|
|
|
+`loop()` only accepts a new TCP client after the previous one disconnects. There is no concurrency and no idle timeout — the connection stays open until SigmaStudio closes it.
|
|
|
+
|
|
|
+`maintainWifi()` is called at the top of every `loop()` iteration and checks WiFi status every 5 seconds, attempting reconnect if the link has dropped.
|
|
|
+
|
|
|
+## Build / Version Scheme
|
|
|
+
|
|
|
+The build identifier uses `YYMMDD_rev` format (e.g. `260304_13`). It appears in two places that must always match:
|
|
|
+
|
|
|
+1. The file header comment: `// Build: 260304_13 (YYMMDD_rev)`
|
|
|
+2. The runtime constant: `const char* version = "VER: 260304_13";`
|
|
|
+
|
|
|
+When cutting a new build, update both. The `version` constant is displayed on the LCD and printed to Serial at boot.
|
|
|
+
|
|
|
+## WiFi Credentials
|
|
|
+
|
|
|
+Hardcoded at the top of `ModulosDSP_101.ino`:
|
|
|
+
|
|
|
+```cpp
|
|
|
+const char* ssid = "alfred";
|
|
|
+const char* password = "alfred16";
|
|
|
+```
|
|
|
+
|
|
|
+Change before flashing to a different network. No runtime config exists. `WiFi.setAutoReconnect(true)` is set in `setup()` and `maintainWifi()` handles mid-session drops.
|
|
|
+
|
|
|
+## WiFi Watchdog — `maintainWifi()`
|
|
|
+
|
|
|
+Runs every 5 seconds (throttled by `millis()`). On first detection of a drop: logs to Serial, updates LCD row 3, sets `s_wifiLost = true`, calls `WiFi.reconnect()`. On recovery: calls `tcpServer.begin()` to re-register the listening socket (required after the TCP/IP stack resets), then calls `printWifiInfo()` to update the LCD with the current IP. Does not run while `handleTcpBridgeClient()` is blocking — that is fine because an active TCP session implies WiFi is up.
|
|
|
+
|
|
|
+## LCD — Optional Hardware
|
|
|
+
|
|
|
+The LCD is detected at boot via the return value of `lcd.begin(20, 4)` (hd44780 returns 0 on success). The result is stored in `static bool s_lcdOk`. All subsequent `lcd.*` calls throughout the sketch are gated on `s_lcdOk`. If the LCD is absent the sketch boots and runs normally; all status information is available on Serial (115200 baud) instead.
|
|
|
+
|
|
|
+## I²C Addresses
|
|
|
+
|
|
|
+| Device | 8-bit | 7-bit (Wire) |
|
|
|
+|--------|-------|--------------|
|
|
|
+| ADAU1401 DSP | 0x68 | 0x34 |
|
|
|
+| 24C256 EEPROM | 0xA0 | 0x50 |
|
|
|
+
|
|
|
+Both devices share the bus at 400 kHz on SDA=GPIO13, SCL=GPIO12.
|
|
|
+
|
|
|
+## SigmaTCP Protocol Details
|
|
|
+
|
|
|
+SigmaStudio sends `chipAddr` as a **chip index** (0x01 = DSP, 0x02 = EEPROM), not a raw I²C address. `chipAddrTo7bit()` maps these — and also handles legacy 8-bit addresses (0x68, 0xA0) and already-shifted 7-bit addresses (0x34, 0x50).
|
|
|
+
|
|
|
+**WRITE (opcode 0x09) — 10-byte header:**
|
|
|
+
|
|
|
+```
|
|
|
+[0] 0x09 command
|
|
|
+[1] safeload 1 = use safeload registers, 0 = direct write
|
|
|
+[2] placement reserved
|
|
|
+[3-4] totalLen big-endian: header + payload bytes
|
|
|
+[5] chipAddr chip index or I2C address
|
|
|
+[6-7] dataLen big-endian: payload bytes
|
|
|
+[8-9] address big-endian: DSP/EEPROM start address
|
|
|
+[10+] payload
|
|
|
+```
|
|
|
+
|
|
|
+`totalLen` is validated against `WRITE_HDR_LEN + dataLen` immediately after parsing. A mismatch drops the connection — do not remove this check; it prevents `dataLen` from being blindly trusted to drive buffer waits or I²C writes when a packet is structurally inconsistent.
|
|
|
+
|
|
|
+ACK response: `{0x09, 0x00, 0x04, status}` (4 bytes). `status` reflects the actual I²C result — `writeRegisterBlock()` returns `bool` and the result is threaded through to the ACK. Do not replace with a hardcoded success.
|
|
|
+
|
|
|
+**READ (opcode 0x0A) — 8-byte header:**
|
|
|
+
|
|
|
+```
|
|
|
+[0] 0x0A
|
|
|
+[1-2] totalLen
|
|
|
+[3] chipAddr
|
|
|
+[4-5] dataLen
|
|
|
+[6-7] address
|
|
|
+```
|
|
|
+
|
|
|
+Response: `{0x0B, totalLen_hi, totalLen_lo, status, dataLen_hi, dataLen_lo, [payload]}` (6-byte header + data). On I²C failure, zeros fill the payload rather than dropping the connection — this allows SigmaStudio to probe an unresponsive DSP without aborting.
|
|
|
+
|
|
|
+## Safeload — Critical Rules
|
|
|
+
|
|
|
+The DSP has 5 safeload slots. Filling them and writing `0x003C` to the Core Register (0x081C) commits all pending parameters atomically (no audio glitch).
|
|
|
+
|
|
|
+- `s_safeload_count` is **static** in `DSPWriter.cpp` — it survives across `DSPWriter` instances.
|
|
|
+- **`DSPWriter::resetSafeload()` must be called at both the start and end of every TCP session.** It is called at the top of `handleTcpBridgeClient()` (session start) and just before `client.stop()` (session end).
|
|
|
+- `resetSafeload()` now **flushes** before resetting: if `s_safeload_count > 0` it writes `{0x00, 0x3C}` to the Core Register to trigger IST, clearing the DSP's own internal safeload counter. A session ending with `count == 0` (normal case) is a no-op.
|
|
|
+- Safeload writes are always 5 bytes: `{0x00, byte1, byte2, byte3, byte4}`. The leading `0x00` is mandatory padding.
|
|
|
+- When `safeload == 1` in a WRITE packet, `dataLen` must be a multiple of 4 (each parameter is 4 bytes). The sketch validates this and drops the connection if violated.
|
|
|
+
|
|
|
+## `writeRegisterBlock` — Error Propagation
|
|
|
+
|
|
|
+`DSPWriter::writeRegisterBlock()` returns `bool`. On any `Wire.endTransmission()` error it logs the failing address and error code to Serial and returns `false` immediately (stops writing — continuing would advance the address pointer and write misaligned data). The return value is captured at the call site and passed directly to `sendWriteAck()`.
|
|
|
+
|
|
|
+## Register Sizes
|
|
|
+
|
|
|
+`registerSizeForAddress()` derives the I²C bytes-per-register from the address:
|
|
|
+
|
|
|
+| Address range | Register size |
|
|
|
+|---------------|--------------|
|
|
|
+| 0x0000–0x03FF (Parameter RAM) | 4 bytes |
|
|
|
+| 0x0400–0x07FF (Program RAM) | 5 bytes |
|
|
|
+| 0x081C (Core Register), dataLen == 2 | 2 bytes (R0 reset) |
|
|
|
+| 0x081C (Core Register), dataLen == 24 | 1 byte (24 x 1-byte hardware config regs) |
|
|
|
+| Everything else (hardware regs) | 1 byte |
|
|
|
+
|
|
|
+Do not hard-code register sizes elsewhere — always use this function or the constants from `DSPWriter.h`.
|
|
|
+
|
|
|
+## TCP Receive Buffer
|
|
|
+
|
|
|
+`dataBuffer` is 50 KB, statically allocated. The receive loop always drains `client.available()` into it before processing — this keeps the TCP receive window open. If you only drain during processing, SigmaStudio hits ZeroWindow and stalls mid-transfer.
|
|
|
+
|
|
|
+Processing is decoupled from reception: the inner loop works from buffer contents (`readIndex`/`writeIndex`) without checking `client.available()`. Large program blocks that span multiple TCP segments are handled correctly this way.
|
|
|
+
|
|
|
+When the buffer is fully consumed, all three index variables reset to 0 to prevent unbounded growth.
|
|
|
+
|
|
|
+## EEPROM Write Constraints
|
|
|
+
|
|
|
+- Page size: 64 bytes. Writes crossing a page boundary must be split.
|
|
|
+- Max I²C bytes per transaction: 28 (Wire buffer limit minus the 2-byte address overhead).
|
|
|
+- After each chunk, `i2cAckPoll()` polls for ACK up to 120 ms — this is the EEPROM's internal write cycle; do not remove this delay.
|
|
|
+
|
|
|
+## OTA Firmware Update
|
|
|
+
|
|
|
+Routes: `GET /ota` → `handleOtaPage()`, `POST /ota_do` → `handleOtaDone()` + `handleOtaStream()`.
|
|
|
+
|
|
|
+Uses the ESP32 Arduino core's built-in `Update` library (`#include <Update.h>`). The upload handler calls `Update.begin(UPDATE_SIZE_UNKNOWN)` → `Update.write()` per chunk → `Update.end(true)`. On success `handleOtaDone()` sends a 200 response then calls `ESP.restart()` after a 500 ms delay. The LED turns cyan during the flash write.
|
|
|
+
|
|
|
+The firmware binary is produced by Arduino IDE: **Sketch → Export Compiled Binary**. Upload the `.bin` file (not `.elf` or `.map`).
|
|
|
+
|
|
|
+## Fixed-Point Format
|
|
|
+
|
|
|
+ADAU1401 parameter RAM uses **5.23 fixed-point** (4 bytes per word). `DataConversion::floatToFixed()` and `::intToFixed()` convert native C types to the 5-byte safeload form `{0x00, b3, b2, b1, b0}`.
|
|
|
+
|
|
|
+## Known Stubs and Intentional Gaps
|
|
|
+
|
|
|
+- `DSPWriter::downloadProgram()` is commented out. It was intended for standalone firmware loading at boot; this use case is now handled via the EEPROM (DSP reads EEPROM autonomously via SELFBOOT pin).
|
|
|
+- WiFi credentials are compile-time constants — no captive portal or NVS storage.
|
|
|
+- Only one TCP client is served at a time; a second SigmaStudio instance cannot connect simultaneously.
|
|
|
+
|
|
|
+## Files
|
|
|
+
|
|
|
+| File | Role |
|
|
|
+|------|------|
|
|
|
+| `ModulosDSP_101.ino` | Top-level: WiFi, TCP/HTTP servers, I²C helpers, CRC32, OTA |
|
|
|
+| `DSPWriter.h` | I²C address constants, register enums, `DSPWriter` class declaration |
|
|
|
+| `DSPWriter.cpp` | `writeRegister`, `writeRegisterBlock` (returns bool), safeload with flush |
|
|
|
+| `DataConversion.h` / `.cpp` | 5.23 fixed-point to C type conversions |
|
|
|
+| `index_html.h` | Embedded HTML string for the EEPROM upload UI |
|
|
|
+| `ota_html.h` | Embedded HTML string for the OTA firmware update UI |
|