Compare commits

...

2 Commits

View File

@@ -1,309 +1,87 @@
// ------------------------------------------------------
// ESP32 8Channel PWM + WiFiManager + OSC (messages + bundles)
// + Slew + MultiPoint Calibration + OSC Packet Queue + Watchdog Sweep
// ------------------------------------------------------
#include <Arduino.h> #include <Arduino.h>
#include <WiFi.h> #include <RP2040_PWM.h>
#include <WiFiManager.h>
#include <WiFiUdp.h>
#include <OSCMessage.h>
#include <OSCBundle.h>
// ------------------------------- // -------------------------------
// User Configuration // Firmware version
// -------------------------------
const char* FIRMWARE_VERSION = "V1.0";
// -------------------------------
// PWM setup
// ------------------------------- // -------------------------------
const uint8_t NUM_CHANNELS = 8; const uint8_t NUM_CHANNELS = 8;
uint8_t pwmPins[NUM_CHANNELS] = {14, 15, 26, 27, 8, 7, 6, 5};
uint8_t pwmPins[NUM_CHANNELS] = {26, 25, 33, 32, 27, 25, 22, 21};
const uint32_t pwmFrequency = 10000; 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;
// Perchannel OSC addresses (configurable)
const char* oscAddresses[NUM_CHANNELS] = {
"/cpu",
"/cputemp",
"/memory",
"/gpu3d",
"/gputemp",
"/vram",
"/netup",
"/netdown"
};
// ------------------------------- // -------------------------------
// Multipoint Calibration Tables // Calibration tables
// ------------------------------- // -------------------------------
float logicalPoints[5] = {0, 25, 50, 75, 100}; float logicalPoints[5] = {0, 25, 50, 75, 100};
float calibratedPoints[NUM_CHANNELS][5] = { float calibratedPoints[NUM_CHANNELS][5] = {
{0, 25, 50, 75, 99}, // CH0 {0, 25, 50, 75, 99},
{0, 24, 49, 74, 98}, // CH1 {0, 24, 49, 74, 98},
{0, 26, 51, 76, 99}, // CH2 {0, 26, 51, 76, 99},
{0, 25, 50, 75, 97}, // CH3 {0, 25, 50, 75, 97},
{0, 25, 50, 75, 99}, // CH4 {0, 25, 50, 75, 99},
{0, 24, 50, 74, 98}, // CH5 {0, 24, 50, 74, 98},
{0, 25, 49, 75, 97}, // CH6 {0, 25, 49, 75, 97},
{0, 26, 50, 76, 99} // CH7 {0, 26, 50, 76, 99}
}; };
// ------------------------------- // -------------------------------
// Internal Variables // Duty tracking
// ------------------------------- // -------------------------------
float currentDuty[NUM_CHANNELS] = {0}; // logical 0100 float currentDuty[NUM_CHANNELS] = {0.0f};
float targetDuty[NUM_CHANNELS] = {0}; // logical 0100 RP2040_PWM* pwm[NUM_CHANNELS];
unsigned long lastSlewUpdate = 0;
WiFiUDP Udp;
// ------------------------------- // -------------------------------
// OSC Packet Queue // Watchdog
// ------------------------------- // -------------------------------
#define OSC_QUEUE_SIZE 16 unsigned long lastSerialTime = 0;
#define OSC_MAX_PACKET 512 const unsigned long watchdogTimeout = 5000; // 5 seconds
unsigned long lastFadeTime = 0;
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) // Calibration interpolation
// -------------------------------
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;
}
}
}
// -------------------------------
// Multipoint calibration function
// ------------------------------- // -------------------------------
float applyCalibration(uint8_t ch, float logicalDuty) { float applyCalibration(uint8_t ch, float logicalDuty) {
if (logicalDuty <= 0) return calibratedPoints[ch][0]; if (logicalDuty <= 0.0f) return calibratedPoints[ch][0];
if (logicalDuty >= 100) return calibratedPoints[ch][4]; if (logicalDuty >= 100.0f) return calibratedPoints[ch][4];
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
if (logicalDuty >= logicalPoints[i] && logicalDuty <= logicalPoints[i+1]) { if (logicalDuty >= logicalPoints[i] && logicalDuty <= logicalPoints[i+1]) {
float x0 = logicalPoints[i]; float x0 = logicalPoints[i];
float x1 = logicalPoints[i+1]; float x1 = logicalPoints[i+1];
float y0 = calibratedPoints[ch][i]; float y0 = calibratedPoints[ch][i];
float y1 = calibratedPoints[ch][i+1]; float y1 = calibratedPoints[ch][i+1];
float t = (logicalDuty - x0) / (x1 - x0); float t = (logicalDuty - x0) / (x1 - x0);
return y0 + t * (y1 - y0);
return y0 + t * (y1 - y0); // linear interpolation
} }
} }
return calibratedPoints[ch][4]; 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 // Setup
// ------------------------------- // -------------------------------
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
delay(500); delay(300);
Serial.println("ESP32 8channel 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++) { for (int ch = 0; ch < NUM_CHANNELS; ch++) {
ledcAttach(pwmPins[ch], pwmFrequency, pwmResolutionBits); pwm[ch] = new RP2040_PWM(pwmPins[ch], pwmFrequency, 0.0f);
ledcWrite(pwmPins[ch], 0); if (pwm[ch]) pwm[ch]->setPWM();
} }
delay(100); lastSerialTime = millis();
// 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.");
} }
// ------------------------------- // -------------------------------
@@ -312,59 +90,57 @@ void setup() {
void loop() { void loop() {
unsigned long now = millis(); // -------- Serial parsing --------
while (Serial.available()) {
// 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'); String s = Serial.readStringUntil('\n');
s.trim(); s.trim();
// --- Device identification command ---
if (s.equalsIgnoreCase("PING")) {
Serial.print("Analog_System_Monitor_");
Serial.println(FIRMWARE_VERSION);
lastSerialTime = millis();
continue;
}
int eq = s.indexOf('='); int eq = s.indexOf('=');
if (eq > 0) { if (eq > 0) {
int ch = s.substring(0, eq).toInt(); 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) { currentDuty[ch] = val;
targetDuty[ch] = val;
Serial.print("SER CH"); float calibratedDuty = applyCalibration(ch, currentDuty[ch]);
Serial.print(ch); pwm[ch]->setPWM(pwmPins[ch], pwmFrequency, calibratedDuty);
Serial.print(" -> ");
Serial.println(val); lastSerialTime = millis(); // reset watchdog
} }
} }
} }
// CONSTANT-RATE SLEWING (all channels) // -------- Watchdog fade-to-zero (time-based exponential) --------
if (now - lastSlewUpdate >= slewPerPercent) { if (millis() - lastSerialTime > watchdogTimeout) {
lastSlewUpdate = now;
const unsigned long fadeInterval = 1;
if (millis() - lastFadeTime >= fadeInterval) {
lastFadeTime = millis();
for (int ch = 0; ch < NUM_CHANNELS; ch++) { for (int ch = 0; ch < NUM_CHANNELS; ch++) {
if (currentDuty[ch] < targetDuty[ch]) { if (currentDuty[ch] > 0.0f) {
currentDuty[ch] += 1.0f;
if (currentDuty[ch] > targetDuty[ch]) const float fadeFactor = 0.999f;
currentDuty[ch] = targetDuty[ch]; currentDuty[ch] *= fadeFactor;
}
else if (currentDuty[ch] > targetDuty[ch]) { if (currentDuty[ch] < 0.01f)
currentDuty[ch] -= 1.0f; currentDuty[ch] = 0.0f;
if (currentDuty[ch] < targetDuty[ch])
currentDuty[ch] = targetDuty[ch];
}
float calibratedDuty = applyCalibration(ch, currentDuty[ch]); float calibratedDuty = applyCalibration(ch, currentDuty[ch]);
uint32_t pwmValue = (uint32_t)((calibratedDuty / 100.0f) * pwmMax); pwm[ch]->setPWM(pwmPins[ch], pwmFrequency, calibratedDuty);
ledcWrite(pwmPins[ch], pwmValue); }
}
} }
} }
delay(5);
} }