ModulosDSP_101.ino 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913
  1. // Modulos ADAU DSP WiFi + EEPROM (HTTP uploader + Sigma TCP bridge)
  2. // Build: 260304_13 (YYMMDD_rev)
  3. //
  4. // This is a clean restore to the last known good state (v1.3.0/_04)
  5. // with the chipAddr 0x01 fix applied. The receive loop is the original
  6. // simple byte-at-a-time approach which correctly handled large packets.
  7. //
  8. // Changes vs original:
  9. // - sendReadResponse() corrected to 6-byte SigmaTCP header format
  10. // - sendWriteAck() added after every DSP/EEPROM write
  11. // - chipAddrTo7bit() handles 0x01=DSP, 0x02=EEPROM chip indexes
  12. // - I2C read failure returns zeros instead of dropping connection
  13. // - registerSize derived from ADAU1401 address map
  14. // - DSPWriter::resetSafeload() at start of each TCP session
  15. // - WS2812 NeoPixel on GPIO 21 (Waveshare ESP32-S3 Zero)
  16. //
  17. // Changelog v1.3.0:
  18. // - FIX: sendReadResponse() header corrected to 6-byte SigmaTCP format
  19. // (was 4 bytes; SigmaStudio expects: 0x0B, totalLen_hi, totalLen_lo, status, dataLen_hi, dataLen_lo)
  20. // - ADD: sendWriteAck() — SigmaStudio expects a 4-byte ACK after every write
  21. // (was missing; caused immediate disconnect after first write)
  22. // - FIX: I2C read failure now returns zeros instead of dropping TCP connection
  23. // (SigmaStudio can probe/read a DSP that isn't responding yet without aborting)
  24. // - ADD: Hex dump of first bytes of each received command to Serial for diagnostics
  25. // - ADD: printHex() debug helper
  26. //
  27. // Changelog v1.4.0:
  28. // - FIX: TCP receive loop replaced with client.readBytes() + 3s per-packet timeout
  29. // Previous byte-at-a-time loop timed out mid-transfer on large program blocks
  30. // (e.g. 1490-byte program download) because client.available() returns 0
  31. // between TCP segments even when more data is in flight. Now we block-read
  32. // exactly the bytes needed to complete the current packet, so a large program
  33. // download can span multiple TCP segments without triggering a false idle timeout.
  34. // - FIX: Idle timeout now only applies between commands, not during active receive
  35. //
  36. // Changelog v1.2.0:
  37. // - ADD: WS2812 NeoPixel status LED (Waveshare ESP32-S3 Zero, GPIO 21)
  38. // OFF = idle / no TCP client
  39. // GREEN = DSP write in progress
  40. // BLUE = DSP read in progress
  41. // YELLOW = EEPROM write via TCP
  42. // MAGENTA = HTTP EEPROM upload in progress
  43. // RED flash = error (I2C fail, overflow, bad packet)
  44. //
  45. // Changelog v1.1.0:
  46. // - FIX: Buffer overflow check moved to BEFORE write
  47. // - FIX: chipAddrTo7bit() replaced with explicit lookup table
  48. // - FIX: registerSize now derived from ADAU1401 address range
  49. // - FIX: DSPWriter::resetSafeload() called at TCP session start
  50. // - FIX: Safeload dataLen validated as multiple of 4
  51. // - FIX: totalLen vs dataLen cross-validated on WRITE packets
  52. #include <WiFi.h>
  53. #include <Wire.h>
  54. #include <WebServer.h>
  55. #include "DSPWriter.h"
  56. #include <hd44780.h>
  57. #include <hd44780ioClass/hd44780_I2Cexp.h>
  58. #include <Adafruit_NeoPixel.h>
  59. #include "FS.h"
  60. #include <LittleFS.h>
  61. #include <Update.h>
  62. #include <ESPmDNS.h>
  63. //=============================================================
  64. // WiFi / UI
  65. //=============================================================
  66. const char* ssid = "alfred";
  67. const char* password = "alfred16";
  68. const char* hostname = "modulos-dsp";
  69. const char* version = "VER: 260304_13";
  70. #define I2C_SDA 13
  71. #define I2C_SCL 12
  72. WiFiServer tcpServer(8086);
  73. WebServer httpServer(80);
  74. #include "index_html.h"
  75. #include "ota_html.h"
  76. hd44780_I2Cexp lcd;
  77. static bool s_lcdOk = false;
  78. //=============================================================
  79. // NeoPixel status LED (Waveshare ESP32-S3 Zero, GPIO 21)
  80. //=============================================================
  81. #define NEOPIXEL_PIN 21
  82. #define NEOPIXEL_COUNT 1
  83. #define NEOPIXEL_BRIGHT 40
  84. Adafruit_NeoPixel statusLed(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
  85. static void ledSet(uint8_t r, uint8_t g, uint8_t b)
  86. {
  87. statusLed.setPixelColor(0, statusLed.Color(r, g, b));
  88. statusLed.show();
  89. }
  90. static void ledOff() { ledSet(0, 0, 0); }
  91. static void ledCyan() { ledSet(0, NEOPIXEL_BRIGHT/2, NEOPIXEL_BRIGHT/2); }
  92. static void ledGreen() { ledSet(0, NEOPIXEL_BRIGHT, 0); }
  93. static void ledBlue() { ledSet(0, 0, NEOPIXEL_BRIGHT); }
  94. static void ledYellow() { ledSet(NEOPIXEL_BRIGHT, NEOPIXEL_BRIGHT, 0); }
  95. static void ledMagenta() { ledSet(NEOPIXEL_BRIGHT, 0, NEOPIXEL_BRIGHT); }
  96. static void ledErrorFlash()
  97. {
  98. for (int i = 0; i < 2; i++) {
  99. ledSet(NEOPIXEL_BRIGHT, 0, 0); delay(80);
  100. ledOff(); delay(80);
  101. }
  102. }
  103. //=============================================================
  104. // TCP protocol buffer
  105. //=============================================================
  106. static uint8_t dataBuffer[50 * 1024];
  107. #define STATE_START 0
  108. #define STATE_READ_CMD 1
  109. #define STATE_WRITE_CMD 2
  110. #define CMD_WRITE 0x09
  111. #define CMD_READ 0x0A
  112. #define TCP_IDLE_TIMEOUT_MS 30000 // drop session if silent for 30 s
  113. constexpr int WRITE_HDR_LEN = 10;
  114. constexpr int READ_HDR_LEN = 8;
  115. struct adauWriteHeader {
  116. uint8_t command;
  117. uint8_t safeload;
  118. uint8_t placement;
  119. uint16_t totalLen;
  120. uint8_t chipAddr;
  121. uint16_t dataLen;
  122. uint16_t address;
  123. };
  124. struct adauReadHeader {
  125. uint8_t command;
  126. uint16_t totalLen;
  127. uint8_t chipAddr;
  128. uint16_t dataLen;
  129. uint16_t address;
  130. };
  131. static adauWriteHeader writeHeader;
  132. static adauReadHeader readHeader;
  133. static constexpr uint8_t DSP_7BIT = DSP_I2C_ADDRESS; // 0x34
  134. static constexpr uint8_t EEPROM_7BIT = EEPROM_I2C_ADDRESS; // 0x50
  135. //=============================================================
  136. // 24C256 EEPROM
  137. //=============================================================
  138. static constexpr uint32_t EEPROM_SIZE_BYTES = 32768;
  139. static constexpr uint16_t EEPROM_PAGE_SIZE = 64;
  140. static constexpr uint8_t I2C_MAX_DATA_PER_TX = 28;
  141. //=============================================================
  142. // CRC32
  143. //=============================================================
  144. static uint32_t crc32_update(uint32_t crc, const uint8_t* data, size_t len)
  145. {
  146. crc = ~crc;
  147. for (size_t i = 0; i < len; i++) {
  148. crc ^= data[i];
  149. for (int b = 0; b < 8; b++) {
  150. uint32_t mask = -(crc & 1u);
  151. crc = (crc >> 1) ^ (0xEDB88320u & mask);
  152. }
  153. }
  154. return ~crc;
  155. }
  156. //=============================================================
  157. // Helpers
  158. //=============================================================
  159. static uint8_t chipAddrTo7bit(uint8_t chipAddr)
  160. {
  161. switch (chipAddr) {
  162. case 0x01: return 0x34; // chip index 1 = DSP
  163. case 0x02: return 0x50; // chip index 2 = EEPROM
  164. case 0x68: return 0x34;
  165. case 0xA0: return 0x50;
  166. case 0x34: return 0x34;
  167. case 0x50: return 0x50;
  168. default:
  169. if (chipAddr > 0x7F) return (uint8_t)(chipAddr >> 1);
  170. return chipAddr;
  171. }
  172. }
  173. static uint8_t registerSizeForAddress(uint16_t address, uint16_t dataLen)
  174. {
  175. if (address == dspRegister::CoreRegister) {
  176. if (dataLen == 2) return CORE_REGISTER_R0_REGSIZE;
  177. if (dataLen == 24) return HARDWARE_CONF_REGSIZE;
  178. return CORE_REGISTER_R0_REGSIZE;
  179. }
  180. if (address >= DSP_PROG_RAM_START && address <= DSP_PROG_RAM_END) return PROGRAM_REGSIZE;
  181. if (address < DSP_PROG_RAM_START) return PARAMETER_REGSIZE;
  182. return HARDWARE_CONF_REGSIZE;
  183. }
  184. static bool i2cAckPoll(uint8_t addr7, uint32_t timeoutMs = 80)
  185. {
  186. uint32_t start = millis();
  187. while ((millis() - start) < timeoutMs) {
  188. Wire.beginTransmission(addr7);
  189. if (Wire.endTransmission() == 0) return true;
  190. delay(1);
  191. }
  192. return false;
  193. }
  194. static bool eepromWritePageChunk(uint16_t memAddr, const uint8_t* data, uint16_t len)
  195. {
  196. Wire.beginTransmission(EEPROM_7BIT);
  197. Wire.write((uint8_t)(memAddr >> 8));
  198. Wire.write((uint8_t)(memAddr & 0xFF));
  199. for (uint16_t i = 0; i < len; i++) Wire.write(data[i]);
  200. if (Wire.endTransmission() != 0) return false;
  201. return i2cAckPoll(EEPROM_7BIT, 120);
  202. }
  203. static bool eepromWriteBlock(uint16_t memAddr, const uint8_t* data, uint16_t len)
  204. {
  205. while (len) {
  206. uint16_t pageOff = memAddr % EEPROM_PAGE_SIZE;
  207. uint16_t spaceInPage = EEPROM_PAGE_SIZE - pageOff;
  208. uint16_t chunk = len;
  209. if (chunk > spaceInPage) chunk = spaceInPage;
  210. if (chunk > I2C_MAX_DATA_PER_TX) chunk = I2C_MAX_DATA_PER_TX;
  211. if (!eepromWritePageChunk(memAddr, data, chunk)) return false;
  212. memAddr += chunk; data += chunk; len -= chunk;
  213. delay(0);
  214. }
  215. return true;
  216. }
  217. static bool eepromReadBlock(uint16_t memAddr, uint8_t* out, uint16_t len)
  218. {
  219. Wire.beginTransmission(EEPROM_7BIT);
  220. Wire.write((uint8_t)(memAddr >> 8));
  221. Wire.write((uint8_t)(memAddr & 0xFF));
  222. if (Wire.endTransmission(false) != 0) return false;
  223. uint16_t got = 0;
  224. while (got < len) {
  225. uint8_t ask = (len - got) > 32 ? 32 : (len - got);
  226. if (Wire.requestFrom((int)EEPROM_7BIT, (int)ask) != ask) return false;
  227. for (uint8_t i = 0; i < ask; i++) out[got++] = Wire.read();
  228. }
  229. return true;
  230. }
  231. static bool dspReadBlock(uint16_t memAddr, uint8_t* out, uint16_t len)
  232. {
  233. Wire.beginTransmission(DSP_7BIT);
  234. Wire.write((uint8_t)(memAddr >> 8));
  235. Wire.write((uint8_t)(memAddr & 0xFF));
  236. if (Wire.endTransmission(false) != 0) return false;
  237. uint16_t got = 0;
  238. while (got < len) {
  239. uint8_t ask = (len - got) > 32 ? 32 : (len - got);
  240. if (Wire.requestFrom((int)DSP_7BIT, (int)ask) != ask) return false;
  241. for (uint8_t i = 0; i < ask; i++) out[got++] = Wire.read();
  242. }
  243. return true;
  244. }
  245. // SigmaTCP read response: 0x0B, totalLen_hi, totalLen_lo, status, dataLen_hi, dataLen_lo, [payload]
  246. static bool sendReadResponse(WiFiClient& client, const uint8_t* data, uint16_t dataLen, bool ok)
  247. {
  248. uint16_t totalLen = 6 + dataLen;
  249. uint8_t hdr[6];
  250. hdr[0] = 0x0B;
  251. hdr[1] = (uint8_t)(totalLen >> 8);
  252. hdr[2] = (uint8_t)(totalLen & 0xFF);
  253. hdr[3] = ok ? 0x00 : 0x01;
  254. hdr[4] = (uint8_t)(dataLen >> 8);
  255. hdr[5] = (uint8_t)(dataLen & 0xFF);
  256. if (client.write(hdr, sizeof(hdr)) != sizeof(hdr)) return false;
  257. if (dataLen > 0) {
  258. if (ok && data) {
  259. if (client.write(data, dataLen) != dataLen) return false;
  260. } else {
  261. // send zeros so SigmaStudio doesn't stall on a failed read
  262. static uint8_t zeros[256];
  263. uint16_t rem = dataLen;
  264. while (rem) {
  265. uint16_t chunk = rem > sizeof(zeros) ? sizeof(zeros) : rem;
  266. if (client.write(zeros, chunk) != chunk) return false;
  267. rem -= chunk;
  268. }
  269. }
  270. }
  271. return true;
  272. }
  273. // SigmaTCP write ack: 0x09, 0x00, 0x04, status
  274. static bool sendWriteAck(WiFiClient& client, bool ok)
  275. {
  276. uint8_t ack[4] = { 0x09, 0x00, 0x04, ok ? (uint8_t)0x00 : (uint8_t)0x01 };
  277. return client.write(ack, sizeof(ack)) == sizeof(ack);
  278. }
  279. static void printHex(const char* label, const uint8_t* buf, uint16_t len, uint16_t maxPrint = 24)
  280. {
  281. Serial.print(label);
  282. uint16_t n = len < maxPrint ? len : maxPrint;
  283. for (uint16_t i = 0; i < n; i++) {
  284. if (buf[i] < 0x10) Serial.print("0");
  285. Serial.print(buf[i], HEX);
  286. Serial.print(" ");
  287. }
  288. if (len > maxPrint) Serial.print("...");
  289. Serial.println();
  290. }
  291. static void printWifiInfo()
  292. {
  293. Serial.println();
  294. Serial.println("WiFi connected.");
  295. Serial.print("WiFi IP: "); Serial.println(WiFi.localIP());
  296. Serial.printf("Hostname: http://%s.local/\n", hostname);
  297. Serial.print("MAC: "); Serial.println(WiFi.macAddress());
  298. Serial.println("Modulos AudioDSP");
  299. Serial.println(version);
  300. if (s_lcdOk) {
  301. lcd.setCursor(4, 3);
  302. lcd.print(WiFi.localIP());
  303. }
  304. }
  305. //=============================================================
  306. // HTTP EEPROM uploader state
  307. //=============================================================
  308. static volatile bool uploadActive = false;
  309. static volatile bool uploadVerify = false;
  310. static volatile bool uploadFailed = false;
  311. static volatile uint32_t uploadBytes = 0;
  312. static volatile uint32_t uploadCrc = 0;
  313. //=============================================================
  314. // HTTP handlers
  315. //=============================================================
  316. static String contentTypeFor(const String& path) {
  317. if (path.endsWith(".html")) return "text/html";
  318. if (path.endsWith(".css")) return "text/css";
  319. if (path.endsWith(".js")) return "application/javascript";
  320. if (path.endsWith(".png")) return "image/png";
  321. if (path.endsWith(".jpg") || path.endsWith(".jpeg")) return "image/jpeg";
  322. if (path.endsWith(".webp")) return "image/webp";
  323. if (path.endsWith(".svg")) return "image/svg+xml";
  324. if (path.endsWith(".ico")) return "image/x-icon";
  325. if (path.endsWith(".woff")) return "font/woff";
  326. if (path.endsWith(".woff2"))return "font/woff2";
  327. return "application/octet-stream";
  328. }
  329. static bool streamFromFS(String path) {
  330. if (!LittleFS.exists(path)) {
  331. if (path.startsWith("/")) {
  332. String alt = path.substring(1);
  333. if (LittleFS.exists(alt)) path = alt; else return false;
  334. } else {
  335. String alt = "/" + path;
  336. if (LittleFS.exists(alt)) path = alt; else return false;
  337. }
  338. }
  339. File f = LittleFS.open(path, "r");
  340. if (!f) return false;
  341. httpServer.streamFile(f, contentTypeFor(path));
  342. f.close();
  343. return true;
  344. }
  345. static void handleRoot() {
  346. String html = FPSTR(INDEX_HTML);
  347. html.replace("{{IP}}", WiFi.localIP().toString());
  348. httpServer.send(200, "text/html; charset=utf-8", html);
  349. }
  350. static void handleStatus() {
  351. String s;
  352. s += "uploadActive="; s += (uploadActive ? "1" : "0"); s += "\n";
  353. s += "uploadFailed="; s += (uploadFailed ? "1" : "0"); s += "\n";
  354. s += "uploadBytes="; s += String((uint32_t)uploadBytes); s += "\n";
  355. s += "uploadCRC32=0x"; s += String((uint32_t)uploadCrc, HEX); s += "\n";
  356. httpServer.send(200, "text/plain", s);
  357. }
  358. static void handleUploadDone() {
  359. if (uploadFailed) {
  360. httpServer.send(500, "text/plain", "Upload failed.\nCheck Serial log.\n");
  361. return;
  362. }
  363. String msg = "OK\nBytes written: " + String((uint32_t)uploadBytes) +
  364. "\nCRC32: 0x" + String((uint32_t)uploadCrc, HEX) + "\n";
  365. httpServer.send(200, "text/plain", msg);
  366. }
  367. static void handleUploadStream() {
  368. HTTPUpload& up = httpServer.upload();
  369. if (up.status == UPLOAD_FILE_START) {
  370. uploadActive = true; uploadFailed = false; uploadBytes = 0; uploadCrc = 0;
  371. uploadVerify = httpServer.hasArg("verify");
  372. Serial.println(); Serial.print("HTTP upload start: "); Serial.println(up.filename);
  373. Serial.print("Verify: "); Serial.println(uploadVerify ? "yes" : "no");
  374. Wire.beginTransmission(EEPROM_7BIT);
  375. Serial.print("EEPROM probe err: "); Serial.println(Wire.endTransmission());
  376. }
  377. else if (up.status == UPLOAD_FILE_WRITE) {
  378. if (uploadFailed) return;
  379. if ((uploadBytes + up.currentSize) > EEPROM_SIZE_BYTES) {
  380. Serial.println("Upload too large for 24C256"); uploadFailed = true; return;
  381. }
  382. if (!eepromWriteBlock((uint16_t)uploadBytes, up.buf, (uint16_t)up.currentSize)) {
  383. Serial.println("EEPROM write failed"); uploadFailed = true; return;
  384. }
  385. uploadCrc = crc32_update(uploadCrc, up.buf, up.currentSize);
  386. uploadBytes += up.currentSize;
  387. ledMagenta();
  388. }
  389. else if (up.status == UPLOAD_FILE_END) {
  390. Serial.print("HTTP upload end, bytes="); Serial.println((uint32_t)uploadBytes);
  391. if (uploadVerify && !uploadFailed) {
  392. Serial.println("Verify start (CRC32)...");
  393. uint32_t crc = 0;
  394. static uint8_t tmp[256];
  395. uint32_t remaining = uploadBytes; uint16_t addr = 0;
  396. while (remaining) {
  397. uint16_t n = remaining > sizeof(tmp) ? sizeof(tmp) : (uint16_t)remaining;
  398. if (!eepromReadBlock(addr, tmp, n)) { Serial.println("EEPROM read failed"); uploadFailed = true; break; }
  399. crc = crc32_update(crc, tmp, n);
  400. addr += n; remaining -= n; delay(0);
  401. }
  402. Serial.print("Verify CRC32: 0x"); Serial.println(crc, HEX);
  403. if (!uploadFailed && crc != uploadCrc) { Serial.println("CRC mismatch"); uploadFailed = true; }
  404. }
  405. uploadActive = false; ledOff();
  406. Serial.println(uploadFailed ? "HTTP upload result: FAIL" : "HTTP upload result: OK");
  407. }
  408. else if (up.status == UPLOAD_FILE_ABORTED) {
  409. Serial.println("HTTP upload aborted"); uploadActive = false; uploadFailed = true; ledOff();
  410. }
  411. }
  412. //=============================================================
  413. // HTTP DSP reset handler
  414. //=============================================================
  415. static void handleDspReset() {
  416. Serial.println("DSP soft reset requested");
  417. // Stop DSP execution, brief pause, restart. Program and parameter RAM
  418. // are preserved — this restarts execution without reloading from EEPROM.
  419. uint8_t stop[2] = { 0x00, 0x00 };
  420. uint8_t run[2] = { 0x00, 0x01 };
  421. DSPWriter::writeRegister(dspRegister::CoreRegister, sizeof(stop), stop);
  422. delay(100);
  423. DSPWriter::writeRegister(dspRegister::CoreRegister, sizeof(run), run);
  424. Serial.println("DSP soft reset complete");
  425. httpServer.send(200, "text/plain", "DSP soft reset complete.");
  426. }
  427. //=============================================================
  428. // HTTP DSP status handler GET /dsp_status
  429. //=============================================================
  430. static void handleDspStatus() {
  431. // Core Register (0x081C) — 2 bytes, bit 0 = Run
  432. uint8_t coreRaw[2] = {0, 0};
  433. bool coreOk = dspReadBlock(dspRegister::CoreRegister, coreRaw, 2);
  434. uint16_t coreVal = ((uint16_t)coreRaw[0] << 8) | coreRaw[1];
  435. // GPIO All Register (0x0808) — 1 byte
  436. uint8_t gpio = 0;
  437. bool gpioOk = dspReadBlock(dspRegister::GpioAllRegister, &gpio, 1);
  438. // ADC0–3 (0x0809–0x080C) — 1 byte each, read as a 4-byte burst
  439. uint8_t adc[4] = {0, 0, 0, 0};
  440. bool adcOk = dspReadBlock(dspRegister::Adc0, adc, 4);
  441. bool allOk = coreOk && gpioOk && adcOk;
  442. char buf[200];
  443. snprintf(buf, sizeof(buf),
  444. "{\"ok\":%s,\"running\":%s,"
  445. "\"coreReg\":\"0x%04X\","
  446. "\"gpio\":\"0x%02X\","
  447. "\"adc\":[\"0x%02X\",\"0x%02X\",\"0x%02X\",\"0x%02X\"]}",
  448. allOk ? "true" : "false",
  449. (coreOk && (coreVal & 0x01)) ? "true" : "false",
  450. coreVal, gpio,
  451. adc[0], adc[1], adc[2], adc[3]);
  452. httpServer.send(allOk ? 200 : 500, "application/json", buf);
  453. }
  454. //=============================================================
  455. // HTTP GPIO handlers GET /gpio POST /gpio
  456. //=============================================================
  457. static void handleGpioGet() {
  458. uint8_t gpio = 0;
  459. bool gpioOk = dspReadBlock(dspRegister::GpioAllRegister, &gpio, 1);
  460. uint8_t mpcfg[2] = {0, 0};
  461. bool cfgOk = dspReadBlock(dspRegister::MpCfg0, mpcfg, 2);
  462. bool ok = gpioOk && cfgOk;
  463. char buf[100];
  464. snprintf(buf, sizeof(buf),
  465. "{\"ok\":%s,\"gpio\":%u,\"mpcfg0\":%u,\"mpcfg1\":%u}",
  466. ok ? "true" : "false", gpio, mpcfg[0], mpcfg[1]);
  467. httpServer.send(ok ? 200 : 500, "application/json", buf);
  468. }
  469. static void handleGpioSet() {
  470. if (!httpServer.hasArg("value")) {
  471. httpServer.send(400, "text/plain", "Missing 'value' parameter.");
  472. return;
  473. }
  474. long val = httpServer.arg("value").toInt();
  475. if (val < 0 || val > 255) {
  476. httpServer.send(400, "text/plain", "value must be 0-255.");
  477. return;
  478. }
  479. uint8_t b = (uint8_t)val;
  480. DSPWriter::writeRegister(dspRegister::GpioAllRegister, 1, &b);
  481. Serial.printf("GPIO set: 0x%02X\n", b);
  482. httpServer.send(200, "text/plain", "OK");
  483. }
  484. //=============================================================
  485. // HTTP OTA handlers
  486. //=============================================================
  487. static void handleOtaPage() {
  488. String html = FPSTR(OTA_HTML);
  489. html.replace("{{IP}}", WiFi.localIP().toString());
  490. httpServer.send(200, "text/html; charset=utf-8", html);
  491. }
  492. static void handleOtaDone() {
  493. if (Update.hasError()) {
  494. String err = "OTA failed: " + String(Update.errorString());
  495. Serial.println(err);
  496. httpServer.send(500, "text/plain", err);
  497. } else {
  498. httpServer.send(200, "text/plain", "Firmware updated — rebooting now.");
  499. Serial.println("OTA success — rebooting");
  500. delay(500);
  501. ESP.restart();
  502. }
  503. }
  504. static void handleOtaStream() {
  505. HTTPUpload& up = httpServer.upload();
  506. if (up.status == UPLOAD_FILE_START) {
  507. Serial.printf("OTA start: %s\n", up.filename.c_str());
  508. ledCyan();
  509. if (!Update.begin(UPDATE_SIZE_UNKNOWN)) {
  510. Serial.print("OTA begin failed: ");
  511. Update.printError(Serial);
  512. }
  513. }
  514. else if (up.status == UPLOAD_FILE_WRITE) {
  515. if (Update.write(up.buf, up.currentSize) != up.currentSize) {
  516. Serial.print("OTA write failed: ");
  517. Update.printError(Serial);
  518. ledErrorFlash();
  519. }
  520. }
  521. else if (up.status == UPLOAD_FILE_END) {
  522. if (Update.end(true)) {
  523. Serial.printf("OTA end: %u bytes written\n", up.totalSize);
  524. } else {
  525. Serial.print("OTA end failed: ");
  526. Update.printError(Serial);
  527. ledErrorFlash();
  528. }
  529. ledOff();
  530. }
  531. else if (up.status == UPLOAD_FILE_ABORTED) {
  532. Update.abort();
  533. Serial.println("OTA aborted");
  534. ledOff();
  535. }
  536. }
  537. //=============================================================
  538. // Setup
  539. //=============================================================
  540. void setup() {
  541. Wire.begin(I2C_SDA, I2C_SCL);
  542. Wire.setClock(400000);
  543. statusLed.begin();
  544. statusLed.setBrightness(NEOPIXEL_BRIGHT);
  545. statusLed.show();
  546. s_lcdOk = (lcd.begin(20, 4) == 0);
  547. if (s_lcdOk) {
  548. lcd.display(); lcd.backlight();
  549. lcd.setCursor(2, 0); lcd.print("Modulos AudioDSP"); delay(1000);
  550. lcd.setCursor(5, 1); lcd.print("Booting..."); delay(1000);
  551. } else {
  552. Serial.println("LCD not found - continuing without display");
  553. }
  554. Serial.begin(115200); delay(1500);
  555. Serial.println(); Serial.println("Booting...");
  556. Serial.printf("Reset reason: %d\n", (int)esp_reset_reason());
  557. WiFi.mode(WIFI_STA);
  558. WiFi.setAutoReconnect(true);
  559. WiFi.begin(ssid, password);
  560. while (WiFi.waitForConnectResult() != WL_CONNECTED) {
  561. Serial.println("Connection Failed! Rebooting...");
  562. delay(5000); ESP.restart();
  563. }
  564. if (MDNS.begin(hostname)) {
  565. MDNS.addService("http", "tcp", 80);
  566. Serial.printf("mDNS: http://%s.local/\n", hostname);
  567. } else {
  568. Serial.println("mDNS start failed");
  569. }
  570. if (!LittleFS.begin(false)) {
  571. Serial.println("LittleFS mount failed, formatting...");
  572. if (!LittleFS.begin(true)) { Serial.println("LittleFS mount failed even after format"); return; }
  573. }
  574. Serial.println("LittleFS mounted OK");
  575. File root = LittleFS.open("/"); File f = root.openNextFile();
  576. while (f) { Serial.print("LittleFS: "); Serial.println(f.name()); f = root.openNextFile(); }
  577. if (s_lcdOk) { lcd.setCursor(3, 2); lcd.print("File System OK"); delay(1000); }
  578. tcpServer.begin();
  579. httpServer.on("/", HTTP_GET, handleRoot);
  580. httpServer.on("/status", HTTP_GET, handleStatus);
  581. httpServer.on("/upload", HTTP_POST, handleUploadDone, handleUploadStream);
  582. httpServer.on("/ota", HTTP_GET, handleOtaPage);
  583. httpServer.on("/ota_do", HTTP_POST, handleOtaDone, handleOtaStream);
  584. httpServer.on("/dsp_reset", HTTP_POST, handleDspReset);
  585. httpServer.on("/dsp_status", HTTP_GET, handleDspStatus);
  586. httpServer.on("/gpio", HTTP_GET, handleGpioGet);
  587. httpServer.on("/gpio", HTTP_POST, handleGpioSet);
  588. httpServer.onNotFound([]() {
  589. String uri = httpServer.uri();
  590. if (streamFromFS(uri)) return;
  591. Serial.print("HTTP 404: "); Serial.println(uri);
  592. httpServer.send(404, "text/plain", "Not found: " + uri);
  593. });
  594. httpServer.begin();
  595. if (s_lcdOk) {
  596. lcd.setCursor(4, 3); lcd.print("System Ready"); delay(1000);
  597. lcd.clear();
  598. lcd.setCursor(2, 0); lcd.print("Modulos AudioDSP");
  599. lcd.setCursor(3, 1); lcd.print(version); delay(500);
  600. }
  601. printWifiInfo();
  602. Serial.print("HTTP uploader: http://"); Serial.print(WiFi.localIP()); Serial.println("/");
  603. }
  604. //=============================================================
  605. // TCP bridge
  606. //=============================================================
  607. //=============================================================
  608. // TCP bridge
  609. //=============================================================
  610. static void handleTcpBridgeClient(WiFiClient& client)
  611. {
  612. Serial.println("TCP new connection");
  613. DSPWriter::resetSafeload();
  614. int writeIndex = 0; // next free slot in dataBuffer
  615. int readIndex = 0; // start of current unprocessed command
  616. int receivedByteCount = 0; // total bytes written into dataBuffer
  617. int currentState = STATE_START;
  618. uint32_t lastActivityMs = millis(); // idle watchdog
  619. while (client.connected()) {
  620. httpServer.handleClient();
  621. delay(0);
  622. // ------------------------------------------------------------------
  623. // STEP 1: Always drain the TCP stack into dataBuffer.
  624. // Do this unconditionally every loop iteration — this is what keeps
  625. // the TCP receive window open. If we only drain when we feel like it,
  626. // the window goes to zero and SigmaStudio stops sending (ZeroWindow).
  627. // ------------------------------------------------------------------
  628. while (client.available()) {
  629. if (writeIndex >= (int)sizeof(dataBuffer)) {
  630. Serial.println("TCP RX overflow");
  631. ledErrorFlash(); client.stop(); return;
  632. }
  633. int b = client.read();
  634. if (b < 0) break;
  635. dataBuffer[writeIndex++] = (uint8_t)b;
  636. receivedByteCount++;
  637. lastActivityMs = millis();
  638. }
  639. // ------------------------------------------------------------------
  640. // STEP 2: Process whatever is in the buffer.
  641. // This is driven purely by buffer contents, not by client.available().
  642. // We loop here processing commands until we run out of buffered data.
  643. // ------------------------------------------------------------------
  644. bool processedSomething = true;
  645. while (processedSomething && client.connected()) {
  646. processedSomething = false;
  647. // --- STATE_START: identify opcode ---
  648. if (currentState == STATE_START) {
  649. if (receivedByteCount <= readIndex) {
  650. // Buffer empty — reset for next command
  651. writeIndex = readIndex = receivedByteCount = 0;
  652. ledOff();
  653. break; // nothing to process, go back to receive loop
  654. }
  655. printHex("TCP RX: ", &dataBuffer[readIndex], (uint16_t)(receivedByteCount - readIndex));
  656. uint8_t op = dataBuffer[readIndex];
  657. if (op == CMD_WRITE) { currentState = STATE_WRITE_CMD; processedSomething = true; }
  658. else if (op == CMD_READ) { currentState = STATE_READ_CMD; processedSomething = true; }
  659. else {
  660. Serial.printf("TCP invalid opcode: 0x%02X\n", op);
  661. ledErrorFlash();
  662. client.stop(); return;
  663. }
  664. }
  665. // --- STATE_WRITE_CMD ---
  666. if (currentState == STATE_WRITE_CMD) {
  667. // Need full header first
  668. if (receivedByteCount < (readIndex + WRITE_HDR_LEN)) break;
  669. writeHeader.safeload = dataBuffer[readIndex + 1];
  670. writeHeader.placement = dataBuffer[readIndex + 2];
  671. writeHeader.totalLen = (uint16_t)((dataBuffer[readIndex + 3] << 8) | dataBuffer[readIndex + 4]);
  672. writeHeader.chipAddr = dataBuffer[readIndex + 5];
  673. writeHeader.dataLen = (uint16_t)((dataBuffer[readIndex + 6] << 8) | dataBuffer[readIndex + 7]);
  674. writeHeader.address = (uint16_t)((dataBuffer[readIndex + 8] << 8) | dataBuffer[readIndex + 9]);
  675. if (writeHeader.totalLen != WRITE_HDR_LEN + writeHeader.dataLen) {
  676. Serial.printf("TCP WRITE bad totalLen: got %u expected %u\n",
  677. writeHeader.totalLen, WRITE_HDR_LEN + writeHeader.dataLen);
  678. ledErrorFlash(); client.stop(); return;
  679. }
  680. // Need full payload — if not here yet, break back to receive loop
  681. if (receivedByteCount < (readIndex + WRITE_HDR_LEN + (int)writeHeader.dataLen)) {
  682. Serial.printf("TCP WRITE buffering: have %d need %d bytes\n",
  683. receivedByteCount - readIndex,
  684. WRITE_HDR_LEN + (int)writeHeader.dataLen);
  685. break;
  686. }
  687. readIndex += WRITE_HDR_LEN;
  688. uint8_t target7 = chipAddrTo7bit(writeHeader.chipAddr);
  689. if (target7 == EEPROM_7BIT) {
  690. ledYellow();
  691. bool ok = eepromWriteBlock(writeHeader.address, &dataBuffer[readIndex], writeHeader.dataLen);
  692. readIndex += writeHeader.dataLen;
  693. sendWriteAck(client, ok);
  694. if (!ok) { Serial.println("TCP EEPROM write failed"); ledErrorFlash(); }
  695. else ledOff();
  696. currentState = STATE_START;
  697. processedSomething = true;
  698. continue;
  699. }
  700. if (target7 != DSP_7BIT) {
  701. Serial.printf("TCP unknown chipAddr: 0x%02X\n", writeHeader.chipAddr);
  702. ledErrorFlash(); client.stop(); return;
  703. }
  704. ledGreen();
  705. uint8_t registerSize = registerSizeForAddress(writeHeader.address, writeHeader.dataLen);
  706. uint16_t regAddress = writeHeader.address;
  707. Serial.printf("TCP WRITE addr=0x%04X len=%u regSz=%u safeload=%u\n",
  708. writeHeader.address, writeHeader.dataLen, registerSize, writeHeader.safeload);
  709. bool writeOk = true;
  710. if (writeHeader.safeload == 1) {
  711. if (writeHeader.dataLen % 4 != 0) {
  712. Serial.printf("TCP safeload dataLen %u not multiple of 4\n", writeHeader.dataLen);
  713. ledErrorFlash(); client.stop(); return;
  714. }
  715. int writeCount = writeHeader.dataLen / 4;
  716. int slri = readIndex;
  717. DSPWriter dspWriter;
  718. while (writeCount > 0) {
  719. uint8_t da[5] = { 0x00, dataBuffer[slri], dataBuffer[slri+1],
  720. dataBuffer[slri+2], dataBuffer[slri+3] };
  721. dspWriter.safeload_writeRegister(regAddress, da, writeCount == 1);
  722. regAddress++; slri += 4; writeCount--; delay(0);
  723. }
  724. } else {
  725. writeOk = DSPWriter::writeRegisterBlock(regAddress, writeHeader.dataLen,
  726. &dataBuffer[readIndex], registerSize);
  727. if (!writeOk) Serial.println("TCP DSP block write failed");
  728. }
  729. readIndex += writeHeader.dataLen;
  730. if (!writeOk) ledErrorFlash(); else ledOff();
  731. sendWriteAck(client, writeOk);
  732. currentState = STATE_START;
  733. processedSomething = true;
  734. continue;
  735. }
  736. // --- STATE_READ_CMD ---
  737. if (currentState == STATE_READ_CMD) {
  738. if (receivedByteCount < (readIndex + READ_HDR_LEN)) break;
  739. readHeader.totalLen = (uint16_t)((dataBuffer[readIndex + 1] << 8) | dataBuffer[readIndex + 2]);
  740. readHeader.chipAddr = dataBuffer[readIndex + 3];
  741. readHeader.dataLen = (uint16_t)((dataBuffer[readIndex + 4] << 8) | dataBuffer[readIndex + 5]);
  742. readHeader.address = (uint16_t)((dataBuffer[readIndex + 6] << 8) | dataBuffer[readIndex + 7]);
  743. readIndex += READ_HDR_LEN;
  744. uint8_t target7 = chipAddrTo7bit(readHeader.chipAddr);
  745. Serial.printf("TCP READ chip=0x%02X addr=0x%04X len=%u\n",
  746. readHeader.chipAddr, readHeader.address, readHeader.dataLen);
  747. if (readHeader.dataLen > 4096) { readHeader.dataLen = 4096; }
  748. static uint8_t readOut[4096];
  749. bool ok = false;
  750. ledBlue();
  751. if (target7 == EEPROM_7BIT) ok = eepromReadBlock(readHeader.address, readOut, readHeader.dataLen);
  752. else if (target7 == DSP_7BIT) ok = dspReadBlock (readHeader.address, readOut, readHeader.dataLen);
  753. else {
  754. Serial.printf("TCP unknown chipAddr (READ): 0x%02X\n", readHeader.chipAddr);
  755. }
  756. if (!ok) Serial.println("TCP READ I2C failed - sending zeros");
  757. if (!sendReadResponse(client, ok ? readOut : nullptr, readHeader.dataLen, ok)) {
  758. Serial.println("TCP READ send failed"); ledErrorFlash(); client.stop(); return;
  759. }
  760. ledOff();
  761. currentState = STATE_START;
  762. processedSomething = true;
  763. continue;
  764. }
  765. } // end process loop
  766. // Idle path — no data waiting, no command in progress.
  767. if (currentState == STATE_START && receivedByteCount == readIndex) {
  768. if (!client.available()) {
  769. if (millis() - lastActivityMs > TCP_IDLE_TIMEOUT_MS) {
  770. Serial.println("TCP idle timeout — closing session");
  771. ledErrorFlash();
  772. break;
  773. }
  774. httpServer.handleClient();
  775. delay(10);
  776. }
  777. }
  778. } // end main while loop
  779. DSPWriter::resetSafeload(); // flush any mid-session safeload before disconnect
  780. client.stop();
  781. Serial.println("TCP disconnected");
  782. ledOff();
  783. }
  784. //=============================================================
  785. // WiFi watchdog
  786. //=============================================================
  787. static uint32_t s_wifiLastCheck = 0;
  788. static bool s_wifiLost = false;
  789. static void maintainWifi()
  790. {
  791. if (millis() - s_wifiLastCheck < 5000) return;
  792. s_wifiLastCheck = millis();
  793. if (WiFi.status() != WL_CONNECTED) {
  794. if (!s_wifiLost) {
  795. Serial.println("WiFi lost");
  796. if (s_lcdOk) { lcd.setCursor(0, 3); lcd.print("WiFi lost... "); }
  797. s_wifiLost = true;
  798. }
  799. WiFi.reconnect();
  800. } else if (s_wifiLost) {
  801. Serial.print("WiFi reconnected, IP: "); Serial.println(WiFi.localIP());
  802. MDNS.begin(hostname);
  803. MDNS.addService("http", "tcp", 80);
  804. tcpServer.begin(); // re-register listening socket with recovered stack
  805. printWifiInfo(); // update LCD with current IP
  806. s_wifiLost = false;
  807. }
  808. }
  809. //=============================================================
  810. // Loop
  811. //=============================================================
  812. void loop() {
  813. maintainWifi();
  814. httpServer.handleClient();
  815. if (uploadActive) { delay(1); return; }
  816. WiFiClient client = tcpServer.available();
  817. if (client) handleTcpBridgeClient(client);
  818. delay(1);
  819. }