From 530d4704bc63fad7a007c06e71906f5ad1baa076 Mon Sep 17 00:00:00 2001 From: Robin Cerny Date: Fri, 16 Jan 2026 09:25:41 +0100 Subject: [PATCH] added arduino program --- .../analog_system_monitor_arduino.ino | 370 ++++++++++++++++++ 1 file changed, 370 insertions(+) create mode 100644 analog_system_monitor_arduino/analog_system_monitor_arduino.ino diff --git a/analog_system_monitor_arduino/analog_system_monitor_arduino.ino b/analog_system_monitor_arduino/analog_system_monitor_arduino.ino new file mode 100644 index 0000000..0bf9ceb --- /dev/null +++ b/analog_system_monitor_arduino/analog_system_monitor_arduino.ino @@ -0,0 +1,370 @@ +// ------------------------------------------------------ +// 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 +// ------------------------------- + +const uint8_t NUM_CHANNELS = 8; + +uint8_t pwmPins[NUM_CHANNELS] = {26, 25, 33, 32, 27, 25, 22, 21}; + +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 +// ------------------------------- + +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 +}; + +// ------------------------------- +// Internal Variables +// ------------------------------- + +float currentDuty[NUM_CHANNELS] = {0}; // logical 0–100 +float targetDuty[NUM_CHANNELS] = {0}; // logical 0–100 + +unsigned long lastSlewUpdate = 0; + +WiFiUDP Udp; + +// ------------------------------- +// OSC Packet Queue +// ------------------------------- + +#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; +} + +// ------------------------------- +// 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 +// ------------------------------- + +float applyCalibration(uint8_t ch, float logicalDuty) { + if (logicalDuty <= 0) return calibratedPoints[ch][0]; + if (logicalDuty >= 100) 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 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); + + for (int ch = 0; ch < NUM_CHANNELS; ch++) { + ledcAttach(pwmPins[ch], pwmFrequency, pwmResolutionBits); + ledcWrite(pwmPins[ch], 0); + } + + 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."); +} + +// ------------------------------- +// Loop +// ------------------------------- + +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()) { + 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(); + + 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); + } + } + } + + // CONSTANT-RATE SLEWING (all channels) + if (now - lastSlewUpdate >= slewPerPercent) { + lastSlewUpdate = now; + + for (int ch = 0; ch < NUM_CHANNELS; ch++) { + + if (currentDuty[ch] < targetDuty[ch]) { + currentDuty[ch] += 1.0f; + if (currentDuty[ch] > targetDuty[ch]) + currentDuty[ch] = targetDuty[ch]; + } + 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); +}