Explorar o código

GPIO register endpoint

Benjamin Harris hai 1 mes
pai
achega
5ace8a604f
Modificáronse 3 ficheiros con 128 adicións e 8 borrados
  1. 22 8
      CLAUDE.md
  2. 34 0
      ModulosDSP_101.ino
  3. 72 0
      ota_html.h

+ 22 - 8
CLAUDE.md

@@ -52,10 +52,10 @@ The LCD is detected at boot via the return value of `lcd.begin(20, 4)` (hd44780
 
 ## I²C Addresses
 
-| Device | 8-bit | 7-bit (Wire) |
-|--------|-------|--------------|
-| ADAU1401 DSP | 0x68 | 0x34 |
-| 24C256 EEPROM | 0xA0 | 0x50 |
+| Device          | 8-bit | 7-bit (Wire) |
+| --------------- | ----- | ------------ |
+| ADAU1401 DSP    | 0x68  | 0x34         |
+| 24C256 EEPROM   | 0xA0  | 0x50         |
 
 Both devices share the bus at 400 kHz on SDA=GPIO13, SCL=GPIO12.
 
@@ -65,7 +65,7 @@ SigmaStudio sends `chipAddr` as a **chip index** (0x01 = DSP, 0x02 = EEPROM), no
 
 **WRITE (opcode 0x09) — 10-byte header:**
 
-```
+```text
 [0]   0x09       command
 [1]   safeload   1 = use safeload registers, 0 = direct write
 [2]   placement  reserved
@@ -82,7 +82,7 @@ ACK response: `{0x09, 0x00, 0x04, status}` (4 bytes). `status` reflects the actu
 
 **READ (opcode 0x0A) — 8-byte header:**
 
-```
+```text
 [0]   0x0A
 [1-2] totalLen
 [3]   chipAddr
@@ -111,7 +111,7 @@ The DSP has 5 safeload slots. Filling them and writing `0x003C` to the Core Regi
 `registerSizeForAddress()` derives the I²C bytes-per-register from the address:
 
 | Address range | Register size |
-|---------------|--------------|
+| ------------- | ------------- |
 | 0x0000–0x03FF (Parameter RAM) | 4 bytes |
 | 0x0400–0x07FF (Program RAM) | 5 bytes |
 | 0x081C (Core Register), dataLen == 2 | 2 bytes (R0 reset) |
@@ -134,6 +134,20 @@ 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.
 
+## GPIO Register — `GET /gpio` and `POST /gpio`
+
+`handleGpioGet()` reads `GpioAllRegister` (0x0808, 1 byte) and `MpCfg0/1` (0x0820–0x0821, 1 byte each) and returns JSON:
+
+```json
+{"ok":true,"gpio":0,"mpcfg0":0,"mpcfg1":0}
+```
+
+`handleGpioSet()` accepts a form-encoded `value` parameter (0–255) and writes it to `GpioAllRegister` via `DSPWriter::writeRegister`. Returns 400 if `value` is missing or out of range, 200 on success.
+
+The `/ota` page adds a row of four toggle buttons (GP0–GP3). Each click XORs the corresponding bit in the cached `gpioVal` and POSTs the new byte to `/gpio`. The page also polls `GET /gpio` every 5 seconds to stay in sync with any external state changes. Buttons render green (btn-success) when the bit is high, outline-secondary when low.
+
+Only output-configured pins respond to writes — which pins are outputs is determined by MpCfg registers (read-only in the UI, visible in the JSON response).
+
 ## 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.
@@ -177,7 +191,7 @@ ADAU1401 parameter RAM uses **5.23 fixed-point** (4 bytes per word). `DataConver
 ## Files
 
 | File | Role |
-|------|------|
+| ---- | ---- |
 | `ModulosDSP_101.ino` | Top-level: WiFi, TCP/HTTP servers, I²C helpers, CRC32, OTA |
 | `DSPWriter.h` | I²C address constants, register enums, `DSPWriter` class declaration |
 | `DSPWriter.cpp` | `writeRegister`, `writeRegisterBlock` (returns bool), safeload with flush |

+ 34 - 0
ModulosDSP_101.ino

@@ -497,6 +497,38 @@ static void handleDspStatus() {
   httpServer.send(allOk ? 200 : 500, "application/json", buf);
 }
 
+//=============================================================
+// HTTP GPIO handlers  GET /gpio  POST /gpio
+//=============================================================
+static void handleGpioGet() {
+  uint8_t gpio = 0;
+  bool gpioOk = dspReadBlock(dspRegister::GpioAllRegister, &gpio, 1);
+  uint8_t mpcfg[2] = {0, 0};
+  bool cfgOk = dspReadBlock(dspRegister::MpCfg0, mpcfg, 2);
+  bool ok = gpioOk && cfgOk;
+  char buf[100];
+  snprintf(buf, sizeof(buf),
+    "{\"ok\":%s,\"gpio\":%u,\"mpcfg0\":%u,\"mpcfg1\":%u}",
+    ok ? "true" : "false", gpio, mpcfg[0], mpcfg[1]);
+  httpServer.send(ok ? 200 : 500, "application/json", buf);
+}
+
+static void handleGpioSet() {
+  if (!httpServer.hasArg("value")) {
+    httpServer.send(400, "text/plain", "Missing 'value' parameter.");
+    return;
+  }
+  long val = httpServer.arg("value").toInt();
+  if (val < 0 || val > 255) {
+    httpServer.send(400, "text/plain", "value must be 0-255.");
+    return;
+  }
+  uint8_t b = (uint8_t)val;
+  DSPWriter::writeRegister(dspRegister::GpioAllRegister, 1, &b);
+  Serial.printf("GPIO set: 0x%02X\n", b);
+  httpServer.send(200, "text/plain", "OK");
+}
+
 //=============================================================
 // HTTP OTA handlers
 //=============================================================
@@ -611,6 +643,8 @@ void setup() {
   httpServer.on("/ota_do", HTTP_POST, handleOtaDone, handleOtaStream);
   httpServer.on("/dsp_reset",  HTTP_POST, handleDspReset);
   httpServer.on("/dsp_status", HTTP_GET,  handleDspStatus);
+  httpServer.on("/gpio",       HTTP_GET,  handleGpioGet);
+  httpServer.on("/gpio",       HTTP_POST, handleGpioSet);
   httpServer.onNotFound([]() {
     String uri = httpServer.uri();
     if (streamFromFS(uri)) return;

+ 72 - 0
ota_html.h

@@ -85,6 +85,19 @@ static const char OTA_HTML[] PROGMEM = R"HTML(
   </div>
   <div id="resetResult" class="alert alert-sm" role="alert"></div>
 
+  <hr class="mt-3">
+  <h6 class="text-muted mb-2"><i class="fa fa-toggle-on"></i> GPIO Control</h6>
+  <div class="d-flex gap-2 mb-1">
+    <button id="gp0" class="btn btn-outline-secondary btn-sm" onclick="toggleGpioPin(0)">GP0</button>
+    <button id="gp1" class="btn btn-outline-secondary btn-sm" onclick="toggleGpioPin(1)">GP1</button>
+    <button id="gp2" class="btn btn-outline-secondary btn-sm" onclick="toggleGpioPin(2)">GP2</button>
+    <button id="gp3" class="btn btn-outline-secondary btn-sm" onclick="toggleGpioPin(3)">GP3</button>
+  </div>
+  <div class="form-text mb-1">
+    Writes to GPIO All Register (0x0808). Only output-configured pins respond (set via MpCfg).
+  </div>
+  <div id="gpioResult" style="display:none; margin-top:0.4rem;" class="alert alert-sm" role="alert"></div>
+
   <hr class="mt-3">
   <a href="/" class="text-muted small"><i class="fa fa-arrow-left"></i> Back to EEPROM Uploader</a>
 </div>
@@ -153,6 +166,65 @@ function dspReset() {
   xhr.send();
 }
 
+var gpioVal = null;
+
+function updateGpioBtns(val) {
+  for (var i = 0; i < 4; i++) {
+    var btn = document.getElementById('gp' + i);
+    if (val & (1 << i)) {
+      btn.className = 'btn btn-success btn-sm';
+    } else {
+      btn.className = 'btn btn-outline-secondary btn-sm';
+    }
+  }
+}
+
+function fetchGpio() {
+  var xhr = new XMLHttpRequest();
+  xhr.open('GET', '/gpio', true);
+  xhr.timeout = 1800;
+  xhr.onload = function() {
+    if (xhr.status === 200) {
+      try {
+        var d = JSON.parse(xhr.responseText);
+        gpioVal = d.gpio;
+        updateGpioBtns(gpioVal);
+      } catch(e) {}
+    }
+  };
+  xhr.send();
+}
+
+function toggleGpioPin(pin) {
+  if (gpioVal === null) return;
+  gpioVal ^= (1 << pin);
+  updateGpioBtns(gpioVal);
+  var box = document.getElementById('gpioResult');
+  var xhr = new XMLHttpRequest();
+  xhr.open('POST', '/gpio', true);
+  xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+  xhr.onload = function() {
+    box.style.display = 'block';
+    if (xhr.status === 200) {
+      box.className = 'alert alert-success alert-sm';
+      box.innerHTML = '<i class="fa fa-check"></i> GP' + pin + ' toggled (0x' +
+                      (gpioVal & 0xFF).toString(16).padStart(2,'0').toUpperCase() + ')';
+    } else {
+      box.className = 'alert alert-danger alert-sm';
+      box.innerHTML = '<i class="fa fa-times"></i> GPIO write failed.';
+    }
+  };
+  xhr.onerror = function() {
+    box.style.display = 'block';
+    box.className = 'alert alert-danger alert-sm';
+    box.innerHTML = '<i class="fa fa-times"></i> Request failed.';
+  };
+  xhr.send('value=' + gpioVal);
+}
+
+fetchGpio();
+setInterval(fetchGpio, 5000);
+
 document.getElementById('otaForm').addEventListener('submit', function(e) {
   e.preventDefault();
   var file = document.getElementById('fwFile').files[0];