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
static const float GOOD_SPREAD_C = 5.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;
@@ -71,10 +72,13 @@ 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_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 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

View File

@@ -24,6 +24,7 @@ public:
spreadSamples_(0),
cycleCount_(0),
aboveSetpoint_(false),
sessionStartMs_(0),
phaseStartMs_(0),
resultKp_(PID_KP),
resultKi_(PID_KI),
@@ -34,6 +35,29 @@ public:
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) {
if (setpointC < 25.0f || setpointC > TARGET_MAX_C) {
return false;
@@ -44,10 +68,13 @@ public:
relayLow_ = setpointC - AUTOTUNE_HYSTERESIS_C;
resetMeasurements();
phase_ = Phase::Preheat;
phaseStartMs_ = millis();
sessionStartMs_ = millis();
phaseStartMs_ = sessionStartMs_;
Serial.print(F("autotune: preheat to "));
Serial.print(preheatTargetC(), 1);
Serial.print(F("-"));
Serial.print(setpointC_, 1);
Serial.println(F("C"));
Serial.println(F("C avg"));
return true;
}
@@ -56,9 +83,13 @@ public:
Serial.println(F("autotune: cancelled"));
}
phase_ = Phase::Idle;
sessionStartMs_ = 0;
}
void reset() { phase_ = Phase::Idle; }
void reset() {
phase_ = Phase::Idle;
sessionStartMs_ = 0;
}
float setpoint() const { return setpointC_; }
@@ -81,13 +112,23 @@ public:
return phase_;
}
if (nowMs - phaseStartMs_ > AUTOTUNE_TIMEOUT_MS) {
fail(F("autotune: abort — timeout"));
if (nowMs - sessionStartMs_ > AUTOTUNE_SESSION_TIMEOUT_MS) {
fail(F("autotune: abort — session timeout"));
return phase_;
}
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);
} else {
heaterDutyOut = AUTOTUNE_PREHEAT_DUTY;
@@ -132,7 +173,9 @@ private:
peakSinceCross_ = avgTempC;
valleySinceCross_ = avgTempC;
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() {
@@ -158,13 +201,18 @@ private:
Serial.print(F("autotune: cycle "));
Serial.print(cycleCount_);
Serial.print(F("/"));
Serial.print(AUTOTUNE_CYCLES_REQUIRED);
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) {
const uint32_t period = nowMs - lastCrossMs_;
if (period > 8000 && period < 900000) {
if (period > 8000 && period < AUTOTUNE_RELAY_PERIOD_MAX_MS) {
periodSumMs_ += period;
++periodCount_;
}
@@ -173,11 +221,11 @@ private:
peakSinceCross_ = valleySinceCross_;
if (cycleCount_ >= AUTOTUNE_CYCLES_REQUIRED && periodCount_ >= 3 && amplitudeCount_ >= 3) {
finish();
finish(nowMs);
}
}
void finish() {
void finish(uint32_t nowMs) {
const float avgPeriodSec =
static_cast<float>(periodSumMs_ / periodCount_) / 1000.0f;
const float avgAmplitude = amplitudeSum_ / static_cast<float>(amplitudeCount_);
@@ -212,7 +260,9 @@ private:
}
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(resultKp_, 3);
Serial.print(F(" Ki="));
@@ -226,6 +276,7 @@ private:
void fail(const __FlashStringHelper *reason) {
Serial.println(reason);
phase_ = Phase::Failed;
sessionStartMs_ = 0;
}
Phase phase_;
@@ -243,6 +294,7 @@ private:
uint16_t spreadSamples_;
uint8_t cycleCount_;
bool aboveSetpoint_;
uint32_t sessionStartMs_;
uint32_t phaseStartMs_;
float resultKp_;
float resultKi_;

View File

@@ -104,6 +104,16 @@ public:
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() {
if (autotuner_.phase() != PidAutotuner::Phase::Done) {
return false;
@@ -358,10 +368,22 @@ private:
bool isBalancedChamber() const { return cornerSpreadC_ <= GOOD_SPREAD_C; }
float maxHeatStopTemp(float avgTempC) const {
if (isBalancedChamber() && avgTempC < targetTempC_) {
return targetTempC_ + BALANCED_MAX_ABOVE_TARGET_C;
if (avgTempC >= targetTempC_) {
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 {

View File

@@ -138,7 +138,17 @@ void printStatus(float avgTemp, float minTemp, float maxTemp) {
Serial.print(thermal.isFailSafeActive() ? F("YES") : F("no"));
Serial.print(F(" mode="));
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()) {
Serial.print(F("learned"));
} else {
@@ -284,7 +294,7 @@ void processSerialLine(const char *line) {
Serial.println(F("ERR autotune already running"));
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;
}