This commit is contained in:
2026-07-05 20:15:29 +02:00
parent 960831529e
commit 02e51717e8
4 changed files with 192 additions and 72 deletions

View File

@@ -75,13 +75,14 @@ static const float SPREAD_EMA_ALPHA = 0.45f;
// PID auto-tune (relay method) — run with: autotune 45
static const float AUTOTUNE_HYSTERESIS_C = 0.4f;
static const float AUTOTUNE_PREHEAT_BAND_C = 5.0f;
static const float AUTOTUNE_PREHEAT_BAND_C = 3.0f;
static const float AUTOTUNE_PREHEAT_DUTY = 100.0f;
static const uint8_t AUTOTUNE_PREHEAT_FAN_PWM = 70; // low fan for entire autotune
static const uint8_t AUTOTUNE_CYCLES_REQUIRED = 6;
static const uint8_t AUTOTUNE_PREHEAT_FAN_PWM = 0; // fan off — maximize heat-up
static const uint8_t AUTOTUNE_CYCLES_REQUIRED = 5;
static const uint32_t AUTOTUNE_PREHEAT_TIMEOUT_MS = 1200000UL; // 20 min
static const uint32_t AUTOTUNE_SESSION_TIMEOUT_MS = 3600000UL; // 60 min total
static const uint32_t AUTOTUNE_RELAY_PERIOD_MAX_MS = 2400000UL; // count periods up to 40 min
static const uint32_t AUTOTUNE_RELAY_STALL_MS = 1500000UL; // 25 min in relay, 0 cycles
static const uint32_t AUTOTUNE_SESSION_TIMEOUT_MS = 3600000UL; // 60 min total
static const uint32_t AUTOTUNE_RELAY_PERIOD_MAX_MS = 2400000UL;
// ---------------------------------------------------------------------------
// Timing

View File

@@ -24,6 +24,7 @@ public:
spreadSamples_(0),
cycleCount_(0),
aboveSetpoint_(false),
useMaxSensorPv_(false),
sessionStartMs_(0),
phaseStartMs_(0),
resultKp_(PID_KP),
@@ -47,12 +48,14 @@ public:
float preheatTargetC() const { return setpointC_ - AUTOTUNE_PREHEAT_BAND_C; }
bool usesMaxSensor() const { return useMaxSensorPv_; }
const char *phaseName() const {
switch (phase_) {
case Phase::Preheat:
return "preheat";
case Phase::Relay:
return "relay";
return useMaxSensorPv_ ? "relay-max" : "relay-avg";
default:
return "";
}
@@ -124,8 +127,8 @@ public:
fail(F("autotune: abort — preheat timeout"));
return phase_;
}
if (avgTempC >= preheatTargetC()) {
enterRelay(avgTempC, nowMs);
if (avgTempC >= preheatTargetC() || maxTempC >= setpointC_ - 2.0f) {
enterRelay(avgTempC, maxTempC, spreadC, nowMs);
} else {
heaterDutyOut = AUTOTUNE_PREHEAT_DUTY;
}
@@ -137,27 +140,44 @@ public:
return phase_;
}
if (nowMs - sessionStartMs_ > AUTOTUNE_SESSION_TIMEOUT_MS) {
fail(F("autotune: abort — session timeout"));
return phase_;
}
if (cycleCount_ == 0 && nowMs - phaseStartMs_ > AUTOTUNE_RELAY_STALL_MS) {
Serial.print(F("autotune: relay stalled — avg "));
Serial.print(avgTempC, 1);
Serial.print(F("C max "));
Serial.print(maxTempC, 1);
Serial.println(F("C (spread too large for avg to cross setpoint?)"));
fail(F("autotune: abort — no oscillation"));
return phase_;
}
const float pv = useMaxSensorPv_ ? maxTempC : avgTempC;
spreadSum_ += spreadC;
++spreadSamples_;
if (avgTempC > peakSinceCross_) {
peakSinceCross_ = avgTempC;
if (pv > peakSinceCross_) {
peakSinceCross_ = pv;
}
if (avgTempC < valleySinceCross_) {
valleySinceCross_ = avgTempC;
if (pv < valleySinceCross_) {
valleySinceCross_ = pv;
}
bool heatOn = false;
if (avgTempC <= relayLow_) {
if (pv <= relayLow_) {
heatOn = true;
} else if (avgTempC >= relayHigh_) {
} else if (pv >= relayHigh_) {
heatOn = false;
} else {
heatOn = !aboveSetpoint_;
}
heaterDutyOut = heatOn ? 100.0f : 0.0f;
const bool nowAbove = avgTempC >= setpointC_;
const bool nowAbove = pv >= setpointC_;
if (nowAbove != aboveSetpoint_) {
onSetpointCrossing(nowMs);
aboveSetpoint_ = nowAbove;
@@ -167,14 +187,18 @@ public:
}
private:
void enterRelay(float avgTempC, uint32_t nowMs) {
void enterRelay(float avgTempC, float maxTempC, float spreadC, uint32_t nowMs) {
phase_ = Phase::Relay;
phaseStartMs_ = nowMs;
aboveSetpoint_ = avgTempC >= setpointC_;
peakSinceCross_ = avgTempC;
valleySinceCross_ = avgTempC;
useMaxSensorPv_ = spreadC > GOOD_SPREAD_C;
const float pv = useMaxSensorPv_ ? maxTempC : avgTempC;
aboveSetpoint_ = pv >= setpointC_;
peakSinceCross_ = pv;
valleySinceCross_ = pv;
lastCrossMs_ = 0;
Serial.print(F("autotune: relay test started ("));
Serial.print(F("autotune: relay "));
Serial.print(useMaxSensorPv_ ? F("max-sensor") : F("avg"));
Serial.print(F(" ("));
Serial.print((nowMs - sessionStartMs_) / 1000UL);
Serial.println(F("s preheat)"));
}
@@ -191,6 +215,7 @@ private:
spreadSamples_ = 0;
cycleCount_ = 0;
aboveSetpoint_ = false;
useMaxSensorPv_ = false;
}
void onSetpointCrossing(uint32_t nowMs) {
@@ -295,6 +320,7 @@ private:
uint16_t spreadSamples_;
uint8_t cycleCount_;
bool aboveSetpoint_;
bool useMaxSensorPv_;
uint32_t sessionStartMs_;
uint32_t phaseStartMs_;
float resultKp_;