mirror of
https://github.com/vincentvdwal/crust-craft.git
synced 2026-08-18 09:26:37 +02:00
add PID
This commit is contained in:
Vendored
+3
-1
@@ -1,8 +1,10 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+48
-1
@@ -8,6 +8,7 @@
|
||||
#include <ElegantOTA.h>
|
||||
|
||||
#include "secrets.h"
|
||||
#include "pid.h"
|
||||
|
||||
AsyncWebServer server(80);
|
||||
AsyncWebSocket ws("/ws");
|
||||
@@ -18,7 +19,7 @@ String jsonString;
|
||||
|
||||
// Timer variables
|
||||
unsigned long lastTime = 0;
|
||||
unsigned long timerDelay = 250;
|
||||
unsigned long timerDelay = 500;
|
||||
|
||||
int relay = 21;
|
||||
int thermoDO = 19;
|
||||
@@ -37,9 +38,12 @@ unsigned long lastSwitch = 0;
|
||||
unsigned long autoSwitchDelay = 20000; // 20s
|
||||
float pwmSwitchDelayOn = 4000; // 4s
|
||||
float pwmSwitchDelayOff = 7000; // 7s
|
||||
float power = 0;
|
||||
|
||||
String mode = "off";
|
||||
|
||||
PIDController pid(1.0, 0.5, 1.0, 100.0, 0.0, 50.0); // Kp, Ki, Kd, max, min, integ_max
|
||||
|
||||
// Get Sensor Readings and return JSON object
|
||||
String getSensorReadings()
|
||||
{
|
||||
@@ -60,6 +64,9 @@ String getSensorReadings()
|
||||
readings["derived_overshoot"] = derivedOverShoot;
|
||||
readings["derived_undershoot"] = derivedUnderShoot;
|
||||
readings["mode"] = mode;
|
||||
readings["pid"] = power;
|
||||
readings["pwm_on"] = pwmSwitchDelayOn;
|
||||
readings["pwm_off"] = pwmSwitchDelayOff;
|
||||
|
||||
serializeJson(readings, jsonString);
|
||||
return jsonString;
|
||||
@@ -133,6 +140,7 @@ void handleWebSocketMessage(void *arg, uint8_t *data, size_t len)
|
||||
{
|
||||
String target = message.substring(15, 18);
|
||||
targetTemp = target.toFloat();
|
||||
pid.setSetpoint(targetTemp);
|
||||
Serial.printf("\nTarget temp set to ");
|
||||
Serial.print(targetTemp);
|
||||
Serial.printf("°C");
|
||||
@@ -147,6 +155,10 @@ void handleWebSocketMessage(void *arg, uint8_t *data, size_t len)
|
||||
{
|
||||
mode = "pwm";
|
||||
}
|
||||
else if (message.indexOf("pid") > 0)
|
||||
{
|
||||
mode = "pid";
|
||||
}
|
||||
else
|
||||
{
|
||||
mode = "off";
|
||||
@@ -265,6 +277,37 @@ void regulateRelais()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mode == "pid")
|
||||
{
|
||||
// output = Kp×error + Ki×∫error×dt + Kd×(Δerror/Δt)`[^2][^5]
|
||||
power = pid.compute(temperature);
|
||||
pwmSwitchDelayOff = (pwmSwitchDelayOn / (power / 100)) - pwmSwitchDelayOn;
|
||||
|
||||
if (pwmSwitchDelayOff > 0)
|
||||
{
|
||||
if ((millis() - lastSwitch) > pwmSwitchDelayOn)
|
||||
{
|
||||
if (digitalRead(relay) == HIGH)
|
||||
{
|
||||
digitalWrite(relay, LOW);
|
||||
lastSwitch = millis();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pwmSwitchDelayOff > 0)
|
||||
{
|
||||
|
||||
if ((millis() - lastSwitch) > pwmSwitchDelayOff)
|
||||
{
|
||||
if (digitalRead(relay) == LOW)
|
||||
{
|
||||
digitalWrite(relay, HIGH);
|
||||
lastSwitch = millis();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup()
|
||||
@@ -282,6 +325,10 @@ void setup()
|
||||
|
||||
server.serveStatic("/", LittleFS, "/");
|
||||
|
||||
// Create PID controller instance
|
||||
pid.setSetpoint(targetTemp);
|
||||
pid.setDt(4);
|
||||
|
||||
ElegantOTA.begin(&server);
|
||||
|
||||
// Start server
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
#include "pid.h"
|
||||
|
||||
double PIDController::clamp(double value, double min_val, double max_val) const
|
||||
{
|
||||
if (value < min_val)
|
||||
return min_val;
|
||||
if (value > max_val)
|
||||
return max_val;
|
||||
return value;
|
||||
}
|
||||
|
||||
PIDController::PIDController(double Kp, double Ki, double Kd,
|
||||
double max_out, double min_out, double integ_max)
|
||||
: kp(Kp), ki(Ki), kd(Kd), setpoint(0.0), integral(0.0),
|
||||
prev_error(0.0), max_output(max_out), min_output(min_out),
|
||||
integral_max(integ_max), dt(0.1), first_run(true) {}
|
||||
|
||||
void PIDController::setSetpoint(double sp)
|
||||
{
|
||||
setpoint = sp;
|
||||
}
|
||||
|
||||
void PIDController::setDt(double sample_time)
|
||||
{
|
||||
dt = sample_time;
|
||||
}
|
||||
|
||||
double PIDController::compute(double current_temp)
|
||||
{
|
||||
double error = setpoint - current_temp;
|
||||
|
||||
// Proportional
|
||||
double p_term = kp * error;
|
||||
|
||||
// Integral + anti-windup
|
||||
integral += ki * error * dt;
|
||||
integral = clamp(integral, -integral_max, integral_max);
|
||||
|
||||
// Derivative
|
||||
double d_term = 0.0;
|
||||
if (!first_run)
|
||||
{
|
||||
d_term = kd * (error - prev_error) / dt;
|
||||
}
|
||||
first_run = false;
|
||||
|
||||
// Output
|
||||
double output = p_term + integral + d_term;
|
||||
output = clamp(output, min_output, max_output);
|
||||
|
||||
prev_error = error;
|
||||
return output;
|
||||
}
|
||||
|
||||
void PIDController::reset()
|
||||
{
|
||||
integral = 0.0;
|
||||
prev_error = 0.0;
|
||||
first_run = true;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef PID_CONTROLLER_H
|
||||
#define PID_CONTROLLER_H
|
||||
|
||||
class PIDController
|
||||
{
|
||||
private:
|
||||
double kp, ki, kd, setpoint, integral, prev_error;
|
||||
double max_output, min_output, integral_max, dt;
|
||||
bool first_run;
|
||||
|
||||
// Clamp function (C++11 compatible)
|
||||
double clamp(double value, double min_val, double max_val) const;
|
||||
|
||||
public:
|
||||
PIDController(double Kp, double Ki, double Kd,
|
||||
double max_out = 100.0, double min_out = 0.0,
|
||||
double integ_max = 50.0);
|
||||
|
||||
void setSetpoint(double sp);
|
||||
void setDt(double sample_time);
|
||||
double compute(double current_temp);
|
||||
void reset();
|
||||
};
|
||||
|
||||
#endif
|
||||
+36
-12
@@ -16,25 +16,28 @@
|
||||
|
||||
const now = new Date();
|
||||
|
||||
let mode = 'off';
|
||||
let temp = 300;
|
||||
let relais = 0;
|
||||
let targetTemp = 300;
|
||||
let mode = $state('off');
|
||||
let temp = $state(300);
|
||||
let relais = $state(0);
|
||||
let targetTemp = $state(300);
|
||||
let setOverShoot = 20;
|
||||
let setUnderShoot = 20;
|
||||
let targetTempOverShoot = targetTemp;
|
||||
let targetTempUnderShoot = targetTemp;
|
||||
let targetTempOverShoot = $state(targetTemp);
|
||||
let targetTempUnderShoot = $state(targetTemp);
|
||||
let calculatedPidOutput = $state(0);
|
||||
|
||||
let lastSwitch = new Date();
|
||||
let lastSwitchDuration = 0;
|
||||
|
||||
let canvas: HTMLCanvasElement;
|
||||
|
||||
let pauseGraphUpdate = false;
|
||||
let pauseGraphUpdate = $state(false);
|
||||
let spoofInterval = 250;
|
||||
let speedFactor = 250 / spoofInterval;
|
||||
const switchDelay = (30 / speedFactor) * 1000;
|
||||
let pwmOn = $state(4);
|
||||
let pwmOnDelay = (4 / speedFactor) * 1000;
|
||||
let pwmOff = $state(7);
|
||||
let pwmOffDelay = (7 / speedFactor) * 1000;
|
||||
|
||||
let maxValues = 4 * 2 * 60 * 60; // 120 minutes (4 values/sec)
|
||||
@@ -352,6 +355,12 @@
|
||||
targetTempOverShoot = myObj[key];
|
||||
} else if (key === 'derived_undershoot') {
|
||||
targetTempUnderShoot = myObj[key];
|
||||
} else if (key === 'pid') {
|
||||
calculatedPidOutput = myObj[key];
|
||||
} else if (key === 'pwm_on') {
|
||||
pwmOn = Number(myObj[key]) / 1000;
|
||||
} else if (key === 'pwm_off') {
|
||||
pwmOff = Number(myObj[key]) / 1000;
|
||||
} else if (key === 'mode') {
|
||||
mode = myObj[key];
|
||||
modeSpan.innerText = mode;
|
||||
@@ -402,6 +411,7 @@
|
||||
const target = e.target as HTMLTextAreaElement;
|
||||
let value = Number(target?.value);
|
||||
console.log('Changed pwm on to ' + value);
|
||||
pwmOn = value;
|
||||
if (!import.meta.env.DEV) {
|
||||
ws.send('setPWMOn: ' + pad(value, 3));
|
||||
} else {
|
||||
@@ -413,6 +423,7 @@
|
||||
const target = e.target as HTMLTextAreaElement;
|
||||
let value = Number(target?.value);
|
||||
console.log('Changed pwm off to ' + value);
|
||||
pwmOff = value;
|
||||
if (!import.meta.env.DEV) {
|
||||
ws.send('setPWMOff: ' + pad(value, 3));
|
||||
} else {
|
||||
@@ -486,7 +497,7 @@
|
||||
<div class="graph">
|
||||
<div class="plot-wrapper w-[800px]">
|
||||
<div id="plot"></div>
|
||||
<canvas bind:this={canvas} id="acquisitions"></canvas>
|
||||
<canvas bind:this={canvas} id="crusty"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-grid settings">
|
||||
@@ -498,7 +509,7 @@
|
||||
type="number"
|
||||
min="0"
|
||||
max="600"
|
||||
placeholder="300"
|
||||
value={targetTemp}
|
||||
id="target_temperature"
|
||||
onchange={changeTargetTemp}
|
||||
/>
|
||||
@@ -513,11 +524,12 @@
|
||||
<select class="rounded" name="modes" id="mode_selector" onchange={changeMode}>
|
||||
<option value="auto_switch">Auto Switch</option>
|
||||
<option value="pwm">PWM</option>
|
||||
<option value="pid">PID</option>
|
||||
<option value="off" selected>Manual</option>
|
||||
</select>
|
||||
</p>
|
||||
{#if mode === 'pwm'}
|
||||
<div class="gap-15 mt-2 flex items-center">
|
||||
<div class="mt-2 flex items-center gap-15">
|
||||
<div>on</div>
|
||||
<div>off</div>
|
||||
</div>
|
||||
@@ -528,7 +540,7 @@
|
||||
type="number"
|
||||
min="0"
|
||||
max="120"
|
||||
placeholder="4"
|
||||
bind:value={pwmOn}
|
||||
id="pwm_on"
|
||||
onchange={changePwmOn}
|
||||
/>
|
||||
@@ -538,12 +550,24 @@
|
||||
type="number"
|
||||
min="0"
|
||||
max="120"
|
||||
placeholder="7"
|
||||
bind:value={pwmOff}
|
||||
id="pwm_off"
|
||||
onchange={changePwmOff}
|
||||
/>
|
||||
<label for="pwm_off"> s </label>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center gap-1">
|
||||
{((pwmOn / (pwmOn + pwmOff)) * 100).toFixed(0)}%, {(
|
||||
(pwmOn / (pwmOn + pwmOff)) *
|
||||
1200
|
||||
).toFixed(0)}W
|
||||
</div>
|
||||
{/if}
|
||||
{#if mode === 'pid'}
|
||||
<div class="mt-3 flex items-center gap-1">
|
||||
PID: {calculatedPidOutput.toFixed(0)}% {(calculatedPidOutput * 12).toFixed(0)}W
|
||||
</div>
|
||||
<div>ON:{pwmOn.toFixed(2)}s OFF:{pwmOff.toFixed(2)}s</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="card flex cursor-pointer gap-2">
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"m.homeserver": {
|
||||
"base_url": "tjet.nl"
|
||||
},
|
||||
"org.matrix.msc2965.authentication": {
|
||||
"issuer": "https://account.tjet.nl/",
|
||||
"account": "https://account.tjet.nl/account/"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user