refine logging

This commit is contained in:
2026-07-05 16:09:42 +02:00
parent 161cbab8a7
commit 0e4a30adf7
4 changed files with 107 additions and 19 deletions

View File

@@ -51,6 +51,7 @@ static const float APPROACH_BAND_C = 4.0f;
// When spread is good, allow hottest corner slightly above target so avg can reach setpoint // When spread is good, allow hottest corner slightly above target so avg can reach setpoint
static const float GOOD_SPREAD_C = 5.0f; static const float GOOD_SPREAD_C = 5.0f;
static const float BALANCED_MAX_ABOVE_TARGET_C = 2.0f; static const float BALANCED_MAX_ABOVE_TARGET_C = 2.0f;
static const float SPREAD_HEADROOM_FACTOR = 0.5f; // extra max-corner °C per °C of spread
static const uint16_t HEATER_CYCLE_MS = 3000; static const uint16_t HEATER_CYCLE_MS = 3000;
@@ -71,10 +72,13 @@ static const float SPREAD_EMA_ALPHA = 0.45f;
// PID auto-tune (relay method) — run with: autotune 45 // PID auto-tune (relay method) — run with: autotune 45
static const float AUTOTUNE_HYSTERESIS_C = 0.4f; static const float AUTOTUNE_HYSTERESIS_C = 0.4f;
static const float AUTOTUNE_PREHEAT_BAND_C = 5.0f; static const float AUTOTUNE_PREHEAT_BAND_C = 5.0f;
static const float AUTOTUNE_PREHEAT_DUTY = 80.0f; static const float AUTOTUNE_PREHEAT_DUTY = 100.0f;
static const uint8_t AUTOTUNE_PREHEAT_FAN_PWM = 70; // light mixing only while preheating
static const float AUTOTUNE_ABORT_ABOVE_C = 15.0f; static const float AUTOTUNE_ABORT_ABOVE_C = 15.0f;
static const uint8_t AUTOTUNE_CYCLES_REQUIRED = 6; static const uint8_t AUTOTUNE_CYCLES_REQUIRED = 6;
static const uint32_t AUTOTUNE_TIMEOUT_MS = 1800000UL; 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
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Timing // Timing

View File

