Răsfoiți Sursa

ADC readback status endpoint

Benjamin Harris 1 lună în urmă
părinte
comite
289f9cf373
4 a modificat fișierele cu 127 adăugiri și 3 ștergeri
  1. 23 1
      CLAUDE.md
  2. 35 1
      ModulosDSP_101.ino
  3. 11 0
      README.md
  4. 58 1
      ota_html.h

+ 23 - 1
CLAUDE.md

@@ -18,7 +18,7 @@ Two servers run in parallel — one TCP, one HTTP:
 - `tcpServer` (port 8086): SigmaTCP protocol. `handleTcpBridgeClient()` blocks `loop()` for the duration of a session (single client at a time).
 - `httpServer` (port 80): EEPROM upload (`/`), OTA firmware update (`/ota`), and status endpoints. `httpServer.handleClient()` is called inside the TCP client loop so HTTP stays responsive during a TCP session.
 
-`loop()` only accepts a new TCP client after the previous one disconnects. There is no concurrency and no idle timeout — the connection stays open until SigmaStudio closes it.
+`loop()` only accepts a new TCP client after the previous one disconnects. There is no concurrency. Sessions time out after `TCP_IDLE_TIMEOUT_MS` (30 s) of inactivity — `lastActivityMs` is reset on every received byte and checked in the idle path between commands.
 
 `maintainWifi()` is called at the top of every `loop()` iteration and checks WiFi status every 5 seconds, attempting reconnect if the link has dropped.
 
@@ -134,6 +134,28 @@ When the buffer is fully consumed, all three index variables reset to 0 to preve
 - Max I²C bytes per transaction: 28 (Wire buffer limit minus the 2-byte address overhead).
 - After each chunk, `i2cAckPoll()` polls for ACK up to 120 ms — this is the EEPROM's internal write cycle; do not remove this delay.
 
+## DSP Soft Reset — `POST /dsp_reset`
+
+`handleDspReset()` writes `{0x00, 0x00}` (stop) to the Core Register, waits 100 ms, then writes `{0x00, 0x01}` (run). Parameter and program RAM are preserved — the DSP restarts execution without reloading from EEPROM. Returns 200 `"DSP soft reset complete."` on success.
+
+## DSP Status — `GET /dsp_status`
+
+`handleDspStatus()` reads three register regions via I²C and returns a JSON object:
+
+```json
+{"ok":true,"running":true,"coreReg":"0x0001","gpio":"0x00","adc":["0x00","0x00","0x00","0x00"]}
+```
+
+| Field | Source | Notes |
+| ----- | ------ | ----- |
+| `ok` | AND of all three read results | `false` if any I²C read failed |
+| `running` | Core Register bit 0 | `true` when DSP is executing |
+| `coreReg` | 0x081C (2 bytes) | Full register value as hex string |
+| `gpio` | 0x0808 (1 byte) | GPIO All Register |
+| `adc[0–3]` | 0x0809–0x080C (4-byte burst) | ADC input values |
+
+HTTP status is 200 on success, 500 if any I²C read failed. The `/ota` page polls this endpoint every 2 seconds via XHR (`pollStatus()`) and displays a live status card at the top of the page.
+
 ## OTA Firmware Update
 
 Routes: `GET /ota` → `handleOtaPage()`, `POST /ota_do` → `handleOtaDone()` + `handleOtaStream()`.

+ 35 - 1
ModulosDSP_101.ino

@@ -464,6 +464,39 @@ static void handleDspReset() {
   httpServer.send(200, "text/plain", "DSP soft reset complete.");
 }
 
+//=============================================================
+// HTTP DSP status handler  GET /dsp_status
+//=============================================================
+static void handleDspStatus() {
+  // Core Register (0x081C) — 2 bytes, bit 0 = Run
+  uint8_t coreRaw[2] = {0, 0};
+  bool coreOk = dspReadBlock(dspRegister::CoreRegister, coreRaw, 2);
+  uint16_t coreVal = ((uint16_t)coreRaw[0] << 8) | coreRaw[1];
+
+  // GPIO All Register (0x0808) — 1 byte
+  uint8_t gpio = 0;
+  bool gpioOk = dspReadBlock(dspRegister::GpioAllRegister, &gpio, 1);
+
+  // ADC0–3 (0x0809–0x080C) — 1 byte each, read as a 4-byte burst
+  uint8_t adc[4] = {0, 0, 0, 0};
+  bool adcOk = dspReadBlock(dspRegister::Adc0, adc, 4);
+
+  bool allOk = coreOk && gpioOk && adcOk;
+
+  char buf[200];
+  snprintf(buf, sizeof(buf),
+    "{\"ok\":%s,\"running\":%s,"
+    "\"coreReg\":\"0x%04X\","
+    "\"gpio\":\"0x%02X\","
+    "\"adc\":[\"0x%02X\",\"0x%02X\",\"0x%02X\",\"0x%02X\"]}",
+    allOk  ? "true" : "false",
+    (coreOk && (coreVal & 0x01)) ? "true" : "false",
+    coreVal, gpio,
+    adc[0], adc[1], adc[2], adc[3]);
+
+  httpServer.send(allOk ? 200 : 500, "application/json", buf);
+}
+
 //=============================================================
 // HTTP OTA handlers
 //=============================================================
