initial serial implementation, switched to RP2040
This commit is contained in:
@@ -1,309 +1,80 @@
|
||||
// ------------------------------------------------------
|
||||
// ESP32 8‑Channel PWM + WiFiManager + OSC (messages + bundles)
|
||||
// + Slew + Multi‑Point Calibration + OSC Packet Queue + Watchdog Sweep
|
||||
// ------------------------------------------------------
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <WiFiManager.h>
|
||||
#include <WiFiUdp.h>
|
||||
#include <OSCMessage.h>
|
||||
#include <OSCBundle.h>
|
||||
|
||||
// -------------------------------
|
||||
// User Configuration
|
||||
// -------------------------------
|
||||
#include <RP2040_PWM.h>
|
||||
|
||||
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) {
|
||||
|
||||
// Apply fade only every X ms
|
||||
const unsigned long fadeInterval = 1; // adjust for slower/faster decay
|
||||
|
||||
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; // exponential decay per interval
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user