safety commit
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "fan_step_response.h"
|
||||
#include "pid_autotuner.h"
|
||||
#include "pid_controller.h"
|
||||
#include "settings_store.h"
|
||||
@@ -10,11 +11,12 @@
|
||||
|
||||
class ThermalController {
|
||||
public:
|
||||
enum class HeaterBlock : uint8_t { None, Cutoff, Corner, Allow, Autotune };
|
||||
enum class HeaterBlock : uint8_t { None, Cutoff, Corner, Allow, Autotune, StepResp };
|
||||
|
||||
ThermalController()
|
||||
: pid_(PID_KP, PID_KI, PID_KD, 0.0f, 100.0f),
|
||||
autotuner_(),
|
||||
stepresp_(),
|
||||
targetTempC_(TARGET_TEMP_C),
|
||||
heaterDutyPercent_(0.0f),
|
||||
heaterAllowancePercent_(100.0f),
|
||||
@@ -40,6 +42,7 @@ public:
|
||||
pinMode(FAN_PIN, OUTPUT);
|
||||
pinMode(HEATER_PIN, OUTPUT);
|
||||
digitalWrite(HEATER_PIN, LOW);
|
||||
writeFan(0);
|
||||
|
||||
pid_.setSetpoint(targetTempC_);
|
||||
pid_.reset();
|
||||
@@ -105,7 +108,7 @@ public:
|
||||
bool isAdaptive() const { return adaptiveEnabled_; }
|
||||
|
||||
bool startAutotune(float setpointC) {
|
||||
if (autotuner_.isActive()) {
|
||||
if (autotuner_.isActive() || stepresp_.isActive()) {
|
||||
return false;
|
||||
}
|
||||
adaptiveEnabled_ = false;
|
||||
@@ -128,6 +131,46 @@ public:
|
||||
|
||||
float autotunePreheatTargetC() const { return autotuner_.preheatTargetC(); }
|
||||
|
||||
bool startStepResponse(float targetC, float heaterPct) {
|
||||
if (autotuner_.isActive() || stepresp_.isActive()) {
|
||||
return false;
|
||||
}
|
||||
stopFanTest();
|
||||
adaptiveEnabled_ = false;
|
||||
cutoffActive_ = false;
|
||||
pid_.reset();
|
||||
setTarget(targetC, false);
|
||||
if (!stepresp_.start(targetC, heaterPct)) {
|
||||
return false;
|
||||
}
|
||||
writeFan(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void stopStepResponse() {
|
||||
stepresp_.abort();
|
||||
writeFan(0);
|
||||
}
|
||||
|
||||
bool isStepResponseActive() const { return stepresp_.isActive(); }
|
||||
|
||||
uint32_t stepResponseElapsedMs(uint32_t nowMs) const { return stepresp_.elapsedMs(nowMs); }
|
||||
|
||||
const char *stepResponsePhaseName() const { return stepresp_.phaseName(); }
|
||||
|
||||
uint8_t stepResponseStepIndex() const { return stepresp_.stepIndex(); }
|
||||
|
||||
uint8_t stepResponseStepCount() const { return stepresp_.stepCount(); }
|
||||
|
||||
float stepResponseHeaterPct() const { return stepresp_.heaterPct(); }
|
||||
|
||||
void logStepResponseIfDue(const float *sensorTemps, const bool *sensorValid, uint8_t sensorCount,
|
||||
float avgTempC, float minTempC, float maxTempC, float spreadC,
|
||||
uint32_t nowMs) {
|
||||
stepresp_.logIfDue(sensorTemps, sensorValid, sensorCount, avgTempC, minTempC, maxTempC,
|
||||
spreadC, nowMs);
|
||||
}
|
||||
|
||||
bool commitAutotuneIfDone() {
|
||||
if (autotuner_.phase() != PidAutotuner::Phase::Done) {
|
||||
return false;
|
||||
@@ -206,7 +249,7 @@ public:
|
||||
if (isIdle()) {
|
||||
return INFINITY;
|
||||
}
|
||||
return targetTempC_ * (1.0f + OVERTEMP_FRACTION);
|
||||
return EMERGENCY_MAX_TEMP_C;
|
||||
}
|
||||
|
||||
bool isCutoffActive() const { return cutoffActive_; }
|
||||
@@ -249,6 +292,8 @@ public:
|
||||
return "allow";
|
||||
case HeaterBlock::Autotune:
|
||||
return "autotune";
|
||||
case HeaterBlock::StepResp:
|
||||
return "stepresp";
|
||||
default:
|
||||
return "none";
|
||||
}
|
||||
@@ -264,6 +309,11 @@ public:
|
||||
SPREAD_EMA_ALPHA * cornerSpreadC +
|
||||
(1.0f - SPREAD_EMA_ALPHA) * cornerSpreadC_;
|
||||
|
||||
if (stepresp_.isActive()) {
|
||||
updateStepResponse(avgTempC, maxTempC, nowMs);
|
||||
return;
|
||||
}
|
||||
|
||||
if (autotuner_.isActive()) {
|
||||
updateAutotune(avgTempC, maxTempC, nowMs);
|
||||
return;
|
||||
@@ -296,6 +346,7 @@ public:
|
||||
applyFan(millis());
|
||||
pid_.reset();
|
||||
autotuner_.abort();
|
||||
stepresp_.abort();
|
||||
}
|
||||
|
||||
void forceHeaterOff() {
|
||||
@@ -307,16 +358,51 @@ public:
|
||||
|
||||
void writeFan(uint8_t pwm) {
|
||||
fanPwm_ = pwm;
|
||||
if (pwm == 0) {
|
||||
// Re-assert output and stop Timer0 PWM on D5 — analogWrite(0) can leave the pin driving
|
||||
pinMode(FAN_PIN, OUTPUT);
|
||||
digitalWrite(FAN_PIN, LOW);
|
||||
} else {
|
||||
analogWrite(FAN_PIN, pwm);
|
||||
pinMode(FAN_PIN, OUTPUT);
|
||||
|
||||
if (FAN_PWM_INVERT) {
|
||||
if (pwm == 0) {
|
||||
digitalWrite(FAN_PIN, HIGH);
|
||||
return;
|
||||
}
|
||||
if (pwm >= 254) {
|
||||
digitalWrite(FAN_PIN, LOW);
|
||||
return;
|
||||
}
|
||||
analogWrite(FAN_PIN, static_cast<uint8_t>(255 - pwm));
|
||||
return;
|
||||
}
|
||||
|
||||
if (pwm == 0) {
|
||||
digitalWrite(FAN_PIN, LOW);
|
||||
return;
|
||||
}
|
||||
if (pwm >= 254) {
|
||||
digitalWrite(FAN_PIN, HIGH);
|
||||
return;
|
||||
}
|
||||
analogWrite(FAN_PIN, pwm);
|
||||
}
|
||||
|
||||
private:
|
||||
void updateStepResponse(float avgTempC, float maxTempC, uint32_t nowMs) {
|
||||
float duty = 0.0f;
|
||||
uint8_t fan = 0;
|
||||
stepresp_.update(avgTempC, maxTempC, cornerSpreadC_, nowMs, duty, fan);
|
||||
|
||||
heaterDutyPercent_ = duty;
|
||||
heaterAllowancePercent_ = duty;
|
||||
heaterBlock_ = duty > 0.0f ? HeaterBlock::StepResp : HeaterBlock::None;
|
||||
applyHeaterBurst(nowMs);
|
||||
writeFan(fan);
|
||||
lastHeaterUpdateMs_ = nowMs;
|
||||
|
||||
if (stepresp_.phase() == FanStepResponse::Phase::Done ||
|
||||
stepresp_.phase() == FanStepResponse::Phase::Failed) {
|
||||
stepresp_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void updateAutotune(float avgTempC, float maxTempC, uint32_t nowMs) {
|
||||
float duty = 0.0f;
|
||||
uint8_t fan = FAN_HEAT_MIN_PWM;
|
||||
@@ -439,22 +525,10 @@ private:
|
||||
}
|
||||
|
||||
float maxHeatStopTemp(float avgTempC) const {
|
||||
if (avgTempC >= targetTempC_) {
|
||||
return targetTempC_;
|
||||
if (!shouldLimitMaxCorner(avgTempC)) {
|
||||
return EMERGENCY_MAX_TEMP_C;
|
||||
}
|
||||
|
||||
float stopAt = targetTempC_;
|
||||
if (isBalancedChamber()) {
|
||||
stopAt = targetTempC_ + BALANCED_MAX_ABOVE_TARGET_C;
|
||||
} else {
|
||||
stopAt = targetTempC_ + cornerSpreadC_ * SPREAD_HEADROOM_FACTOR + 1.0f;
|
||||
}
|
||||
|
||||
const float cutoff = cutoffThreshold();
|
||||
if (stopAt > cutoff) {
|
||||
stopAt = cutoff;
|
||||
}
|
||||
return stopAt;
|
||||
return EMERGENCY_MAX_TEMP_C - CORNER_STOP_MARGIN_C;
|
||||
}
|
||||
|
||||
float allowanceFromMaxCorner(float maxTempC, float avgTempC) const {
|
||||
@@ -614,7 +688,7 @@ private:
|
||||
}
|
||||
|
||||
if (isIdle()) {
|
||||
if (!sensorWarmValid_ || lastMaxTempC_ >= IDLE_AUTO_FAN_OFF_TEMP_C) {
|
||||
if (sensorWarmValid_ && lastMaxTempC_ >= IDLE_AUTO_FAN_OFF_TEMP_C) {
|
||||
writeFan(FAN_MAX_PWM);
|
||||
} else if (fanIdleOverride_) {
|
||||
writeFan(FAN_IDLE_PWM);
|
||||
@@ -635,6 +709,7 @@ private:
|
||||
|
||||
PidController pid_;
|
||||
PidAutotuner autotuner_;
|
||||
FanStepResponse stepresp_;
|
||||
float targetTempC_;
|
||||
float heaterDutyPercent_;
|
||||
float heaterAllowancePercent_;
|
||||
|
||||
Reference in New Issue
Block a user