ModulosDSP_101.ino 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701
  1. // Modulos ADAU DSP WiFi + EEPROM (HTTP uploader + Sigma TCP bridge)
  2. // Ver 1.3.1
  3. // March 2026
  4. //
  5. // This is a clean restore to the last known good state (v1.3.0/_04)
  6. // with the chipAddr 0x01 fix applied. The receive loop is the original
  7. // simple byte-at-a-time approach which correctly handled large packets.
  8. //
  9. // Changes vs original:
  10. // - sendReadResponse() corrected to 6-byte SigmaTCP header format
  11. // - sendWriteAck() added after every DSP/EEPROM write
  12. // - chipAddrTo7bit() handles 0x01=DSP, 0x02=EEPROM chip indexes
  13. // - I2C read failure returns zeros instead of dropping connection
  14. // - registerSize derived from ADAU1401 address map
  15. // - DSPWriter::resetSafeload() at start of each TCP session
  16. // - WS2812 NeoPixel on GPIO 21 (Waveshare ESP32-S3 Zero)
  17. //
  18. // Changelog v1.3.0:
  19. // - FIX: sendReadResponse() header corrected to 6-byte SigmaTCP format
  20. // (was 4 bytes; SigmaStudio expects: 0x0B, totalLen_hi, totalLen_lo, status, dataLen_hi, dataLen_lo)
  21. // - ADD: sendWriteAck() — SigmaStudio expects a 4-byte ACK after every write
  22. // (was missing; caused immediate disconnect after first write)
  23. // - FIX: I2C read failure now returns zeros instead of dropping TCP connection
  24. // (SigmaStudio can probe/read a DSP that isn't responding yet without aborting)
  25. // - ADD: Hex dump of first bytes of each received command to Serial for diagnostics
  26. // - ADD: printHex() debug helper
  27. //
  28. // Changelog v1.4.0:
  29. // - FIX: TCP receive loop replaced with client.readBytes() + 3s per-packet timeout
  30. // Previous byte-at-a-time loop timed out mid-transfer on large program blocks
  31. // (e.g. 1490-byte program download) because client.available() returns 0
  32. // between TCP segments even when more data is in flight. Now we block-read
  33. // exactly the bytes needed to complete the current packet, so a large program
  34. // download can span multiple TCP segments without triggering a false idle timeout.
  35. // - FIX: Idle timeout now only applies between commands, not during active receive
  36. //
  37. // Changelog v1.2.0:
  38. // - ADD: WS2812 NeoPixel status LED (Waveshare ESP32-S3 Zero, GPIO 21)
  39. // OFF = idle / no TCP client
  40. // GREEN = DSP write in progress
  41. // BLUE = DSP read in progress
  42. // YELLOW = EEPROM write via TCP
  43. // MAGENTA = HTTP EEPROM upload in progress
  44. // RED flash = error (I2C fail, overflow, bad packet)
  45. //
  46. // Changelog v1.1.0:
  47. // - FIX: Buffer overflow check moved to BEFORE write
  48. // - FIX: chipAddrTo7bit() replaced with explicit lookup table
  49. // - FIX: registerSize now derived from ADAU1401 address range
  50. // - FIX: DSPWriter::resetSafeload() called at TCP session start
  51. // - FIX: Safeload dataLen validated as multiple of 4
  52. // - FIX: totalLen vs dataLen cross-validated on WRITE packets
  53. #include <WiFi.h>
  54. #include <Wire.h>
  55. #include <WebServer.h>
  56. #include "DSPWriter.h"
  57. #include <hd44780.h>
  58. #include <hd44780ioClass/hd44780_I2Cexp.h>
  59. #include <Adafruit_NeoPixel.h>
  60. #include "FS.h"
  61. #include <LittleFS.h>
  62. //=============================================================
  63. // WiFi / UI
  64. //=============================================================
  65. const char* ssid = "alfred";
  66. const char* password = "alfred16";
  67. const char* version = "VER: 260304_13";
  68. #define I2C_SDA 13
  69. #define I2C_SCL 12
  70. WiFiServer tcpServer(8086);
  71. WebServer httpServer(80);
  72. #include "index_html.h"
  73. hd44780_I2Cexp lcd;
  74. //=============================================================
  75. // NeoPixel status LED (Waveshare ESP32-S3 Zero, GPIO 21)
  76. //=============================================================
  77. #define NEOPIXEL_PIN 21
  78. #define NEOPIXEL_COUNT 1
  79. #define NEOPIXEL_BRIGHT 40
  80. Adafruit_NeoPixel statusLed(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
  81. static void ledSet(uint8_t r, uint8_t g, uint8_t b)
  82. {
  83. statusLed.setPixelColor(0, statusLed.Color(r, g, b));
  84. statusLed.show();
  85. }
  86. static void ledOff() { ledSet(0, 0, 0); }
  87. static void ledCyan() { ledSet(0, NEOPIXEL_BRIGHT/2, NEOPIXEL_BRIGHT/2); }
  88. static void ledGreen() { ledSet(0, NEOPIXEL_BRIGHT, 0); }
  89. static void ledBlue() { ledSet(0, 0, NEOPIXEL_BRIGHT); }
  90. static void ledYellow() { ledSet(NEOPIXEL_BRIGHT, NEOPIXEL_BRIGHT, 0); }
  91. static void ledMagenta() { ledSet(NEOPIXEL_BRIGHT, 0, NEOPIXEL_BRIGHT); }
  92. static void ledErrorFlash()
  93. {
  94. for (int i = 0; i < 2; i++) {
  95. ledSet(NEOPIXEL_BRIGHT, 0, 0); delay(80);
  96. ledOff(); delay(80);
  97. }
  98. }
  99. //=============================================================
  100. // TCP protocol buffer
  101. //=============================================================
  102. static uint8_t dataBuffer[50 * 1024];
  103. #define STATE_START 0
  104. #define STATE_READ_CMD 1
  105. #define STATE_WRITE_CMD 2
  106. #define CMD_WRITE 0x09
  107. #define CMD_READ 0x0A
  108. constexpr int WRITE_HDR_LEN = 10;
  109. constexpr int READ_HDR_LEN = 8;
  110. struct adauWriteHeader {
  111. uint8_t command;
  112. uint8_t safeload;
  113. uint8_t placement;
  114. uint16_t totalLen;
  115. uint8_t chipAddr;
  116. uint16_t dataLen;
  117. uint16_t address;
  118. };
  119. struct adauReadHeader {
  120. uint8_t command;
  121. uint16_t totalLen;
  122. uint8_t chipAddr;
  123. uint16_t dataLen;
  124. uint16_t address;
  125. };
  126. static adauWriteHeader writeHeader;
  127. static adauReadHeader readHeader;
  128. static constexpr uint8_t DSP_7BIT = DSP_I2C_ADDRESS; // 0x34
  129. static constexpr uint8_t EEPROM_7BIT = EEPROM_I2C_ADDRESS; // 0x50
  130. //=============================================================
  131. // 24C256 EEPROM
  132. //=============================================================
  133. static constexpr uint32_t EEPROM_SIZE_BYTES = 32768;
  134. static constexpr uint16_t EEPROM_PAGE_SIZE = 64;
  135. static constexpr uint8_t I2C_MAX_DATA_PER_TX = 28;
  136. //=============================================================
  137. // CRC32
  138. //=============================================================
  139. static uint32_t crc32_update(uint32_t crc, const uint8_t* data, size_t len)
  140. {
  141. crc = ~crc;
  142. for (size_t i = 0; i < len; i++) {
  143. crc ^= data[i];
  144. for (int b = 0; b < 8; b++) {
  145. uint32_t mask = -(crc & 1u);
  146. crc = (crc >> 1) ^ (0xEDB88320u & mask);
  147. }
  148. }
  149. return ~crc;
  150. }
  151. //=============================================================
  152. // Helpers
  153. //=============================================================
  154. static uint8_t chipAddrTo7bit(uint8_t chipAddr)
  155. {
  156. switch (chipAddr) {
  157. case 0x01: return 0x34; // chip index 1 = DSP
  158. case 0x02: return 0x50; // chip index 2 = EEPROM
  159. case 0x68: return 0x34;
  160. case 0xA0: return 0x50;
  161. case 0x34: return 0x34;
  162. case 0x50: return 0x50;
  163. default:
  164. if (chipAddr > 0x7F) return (uint8_t)(chipAddr >> 1);
  165. return chipAddr;
  166. }
  167. }
  168. static uint8_t registerSizeForAddress(uint16_t address, uint16_t dataLen)
  169. {
  170. if (address == dspRegister::CoreRegister) {
  171. if (dataLen == 2) return CORE_REGISTER_R0_REGSIZE;
  172. if (dataLen == 24) return HARDWARE_CONF_REGSIZE;
  173. return CORE_REGISTER_R0_REGSIZE;
  174. }
  175. if (address >= DSP_PROG_RAM_START && address <= DSP_PROG_RAM_END) return PROGRAM_REGSIZE;
  176. if (address < DSP_PROG_RAM_START) return PARAMETER_REGSIZE;
  177. return HARDWARE_CONF_REGSIZE;
  178. }
  179. static bool i2cAckPoll(uint8_t addr7, uint32_t timeoutMs = 80)
  180. {
  181. uint32_t start = millis();
  182. while ((millis() - start) < timeoutMs) {
  183. Wire.beginTransmission(addr7);
  184. if (Wire.endTransmission() == 0) return true;
  185. delay(1);
  186. }
  187. return false;
  188. }
  189. static bool eepromWritePageChunk(uint16_t memAddr, const uint8_t* data, uint16_t len)
  190. {
  191. Wire.beginTransmission(EEPROM_7BIT);
  192. Wire.write((uint8_t)(memAddr >> 8));
  193. Wire.write((uint8_t)(memAddr & 0xFF));
  194. for (uint16_t i = 0; i < len; i++) Wire.write(data[i]);
  195. if (Wire.endTransmission() != 0) return false;
  196. return i2cAckPoll(EEPROM_7BIT, 120);
  197. }
  198. static bool eepromWriteBlock(uint16_t memAddr, const uint8_t* data, uint16_t len)
  199. {
  200. while (len) {
  201. uint16_t pageOff = memAddr % EEPROM_PAGE_SIZE;
  202. uint16_t spaceInPage = EEPROM_PAGE_SIZE - pageOff;
  203. uint16_t chunk = len;
  204. if (chunk > spaceInPage) chunk = spaceInPage;
  205. if (chunk > I2C_MAX_DATA_PER_TX) chunk = I2C_MAX_DATA_PER_TX;
  206. if (!eepromWritePageChunk(memAddr, data, chunk)) return false;
  207. memAddr += chunk; data += chunk; len -= chunk;
  208. delay(0);
  209. }
  210. return true;
  211. }
  212. static bool eepromReadBlock(uint16_t memAddr, uint8_t* out, uint16_t len)
  213. {
  214. Wire.beginTransmission(EEPROM_7BIT);
  215. Wire.write((uint8_t)(memAddr >> 8));
  216. Wire.write((uint8_t)(memAddr & 0xFF));
  217. if (Wire.endTransmission(false) != 0) return false;
  218. uint16_t got = 0;
  219. while (got < len) {
  220. uint8_t ask = (len - got) > 32 ? 32 : (len - got);
  221. if (Wire.requestFrom((int)EEPROM_7BIT, (int)ask) != ask) return false;
  222. for (uint8_t i = 0; i < ask; i++) out[got++] = Wire.read();
  223. }
  224. return true;
  225. }
  226. static bool dspReadBlock(uint16_t memAddr, uint8_t* out, uint16_t len)
  227. {
  228. Wire.beginTransmission(DSP_7BIT);
  229. Wire.write((uint8_t)(memAddr >> 8));
  230. Wire.write((uint8_t)(memAddr & 0xFF));
  231. if (Wire.endTransmission(false) != 0) return false;
  232. uint16_t got = 0;
  233. while (got < len) {
  234. uint8_t ask = (len - got) > 32 ? 32 : (len - got);
  235. if (Wire.requestFrom((int)DSP_7BIT, (int)ask) != ask) return false;
  236. for (uint8_t i = 0; i < ask; i++) out[got++] = Wire.read();
  237. }
  238. return true;
  239. }
  240. // SigmaTCP read response: 0x0B, totalLen_hi, totalLen_lo, status, dataLen_hi, dataLen_lo, [payload]
  241. static bool sendReadResponse(WiFiClient& client, const uint8_t* data, uint16_t dataLen, bool ok)
  242. {
  243. uint16_t totalLen = 6 + dataLen;
  244. uint8_t hdr[6];
  245. hdr[0] = 0x0B;
  246. hdr[1] = (uint8_t)(totalLen >> 8);
  247. hdr[2] = (uint8_t)(totalLen & 0xFF);
  248. hdr[3] = ok ? 0x00 : 0x01;
  249. hdr[4] = (uint8_t)(dataLen >> 8);
  250. hdr[5] = (uint8_t)(dataLen & 0xFF);
  251. if (client.write(hdr, sizeof(hdr)) != sizeof(hdr)) return false;
  252. if (dataLen > 0) {
  253. if (ok && data) {
  254. if (client.write(data, dataLen) != dataLen) return false;
  255. } else {
  256. // send zeros so SigmaStudio doesn't stall on a failed read
  257. static uint8_t zeros[256];
  258. uint16_t rem = dataLen;
  259. while (rem) {
  260. uint16_t chunk = rem > sizeof(zeros) ? sizeof(zeros) : rem;
  261. if (client.write(zeros, chunk) != chunk) return false;
  262. rem -= chunk;
  263. }
  264. }
  265. }
  266. return true;
  267. }
  268. // SigmaTCP write ack: 0x09, 0x00, 0x04, status
  269. static bool sendWriteAck(WiFiClient& client, bool ok)
  270. {
  271. uint8_t ack[4] = { 0x09, 0x00, 0x04, ok ? (uint8_t)0x00 : (uint8_t)0x01 };
  272. return client.write(ack, sizeof(ack)) == sizeof(ack);
  273. }
  274. static void printHex(const char* label, const uint8_t* buf, uint16_t len, uint16_t maxPrint = 24)
  275. {
  276. Serial.print(label);
  277. uint16_t n = len < maxPrint ? len : maxPrint;
  278. for (uint16_t i = 0; i < n; i++) {
  279. if (buf[i] < 0x10) Serial.print("0");
  280. Serial.print(buf[i], HEX);
  281. Serial.print(" ");
  282. }
  283. if (len > maxPrint) Serial.print("...");
  284. Serial.println();
  285. }
  286. static void printWifiInfo()
  287. {
  288. Serial.println();
  289. Serial.println("WiFi connected.");
  290. Serial.print("WiFi IP: "); Serial.println(WiFi.localIP());
  291. Serial.print("MAC: "); Serial.println(WiFi.macAddress());
  292. Serial.println("Modulos AudioDSP");
  293. Serial.println(version);
  294. lcd.setCursor(4, 3);
  295. lcd.print(WiFi.localIP());
  296. }
  297. //=============================================================
  298. // HTTP EEPROM uploader state
  299. //=============================================================
  300. static volatile bool uploadActive = false;
  301. static volatile bool uploadVerify = false;
  302. static volatile bool uploadFailed = false;
  303. static volatile uint32_t uploadBytes = 0;
  304. static volatile uint32_t uploadCrc = 0;
  305. //=============================================================
  306. // HTTP handlers
  307. //=============================================================
  308. static String contentTypeFor(const String& path) {
  309. if (path.endsWith(".html")) return "text/html";
  310. if (path.endsWith(".css")) return "text/css";
  311. if (path.endsWith(".js")) return "application/javascript";
  312. if (path.endsWith(".png")) return "image/png";
  313. if (path.endsWith(".jpg") || path.endsWith(".jpeg")) return "image/jpeg";
  314. if (path.endsWith(".webp")) return "image/webp";
  315. if (path.endsWith(".svg")) return "image/svg+xml";
  316. if (path.endsWith(".ico")) return "image/x-icon";
  317. if (path.endsWith(".woff")) return "font/woff";
  318. if (path.endsWith(".woff2"))return "font/woff2";
  319. return "application/octet-stream";
  320. }
  321. static bool streamFromFS(String path) {
  322. if (!LittleFS.exists(path)) {
  323. if (path.startsWith("/")) {
  324. String alt = path.substring(1);
  325. if (LittleFS.exists(alt)) path = alt; else return false;
  326. } else {
  327. String alt = "/" + path;
  328. if (LittleFS.exists(alt)) path = alt; else return false;
  329. }
  330. }
  331. File f = LittleFS.open(path, "r");
  332. if (!f) return false;
  333. httpServer.streamFile(f, contentTypeFor(path));
  334. f.close();
  335. return true;
  336. }
  337. static void handleRoot() {
  338. String html = FPSTR(INDEX_HTML);
  339. html.replace("{{IP}}", WiFi.localIP().toString());
  340. httpServer.send(200, "text/html; charset=utf-8", html);
  341. }
  342. static void handleStatus() {
  343. String s;
  344. s += "uploadActive="; s += (uploadActive ? "1" : "0"); s += "\n";
  345. s += "uploadFailed="; s += (uploadFailed ? "1" : "0"); s += "\n";
  346. s += "uploadBytes="; s += String((uint32_t)uploadBytes); s += "\n";
  347. s += "uploadCRC32=0x"; s += String((uint32_t)uploadCrc, HEX); s += "\n";
  348. httpServer.send(200, "text/plain", s);
  349. }
  350. static void handleUploadDone() {
  351. if (uploadFailed) {
  352. httpServer.send(500, "text/plain", "Upload failed.\nCheck Serial log.\n");
  353. return;
  354. }
  355. String msg = "OK\nBytes written: " + String((uint32_t)uploadBytes) +
  356. "\nCRC32: 0x" + String((uint32_t)uploadCrc, HEX) + "\n";
  357. httpServer.send(200, "text/plain", msg);
  358. }
  359. static void handleUploadStream() {
  360. HTTPUpload& up = httpServer.upload();
  361. if (up.status == UPLOAD_FILE_START) {
  362. uploadActive = true; uploadFailed = false; uploadBytes = 0; uploadCrc = 0;
  363. uploadVerify = httpServer.hasArg("verify");
  364. Serial.println(); Serial.print("HTTP upload start: "); Serial.println(up.filename);
  365. Serial.print("Verify: "); Serial.println(uploadVerify ? "yes" : "no");
  366. Wire.beginTransmission(EEPROM_7BIT);
  367. Serial.print("EEPROM probe err: "); Serial.println(Wire.endTransmission());
  368. }
  369. else if (up.status == UPLOAD_FILE_WRITE) {
  370. if (uploadFailed) return;
  371. if ((uploadBytes + up.currentSize) > EEPROM_SIZE_BYTES) {
  372. Serial.println("Upload too large for 24C256"); uploadFailed = true; return;
  373. }
  374. if (!eepromWriteBlock((uint16_t)uploadBytes, up.buf, (uint16_t)up.currentSize)) {
  375. Serial.println("EEPROM write failed"); uploadFailed = true; return;
  376. }
  377. uploadCrc = crc32_update(uploadCrc, up.buf, up.currentSize);
  378. uploadBytes += up.currentSize;
  379. ledMagenta();
  380. }
  381. else if (up.status == UPLOAD_FILE_END) {
  382. Serial.print("HTTP upload end, bytes="); Serial.println((uint32_t)uploadBytes);
  383. if (uploadVerify && !uploadFailed) {
  384. Serial.println("Verify start (CRC32)...");
  385. uint32_t crc = 0;
  386. static uint8_t tmp[256];
  387. uint32_t remaining = uploadBytes; uint16_t addr = 0;
  388. while (remaining) {
  389. uint16_t n = remaining > sizeof(tmp) ? sizeof(tmp) : (uint16_t)remaining;
  390. if (!eepromReadBlock(addr, tmp, n)) { Serial.println("EEPROM read failed"); uploadFailed = true; break; }
  391. crc = crc32_update(crc, tmp, n);
  392. addr += n; remaining -= n; delay(0);
  393. }
  394. Serial.print("Verify CRC32: 0x"); Serial.println(crc, HEX);
  395. if (!uploadFailed && crc != uploadCrc) { Serial.println("CRC mismatch"); uploadFailed = true; }
  396. }
  397. uploadActive = false; ledOff();
  398. Serial.println(uploadFailed ? "HTTP upload result: FAIL" : "HTTP upload result: OK");
  399. }
  400. else if (up.status == UPLOAD_FILE_ABORTED) {
  401. Serial.println("HTTP upload aborted"); uploadActive = false; uploadFailed = true; ledOff();
  402. }
  403. }
  404. //=============================================================
  405. // Setup
  406. //=============================================================
  407. void setup() {
  408. Wire.begin(I2C_SDA, I2C_SCL);
  409. Wire.setClock(400000);
  410. statusLed.begin();
  411. statusLed.setBrightness(NEOPIXEL_BRIGHT);
  412. statusLed.show();
  413. lcd.begin(20, 4); lcd.display(); lcd.backlight();
  414. lcd.setCursor(2, 0); lcd.print("Modulos AudioDSP"); delay(1000);
  415. lcd.setCursor(5, 1); lcd.print("Booting..."); delay(1000);
  416. Serial.begin(115200); delay(1500);
  417. Serial.println(); Serial.println("Booting...");
  418. Serial.printf("Reset reason: %d\n", (int)esp_reset_reason());
  419. WiFi.mode(WIFI_STA);
  420. WiFi.begin(ssid, password);
  421. while (WiFi.waitForConnectResult() != WL_CONNECTED) {
  422. Serial.println("Connection Failed! Rebooting...");
  423. delay(5000); ESP.restart();
  424. }
  425. if (!LittleFS.begin(false)) {
  426. Serial.println("LittleFS mount failed, formatting...");
  427. if (!LittleFS.begin(true)) { Serial.println("LittleFS mount failed even after format"); return; }
  428. }
  429. Serial.println("LittleFS mounted OK");
  430. File root = LittleFS.open("/"); File f = root.openNextFile();
  431. while (f) { Serial.print("LittleFS: "); Serial.println(f.name()); f = root.openNextFile(); }
  432. lcd.setCursor(3, 2); lcd.print("File System OK"); delay(1000);
  433. tcpServer.begin();
  434. httpServer.on("/", HTTP_GET, handleRoot);
  435. httpServer.on("/status", HTTP_GET, handleStatus);
  436. httpServer.on("/upload", HTTP_POST, handleUploadDone, handleUploadStream);
  437. httpServer.onNotFound([]() {
  438. String uri = httpServer.uri();
  439. if (streamFromFS(uri)) return;
  440. Serial.print("HTTP 404: "); Serial.println(uri);
  441. httpServer.send(404, "text/plain", "Not found: " + uri);
  442. });
  443. httpServer.begin();
  444. lcd.setCursor(4, 3); lcd.print("System Ready"); delay(1000);
  445. lcd.clear();
  446. lcd.setCursor(2, 0); lcd.print("Modulos AudioDSP");
  447. lcd.setCursor(3, 1); lcd.print(version); delay(500);
  448. printWifiInfo();
  449. Serial.print("HTTP uploader: http://"); Serial.print(WiFi.localIP()); Serial.println("/");
  450. }
  451. //=============================================================
  452. // TCP bridge
  453. //=============================================================
  454. //=============================================================
  455. // TCP bridge
  456. //=============================================================
  457. static void handleTcpBridgeClient(WiFiClient& client)
  458. {
  459. Serial.println("TCP new connection");
  460. DSPWriter::resetSafeload();
  461. int writeIndex = 0; // next free slot in dataBuffer
  462. int readIndex = 0; // start of current unprocessed command
  463. int receivedByteCount = 0; // total bytes written into dataBuffer
  464. int currentState = STATE_START;
  465. while (client.connected()) {
  466. httpServer.handleClient();
  467. delay(0);
  468. // ------------------------------------------------------------------
  469. // STEP 1: Always drain the TCP stack into dataBuffer.
  470. // Do this unconditionally every loop iteration — this is what keeps
  471. // the TCP receive window open. If we only drain when we feel like it,
  472. // the window goes to zero and SigmaStudio stops sending (ZeroWindow).
  473. // ------------------------------------------------------------------
  474. while (client.available()) {
  475. if (writeIndex >= (int)sizeof(dataBuffer)) {
  476. Serial.println("TCP RX overflow");
  477. ledErrorFlash(); client.stop(); return;
  478. }
  479. int b = client.read();
  480. if (b < 0) break;
  481. dataBuffer[writeIndex++] = (uint8_t)b;
  482. receivedByteCount++;
  483. }
  484. // ------------------------------------------------------------------
  485. // STEP 2: Process whatever is in the buffer.
  486. // This is driven purely by buffer contents, not by client.available().
  487. // We loop here processing commands until we run out of buffered data.
  488. // ------------------------------------------------------------------
  489. bool processedSomething = true;
  490. while (processedSomething && client.connected()) {
  491. processedSomething = false;
  492. // --- STATE_START: identify opcode ---
  493. if (currentState == STATE_START) {
  494. if (receivedByteCount <= readIndex) {
  495. // Buffer empty — reset for next command
  496. writeIndex = readIndex = receivedByteCount = 0;
  497. ledOff();
  498. break; // nothing to process, go back to receive loop
  499. }
  500. printHex("TCP RX: ", &dataBuffer[readIndex], (uint16_t)(receivedByteCount - readIndex));
  501. uint8_t op = dataBuffer[readIndex];
  502. if (op == CMD_WRITE) { currentState = STATE_WRITE_CMD; processedSomething = true; }
  503. else if (op == CMD_READ) { currentState = STATE_READ_CMD; processedSomething = true; }
  504. else {
  505. Serial.printf("TCP invalid opcode: 0x%02X\n", op);
  506. ledErrorFlash();
  507. client.stop(); return;
  508. }
  509. }
  510. // --- STATE_WRITE_CMD ---
  511. if (currentState == STATE_WRITE_CMD) {
  512. // Need full header first
  513. if (receivedByteCount < (readIndex + WRITE_HDR_LEN)) break;
  514. writeHeader.safeload = dataBuffer[readIndex + 1];
  515. writeHeader.placement = dataBuffer[readIndex + 2];
  516. writeHeader.totalLen = (uint16_t)((dataBuffer[readIndex + 3] << 8) | dataBuffer[readIndex + 4]);
  517. writeHeader.chipAddr = dataBuffer[readIndex + 5];
  518. writeHeader.dataLen = (uint16_t)((dataBuffer[readIndex + 6] << 8) | dataBuffer[readIndex + 7]);
  519. writeHeader.address = (uint16_t)((dataBuffer[readIndex + 8] << 8) | dataBuffer[readIndex + 9]);
  520. // Need full payload — if not here yet, break back to receive loop
  521. if (receivedByteCount < (readIndex + WRITE_HDR_LEN + (int)writeHeader.dataLen)) {
  522. Serial.printf("TCP WRITE buffering: have %d need %d bytes\n",
  523. receivedByteCount - readIndex,
  524. WRITE_HDR_LEN + (int)writeHeader.dataLen);
  525. break;
  526. }
  527. readIndex += WRITE_HDR_LEN;
  528. uint8_t target7 = chipAddrTo7bit(writeHeader.chipAddr);
  529. if (target7 == EEPROM_7BIT) {
  530. ledYellow();
  531. bool ok = eepromWriteBlock(writeHeader.address, &dataBuffer[readIndex], writeHeader.dataLen);
  532. readIndex += writeHeader.dataLen;
  533. sendWriteAck(client, ok);
  534. if (!ok) { Serial.println("TCP EEPROM write failed"); ledErrorFlash(); }
  535. else ledOff();
  536. currentState = STATE_START;
  537. processedSomething = true;
  538. continue;
  539. }
  540. if (target7 != DSP_7BIT) {
  541. Serial.printf("TCP unknown chipAddr: 0x%02X\n", writeHeader.chipAddr);
  542. ledErrorFlash(); client.stop(); return;
  543. }
  544. ledGreen();
  545. uint8_t registerSize = registerSizeForAddress(writeHeader.address, writeHeader.dataLen);
  546. uint16_t regAddress = writeHeader.address;
  547. Serial.printf("TCP WRITE addr=0x%04X len=%u regSz=%u safeload=%u\n",
  548. writeHeader.address, writeHeader.dataLen, registerSize, writeHeader.safeload);
  549. if (writeHeader.safeload == 1) {
  550. if (writeHeader.dataLen % 4 != 0) {
  551. Serial.printf("TCP safeload dataLen %u not multiple of 4\n", writeHeader.dataLen);
  552. ledErrorFlash(); client.stop(); return;
  553. }
  554. int writeCount = writeHeader.dataLen / 4;
  555. int slri = readIndex;
  556. DSPWriter dspWriter;
  557. while (writeCount > 0) {
  558. uint8_t da[5] = { 0x00, dataBuffer[slri], dataBuffer[slri+1],
  559. dataBuffer[slri+2], dataBuffer[slri+3] };
  560. dspWriter.safeload_writeRegister(regAddress, da, writeCount == 1);
  561. regAddress++; slri += 4; writeCount--; delay(0);
  562. }
  563. } else {
  564. DSPWriter::writeRegisterBlock(regAddress, writeHeader.dataLen,
  565. &dataBuffer[readIndex], registerSize);
  566. }
  567. readIndex += writeHeader.dataLen;
  568. sendWriteAck(client, true);
  569. ledOff();
  570. currentState = STATE_START;
  571. processedSomething = true;
  572. continue;
  573. }
  574. // --- STATE_READ_CMD ---
  575. if (currentState == STATE_READ_CMD) {
  576. if (receivedByteCount < (readIndex + READ_HDR_LEN)) break;
  577. readHeader.totalLen = (uint16_t)((dataBuffer[readIndex + 1] << 8) | dataBuffer[readIndex + 2]);
  578. readHeader.chipAddr = dataBuffer[readIndex + 3];
  579. readHeader.dataLen = (uint16_t)((dataBuffer[readIndex + 4] << 8) | dataBuffer[readIndex + 5]);
  580. readHeader.address = (uint16_t)((dataBuffer[readIndex + 6] << 8) | dataBuffer[readIndex + 7]);
  581. readIndex += READ_HDR_LEN;
  582. uint8_t target7 = chipAddrTo7bit(readHeader.chipAddr);
  583. Serial.printf("TCP READ chip=0x%02X addr=0x%04X len=%u\n",
  584. readHeader.chipAddr, readHeader.address, readHeader.dataLen);
  585. if (readHeader.dataLen > 4096) { readHeader.dataLen = 4096; }
  586. static uint8_t readOut[4096];
  587. bool ok = false;
  588. ledBlue();
  589. if (target7 == EEPROM_7BIT) ok = eepromReadBlock(readHeader.address, readOut, readHeader.dataLen);
  590. else if (target7 == DSP_7BIT) ok = dspReadBlock (readHeader.address, readOut, readHeader.dataLen);
  591. else {
  592. Serial.printf("TCP unknown chipAddr (READ): 0x%02X\n", readHeader.chipAddr);
  593. }
  594. if (!ok) Serial.println("TCP READ I2C failed - sending zeros");
  595. if (!sendReadResponse(client, ok ? readOut : nullptr, readHeader.dataLen, ok)) {
  596. Serial.println("TCP READ send failed"); ledErrorFlash(); client.stop(); return;
  597. }
  598. ledOff();
  599. currentState = STATE_START;
  600. processedSomething = true;
  601. continue;
  602. }
  603. } // end process loop
  604. // No idle timeout — stay connected until SigmaStudio disconnects.
  605. // client.connected() will return false when the TCP connection drops.
  606. if (currentState == STATE_START && receivedByteCount == readIndex) {
  607. if (!client.available()) {
  608. httpServer.handleClient();
  609. delay(10);
  610. }
  611. }
  612. } // end main while loop
  613. client.stop();
  614. Serial.println("TCP disconnected");
  615. ledOff();
  616. }
  617. //=============================================================
  618. // Loop
  619. //=============================================================
  620. void loop() {
  621. httpServer.handleClient();
  622. if (uploadActive) { delay(1); return; }
  623. WiFiClient client = tcpServer.available();
  624. if (client) handleTcpBridgeClient(client);
  625. delay(1);
  626. }