@@ -576,7 +609,8 @@ void setup() {
   httpServer.on("/upload", HTTP_POST, handleUploadDone, handleUploadStream);
   httpServer.on("/ota", HTTP_GET, handleOtaPage);
   httpServer.on("/ota_do", HTTP_POST, handleOtaDone, handleOtaStream);
-  httpServer.on("/dsp_reset", HTTP_POST, handleDspReset);
+  httpServer.on("/dsp_reset",  HTTP_POST, handleDspReset);
+  httpServer.on("/dsp_status", HTTP_GET,  handleDspStatus);
   httpServer.onNotFound([]() {
     String uri = httpServer.uri();
     if (streamFromFS(uri)) return;

+ 11 - 0
README.md

@@ -109,6 +109,17 @@ The 24C256 holds **32 KB**. The DSP reads this EEPROM at power-up via its SELFBO
 
 Navigate to `http://modulos-dsp.local/ota` in any browser.
 
+**Live DSP Status:**
+
+The top of the page shows a live status card that polls `GET /dsp_status` every 2 seconds:
+
+| Field | Register | Description |
+| ----- | -------- | ----------- |
+| Core Register | 0x081C | Full 16-bit register value (hex) |
+| Running | Core Register bit 0 | Green badge when DSP is executing |
+| GPIO | 0x0808 | GPIO All Register value (hex) |
+| ADC 0–3 | 0x0809–0x080C | Raw ADC input values (hex) |
+
 **Firmware Update (OTA):**
 
 1. In Arduino IDE, export the compiled binary: **Sketch → Export Compiled Binary** (produces a `.bin` file).

+ 58 - 1
ota_html.h

@@ -19,13 +19,34 @@ static const char OTA_HTML[] PROGMEM = R"HTML(
     #progressWrap { display: none; margin-top: 1.2rem; }
     #resultBox    { display: none; margin-top: 1rem; }
     #resetResult  { display: none; margin-top: 0.6rem; }
+    .reg-row td   { font-family: monospace; font-size: 0.85rem; padding: 2px 8px; }
+    .reg-row td:first-child { color: #6c757d; width: 120px; }
   </style>
 </head>
 <body>
 <div class="container ota-card">
 
   <h4 class="mb-1"><i class="fa fa-microchip"></i> Device Management</h4>
-  <p class="text-muted small mb-4">Device: <strong>{{IP}}</strong></p>
+  <p class="text-muted small mb-2">Device: <strong>{{IP}}</strong></p>
+
+  <!-- DSP live status card -->
+  <div class="card mb-4">
+    <div class="card-header d-flex justify-content-between align-items-center py-2">
+      <span class="small fw-semibold"><i class="fa fa-tachometer"></i> DSP Status</span>
+      <span id="dspBadge" class="badge bg-secondary">Polling&hellip;</span>
+    </div>
+    <div class="card-body py-2 px-3">
+      <table class="w-100">
+        <tr class="reg-row"><td>Core Register</td><td id="sCoreReg">&mdash;</td></tr>
+        <tr class="reg-row"><td>Running</td>      <td id="sRunning">&mdash;</td></tr>
+        <tr class="reg-row"><td>GPIO</td>         <td id="sGpio">&mdash;</td></tr>
+        <tr class="reg-row"><td>ADC 0&ndash;3</td><td id="sAdc">&mdash;</td></tr>
+      </table>
+      <p class="text-muted mb-0 mt-1" style="font-size:0.72rem">
+        Updated <span id="sUpdated">&mdash;</span> &bull; auto-refreshes every 2 s
+      </p>
+    </div>
+  </div>
 
   <form id="otaForm" action="/ota_do" method="POST" enctype="multipart/form-data">
     <div class="mb-3">
@@ -69,6 +90,42 @@ static const char OTA_HTML[] PROGMEM = R"HTML(
 </div>
 
 <script>
+function pollStatus() {
+  var xhr = new XMLHttpRequest();
+  xhr.open('GET', '/dsp_status', true);
+  xhr.timeout = 1800;
+  xhr.onload = function() {
+    var badge = document.getElementById('dspBadge');
+    if (xhr.status !== 200) {
+      badge.className = 'badge bg-danger'; badge.textContent = 'I2C Error';
+      return;
+    }
+    try {
+      var d = JSON.parse(xhr.responseText);
+      if (d.running) {
+        badge.className = 'badge bg-success'; badge.textContent = 'Running';
+      } else {
+        badge.className = 'badge bg-danger';  badge.textContent = 'Stopped';
+      }
+      document.getElementById('sCoreReg').textContent = d.coreReg;
+      document.getElementById('sRunning').textContent = d.running ? 'Yes' : 'No';
+      document.getElementById('sGpio').textContent    = d.gpio;
+      document.getElementById('sAdc').textContent     = d.adc.join('  ');
+      document.getElementById('sUpdated').textContent =
+        new Date().toLocaleTimeString();
+    } catch(e) {
+      badge.className = 'badge bg-warning'; badge.textContent = 'Parse error';
+    }
+  };
+  xhr.ontimeout = xhr.onerror = function() {
+    var badge = document.getElementById('dspBadge');
+    badge.className = 'badge bg-secondary'; badge.textContent = 'Offline';
+  };
+  xhr.send();
+}
+pollStatus();
+setInterval(pollStatus, 2000);
+
 function dspReset() {
   var btn = document.getElementById('resetBtn');
   var box = document.getElementById('resetResult');