PID anti-windup: conditional integration (back-calculation lite)

This commit is contained in:
2026-10-10 10:12:42 +08:00
parent 30540d884d
commit 5b24b956d2
2 changed files with 92 additions and 13 deletions
+16 -13
View File
@@ -91,21 +91,24 @@ static void pid_compute(void) {
return; return;
} }
/* 位置式 PID */ /* 位置式 PID(带抗积分饱和):输出饱和时积分仅沿退饱和方向更新,
防止升温段积分顶满后长时间维持 100% 输出(此前实测目标 50℃ 冲到 59℃) */
float err = s_target - t; float err = s_target - t;
s_integral += err; float u_noi = HEATER_PID_KP * err
{ + HEATER_PID_KD * (err - s_prev_err); /* 比例 + 微分 */
/* 积分限幅(抗饱和):积分项贡献不超过满量程 */ float integ_new = s_integral + err;
float imax = 100.0f / HEATER_PID_KI; float u = u_noi + HEATER_PID_KI * integ_new;
if (s_integral > imax) s_integral = imax;
if (s_integral < -imax) s_integral = -imax;
}
float deriv = err - s_prev_err;
s_prev_err = err;
float u = HEATER_PID_KP * err + HEATER_PID_KI * s_integral + HEATER_PID_KD * deriv; if (u > 100.0f) {
if (u < 0.0f) u = 0.0f; /* 降温靠自然散热,输出不为负 */ u = 100.0f;
if (u > 100.0f) u = 100.0f; if (err < 0.0f) s_integral = integ_new; /* 超上限:仅误差为负(降温方向)才吸收 */
} else if (u < 0.0f) {
u = 0.0f;
if (err > 0.0f) s_integral = integ_new; /* 超下限:仅误差为正(升温方向)才吸收 */
} else {
s_integral = integ_new; /* 未饱和:正常积分 */
}
s_prev_err = err;
/* 输出量化到 10% 档位(四舍五入) */ /* 输出量化到 10% 档位(四舍五入) */
s_duty10 = (uint8_t)((u + (float)HEATER_PWM_STEP_PCT / 2.0f) / (float)HEATER_PWM_STEP_PCT); s_duty10 = (uint8_t)((u + (float)HEATER_PWM_STEP_PCT / 2.0f) / (float)HEATER_PWM_STEP_PCT);
+76
View File
@@ -0,0 +1,76 @@
# -*- coding: utf-8 -*-
"""FilmForge PID anti-windup verification: target 50C, track steady-state."""
import serial
import time
import threading
def sum8(seq):
return sum(seq) & 0xFF
def frame(cmd):
p = cmd.encode()
body = bytes([0xD5, 0x01, len(p)]) + p
return body + bytes([sum8(body[1:])])
rx = bytearray()
stop = threading.Event()
def reader(buf, handle):
while not stop.is_set():
data = handle.read(256)
if data:
buf.extend(data)
handle.close()
def frames(buf):
out, i, b = [], 0, bytes(buf)
while i < len(b):
if b[i] == 0xB5 and i + 3 < len(b):
total = 3 + b[i + 2] + 1
if i + total <= len(b):
out.append(b[i:i + total]); i += total; continue
i += 1
return out
s5 = serial.Serial('COM5', 115200, timeout=0.5)
t = threading.Thread(target=reader, args=(rx, s5))
t.start()
time.sleep(0.5)
def cmd(c, wait=0.8):
rx.clear()
s5.write(frame(c))
time.sleep(wait)
fs = frames(rx)
payload = [f[3:3+f[2]].decode('ascii','replace') for f in fs if len(f) >= 4 and f[1] == 0xF0]
print('[%s] resp: %s' % (c, payload if payload else '<none>'))
cmd('M4S50')
cmd('M3S1')
cmd('M2S500')
print('--- heating to 50C, 150s sampling ---')
t0 = time.time()
last_print = -10
peak = 0
while time.time() - t0 < 150:
time.sleep(2.0)
fs = frames(rx)
vals = []
for f in fs:
if len(f) >= 4 and f[1] == 0xF0 and f[2] >= 2:
try:
vals.append(float(f[3:3+f[2]]))
except ValueError:
pass
if vals:
peak = max(peak, max(vals))
dt = time.time() - t0
if dt >= last_print + 10 and vals:
print(' t+%03ds %.1f C (peak %.1f)' % (dt, vals[-1], peak))
last_print = dt - (dt % 10)
cmd('M3S0', wait=1.0)
cmd('M2S0')
stop.set(); t.join()
print('PEAK=%0.1f (target 50, overshoot %+.1f)' % (peak, peak - 50))
print('DONE')