@@ -24,6 +24,7 @@ public:
spreadSamples_(0), spreadSamples_(0),
cycleCount_(0), cycleCount_(0),
aboveSetpoint_(false), aboveSetpoint_(false),
sessionStartMs_(0),
phaseStartMs_(0), phaseStartMs_(0),
resultKp_(PID_KP), resultKp_(PID_KP),
resultKi_(PID_KI), resultKi_(PID_KI),
@@ -34,6 +35,29 @@ public:
bool isActive() const { return phase_ == Phase::Preheat || phase_ == Phase::Relay; } bool isActive() const { return phase_ == Phase::Preheat || phase_ == Phase::Relay; }
uint32_t elapsedMs(uint32_t nowMs) const {
if (sessionStartMs_ == 0) {
return 0;
}
return nowMs - sessionStartMs_;
}
uint8_t cycleCount() const { return cycleCount_; }
uint8_t periodCount() const { return periodCount_; }
float preheatTargetC() const { return setpointC_ - AUTOTUNE_PREHEAT_BAND_C; }
const char *phaseName() const {
switch (phase_) {
case Phase::Preheat:
return "preheat";
case Phase::Relay:
return "relay";
default:
return "";
}
}
bool start(float setpointC) { bool start(float setpointC) {
if (setpointC < 25.0f || setpointC > TARGET_MAX_C) { if (setpointC < 25.0f || setpointC > TARGET_MAX_C) {
return false; return false;
@@ -44,10 +68,13 @@ public:
relayLow_ = setpointC - AUTOTUNE_HYSTERESIS_C; relayLow_ = setpointC - AUTOTUNE_HYSTERESIS_C;
resetMeasurements(); resetMeasurements();
phase_ = Phase::Preheat; phase_ = Phase::Preheat;
phaseStartMs_ = millis(); sessionStartMs_ = millis();
phaseStartMs_ = sessionStartMs_;
Serial.print(F("autotune: preheat to ")); Serial.print(F("autotune: preheat to "));
Serial.print(preheatTargetC(), 1);
Serial.print(F("-"));
Serial.print(setpointC_, 1); Serial.print(setpointC_, 1);
Serial.println(F("C")); Serial.println(F("C avg"));
return true; return true;
} }
@@ -56,9 +83,13 @@ public:
Serial.println(F("autotune: cancelled")); Serial.println(F("autotune: cancelled"));
} }
phase_ = Phase::Idle; phase_ = Phase::Idle;
sessionStartMs_ = 0;
} }
void reset() { phase_ = Phase::Idle; } void reset() {
phase_ = Phase::Idle;
sessionStartMs_ = 0;
}
float setpoint() const { return setpointC_; } float setpoint() const { return setpointC_; }
@@ -81,13 +112,23 @@ public:
return phase_; return phase_;
} }
if (nowMs - phaseStartMs_ > AUTOTUNE_TIMEOUT_MS) { if (nowMs - sessionStartMs_ > AUTOTUNE_SESSION_TIMEOUT_MS) {
fail(F("autotune: abort — timeout")); fail(F("autotune: abort — session timeout"));
return phase_; return phase_;
} }
if (phase_ == Phase::Preheat) { if (phase_ == Phase::Preheat) {
if (avgTempC >= setpointC_ - AUTOTUNE_PREHEAT_BAND_C) { fanPwmOut = AUTOTUNE_PREHEAT_FAN_PWM;
if (nowMs - phaseStartMs_ > AUTOTUNE_PREHEAT_TIMEOUT_MS) {
Serial.print(F("autotune: preheat failed — avg "));
Serial.print(avgTempC, 1);
Serial.print(F("C after "));
Serial.print((nowMs - sessionStartMs_) / 60000UL);
Serial.println(F(" min"));
fail(F("autotune: abort — preheat timeout"));
return phase_;
}
if (avgTempC >= preheatTargetC()) {
enterRelay(avgTempC, nowMs); enterRelay(avgTempC, nowMs);
} else { } else {
heaterDutyOut = AUTOTUNE_PREHEAT_DUTY; heaterDutyOut = AUTOTUNE_PREHEAT_DUTY;
@@ -132,7 +173,9 @@ private:
peakSinceCross_ = avgTempC; peakSinceCross_ = avgTempC;
valleySinceCross_ = avgTempC; valleySinceCross_ = avgTempC;
lastCrossMs_ = 0; lastCrossMs_ = 0;
Serial.println(F("autotune: relay test started")); Serial.print(F("autotune: relay test started ("));
Serial.print((nowMs - sessionStartMs_) / 1000UL);
Serial.println(F("s preheat)"));
} }
void resetMeasurements() { void resetMeasurements() {
@@ -158,13 +201,18 @@ private:
Serial.print(F("autotune: cycle ")); Serial.print(F("autotune: cycle "));
Serial.print(cycleCount_); Serial.print(cycleCount_);
Serial.print(F("/"));
Serial.print(AUTOTUNE_CYCLES_REQUIRED);
Serial.print(F(" amp=")); Serial.print(F(" amp="));
Serial.println(amplitude, 2); Serial.print(amplitude, 2);
Serial.print(F("C elapsed="));
Serial.print((nowMs - sessionStartMs_) / 1000UL);
Serial.println(F("s"));
} }
if (lastCrossMs_ > 0) { if (lastCrossMs_ > 0) {
const uint32_t period = nowMs - lastCrossMs_; const uint32_t period = nowMs - lastCrossMs_;
if (period > 8000 && period < 900000) { if (period > 8000 && period < AUTOTUNE_RELAY_PERIOD_MAX_MS) {
periodSumMs_ += period; periodSumMs_ += period;
++periodCount_; ++periodCount_;
} }
@@ -173,11 +221,11 @@ private:
peakSinceCross_ = valleySinceCross_; peakSinceCross_ = valleySinceCross_;
if (cycleCount_ >= AUTOTUNE_CYCLES_REQUIRED && periodCount_ >= 3 && amplitudeCount_ >= 3) { if (cycleCount_ >= AUTOTUNE_CYCLES_REQUIRED && periodCount_ >= 3 && amplitudeCount_ >= 3) {
finish(); finish(nowMs);
} }
} }
void finish() { void finish(uint32_t nowMs) {
const float avgPeriodSec = const float avgPeriodSec =
static_cast<float>(periodSumMs_ / periodCount_) / 1000.0f; static_cast<float>(periodSumMs_ / periodCount_) / 1000.0f;
const float avgAmplitude = amplitudeSum_ / static_cast<float>(amplitudeCount_); const float avgAmplitude = amplitudeSum_ / static_cast<float>(amplitudeCount_);
@@ -212,7 +260,9 @@ private:
} }
phase_ = Phase::Done; phase_ = Phase::Done;
Serial.println(F("autotune: done")); Serial.print(F("autotune: done in "));
Serial.print((nowMs - sessionStartMs_) / 1000UL);
Serial.println(F("s"));
Serial.print(F(" Kp=")); Serial.print(F(" Kp="));
Serial.print(resultKp_, 3); Serial.print(resultKp_, 3);
Serial.print(F(" Ki=")); Serial.print(F(" Ki="));
@@ -226,6 +276,7 @@ private:
void fail(const __FlashStringHelper *reason) { void fail(const __FlashStringHelper *reason) {
Serial.println(reason); Serial.println(reason);
phase_ = Phase::Failed; phase_ = Phase::Failed;
sessionStartMs_ = 0;
} }
Phase phase_; Phase phase_;
@@ -243,6 +294,7 @@ private:
uint16_t spreadSamples_; uint16_t spreadSamples_;
uint8_t cycleCount_; uint8_t cycleCount_;
bool aboveSetpoint_; bool aboveSetpoint_;
uint32_t sessionStartMs_;
uint32_t phaseStartMs_; uint32_t phaseStartMs_;
float resultKp_; float resultKp_;
float resultKi_; float resultKi_;

View File

@@ -104,6 +104,16 @@ public:
bool isAutotuning() const { return autotuner_.isActive(); } bool isAutotuning() const { return autotuner_.isActive(); }
uint32_t autotuneElapsedMs(uint32_t nowMs) const { return autotuner_.elapsedMs(nowMs); }
const char *autotunePhaseName() const { return autotuner_.phaseName(); }
uint8_t autotuneCycleCount() const { return autotuner_.cycleCount(); }
uint8_t autotunePeriodCount() const { return autotuner_.periodCount(); }
float autotunePreheatTargetC() const { return autotuner_.preheatTargetC(); }
bool commitAutotuneIfDone() { bool commitAutotuneIfDone() {
if (autotuner_.phase() != PidAutotuner::Phase::Done) { if (autotuner_.phase() != PidAutotuner::Phase::Done) {
return false; return false;
@@ -358,12 +368,24 @@ private:
bool isBalancedChamber() const { return cornerSpreadC_ <= GOOD_SPREAD_C; } bool isBalancedChamber() const { return cornerSpreadC_ <= GOOD_SPREAD_C; }
float maxHeatStopTemp(float avgTempC) const { float maxHeatStopTemp(float avgTempC) const {
if (isBalancedChamber() && avgTempC < targetTempC_) { if (avgTempC >= targetTempC_) {
return targetTempC_ + BALANCED_MAX_ABOVE_TARGET_C;
}
return targetTempC_; return targetTempC_;
} }
float stopAt = targetTempC_;
if (isBalancedChamber()) {
stopAt = targetTempC_ + BALANCED_MAX_ABOVE_TARGET_C;
} else {
stopAt = targetTempC_ + cornerSpreadC_ * SPREAD_HEADROOM_FACTOR + 1.0f;
}
const float belowCutoff = cutoffThreshold() - 0.1f;
if (stopAt > belowCutoff) {
stopAt = belowCutoff;
}
return stopAt;
}
float allowanceFromMaxCorner(float maxTempC, float avgTempC) const { float allowanceFromMaxCorner(float maxTempC, float avgTempC) const {
if (isBalancedChamber() && avgTempC < targetTempC_) { if (isBalancedChamber() && avgTempC < targetTempC_) {
return 100.0f; return 100.0f;

View File

@@ -138,7 +138,17 @@ void printStatus(float avgTemp, float minTemp, float maxTemp) {
Serial.print(thermal.isFailSafeActive() ? F("YES") : F("no")); Serial.print(thermal.isFailSafeActive() ? F("YES") : F("no"));
Serial.print(F(" mode=")); Serial.print(F(" mode="));
if (thermal.isAutotuning()) { if (thermal.isAutotuning()) {
Serial.print(F("autotune")); Serial.print(F("autotune/"));
Serial.print(thermal.autotunePhaseName());
Serial.print(F(" "));
Serial.print(thermal.autotuneElapsedMs(millis()) / 1000UL);
Serial.print(F("s "));
Serial.print(thermal.autotuneCycleCount());
Serial.print(F("/"));
Serial.print(AUTOTUNE_CYCLES_REQUIRED);
Serial.print(F("cyc pre>="));
Serial.print(thermal.autotunePreheatTargetC(), 0);
Serial.print(F("C"));
} else if (thermal.isAdaptive()) { } else if (thermal.isAdaptive()) {
Serial.print(F("learned")); Serial.print(F("learned"));
} else { } else {
@@ -284,7 +294,7 @@ void processSerialLine(const char *line) {
Serial.println(F("ERR autotune already running")); Serial.println(F("ERR autotune already running"));
return; return;
} }
Serial.println(F("OK autotune started — keep chamber closed, wait ~10-20 min")); Serial.println(F("OK autotune started — preheat then relay, typically 15-40 min"));
return; return;
} }