diff --git a/analog_system_monitor_arduino/analog_system_monitor_arduino.ino b/analog_system_monitor_arduino/analog_system_monitor_arduino.ino index 0bf9ceb..507af32 100644 --- a/analog_system_monitor_arduino/analog_system_monitor_arduino.ino +++ b/analog_system_monitor_arduino/analog_system_monitor_arduino.ino @@ -1,309 +1,80 @@ -// ------------------------------------------------------ -// ESP32 8‑Channel PWM + WiFiManager + OSC (messages + bundles) -// + Slew + Multi‑Point Calibration + OSC Packet Queue + Watchdog Sweep -// ------------------------------------------------------ - #include -#include -#include -#include -#include -#include - -// ------------------------------- -// User Configuration -// ------------------------------- +#include const uint8_t NUM_CHANNELS = 8; - -uint8_t pwmPins[NUM_CHANNELS] = {26, 25, 33, 32, 27, 25, 22, 21}; +uint8_t pwmPins[NUM_CHANNELS] = {14, 15, 26, 27, 8, 7, 6, 5}; const uint32_t pwmFrequency = 10000; -const uint8_t pwmResolutionBits = 10; -const uint32_t pwmMax = (1 << pwmResolutionBits) - 1; - -// Slew rate: time to change 1% duty (ms) -unsigned long slewPerPercent = 50; - -// OSC UDP port -const uint16_t oscPort = 9000; - -// Per‑channel OSC addresses (configurable) -const char* oscAddresses[NUM_CHANNELS] = { - "/cpu", - "/cputemp", - "/memory", - "/gpu3d", - "/gputemp", - "/vram", - "/netup", - "/netdown" -}; // ------------------------------- -// Multi‑point Calibration Tables +// Calibration tables // ------------------------------- float logicalPoints[5] = {0, 25, 50, 75, 100}; float calibratedPoints[NUM_CHANNELS][5] = { - {0, 25, 50, 75, 99}, // CH0 - {0, 24, 49, 74, 98}, // CH1 - {0, 26, 51, 76, 99}, // CH2 - {0, 25, 50, 75, 97}, // CH3 - {0, 25, 50, 75, 99}, // CH4 - {0, 24, 50, 74, 98}, // CH5 - {0, 25, 49, 75, 97}, // CH6 - {0, 26, 50, 76, 99} // CH7 + {0, 25, 50, 75, 99}, + {0, 24, 49, 74, 98}, + {0, 26, 51, 76, 99}, + {0, 25, 50, 75, 97}, + {0, 25, 50, 75, 99}, + {0, 24, 50, 74, 98}, + {0, 25, 49, 75, 97}, + {0, 26, 50, 76, 99} }; // ------------------------------- -// Internal Variables +// Duty tracking // ------------------------------- -float currentDuty[NUM_CHANNELS] = {0}; // logical 0–100 -float targetDuty[NUM_CHANNELS] = {0}; // logical 0–100 +float currentDuty[NUM_CHANNELS] = {0.0f}; -unsigned long lastSlewUpdate = 0; - -WiFiUDP Udp; +RP2040_PWM* pwm[NUM_CHANNELS]; // ------------------------------- -// OSC Packet Queue +// Watchdog // ------------------------------- -#define OSC_QUEUE_SIZE 16 -#define OSC_MAX_PACKET 512 - -struct OscPacket { - int len; - uint8_t data[OSC_MAX_PACKET]; -}; - -OscPacket oscQueue[OSC_QUEUE_SIZE]; -volatile int oscHead = 0; -volatile int oscTail = 0; - -void enqueueOscPacket() { - int packetSize = Udp.parsePacket(); - if (packetSize <= 0) return; - - int nextHead = (oscHead + 1) % OSC_QUEUE_SIZE; - if (nextHead == oscTail) { - // Queue full → drop packet - return; - } - - OscPacket &pkt = oscQueue[oscHead]; - pkt.len = Udp.read(pkt.data, OSC_MAX_PACKET); - oscHead = nextHead; -} - -bool dequeueOscPacket(OscPacket &pkt) { - if (oscTail == oscHead) return false; - pkt = oscQueue[oscTail]; - oscTail = (oscTail + 1) % OSC_QUEUE_SIZE; - return true; -} +unsigned long lastSerialTime = 0; +const unsigned long watchdogTimeout = 5000; // 5 seconds +const float fadeStep = 0.001f; // 0.001% per ms +unsigned long lastFadeTime = 0; // ------------------------------- -// Watchdog + Sweep (smooth using slewPerPercent) -// ------------------------------- - -unsigned long lastOscTime = 0; -unsigned long watchdogTimeoutMs = 5000; // configurable -unsigned long lastSweepStep = 0; - -int sweepValue = 0; -int sweepDirection = 1; // +1 or -1 -bool inSweepMode = false; - -void updateWatchdogAndSweep() { - unsigned long now = millis(); - - // Enter sweep mode if no OSC for watchdogTimeoutMs - if (!inSweepMode && (now - lastOscTime > watchdogTimeoutMs)) { - inSweepMode = true; - Serial.println("Watchdog: No OSC, entering sweep mode."); - sweepValue = 0; - sweepDirection = 1; - lastSweepStep = now; - } - - // Exit sweep mode immediately when OSC resumes - if (inSweepMode && (now - lastOscTime <= watchdogTimeoutMs)) { - inSweepMode = false; - Serial.println("Watchdog: OSC resumed, exiting sweep mode."); - } - - // Smooth sweep using the same slew timing as boot-up - if (inSweepMode && (now - lastSweepStep >= slewPerPercent)) { - lastSweepStep = now; - - sweepValue += sweepDirection; - - if (sweepValue >= 100) { - sweepValue = 100; - sweepDirection = -1; - } else if (sweepValue <= 0) { - sweepValue = 0; - sweepDirection = 1; - } - - // Set all channels to the sweep target - for (int ch = 0; ch < NUM_CHANNELS; ch++) { - targetDuty[ch] = sweepValue; - } - } -} - -// ------------------------------- -// Multi‑point calibration function +// Calibration interpolation // ------------------------------- float applyCalibration(uint8_t ch, float logicalDuty) { - if (logicalDuty <= 0) return calibratedPoints[ch][0]; - if (logicalDuty >= 100) return calibratedPoints[ch][4]; + if (logicalDuty <= 0.0f) return calibratedPoints[ch][0]; + if (logicalDuty >= 100.0f) return calibratedPoints[ch][4]; for (int i = 0; i < 4; i++) { if (logicalDuty >= logicalPoints[i] && logicalDuty <= logicalPoints[i+1]) { - float x0 = logicalPoints[i]; float x1 = logicalPoints[i+1]; float y0 = calibratedPoints[ch][i]; float y1 = calibratedPoints[ch][i+1]; - float t = (logicalDuty - x0) / (x1 - x0); - - return y0 + t * (y1 - y0); // linear interpolation + return y0 + t * (y1 - y0); } } - return calibratedPoints[ch][4]; } -// ------------------------------- -// OSC routing (single message) -// ------------------------------- - -void routeOscMessage(OSCMessage &msg) { - // Any valid OSC message resets watchdog and exits sweep mode - lastOscTime = millis(); - - for (uint8_t ch = 0; ch < NUM_CHANNELS; ch++) { - if (msg.fullMatch(oscAddresses[ch])) { - - float v = 0.0f; - - if (msg.isFloat(0)) { - v = msg.getFloat(0); - } else if (msg.isInt(0)) { - v = (float)msg.getInt(0); - } else { - return; - } - - if (v < 0.0f) v = 0.0f; - if (v > 1.0f) v = 1.0f; - - targetDuty[ch] = v * 100.0f; - - Serial.print("OSC CH"); - Serial.print(ch); - Serial.print(" -> "); - Serial.println(targetDuty[ch]); - } - } -} - -// ------------------------------- -// Process OSC queue (messages + bundles) -// ------------------------------- - -void processOscQueue() { - OscPacket pkt; - - while (dequeueOscPacket(pkt)) { - - OSCBundle bundle; - OSCMessage msg; - - for (int i = 0; i < pkt.len; i++) { - bundle.fill(pkt.data[i]); - msg.fill(pkt.data[i]); - } - - if (!bundle.hasError()) { - for (int i = 0; i < bundle.size(); i++) { - OSCMessage *m = bundle.getOSCMessage(i); - if (m) routeOscMessage(*m); - } - } - else if (!msg.hasError()) { - routeOscMessage(msg); - } - } -} - // ------------------------------- // Setup // ------------------------------- void setup() { Serial.begin(115200); - delay(500); - - Serial.println("ESP32 8‑channel PWM + WiFiManager + OSC + Queue + Watchdog starting..."); - - WiFiManager wifiManager; - wifiManager.setHostname("ESP32-PWM-OSC"); - if (!wifiManager.autoConnect("ESP32-PWM-OSC")) { - Serial.println("Failed to connect, restarting..."); - delay(3000); - ESP.restart(); - } - - Serial.print("Connected. IP: "); - Serial.println(WiFi.localIP()); - - Udp.begin(oscPort); - Serial.print("Listening for OSC on port "); - Serial.println(oscPort); + delay(300); for (int ch = 0; ch < NUM_CHANNELS; ch++) { - ledcAttach(pwmPins[ch], pwmFrequency, pwmResolutionBits); - ledcWrite(pwmPins[ch], 0); + pwm[ch] = new RP2040_PWM(pwmPins[ch], pwmFrequency, 0.0f); + if (pwm[ch]) pwm[ch]->setPWM(); } - delay(100); - - // BOOT-UP SWEEP (all channels together, using slewPerPercent) - for (int d = 0; d <= 100; d++) { - for (int ch = 0; ch < NUM_CHANNELS; ch++) { - float calibratedDuty = applyCalibration(ch, d); - uint32_t pwmValue = (uint32_t)((calibratedDuty / 100.0f) * pwmMax); - ledcWrite(pwmPins[ch], pwmValue); - } - delay(slewPerPercent); - } - - for (int d = 100; d >= 0; d--) { - for (int ch = 0; ch < NUM_CHANNELS; ch++) { - float calibratedDuty = applyCalibration(ch, d); - uint32_t pwmValue = (uint32_t)((calibratedDuty / 100.0f) * pwmMax); - ledcWrite(pwmPins[ch], pwmValue); - } - delay(slewPerPercent); - } - - for (int ch = 0; ch < NUM_CHANNELS; ch++) { - currentDuty[ch] = 0; - targetDuty[ch] = 0; - ledcWrite(pwmPins[ch], 0); - } - - lastOscTime = millis(); // start in normal mode - Serial.println("Ready. OSC + Serial + Queue + Watchdog active."); + lastSerialTime = millis(); } // ------------------------------- @@ -312,59 +83,49 @@ void setup() { void loop() { - unsigned long now = millis(); - - // Grab any incoming OSC packets into the queue - enqueueOscPacket(); - - // Process queued OSC packets - processOscQueue(); - - // Watchdog + sweep mode handling - updateWatchdogAndSweep(); - - // SERIAL INPUT HANDLING (X=YY) - if (Serial.available()) { + // -------- Serial parsing -------- + while (Serial.available()) { String s = Serial.readStringUntil('\n'); s.trim(); - int eq = s.indexOf('='); if (eq > 0) { int ch = s.substring(0, eq).toInt(); - int val = s.substring(eq + 1).toInt(); + float val = s.substring(eq + 1).toFloat(); + if (ch >= 0 && ch < NUM_CHANNELS && val >= 0.0f && val <= 100.0f) { - if (ch >= 0 && ch < NUM_CHANNELS && val >= 0 && val <= 100) { - targetDuty[ch] = val; - Serial.print("SER CH"); - Serial.print(ch); - Serial.print(" -> "); - Serial.println(val); + currentDuty[ch] = val; + + float calibratedDuty = applyCalibration(ch, currentDuty[ch]); + pwm[ch]->setPWM(pwmPins[ch], pwmFrequency, calibratedDuty); + + lastSerialTime = millis(); // reset watchdog } } } - // CONSTANT-RATE SLEWING (all channels) - if (now - lastSlewUpdate >= slewPerPercent) { - lastSlewUpdate = now; + // -------- Watchdog fade-to-zero (time-based exponential) -------- + if (millis() - lastSerialTime > watchdogTimeout) { - for (int ch = 0; ch < NUM_CHANNELS; ch++) { + // Apply fade only every X ms + const unsigned long fadeInterval = 1; // adjust for slower/faster decay - if (currentDuty[ch] < targetDuty[ch]) { - currentDuty[ch] += 1.0f; - if (currentDuty[ch] > targetDuty[ch]) - currentDuty[ch] = targetDuty[ch]; + if (millis() - lastFadeTime >= fadeInterval) { + lastFadeTime = millis(); + + for (int ch = 0; ch < NUM_CHANNELS; ch++) { + + if (currentDuty[ch] > 0.0f) { + + const float fadeFactor = 0.999f; // exponential decay per interval + currentDuty[ch] *= fadeFactor; + + if (currentDuty[ch] < 0.01f) + currentDuty[ch] = 0.0f; + + float calibratedDuty = applyCalibration(ch, currentDuty[ch]); + pwm[ch]->setPWM(pwmPins[ch], pwmFrequency, calibratedDuty); + } } - else if (currentDuty[ch] > targetDuty[ch]) { - currentDuty[ch] -= 1.0f; - if (currentDuty[ch] < targetDuty[ch]) - currentDuty[ch] = targetDuty[ch]; - } - - float calibratedDuty = applyCalibration(ch, currentDuty[ch]); - uint32_t pwmValue = (uint32_t)((calibratedDuty / 100.0f) * pwmMax); - ledcWrite(pwmPins[ch], pwmValue); } } - - delay(5); }