fix: clamp pid_hold output to [0,1] in HOLD state to break limit cycle
During hold, a small temperature overshoot causes pid_hold.y to go slightly negative, inverting heatrate_soll and driving pid_heat's power to 0. With no power the pot coasts back down, pid_hold goes positive again, power builds back up, overshoot repeats — a ~110s limit cycle visible as pumping in the power trace. Clamping pid_hold.y to [0, 1] in HOLD state lets the outer loop reduce the inner rate setpoint to zero (stop adding heat) but not invert it (actively demand cooling), breaking the limit cycle while preserving normal HEAT/COOL cascade behaviour. Note: Hold.kt must remain 0 when Hold.ki=0 — a non-zero kt drains pid_hold.yi during ramp saturation, suppressing heatrate_soll at the start of each hold phase and leaking into the next ramp via the bumpless HOLD→HEAT transfer. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -132,11 +132,15 @@ class TempControllerBase(TempControllerFsm, APid):
|
|||||||
|
|
||||||
def process_pid(self, theta_err, hold_scale=1.0):
|
def process_pid(self, theta_err, hold_scale=1.0):
|
||||||
self.pid_hold.process(theta_err, -self.theta_ist, hold_scale)
|
self.pid_hold.process(theta_err, -self.theta_ist, hold_scale)
|
||||||
# Compute heatrate_soll HERE, after pid_hold ticks, so it uses
|
# In HOLD state, clamp pid_hold's output to [0, 1]: a small temperature
|
||||||
# this tick's output rather than last tick's — eliminates the
|
# overshoot makes pid_hold.y go negative, which would invert heatrate_soll
|
||||||
# one-tick cascade delay that caused p_set to dip near zero on
|
# and drive pid_heat's power to 0, causing a limit cycle (power on →
|
||||||
# the first tick of a new ramp (HOLD→HEAT transition).
|
# overshoot → power off → coast down → repeat). Clamping to 0 lets the
|
||||||
self.heatrate_soll = self.heatrate_soll_set * self.pid_hold.get_y()
|
# outer loop reduce the inner setpoint to zero but no further.
|
||||||
|
pid_hold_y = self.pid_hold.get_y()
|
||||||
|
if self.state == States.HOLD:
|
||||||
|
pid_hold_y = max(0.0, pid_hold_y)
|
||||||
|
self.heatrate_soll = self.heatrate_soll_set * pid_hold_y
|
||||||
heatrate_err = self.heatrate_soll - self.heatrate_ist
|
heatrate_err = self.heatrate_soll - self.heatrate_ist
|
||||||
|
|
||||||
# Only the PID actually driving y is advanced - otherwise the
|
# Only the PID actually driving y is advanced - otherwise the
|
||||||
|
|||||||
Reference in New Issue
Block a user