added arduino program
This commit is contained in:
370
analog_system_monitor_arduino/analog_system_monitor_arduino.ino
Normal file
370
analog_system_monitor_arduino/analog_system_monitor_arduino.ino
Normal file
@@ -0,0 +1,370 @@
|
|||||||
|
// ------------------------------------------------------
|
||||||
|
// 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
|
||||||
|
// -------------------------------
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user