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 <WiFi.h>
#include <WiFiManager.h>
#include <WiFiUdp.h>
#include <OSCMessage.h>
#include <OSCBundle.h>
#include <RP2040_PWM.h>
// -------------------------------
// User Configuration
// Firmware version
// -------------------------------
const char* FIRMWARE_VERSION = "V1.0";
// -------------------------------
// PWM setup
// -------------------------------
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;
// 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 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 0100
float targetDuty[NUM_CHANNELS] = {0}; // logical 0100
unsigned long lastSlewUpdate = 0;
WiFiUDP Udp;
float currentDuty[NUM_CHANNELS] = {0.0f};
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
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;
}
}
}
// -------------------------------
// Multipoint 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 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);
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 +90,57 @@ 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();
// --- Device identification command ---
if (s.equalsIgnoreCase("PING")) {
Serial.print("Analog_System_Monitor_");
Serial.println(FIRMWARE_VERSION);
lastSerialTime = millis();
continue;
}
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) {
const unsigned long fadeInterval = 1;
if (millis() - lastFadeTime >= fadeInterval) {
lastFadeTime = millis();
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];
}
if (currentDuty[ch] > 0.0f) {
const float fadeFactor = 0.999f;
currentDuty[ch] *= fadeFactor;
if (currentDuty[ch] < 0.01f)
currentDuty[ch] = 0.0f;
float calibratedDuty = applyCalibration(ch, currentDuty[ch]);
uint32_t pwmValue = (uint32_t)((calibratedDuty / 100.0f) * pwmMax);
ledcWrite(pwmPins[ch], pwmValue);
pwm[ch]->setPWM(pwmPins[ch], pwmFrequency, calibratedDuty);
}
}
}
}
delay(5);
}