ModulosDSP_101.ino 36 KB

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