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 <Arduino.h>
|
||||||
#include <WiFi.h>
|
#include <RP2040_PWM.h>
|
||||||
#include <WiFiManager.h>
|
|
||||||
#include <WiFiUdp.h>
|
|
||||||
#include <OSCMessage.h>
|
|
||||||
#include <OSCBundle.h>
|
|
||||||
|
|
||||||
// -------------------------------
|
|
||||||
// User Configuration
|
|
||||||
// -------------------------------
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
// 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 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 0–100
|
float currentDuty[NUM_CHANNELS] = {0.0f};
|
||||||
float targetDuty[NUM_CHANNELS] = {0}; // logical 0–100
|
|
||||||
|
|
||||||
unsigned long lastSlewUpdate = 0;
|
RP2040_PWM* pwm[NUM_CHANNELS];
|
||||||
|
|
||||||
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
|
||||||
|
const float fadeStep = 0.001f; // 0.001% per ms
|
||||||
struct OscPacket {
|
unsigned long lastFadeTime = 0;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------
|
|
||||||
// Multi‑point 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 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++) {
|
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 +83,49 @@ 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();
|
||||||
|
|
||||||
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;
|
|
||||||
|
// 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++) {
|
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; // exponential decay per interval
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user