|
@@ -0,0 +1,701 @@
|
|
|
|
|
+// Modulos ADAU DSP WiFi + EEPROM (HTTP uploader + Sigma TCP bridge)
|
|
|
|
|
+// Ver 1.3.1
|
|
|
|
|
+// March 2026
|
|
|
|
|
+//
|
|
|
|
|
+// This is a clean restore to the last known good state (v1.3.0/_04)
|
|
|
|
|
+// with the chipAddr 0x01 fix applied. The receive loop is the original
|
|
|
|
|
+// simple byte-at-a-time approach which correctly handled large packets.
|
|
|
|
|
+//
|
|
|
|
|
+// Changes vs original:
|
|
|
|
|
+// - sendReadResponse() corrected to 6-byte SigmaTCP header format
|
|
|
|
|
+// - sendWriteAck() added after every DSP/EEPROM write
|
|
|
|
|
+// - chipAddrTo7bit() handles 0x01=DSP, 0x02=EEPROM chip indexes
|
|
|
|
|
+// - I2C read failure returns zeros instead of dropping connection
|
|
|
|
|
+// - registerSize derived from ADAU1401 address map
|
|
|
|
|
+// - DSPWriter::resetSafeload() at start of each TCP session
|
|
|
|
|
+// - WS2812 NeoPixel on GPIO 21 (Waveshare ESP32-S3 Zero)
|
|
|
|
|
+//
|
|
|
|
|
+// Changelog v1.3.0:
|
|
|
|
|
+// - FIX: sendReadResponse() header corrected to 6-byte SigmaTCP format
|
|
|
|
|
+// (was 4 bytes; SigmaStudio expects: 0x0B, totalLen_hi, totalLen_lo, status, dataLen_hi, dataLen_lo)
|
|
|
|
|
+// - ADD: sendWriteAck() — SigmaStudio expects a 4-byte ACK after every write
|
|
|
|
|
+// (was missing; caused immediate disconnect after first write)
|
|
|
|
|
+// - FIX: I2C read failure now returns zeros instead of dropping TCP connection
|
|
|
|
|
+// (SigmaStudio can probe/read a DSP that isn't responding yet without aborting)
|
|
|
|
|
+// - ADD: Hex dump of first bytes of each received command to Serial for diagnostics
|
|
|
|
|
+// - ADD: printHex() debug helper
|
|
|
|
|
+//
|
|
|
|
|
+// Changelog v1.4.0:
|
|
|
|
|
+// - FIX: TCP receive loop replaced with client.readBytes() + 3s per-packet timeout
|
|
|
|
|
+// Previous byte-at-a-time loop timed out mid-transfer on large program blocks
|
|
|
|
|
+// (e.g. 1490-byte program download) because client.available() returns 0
|
|
|
|
|
+// between TCP segments even when more data is in flight. Now we block-read
|
|
|
|
|
+// exactly the bytes needed to complete the current packet, so a large program
|
|
|
|
|
+// download can span multiple TCP segments without triggering a false idle timeout.
|
|
|
|
|
+// - FIX: Idle timeout now only applies between commands, not during active receive
|
|
|
|
|
+//
|
|
|
|
|
+// Changelog v1.2.0:
|
|
|
|
|
+// - ADD: WS2812 NeoPixel status LED (Waveshare ESP32-S3 Zero, GPIO 21)
|
|
|
|
|
+// OFF = idle / no TCP client
|
|
|
|
|
+// GREEN = DSP write in progress
|
|
|
|
|
+// BLUE = DSP read in progress
|
|
|
|
|
+// YELLOW = EEPROM write via TCP
|
|
|
|
|
+// MAGENTA = HTTP EEPROM upload in progress
|
|
|
|
|
+// RED flash = error (I2C fail, overflow, bad packet)
|
|
|
|
|
+//
|
|
|
|
|
+// Changelog v1.1.0:
|
|
|
|
|
+// - FIX: Buffer overflow check moved to BEFORE write
|
|
|
|
|
+// - FIX: chipAddrTo7bit() replaced with explicit lookup table
|
|
|
|
|
+// - FIX: registerSize now derived from ADAU1401 address range
|
|
|
|
|
+// - FIX: DSPWriter::resetSafeload() called at TCP session start
|
|
|
|
|
+// - FIX: Safeload dataLen validated as multiple of 4
|
|
|
|
|
+// - FIX: totalLen vs dataLen cross-validated on WRITE packets
|
|
|
|
|
+
|
|
|
|
|
+#include <WiFi.h>
|
|
|
|
|
+#include <Wire.h>
|
|
|
|
|
+#include <WebServer.h>
|
|
|
|
|
+#include "DSPWriter.h"
|
|
|
|
|
+#include <hd44780.h>
|
|
|
|
|
+#include <hd44780ioClass/hd44780_I2Cexp.h>
|
|
|
|
|
+#include <Adafruit_NeoPixel.h>
|
|
|
|
|
+#include "FS.h"
|
|
|
|
|
+#include <LittleFS.h>
|
|
|
|
|
+
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+// WiFi / UI
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+const char* ssid = "alfred";
|
|
|
|
|
+const char* password = "alfred16";
|
|
|
|
|
+const char* version = "VER: 260304_13";
|
|
|
|
|
+
|
|
|
|
|
+#define I2C_SDA 13
|
|
|
|
|
+#define I2C_SCL 12
|
|
|
|
|
+
|
|
|
|
|
+WiFiServer tcpServer(8086);
|
|
|
|
|
+WebServer httpServer(80);
|
|
|
|
|
+
|
|
|
|
|
+#include "index_html.h"
|
|
|
|
|
+
|
|
|
|
|
+hd44780_I2Cexp lcd;
|
|
|
|
|
+
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+// NeoPixel status LED (Waveshare ESP32-S3 Zero, GPIO 21)
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+#define NEOPIXEL_PIN 21
|
|
|
|
|
+#define NEOPIXEL_COUNT 1
|
|
|
|
|
+#define NEOPIXEL_BRIGHT 40
|
|
|
|
|
+
|
|
|
|
|
+Adafruit_NeoPixel statusLed(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
|
|
|
|
|
+
|
|
|
|
|
+static void ledSet(uint8_t r, uint8_t g, uint8_t b)
|
|
|
|
|
+{
|
|
|
|
|
+ statusLed.setPixelColor(0, statusLed.Color(r, g, b));
|
|
|
|
|
+ statusLed.show();
|
|
|
|
|
+}
|
|
|
|
|
+static void ledOff() { ledSet(0, 0, 0); }
|
|
|
|
|
+static void ledCyan() { ledSet(0, NEOPIXEL_BRIGHT/2, NEOPIXEL_BRIGHT/2); }
|
|
|
|
|
+static void ledGreen() { ledSet(0, NEOPIXEL_BRIGHT, 0); }
|
|
|
|
|
+static void ledBlue() { ledSet(0, 0, NEOPIXEL_BRIGHT); }
|
|
|
|
|
+static void ledYellow() { ledSet(NEOPIXEL_BRIGHT, NEOPIXEL_BRIGHT, 0); }
|
|
|
|
|
+static void ledMagenta() { ledSet(NEOPIXEL_BRIGHT, 0, NEOPIXEL_BRIGHT); }
|
|
|
|
|
+static void ledErrorFlash()
|
|
|
|
|
+{
|
|
|
|
|
+ for (int i = 0; i < 2; i++) {
|
|
|
|
|
+ ledSet(NEOPIXEL_BRIGHT, 0, 0); delay(80);
|
|
|
|
|
+ ledOff(); delay(80);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+// TCP protocol buffer
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+static uint8_t dataBuffer[50 * 1024];
|
|
|
|
|
+
|
|
|
|
|
+#define STATE_START 0
|
|
|
|
|
+#define STATE_READ_CMD 1
|
|
|
|
|
+#define STATE_WRITE_CMD 2
|
|
|
|
|
+#define CMD_WRITE 0x09
|
|
|
|
|
+#define CMD_READ 0x0A
|
|
|
|
|
+
|
|
|
|
|
+constexpr int WRITE_HDR_LEN = 10;
|
|
|
|
|
+constexpr int READ_HDR_LEN = 8;
|
|
|
|
|
+
|
|
|
|
|
+struct adauWriteHeader {
|
|
|
|
|
+ uint8_t command;
|
|
|
|
|
+ uint8_t safeload;
|
|
|
|
|
+ uint8_t placement;
|
|
|
|
|
+ uint16_t totalLen;
|
|
|
|
|
+ uint8_t chipAddr;
|
|
|
|
|
+ uint16_t dataLen;
|
|
|
|
|
+ uint16_t address;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+struct adauReadHeader {
|
|
|
|
|
+ uint8_t command;
|
|
|
|
|
+ uint16_t totalLen;
|
|
|
|
|
+ uint8_t chipAddr;
|
|
|
|
|
+ uint16_t dataLen;
|
|
|
|
|
+ uint16_t address;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+static adauWriteHeader writeHeader;
|
|
|
|
|
+static adauReadHeader readHeader;
|
|
|
|
|
+
|
|
|
|
|
+static constexpr uint8_t DSP_7BIT = DSP_I2C_ADDRESS; // 0x34
|
|
|
|
|
+static constexpr uint8_t EEPROM_7BIT = EEPROM_I2C_ADDRESS; // 0x50
|
|
|
|
|
+
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+// 24C256 EEPROM
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+static constexpr uint32_t EEPROM_SIZE_BYTES = 32768;
|
|
|
|
|
+static constexpr uint16_t EEPROM_PAGE_SIZE = 64;
|
|
|
|
|
+static constexpr uint8_t I2C_MAX_DATA_PER_TX = 28;
|
|
|
|
|
+
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+// CRC32
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+static uint32_t crc32_update(uint32_t crc, const uint8_t* data, size_t len)
|
|
|
|
|
+{
|
|
|
|
|
+ crc = ~crc;
|
|
|
|
|
+ for (size_t i = 0; i < len; i++) {
|
|
|
|
|
+ crc ^= data[i];
|
|
|
|
|
+ for (int b = 0; b < 8; b++) {
|
|
|
|
|
+ uint32_t mask = -(crc & 1u);
|
|
|
|
|
+ crc = (crc >> 1) ^ (0xEDB88320u & mask);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return ~crc;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+// Helpers
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+static uint8_t chipAddrTo7bit(uint8_t chipAddr)
|
|
|
|
|
+{
|
|
|
|
|
+ switch (chipAddr) {
|
|
|
|
|
+ case 0x01: return 0x34; // chip index 1 = DSP
|
|
|
|
|
+ case 0x02: return 0x50; // chip index 2 = EEPROM
|
|
|
|
|
+ case 0x68: return 0x34;
|
|
|
|
|
+ case 0xA0: return 0x50;
|
|
|
|
|
+ case 0x34: return 0x34;
|
|
|
|
|
+ case 0x50: return 0x50;
|
|
|
|
|
+ default:
|
|
|
|
|
+ if (chipAddr > 0x7F) return (uint8_t)(chipAddr >> 1);
|
|
|
|
|
+ return chipAddr;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static uint8_t registerSizeForAddress(uint16_t address, uint16_t dataLen)
|
|
|
|
|
+{
|
|
|
|
|
+ if (address == dspRegister::CoreRegister) {
|
|
|
|
|
+ if (dataLen == 2) return CORE_REGISTER_R0_REGSIZE;
|
|
|
|
|
+ if (dataLen == 24) return HARDWARE_CONF_REGSIZE;
|
|
|
|
|
+ return CORE_REGISTER_R0_REGSIZE;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (address >= DSP_PROG_RAM_START && address <= DSP_PROG_RAM_END) return PROGRAM_REGSIZE;
|
|
|
|
|
+ if (address < DSP_PROG_RAM_START) return PARAMETER_REGSIZE;
|
|
|
|
|
+ return HARDWARE_CONF_REGSIZE;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static bool i2cAckPoll(uint8_t addr7, uint32_t timeoutMs = 80)
|
|
|
|
|
+{
|
|
|
|
|
+ uint32_t start = millis();
|
|
|
|
|
+ while ((millis() - start) < timeoutMs) {
|
|
|
|
|
+ Wire.beginTransmission(addr7);
|
|
|
|
|
+ if (Wire.endTransmission() == 0) return true;
|
|
|
|
|
+ delay(1);
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static bool eepromWritePageChunk(uint16_t memAddr, const uint8_t* data, uint16_t len)
|
|
|
|
|
+{
|
|
|
|
|
+ Wire.beginTransmission(EEPROM_7BIT);
|
|
|
|
|
+ Wire.write((uint8_t)(memAddr >> 8));
|
|
|
|
|
+ Wire.write((uint8_t)(memAddr & 0xFF));
|
|
|
|
|
+ for (uint16_t i = 0; i < len; i++) Wire.write(data[i]);
|
|
|
|
|
+ if (Wire.endTransmission() != 0) return false;
|
|
|
|
|
+ return i2cAckPoll(EEPROM_7BIT, 120);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static bool eepromWriteBlock(uint16_t memAddr, const uint8_t* data, uint16_t len)
|
|
|
|
|
+{
|
|
|
|
|
+ while (len) {
|
|
|
|
|
+ uint16_t pageOff = memAddr % EEPROM_PAGE_SIZE;
|
|
|
|
|
+ uint16_t spaceInPage = EEPROM_PAGE_SIZE - pageOff;
|
|
|
|
|
+ uint16_t chunk = len;
|
|
|
|
|
+ if (chunk > spaceInPage) chunk = spaceInPage;
|
|
|
|
|
+ if (chunk > I2C_MAX_DATA_PER_TX) chunk = I2C_MAX_DATA_PER_TX;
|
|
|
|
|
+ if (!eepromWritePageChunk(memAddr, data, chunk)) return false;
|
|
|
|
|
+ memAddr += chunk; data += chunk; len -= chunk;
|
|
|
|
|
+ delay(0);
|
|
|
|
|
+ }
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static bool eepromReadBlock(uint16_t memAddr, uint8_t* out, uint16_t len)
|
|
|
|
|
+{
|
|
|
|
|
+ Wire.beginTransmission(EEPROM_7BIT);
|
|
|
|
|
+ Wire.write((uint8_t)(memAddr >> 8));
|
|
|
|
|
+ Wire.write((uint8_t)(memAddr & 0xFF));
|
|
|
|
|
+ if (Wire.endTransmission(false) != 0) return false;
|
|
|
|
|
+ uint16_t got = 0;
|
|
|
|
|
+ while (got < len) {
|
|
|
|
|
+ uint8_t ask = (len - got) > 32 ? 32 : (len - got);
|
|
|
|
|
+ if (Wire.requestFrom((int)EEPROM_7BIT, (int)ask) != ask) return false;
|
|
|
|
|
+ for (uint8_t i = 0; i < ask; i++) out[got++] = Wire.read();
|
|
|
|
|
+ }
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static bool dspReadBlock(uint16_t memAddr, uint8_t* out, uint16_t len)
|
|
|
|
|
+{
|
|
|
|
|
+ Wire.beginTransmission(DSP_7BIT);
|
|
|
|
|
+ Wire.write((uint8_t)(memAddr >> 8));
|
|
|
|
|
+ Wire.write((uint8_t)(memAddr & 0xFF));
|
|
|
|
|
+ if (Wire.endTransmission(false) != 0) return false;
|
|
|
|
|
+ uint16_t got = 0;
|
|
|
|
|
+ while (got < len) {
|
|
|
|
|
+ uint8_t ask = (len - got) > 32 ? 32 : (len - got);
|
|
|
|
|
+ if (Wire.requestFrom((int)DSP_7BIT, (int)ask) != ask) return false;
|
|
|
|
|
+ for (uint8_t i = 0; i < ask; i++) out[got++] = Wire.read();
|
|
|
|
|
+ }
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// SigmaTCP read response: 0x0B, totalLen_hi, totalLen_lo, status, dataLen_hi, dataLen_lo, [payload]
|
|
|
|
|
+static bool sendReadResponse(WiFiClient& client, const uint8_t* data, uint16_t dataLen, bool ok)
|
|
|
|
|
+{
|
|
|
|
|
+ uint16_t totalLen = 6 + dataLen;
|
|
|
|
|
+ uint8_t hdr[6];
|
|
|
|
|
+ hdr[0] = 0x0B;
|
|
|
|
|
+ hdr[1] = (uint8_t)(totalLen >> 8);
|
|
|
|
|
+ hdr[2] = (uint8_t)(totalLen & 0xFF);
|
|
|
|
|
+ hdr[3] = ok ? 0x00 : 0x01;
|
|
|
|
|
+ hdr[4] = (uint8_t)(dataLen >> 8);
|
|
|
|
|
+ hdr[5] = (uint8_t)(dataLen & 0xFF);
|
|
|
|
|
+ if (client.write(hdr, sizeof(hdr)) != sizeof(hdr)) return false;
|
|
|
|
|
+ if (dataLen > 0) {
|
|
|
|
|
+ if (ok && data) {
|
|
|
|
|
+ if (client.write(data, dataLen) != dataLen) return false;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // send zeros so SigmaStudio doesn't stall on a failed read
|
|
|
|
|
+ static uint8_t zeros[256];
|
|
|
|
|
+ uint16_t rem = dataLen;
|
|
|
|
|
+ while (rem) {
|
|
|
|
|
+ uint16_t chunk = rem > sizeof(zeros) ? sizeof(zeros) : rem;
|
|
|
|
|
+ if (client.write(zeros, chunk) != chunk) return false;
|
|
|
|
|
+ rem -= chunk;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// SigmaTCP write ack: 0x09, 0x00, 0x04, status
|
|
|
|
|
+static bool sendWriteAck(WiFiClient& client, bool ok)
|
|
|
|
|
+{
|
|
|
|
|
+ uint8_t ack[4] = { 0x09, 0x00, 0x04, ok ? (uint8_t)0x00 : (uint8_t)0x01 };
|
|
|
|
|
+ return client.write(ack, sizeof(ack)) == sizeof(ack);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void printHex(const char* label, const uint8_t* buf, uint16_t len, uint16_t maxPrint = 24)
|
|
|
|
|
+{
|
|
|
|
|
+ Serial.print(label);
|
|
|
|
|
+ uint16_t n = len < maxPrint ? len : maxPrint;
|
|
|
|
|
+ for (uint16_t i = 0; i < n; i++) {
|
|
|
|
|
+ if (buf[i] < 0x10) Serial.print("0");
|
|
|
|
|
+ Serial.print(buf[i], HEX);
|
|
|
|
|
+ Serial.print(" ");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (len > maxPrint) Serial.print("...");
|
|
|
|
|
+ Serial.println();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void printWifiInfo()
|
|
|
|
|
+{
|
|
|
|
|
+ Serial.println();
|
|
|
|
|
+ Serial.println("WiFi connected.");
|
|
|
|
|
+ Serial.print("WiFi IP: "); Serial.println(WiFi.localIP());
|
|
|
|
|
+ Serial.print("MAC: "); Serial.println(WiFi.macAddress());
|
|
|
|
|
+ Serial.println("Modulos AudioDSP");
|
|
|
|
|
+ Serial.println(version);
|
|
|
|
|
+ lcd.setCursor(4, 3);
|
|
|
|
|
+ lcd.print(WiFi.localIP());
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+// HTTP EEPROM uploader state
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+static volatile bool uploadActive = false;
|
|
|
|
|
+static volatile bool uploadVerify = false;
|
|
|
|
|
+static volatile bool uploadFailed = false;
|
|
|
|
|
+static volatile uint32_t uploadBytes = 0;
|
|
|
|
|
+static volatile uint32_t uploadCrc = 0;
|
|
|
|
|
+
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+// HTTP handlers
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+static String contentTypeFor(const String& path) {
|
|
|
|
|
+ if (path.endsWith(".html")) return "text/html";
|
|
|
|
|
+ if (path.endsWith(".css")) return "text/css";
|
|
|
|
|
+ if (path.endsWith(".js")) return "application/javascript";
|
|
|
|
|
+ if (path.endsWith(".png")) return "image/png";
|
|
|
|
|
+ if (path.endsWith(".jpg") || path.endsWith(".jpeg")) return "image/jpeg";
|
|
|
|
|
+ if (path.endsWith(".webp")) return "image/webp";
|
|
|
|
|
+ if (path.endsWith(".svg")) return "image/svg+xml";
|
|
|
|
|
+ if (path.endsWith(".ico")) return "image/x-icon";
|
|
|
|
|
+ if (path.endsWith(".woff")) return "font/woff";
|
|
|
|
|
+ if (path.endsWith(".woff2"))return "font/woff2";
|
|
|
|
|
+ return "application/octet-stream";
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static bool streamFromFS(String path) {
|
|
|
|
|
+ if (!LittleFS.exists(path)) {
|
|
|
|
|
+ if (path.startsWith("/")) {
|
|
|
|
|
+ String alt = path.substring(1);
|
|
|
|
|
+ if (LittleFS.exists(alt)) path = alt; else return false;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ String alt = "/" + path;
|
|
|
|
|
+ if (LittleFS.exists(alt)) path = alt; else return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ File f = LittleFS.open(path, "r");
|
|
|
|
|
+ if (!f) return false;
|
|
|
|
|
+ httpServer.streamFile(f, contentTypeFor(path));
|
|
|
|
|
+ f.close();
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void handleRoot() {
|
|
|
|
|
+ String html = FPSTR(INDEX_HTML);
|
|
|
|
|
+ html.replace("{{IP}}", WiFi.localIP().toString());
|
|
|
|
|
+ httpServer.send(200, "text/html; charset=utf-8", html);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void handleStatus() {
|
|
|
|
|
+ String s;
|
|
|
|
|
+ s += "uploadActive="; s += (uploadActive ? "1" : "0"); s += "\n";
|
|
|
|
|
+ s += "uploadFailed="; s += (uploadFailed ? "1" : "0"); s += "\n";
|
|
|
|
|
+ s += "uploadBytes="; s += String((uint32_t)uploadBytes); s += "\n";
|
|
|
|
|
+ s += "uploadCRC32=0x"; s += String((uint32_t)uploadCrc, HEX); s += "\n";
|
|
|
|
|
+ httpServer.send(200, "text/plain", s);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void handleUploadDone() {
|
|
|
|
|
+ if (uploadFailed) {
|
|
|
|
|
+ httpServer.send(500, "text/plain", "Upload failed.\nCheck Serial log.\n");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ String msg = "OK\nBytes written: " + String((uint32_t)uploadBytes) +
|
|
|
|
|
+ "\nCRC32: 0x" + String((uint32_t)uploadCrc, HEX) + "\n";
|
|
|
|
|
+ httpServer.send(200, "text/plain", msg);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void handleUploadStream() {
|
|
|
|
|
+ HTTPUpload& up = httpServer.upload();
|
|
|
|
|
+
|
|
|
|
|
+ if (up.status == UPLOAD_FILE_START) {
|
|
|
|
|
+ uploadActive = true; uploadFailed = false; uploadBytes = 0; uploadCrc = 0;
|
|
|
|
|
+ uploadVerify = httpServer.hasArg("verify");
|
|
|
|
|
+ Serial.println(); Serial.print("HTTP upload start: "); Serial.println(up.filename);
|
|
|
|
|
+ Serial.print("Verify: "); Serial.println(uploadVerify ? "yes" : "no");
|
|
|
|
|
+ Wire.beginTransmission(EEPROM_7BIT);
|
|
|
|
|
+ Serial.print("EEPROM probe err: "); Serial.println(Wire.endTransmission());
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (up.status == UPLOAD_FILE_WRITE) {
|
|
|
|
|
+ if (uploadFailed) return;
|
|
|
|
|
+ if ((uploadBytes + up.currentSize) > EEPROM_SIZE_BYTES) {
|
|
|
|
|
+ Serial.println("Upload too large for 24C256"); uploadFailed = true; return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!eepromWriteBlock((uint16_t)uploadBytes, up.buf, (uint16_t)up.currentSize)) {
|
|
|
|
|
+ Serial.println("EEPROM write failed"); uploadFailed = true; return;
|
|
|
|
|
+ }
|
|
|
|
|
+ uploadCrc = crc32_update(uploadCrc, up.buf, up.currentSize);
|
|
|
|
|
+ uploadBytes += up.currentSize;
|
|
|
|
|
+ ledMagenta();
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (up.status == UPLOAD_FILE_END) {
|
|
|
|
|
+ Serial.print("HTTP upload end, bytes="); Serial.println((uint32_t)uploadBytes);
|
|
|
|
|
+ if (uploadVerify && !uploadFailed) {
|
|
|
|
|
+ Serial.println("Verify start (CRC32)...");
|
|
|
|
|
+ uint32_t crc = 0;
|
|
|
|
|
+ static uint8_t tmp[256];
|
|
|
|
|
+ uint32_t remaining = uploadBytes; uint16_t addr = 0;
|
|
|
|
|
+ while (remaining) {
|
|
|
|
|
+ uint16_t n = remaining > sizeof(tmp) ? sizeof(tmp) : (uint16_t)remaining;
|
|
|
|
|
+ if (!eepromReadBlock(addr, tmp, n)) { Serial.println("EEPROM read failed"); uploadFailed = true; break; }
|
|
|
|
|
+ crc = crc32_update(crc, tmp, n);
|
|
|
|
|
+ addr += n; remaining -= n; delay(0);
|
|
|
|
|
+ }
|
|
|
|
|
+ Serial.print("Verify CRC32: 0x"); Serial.println(crc, HEX);
|
|
|
|
|
+ if (!uploadFailed && crc != uploadCrc) { Serial.println("CRC mismatch"); uploadFailed = true; }
|
|
|
|
|
+ }
|
|
|
|
|
+ uploadActive = false; ledOff();
|
|
|
|
|
+ Serial.println(uploadFailed ? "HTTP upload result: FAIL" : "HTTP upload result: OK");
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (up.status == UPLOAD_FILE_ABORTED) {
|
|
|
|
|
+ Serial.println("HTTP upload aborted"); uploadActive = false; uploadFailed = true; ledOff();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+// Setup
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+void setup() {
|
|
|
|
|
+ Wire.begin(I2C_SDA, I2C_SCL);
|
|
|
|
|
+ Wire.setClock(400000);
|
|
|
|
|
+
|
|
|
|
|
+ statusLed.begin();
|
|
|
|
|
+ statusLed.setBrightness(NEOPIXEL_BRIGHT);
|
|
|
|
|
+ statusLed.show();
|
|
|
|
|
+
|
|
|
|
|
+ lcd.begin(20, 4); lcd.display(); lcd.backlight();
|
|
|
|
|
+ lcd.setCursor(2, 0); lcd.print("Modulos AudioDSP"); delay(1000);
|
|
|
|
|
+ lcd.setCursor(5, 1); lcd.print("Booting..."); delay(1000);
|
|
|
|
|
+
|
|
|
|
|
+ Serial.begin(115200); delay(1500);
|
|
|
|
|
+ Serial.println(); Serial.println("Booting...");
|
|
|
|
|
+ Serial.printf("Reset reason: %d\n", (int)esp_reset_reason());
|
|
|
|
|
+
|
|
|
|
|
+ WiFi.mode(WIFI_STA);
|
|
|
|
|
+ WiFi.begin(ssid, password);
|
|
|
|
|
+ while (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
|
|
|
|
+ Serial.println("Connection Failed! Rebooting...");
|
|
|
|
|
+ delay(5000); ESP.restart();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!LittleFS.begin(false)) {
|
|
|
|
|
+ Serial.println("LittleFS mount failed, formatting...");
|
|
|
|
|
+ if (!LittleFS.begin(true)) { Serial.println("LittleFS mount failed even after format"); return; }
|
|
|
|
|
+ }
|
|
|
|
|
+ Serial.println("LittleFS mounted OK");
|
|
|
|
|
+ File root = LittleFS.open("/"); File f = root.openNextFile();
|
|
|
|
|
+ while (f) { Serial.print("LittleFS: "); Serial.println(f.name()); f = root.openNextFile(); }
|
|
|
|
|
+
|
|
|
|
|
+ lcd.setCursor(3, 2); lcd.print("File System OK"); delay(1000);
|
|
|
|
|
+
|
|
|
|
|
+ tcpServer.begin();
|
|
|
|
|
+ httpServer.on("/", HTTP_GET, handleRoot);
|
|
|
|
|
+ httpServer.on("/status", HTTP_GET, handleStatus);
|
|
|
|
|
+ httpServer.on("/upload", HTTP_POST, handleUploadDone, handleUploadStream);
|
|
|
|
|
+ httpServer.onNotFound([]() {
|
|
|
|
|
+ String uri = httpServer.uri();
|
|
|
|
|
+ if (streamFromFS(uri)) return;
|
|
|
|
|
+ Serial.print("HTTP 404: "); Serial.println(uri);
|
|
|
|
|
+ httpServer.send(404, "text/plain", "Not found: " + uri);
|
|
|
|
|
+ });
|
|
|
|
|
+ httpServer.begin();
|
|
|
|
|
+
|
|
|
|
|
+ lcd.setCursor(4, 3); lcd.print("System Ready"); delay(1000);
|
|
|
|
|
+ lcd.clear();
|
|
|
|
|
+ lcd.setCursor(2, 0); lcd.print("Modulos AudioDSP");
|
|
|
|
|
+ lcd.setCursor(3, 1); lcd.print(version); delay(500);
|
|
|
|
|
+
|
|
|
|
|
+ printWifiInfo();
|
|
|
|
|
+ Serial.print("HTTP uploader: http://"); Serial.print(WiFi.localIP()); Serial.println("/");
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+// TCP bridge
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+// TCP bridge
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+static void handleTcpBridgeClient(WiFiClient& client)
|
|
|
|
|
+{
|
|
|
|
|
+ Serial.println("TCP new connection");
|
|
|
|
|
+ DSPWriter::resetSafeload();
|
|
|
|
|
+
|
|
|
|
|
+ int writeIndex = 0; // next free slot in dataBuffer
|
|
|
|
|
+ int readIndex = 0; // start of current unprocessed command
|
|
|
|
|
+ int receivedByteCount = 0; // total bytes written into dataBuffer
|
|
|
|
|
+ int currentState = STATE_START;
|
|
|
|
|
+
|
|
|
|
|
+ while (client.connected()) {
|
|
|
|
|
+
|
|
|
|
|
+ httpServer.handleClient();
|
|
|
|
|
+ delay(0);
|
|
|
|
|
+
|
|
|
|
|
+ // ------------------------------------------------------------------
|
|
|
|
|
+ // STEP 1: Always drain the TCP stack into dataBuffer.
|
|
|
|
|
+ // Do this unconditionally every loop iteration — this is what keeps
|
|
|
|
|
+ // the TCP receive window open. If we only drain when we feel like it,
|
|
|
|
|
+ // the window goes to zero and SigmaStudio stops sending (ZeroWindow).
|
|
|
|
|
+ // ------------------------------------------------------------------
|
|
|
|
|
+ while (client.available()) {
|
|
|
|
|
+ if (writeIndex >= (int)sizeof(dataBuffer)) {
|
|
|
|
|
+ Serial.println("TCP RX overflow");
|
|
|
|
|
+ ledErrorFlash(); client.stop(); return;
|
|
|
|
|
+ }
|
|
|
|
|
+ int b = client.read();
|
|
|
|
|
+ if (b < 0) break;
|
|
|
|
|
+ dataBuffer[writeIndex++] = (uint8_t)b;
|
|
|
|
|
+ receivedByteCount++;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ------------------------------------------------------------------
|
|
|
|
|
+ // STEP 2: Process whatever is in the buffer.
|
|
|
|
|
+ // This is driven purely by buffer contents, not by client.available().
|
|
|
|
|
+ // We loop here processing commands until we run out of buffered data.
|
|
|
|
|
+ // ------------------------------------------------------------------
|
|
|
|
|
+ bool processedSomething = true;
|
|
|
|
|
+ while (processedSomething && client.connected()) {
|
|
|
|
|
+ processedSomething = false;
|
|
|
|
|
+
|
|
|
|
|
+ // --- STATE_START: identify opcode ---
|
|
|
|
|
+ if (currentState == STATE_START) {
|
|
|
|
|
+ if (receivedByteCount <= readIndex) {
|
|
|
|
|
+ // Buffer empty — reset for next command
|
|
|
|
|
+ writeIndex = readIndex = receivedByteCount = 0;
|
|
|
|
|
+ ledOff();
|
|
|
|
|
+ break; // nothing to process, go back to receive loop
|
|
|
|
|
+ }
|
|
|
|
|
+ printHex("TCP RX: ", &dataBuffer[readIndex], (uint16_t)(receivedByteCount - readIndex));
|
|
|
|
|
+ uint8_t op = dataBuffer[readIndex];
|
|
|
|
|
+ if (op == CMD_WRITE) { currentState = STATE_WRITE_CMD; processedSomething = true; }
|
|
|
|
|
+ else if (op == CMD_READ) { currentState = STATE_READ_CMD; processedSomething = true; }
|
|
|
|
|
+ else {
|
|
|
|
|
+ Serial.printf("TCP invalid opcode: 0x%02X\n", op);
|
|
|
|
|
+ ledErrorFlash();
|
|
|
|
|
+ client.stop(); return;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // --- STATE_WRITE_CMD ---
|
|
|
|
|
+ if (currentState == STATE_WRITE_CMD) {
|
|
|
|
|
+ // Need full header first
|
|
|
|
|
+ if (receivedByteCount < (readIndex + WRITE_HDR_LEN)) break;
|
|
|
|
|
+
|
|
|
|
|
+ writeHeader.safeload = dataBuffer[readIndex + 1];
|
|
|
|
|
+ writeHeader.placement = dataBuffer[readIndex + 2];
|
|
|
|
|
+ writeHeader.totalLen = (uint16_t)((dataBuffer[readIndex + 3] << 8) | dataBuffer[readIndex + 4]);
|
|
|
|
|
+ writeHeader.chipAddr = dataBuffer[readIndex + 5];
|
|
|
|
|
+ writeHeader.dataLen = (uint16_t)((dataBuffer[readIndex + 6] << 8) | dataBuffer[readIndex + 7]);
|
|
|
|
|
+ writeHeader.address = (uint16_t)((dataBuffer[readIndex + 8] << 8) | dataBuffer[readIndex + 9]);
|
|
|
|
|
+
|
|
|
|
|
+ // Need full payload — if not here yet, break back to receive loop
|
|
|
|
|
+ if (receivedByteCount < (readIndex + WRITE_HDR_LEN + (int)writeHeader.dataLen)) {
|
|
|
|
|
+ Serial.printf("TCP WRITE buffering: have %d need %d bytes\n",
|
|
|
|
|
+ receivedByteCount - readIndex,
|
|
|
|
|
+ WRITE_HDR_LEN + (int)writeHeader.dataLen);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ readIndex += WRITE_HDR_LEN;
|
|
|
|
|
+ uint8_t target7 = chipAddrTo7bit(writeHeader.chipAddr);
|
|
|
|
|
+
|
|
|
|
|
+ if (target7 == EEPROM_7BIT) {
|
|
|
|
|
+ ledYellow();
|
|
|
|
|
+ bool ok = eepromWriteBlock(writeHeader.address, &dataBuffer[readIndex], writeHeader.dataLen);
|
|
|
|
|
+ readIndex += writeHeader.dataLen;
|
|
|
|
|
+ sendWriteAck(client, ok);
|
|
|
|
|
+ if (!ok) { Serial.println("TCP EEPROM write failed"); ledErrorFlash(); }
|
|
|
|
|
+ else ledOff();
|
|
|
|
|
+ currentState = STATE_START;
|
|
|
|
|
+ processedSomething = true;
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (target7 != DSP_7BIT) {
|
|
|
|
|
+ Serial.printf("TCP unknown chipAddr: 0x%02X\n", writeHeader.chipAddr);
|
|
|
|
|
+ ledErrorFlash(); client.stop(); return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ledGreen();
|
|
|
|
|
+ uint8_t registerSize = registerSizeForAddress(writeHeader.address, writeHeader.dataLen);
|
|
|
|
|
+ uint16_t regAddress = writeHeader.address;
|
|
|
|
|
+ Serial.printf("TCP WRITE addr=0x%04X len=%u regSz=%u safeload=%u\n",
|
|
|
|
|
+ writeHeader.address, writeHeader.dataLen, registerSize, writeHeader.safeload);
|
|
|
|
|
+
|
|
|
|
|
+ if (writeHeader.safeload == 1) {
|
|
|
|
|
+ if (writeHeader.dataLen % 4 != 0) {
|
|
|
|
|
+ Serial.printf("TCP safeload dataLen %u not multiple of 4\n", writeHeader.dataLen);
|
|
|
|
|
+ ledErrorFlash(); client.stop(); return;
|
|
|
|
|
+ }
|
|
|
|
|
+ int writeCount = writeHeader.dataLen / 4;
|
|
|
|
|
+ int slri = readIndex;
|
|
|
|
|
+ DSPWriter dspWriter;
|
|
|
|
|
+ while (writeCount > 0) {
|
|
|
|
|
+ uint8_t da[5] = { 0x00, dataBuffer[slri], dataBuffer[slri+1],
|
|
|
|
|
+ dataBuffer[slri+2], dataBuffer[slri+3] };
|
|
|
|
|
+ dspWriter.safeload_writeRegister(regAddress, da, writeCount == 1);
|
|
|
|
|
+ regAddress++; slri += 4; writeCount--; delay(0);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ DSPWriter::writeRegisterBlock(regAddress, writeHeader.dataLen,
|
|
|
|
|
+ &dataBuffer[readIndex], registerSize);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ readIndex += writeHeader.dataLen;
|
|
|
|
|
+ sendWriteAck(client, true);
|
|
|
|
|
+ ledOff();
|
|
|
|
|
+ currentState = STATE_START;
|
|
|
|
|
+ processedSomething = true;
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // --- STATE_READ_CMD ---
|
|
|
|
|
+ if (currentState == STATE_READ_CMD) {
|
|
|
|
|
+ if (receivedByteCount < (readIndex + READ_HDR_LEN)) break;
|
|
|
|
|
+
|
|
|
|
|
+ readHeader.totalLen = (uint16_t)((dataBuffer[readIndex + 1] << 8) | dataBuffer[readIndex + 2]);
|
|
|
|
|
+ readHeader.chipAddr = dataBuffer[readIndex + 3];
|
|
|
|
|
+ readHeader.dataLen = (uint16_t)((dataBuffer[readIndex + 4] << 8) | dataBuffer[readIndex + 5]);
|
|
|
|
|
+ readHeader.address = (uint16_t)((dataBuffer[readIndex + 6] << 8) | dataBuffer[readIndex + 7]);
|
|
|
|
|
+ readIndex += READ_HDR_LEN;
|
|
|
|
|
+
|
|
|
|
|
+ uint8_t target7 = chipAddrTo7bit(readHeader.chipAddr);
|
|
|
|
|
+ Serial.printf("TCP READ chip=0x%02X addr=0x%04X len=%u\n",
|
|
|
|
|
+ readHeader.chipAddr, readHeader.address, readHeader.dataLen);
|
|
|
|
|
+
|
|
|
|
|
+ if (readHeader.dataLen > 4096) { readHeader.dataLen = 4096; }
|
|
|
|
|
+
|
|
|
|
|
+ static uint8_t readOut[4096];
|
|
|
|
|
+ bool ok = false;
|
|
|
|
|
+ ledBlue();
|
|
|
|
|
+
|
|
|
|
|
+ if (target7 == EEPROM_7BIT) ok = eepromReadBlock(readHeader.address, readOut, readHeader.dataLen);
|
|
|
|
|
+ else if (target7 == DSP_7BIT) ok = dspReadBlock (readHeader.address, readOut, readHeader.dataLen);
|
|
|
|
|
+ else {
|
|
|
|
|
+ Serial.printf("TCP unknown chipAddr (READ): 0x%02X\n", readHeader.chipAddr);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!ok) Serial.println("TCP READ I2C failed - sending zeros");
|
|
|
|
|
+ if (!sendReadResponse(client, ok ? readOut : nullptr, readHeader.dataLen, ok)) {
|
|
|
|
|
+ Serial.println("TCP READ send failed"); ledErrorFlash(); client.stop(); return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ledOff();
|
|
|
|
|
+ currentState = STATE_START;
|
|
|
|
|
+ processedSomething = true;
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ } // end process loop
|
|
|
|
|
+
|
|
|
|
|
+ // No idle timeout — stay connected until SigmaStudio disconnects.
|
|
|
|
|
+ // client.connected() will return false when the TCP connection drops.
|
|
|
|
|
+ if (currentState == STATE_START && receivedByteCount == readIndex) {
|
|
|
|
|
+ if (!client.available()) {
|
|
|
|
|
+ httpServer.handleClient();
|
|
|
|
|
+ delay(10);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ } // end main while loop
|
|
|
|
|
+
|
|
|
|
|
+ client.stop();
|
|
|
|
|
+ Serial.println("TCP disconnected");
|
|
|
|
|
+ ledOff();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+// Loop
|
|
|
|
|
+//=============================================================
|
|
|
|
|
+void loop() {
|
|
|
|
|
+ httpServer.handleClient();
|
|
|
|
|
+ if (uploadActive) { delay(1); return; }
|
|
|
|
|
+ WiFiClient client = tcpServer.available();
|
|
|
|
|
+ if (client) handleTcpBridgeClient(client);
|
|
|
|
|
+ delay(1);
|
|
|
|
|
+}
|