This commit is contained in:
2026-08-07 17:36:15 +02:00
parent d1385baa45
commit 31a6764484
9 changed files with 75219 additions and 68 deletions

View File

@@ -12,6 +12,10 @@ static const uint8_t SENSOR_COUNT = sizeof(SENSOR_CHANNELS) / sizeof(SENSOR_CHAN
// SHT31 I2C address (ADDR pin low → 0x44, high → 0x45)
static const uint8_t SHT31_ADDRESS = 0x44;
// Bus timeout (Wire.setWireTimeout) — bounds a stuck I2C transaction so a
// glitch resets the TWI hardware instead of hanging the whole sketch.
static const uint32_t I2C_TIMEOUT_US = 25000UL;
// ---------------------------------------------------------------------------
// Outputs — D5 has hardware PWM; heater on A2 uses burst control (SSR-friendly)
// ---------------------------------------------------------------------------
@@ -25,9 +29,22 @@ static const float TARGET_TEMP_C = 0.0f;
static const float AUTOTUNE_DEFAULT_TEMP_C = 40.0f;
static const float TARGET_MIN_C = 0.0f;
static const float TARGET_MAX_C = 80.0f;
// Absolute max corner — heater off + full fan (decoupled from PID target).
static const float EMERGENCY_MAX_TEMP_C = 70.0f;
static const float CORNER_STOP_MARGIN_C = 3.0f;
// Hard ceiling (sensor / enclosure limit). Cutoff when regulating = target + CUTOFF_ABOVE_TARGET_C.
static const float EMERGENCY_ABSOLUTE_MAX_C = 95.0f;
static const float CUTOFF_ABOVE_TARGET_C = 12.0f;
static const float CUTOFF_RECOVERY_BAND_C = 5.0f;
static const float CORNER_STOP_MARGIN_C = 5.0f;
inline float emergencyCutoffForTarget(float targetC) {
if (targetC <= 0.0f) {
return EMERGENCY_ABSOLUTE_MAX_C;
}
float cutoff = targetC + CUTOFF_ABOVE_TARGET_C;
if (cutoff > EMERGENCY_ABSOLUTE_MAX_C) {
cutoff = EMERGENCY_ABSOLUTE_MAX_C;
}
return cutoff;
}
// Heat PI on average temp (no D term)
static const float HEAT_PI_KP = 4.0f;
@@ -42,15 +59,8 @@ static const float PID_KI = HEAT_PI_KI;
static const float PID_KD = 0.0f;
static const float GOOD_SPREAD_C = 5.0f;
// Tiered heater cap during heat-up (100% until near target)
static const float HEATER_MAX_DUTY_COLD = 100.0f;
static const float HEATER_MAX_DUTY_MID = 75.0f;
static const float HEATER_MAX_DUTY_NEAR = 45.0f;
static const float HEATER_COLD_BELOW_C = 10.0f;
static const float HEATER_WARM_BELOW_C = 3.0f;
// Corner taper only within this band below target (was 10°C — blocked heat-up in uneven chambers)
// Corner taper when avg is near target — keeps hottest sensor below emergency
static const float CORNER_LIMIT_BAND_C = 2.0f;
static const float HEATER_SLEW_UP_PER_S = 18.0f;
static const float MAX_TEMP_HEADROOM_C = 15.0f;
static const uint16_t HEATER_CYCLE_MS = 3000;

View File

@@ -106,7 +106,7 @@ public:
return phase_;
}
if (maxTempC >= EMERGENCY_MAX_TEMP_C) {
if (maxTempC >= emergencyCutoffForTarget(setpointC_)) {
fail(F("autotune: abort — max sensor at emergency limit"));
return phase_;
}

View File

@@ -299,11 +299,13 @@ public:
bool isIdle() const { return targetTempC_ <= 0.0f; }
float emergencyCutoffC() const { return emergencyCutoffForTarget(targetTempC_); }
float cutoffThreshold() const {
if (isIdle()) {
return INFINITY;
}
return EMERGENCY_MAX_TEMP_C;
return emergencyCutoffC();
}
bool isCutoffActive() const { return cutoffActive_; }
@@ -470,7 +472,8 @@ private:
}
void updateRegulating(float avgTempC, float maxTempC, uint32_t nowMs) {
if (maxTempC >= EMERGENCY_MAX_TEMP_C) {
const float cutoffC = emergencyCutoffC();
if (maxTempC >= cutoffC) {
cutoffActive_ = true;
heaterDutyPercent_ = 0.0f;
heaterAllowancePercent_ = 0.0f;
@@ -480,7 +483,7 @@ private:
return;
}
if (cutoffActive_ && maxTempC < EMERGENCY_MAX_TEMP_C - 5.0f) {
if (cutoffActive_ && maxTempC < cutoffC - CUTOFF_RECOVERY_BAND_C) {
cutoffActive_ = false;
heatPi_.reset();
}
@@ -494,24 +497,14 @@ private:
heaterAllowancePercent_ = allowanceFromMaxCorner(maxTempC, avgTempC);
float duty = heatPi_.compute(avgTempC, nowMs);
const float below = targetTempC_ - avgTempC;
if (below > 8.0f) {
const float floor = below > 15.0f ? 75.0f : 60.0f;
if (duty < floor) {
duty = floor;
}
}
const float maxDuty = heaterMaxDuty(avgTempC);
if (duty > maxDuty) {
duty = maxDuty;
}
duty = clampPercent(duty);
if (duty > heaterAllowancePercent_) {
duty = heaterAllowancePercent_;
if (heaterAllowancePercent_ < 100.0f) {
heaterBlock_ = HeaterBlock::Corner;
}
}
heaterDutyPercent_ = applyHeaterRamp(duty, avgTempC, nowMs);
heaterDutyPercent_ = duty;
regulatingFanPwm_ = fanManualActive_ ? fanManualPwm_ : stirFanPwm_;
}
@@ -532,16 +525,17 @@ private:
float maxHeatStopTemp(float avgTempC) const {
if (!shouldLimitMaxCorner(avgTempC)) {
return EMERGENCY_MAX_TEMP_C;
return emergencyCutoffC();
}
return EMERGENCY_MAX_TEMP_C - CORNER_STOP_MARGIN_C;
return emergencyCutoffC() - CORNER_STOP_MARGIN_C;
}
float allowanceFromMaxCorner(float maxTempC, float avgTempC) const {
const float cutoffC = emergencyCutoffC();
if (!shouldLimitMaxCorner(avgTempC)) {
// Heat-up: only taper when a hot corner nears the emergency ceiling
if (maxTempC >= EMERGENCY_MAX_TEMP_C - 3.0f) {
const float headroom = EMERGENCY_MAX_TEMP_C - maxTempC;
// Heat-up: only taper when a hot corner nears the dynamic cutoff
if (maxTempC >= cutoffC - 3.0f) {
const float headroom = cutoffC - maxTempC;
return clampPercent((headroom / 3.0f) * 100.0f);
}
return 100.0f;
@@ -560,38 +554,6 @@ private:
return clampPercent((headroom / MAX_TEMP_HEADROOM_C) * 100.0f);
}
float heaterMaxDuty(float avgTempC) const {
if (avgTempC >= targetTempC_) {
return HEATER_MAX_DUTY_NEAR;
}
const float below = targetTempC_ - avgTempC;
if (below >= HEATER_COLD_BELOW_C) {
return HEATER_MAX_DUTY_COLD;
}
if (below >= HEATER_WARM_BELOW_C) {
return HEATER_MAX_DUTY_MID;
}
return HEATER_MAX_DUTY_NEAR;
}
float applyHeaterRamp(float requestedDuty, float avgTempC, uint32_t nowMs) {
const float maxDuty = heaterMaxDuty(avgTempC);
if (requestedDuty > maxDuty) {
requestedDuty = maxDuty;
}
if (lastHeaterUpdateMs_ > 0 && requestedDuty > heaterDutyPercent_) {
const float dt = static_cast<float>(nowMs - lastHeaterUpdateMs_) / 1000.0f;
const float maxUp = heaterDutyPercent_ + HEATER_SLEW_UP_PER_S * dt;
if (requestedDuty > maxUp) {
requestedDuty = maxUp;
}
}
return requestedDuty;
}
void applyHeaterBurst(uint32_t nowMs) {
if (heaterDutyPercent_ <= 0.0f) {
forceHeaterOff();