generated from hulk/gd32f4xx_template_cmake_vscode
Heater: PID v0.1.14 - banded controller + sensor glitch guards
Rebuild PID as three-zone banded controller: far field P/D full power,
This commit is contained in:
@@ -81,6 +81,182 @@
|
||||
- 协议要点:全幂等(ack 丢可重发)、响应超时建议 300ms×3 次、温度一律 ASCII
|
||||
- **PID 抗积分饱和**(5b24b95):条件积分(back-calculation lite)抑制输出限幅
|
||||
期间的积分累积。
|
||||
- **PID 全面重写(2026-10-15,待真机验证)**——真机 30℃ 阶跃 +2.4℃ 过冲、
|
||||
30~60s 周期衰减震荡(GUI 截图)驱动,三个根因全部修复:
|
||||
① `s_prev_err` 在 err>=0 路径从不更新(c9aa416 early-return 重写时把末尾的
|
||||
`s_prev_err = err` 弄丢)→ D 项退化为 +Kd*err 正向比例,"微分阻尼"从未生效,
|
||||
且把抗饱和判据钉在饱和态冻死积分(仿真中大阶跃贴温和带边缘极限环、到不了
|
||||
目标);改**微分基于温度变化率(D on measurement,-Kd*ΔT/拍)**,`s_prev_temp`
|
||||
全路径刷新,u 钳 [0,100] 防负值量化回绕;
|
||||
② 积分无界(err<0 每拍 -|err| 无下限)→ 过冲/下调目标后积分深负,温和带内
|
||||
u=min(25,u_pd)+Ki*I 永远 <=0 → 加热长时间锁死、跌出温和带才全功率(极限环);
|
||||
改**积分钳 [0, HEATER_PID_I_MAX=2000]**(Ki*I_MAX=100%);
|
||||
③ 12ba9de 的目标斜坡在 c9aa416 被整段删除 → 恢复 `HEATER_RAMP_CPS 0.8℃/s`
|
||||
+ 使能时从当前温度起坡(`s_ramp_target`,升/降同限速);
|
||||
另修三项:④ 抗饱和判据改看**最终输出** u(原先看温和带重算前的 u);⑤ PWM
|
||||
相位**仅升档复位**(原先降档也复位,降占空反而补发一整段 ON 脉冲,系统性
|
||||
多给热);⑥ NTC 故障 **3 拍(1.5s)去抖确认**——真机曲线两个孤立 OPEN 毛刺
|
||||
每次把 PID 状态整个清零也是震荡推手(毛刺拍保持输出不动;注意毛刺根因在
|
||||
采样/硬件侧仍待查,GUI 按原始采样报故障不变)。
|
||||
离线仿真(`sim_pid_check.py`:一阶热容+传感器滞后模型、2s/5% 慢 PWM 执行器、
|
||||
旧/新同模型对比):30℃ 阶跃到达后波动 ±0.4℃→+0.04℃;50℃ 阶跃旧版极限环
|
||||
到不了目标 vs 新版 94s 到达零过冲;50→40 下调 44s 恢复、波动 <±0.6℃。
|
||||
固件版本升 **V0.1.0**(M999 报 `v41.0.1.0`),已烧录上板(OpenOCD GD-LINK,
|
||||
dump_image 读回与 elf 逐字节比对一致;注意该 xpack OpenOCD 的
|
||||
flash write/verify 回显日志会丢失,核验用 dump_image+文件比对)。
|
||||
设计说明:保持**位置式** PID——执行器是绝对占空设定,分段强制关断/使能切换/
|
||||
故障复位在位置式下最干净;增量式的抗饱和优势已由积分钳位+条件积分覆盖。
|
||||
- **PID V2 抗震荡迭代(2026-10-15,V0.1.1 已烧录待真机确认)**——V0.1.0 真机
|
||||
复测:过冲/OPEN 毛刺问题已消(峰 +0.4~0.5、无故障标记),但目标附近仍有
|
||||
**±0.7~1.0℃/15~20s 三角波极限环**(50℃ 目标实测截图)。根因:err<0 硬切
|
||||
0% + err>=0 仅 Kp*err,稳态维持功率(~10% 级)全靠 Ki=0.05 慢填(要几分钟)
|
||||
→ 目标附近等效"P+上侧死区"继电器,叠加热滞后即极限环。V2 改法(`heat.c`
|
||||
pid_compute 重写核心):
|
||||
① **连续 PID 覆盖全误差区间**——去掉 err<0 硬切,过目标后 P 负向顶、积分
|
||||
正向维持,输出钳 [0,100] 天然处理"只能加热";
|
||||
② **Ki 0.05→0.25**(稳态功率 ~1 分钟内建立;扫参显示 0.5 在传感器滞后 8s
|
||||
工况下积分环失稳 ±0.9、1.0 更差,0.25 全工况稳定);
|
||||
③ **积分吸收限窄带**:仅 |err|<HEATER_PID_INTEG_ZONE_C(2℃) 且 P/D 未被
|
||||
钳(u_pd_raw∈(0,cap))时吸收——爬升/温和带钳位段不积分,防瞬态 windup
|
||||
(Ki 提快后若爬升段照吸,30℃ 阶跃仿真冲到 36℃);err<0 且 u>0 照常有界
|
||||
回退;I_MAX 同步 2000→400(Ki*I_MAX=100% 不变)。
|
||||
仿真(`sim_pid_check.py`,V1/V3 对比 + 参数扫描 `python sim_pid_check.py
|
||||
sweep`,基准/恶劣两工况):恶劣工况 50℃ 阶跃到达后 [49.70,50.11](过冲
|
||||
+0.11℃)、末端残余 ±0.02℃;30℃ 阶跃末端 ±0.01~0.02℃;50→40 下调 66s
|
||||
恢复 ±0.02℃。已烧录 V0.1.1(M999 报 `v41.0.1.1`,dump 比对核验一致)。
|
||||
- **PID V3 修"突然飙升 4℃"(2026-10-15,V0.1.2)——DAPLink 旁路抓证定位**:
|
||||
V0.1.1 真机稳态后温度以 0.12℃/s 平滑飙升 +2~4℃ 再回落。定位手段(可复用):
|
||||
① COM5 脚本探针(`probe_once.py`/`probe_state.py`)确认目标 50/加热 ON,
|
||||
并现场抓到 **14 帧连续 OPEN(~1.4s)**——NTC 间歇开路真实存在(硬件侧
|
||||
待修);② **OpenOCD `mrw` 旁路观测**(`observe2.tcl`+`obs_decode.py`,
|
||||
每 500ms 不 halt 读 RAM:duty/integral/ramp/target/temp——注意该 xpack
|
||||
OpenOCD 的 echo/mdw 控制台回显会丢失,须用 TCL 落文件)。全程轨迹证实
|
||||
**无故障参与的 90~120s 自持极限环**:积分 0↔65 打摆(真机 50℃ 维持功率
|
||||
~10-15%,即 I≈40~60 才是正确稳态),机理=**err<0 时 P 被钳 0、输出只剩
|
||||
Ki·I 恒定值**——过线后输出不随超温幅度收敛→温度必然冲过头→过冲把积分
|
||||
排空→排空后 sag→重建再过冲。NTC 毛刺清零积分只是点火器。V0.1.2 修复:
|
||||
① **err<0 时 P 负项+D 直接抵扣积分后钳 0**(输出随超温幅度立即收敛,
|
||||
积分按剩余需要维持→可收敛到稳态不再打摆);② **NTC 短故障(≤3s 且
|
||||
|ΔT|<2℃)无扰还原积分**(确认沿暂存 `s_fault_stash_I/T`,防毛刺清零
|
||||
引发 sag-重建-过冲链);③ I_MAX 400→200(Ki·I_MAX=50%,120℃ 模具维持
|
||||
余量足够)。判定测试基建:`test_run.py`(固定序列 M999→M2S500→M4S50→
|
||||
M3S1→N 秒→收尾;**预定义判据 J1 上报率/J2 稳态均值±0.5/J3 稳态极值±1.0/
|
||||
J4 全程 max≤52/J5 无故障**,>54℃ 自动熔断 M3S0——开发/测试分工,脚本
|
||||
自动判定避免自判自;注意 FF485 frame.payload 已是 str)。
|
||||
- **PID V3 判定迭代与收敛(V0.1.2→V0.1.3,2026-10-15,900s 判定测试)**:
|
||||
- V0.1.2(P-veto + 故障无扰还原 + I_MAX 200)判定:J1/J2/J5 PASS,但
|
||||
**J3 FAIL(稳态窗 [46.6,52.1])、J4 FAIL(max 52.5)**——P-veto 消灭了
|
||||
积分排空式打摆(观测器证实收敛末段积分稳在 ~48=真实维持功率、温度
|
||||
±0.2),但双热容被控对象(鳍片+本体,NTC 只见鳍片)每次顶/谷都给
|
||||
对方"预存"动量,15 分钟才收敛;且斜坡 0.8℃/s 远快于对象,爬升段误差
|
||||
长期 ~2℃ 把积分提前充到 30~50,首冲 +2.5℃。
|
||||
- V0.1.3:**HEATER_RAMP_CPS 0.8→0.25**(爬升误差贴地、积分只在近目标
|
||||
低功率段建立)+ **HEATER_SLOWBAND_DUTY 25→15**(近目标动作放柔,
|
||||
少给鳍片预存过热)。41℃ 冷启判定:**J1~J5 全 PASS**——到达 69s、
|
||||
首冲 +1.20℃、全程 max 51.2、稳态窗 [49.0,50.9]、均值 49.95、
|
||||
1795 帧 0 故障。观测器同步确认:远场(err>7)才见 75% 满功率,
|
||||
温和带内 15% 缓爬,50.06℃ 到达时积分 35.9/占空 5%。
|
||||
- 测试基建沉淀:`test_run.py`(判定+熔断)、`observe2.tcl`+`obs_decode.py`
|
||||
(DAPLink 旁路,**注意符号地址随 bss 布局变,重编译后须 nm 重取**;
|
||||
观测文件被占用时追加不清空)、`sim_pid_check.py`(含 V1/V3/V4 对照、
|
||||
毛刺/噪声注入、扫参)。CMD 坑:`if exist X del X && ...` 文件不存在时
|
||||
整链跳过;多行 `python -c` 偶发被吞——写文件执行。
|
||||
- **遗留(硬件侧)**:NTC 间歇开路(现场抓到 14 帧连续 OPEN ~1.4s,
|
||||
短故障无扰还原已缓解控制影响,但根因在采样/接触)——查 NTC 焊接/
|
||||
压接/走线,必要时 ntc.c 采样侧加防护。独立代码审查 agent 未在时限内
|
||||
返回,本轮定位以仪器化数据(COM5 帧流 + DAPLink 内存轨迹)为准。
|
||||
- **PID 调试全程速览(2026-10-15,V0.1.0→V0.1.14,判定测试自动出结论)**:
|
||||
|
||||
| 版本 | 关键改动 | 判定/实测结果 | 结论 |
|
||||
|---|---|---|---|
|
||||
| V0.1.0 | 全面重写:D-on-measurement+积分钳位+斜坡恢复+故障去抖 | 过冲 +2.4→+0.4 | 旧版 D 项失效/积分无界/斜坡缺失三根因修复 |
|
||||
| V0.1.1 | Ki 0.05→0.25+窄带吸收 | ±1℃ 三角波极限环 | err<0 硬切=继电器死区 |
|
||||
| V0.1.2 | P 参与过线区+故障无扰还原+I_MAX200 | J3/J4 FAIL | P-veto 生效但收敛 15min |
|
||||
| V0.1.3 | 斜坡 0.25C/s+温和带 15% | J1~J5 全 PASS(旧判据) | 稳态 [49.0,50.9] 不满足 ±0.5 |
|
||||
| V0.1.4 | 占空抖动 1.25% 分辨率 | p2p=1.8 FAIL | 量化非主因 |
|
||||
| V0.1.5 | Kp5/Kd60/Ki0.15 连续 PID | p2p=4.2 FAIL | ±2C/90s 慢摆,调参到头 |
|
||||
| V0.1.6 | 分区 bang-bang+慢积分 | 卡 48.6C FAIL | 带外平衡点陷阱 |
|
||||
| V0.1.7 | 带外也吸收 | p2p=4.1 FAIL | 吸收/回退不对称→I 上漂 90 |
|
||||
| V0.1.8 | 对称慢速积分 | p2p=4.0 FAIL | 积分稳了;秒级读数坏值穿透 |
|
||||
| V0.1.9 | 斜率限幅+带内退闸 | 无法判定 | NTC 持续开路(后重焊恢复) |
|
||||
| V0.1.10 | 去上限+RAMP2.0+温和带25% | p2p=2.7 FAIL | 快斜坡生效;坏值仍是主力 |
|
||||
| V0.1.11 | NTC 5 点滑动中值 | **p2p=1.30** | 中值滤波立竿见影 |
|
||||
| V0.1.12 | 近场 ASSIST 减速曲线 | 实时 p2p=**0.31** | 最后 2C 推力真空修复;判定窗被传感器事件污染 |
|
||||
| V0.1.13 | 大偏差冻结(>1.5C) | p2p=1.60 | 塌陷深谷消除(47.1→49.0) |
|
||||
| V0.1.14 | 对称积分加倍 0.30 | p2p=1.80 | 与 13 同水平=硬件平台期 |
|
||||
|
||||
**最终状态(V0.1.14 在板)**:到达 26s(30→50C,提速 9 倍);控制环本体
|
||||
(无事件段)p2p 0.3~0.9C 达标;判定窗残余 1.6~1.8C 全部来自 NTC 焊接点
|
||||
秒级接触不良(-2.5C/4.5s 塌陷、顶格 52.2C 卡死 10s,1.5C 以内坏值仍会穿透
|
||||
三重滤波)。**下一步:彻底重焊/更换 NTC(根治手段),固件三重防护
|
||||
(中值+斜率限幅+大偏差冻结)已就位,重焊后 `python test_run.py 1200` 一键
|
||||
重判(J3 即 ±0.5 规格)。测试环境 55C 上限已按用户要求移除(真机靠物理
|
||||
温度开关)。**
|
||||
- **±0.5℃ 波动压榨迭代(2026-10-15,V0.1.3→V0.1.6,用户规格 ±0.5C,
|
||||
判据 J3 收紧为稳态窗 [目标±0.5])**——V0.1.3 稳态窗 [49.0,50.9](压着
|
||||
旧判据 ±1.0 过)不可接受,连续三轮仪器化迭代:
|
||||
- **V0.1.4 占空抖动(Bresenham)**:载波 2s=4 拍分摊档位余数,平均分辨率
|
||||
5%→1.25%(真机维持功率 ~10-12% 恰卡在 10/15 档间,固定取整迫使
|
||||
0↔15% 满幅切换)。观测器证实抖动生效(duty 5/10/15/20/25 细粒度),
|
||||
**但极限环未消(J3 p2p=1.8 FAIL)→ 量化不是主因**。
|
||||
- **V0.1.5 降增益强阻尼(Kp 10→5 / Kd 30→60 / Ki 0.25→0.15 / 窄带 2→1)**:
|
||||
**J3 p2p=4.2 FAIL**——变成 ±2C/90s 慢摆。观测器抓到机理:带内半周期
|
||||
净吸积分 +18(err 0.5~1.0 持续 30s),过线后 P-veto 每拍泄 2.8 快速
|
||||
排空 → 维持功率记忆反复丢失 → 谷底深潜 48.6。**连续 PID 在本对象
|
||||
(双热容+单向执行器+0.1C 读数量化)上调参到头**;Kd=60 还放大 NTC
|
||||
0.1C 量化跳变成 ±6% duty 抖动。
|
||||
- **V0.1.6 分区 bang-bang + 慢积分学习(结构性换型)**:作用分三区——
|
||||
带内 |err|<HEATER_PID_BAND_C(0.4C):P/D 退出,u=Ki*I 恒功率,积分慢速
|
||||
学习维持功率(I+=err*0.3/拍);带外下方:P 拉回(温和带钳位不变),
|
||||
积分不吸收;带外上方:关断 + I 固定 0.3/拍限速回退(保住维持功率
|
||||
记忆)。波动幅度被物理限制在带宽+滑行量,I 学准后驻留带内。
|
||||
Kd 回 30(60 的噪声放大不可用)、I_MAX 回 400(Ki*I_MAX=60%)。
|
||||
- **V0.1.6 判定 FAIL(带外平衡点陷阱)**:观测器抓到温度卡 48.6C——P 输出
|
||||
恰好平衡散热、err 停带外,积分只在带内学习永远没机会介入。V0.1.7 修复
|
||||
(带外下方 P 未被钳时同样慢速吸收):前半程良好(49.8/integ 37 稳),但
|
||||
**吸收(0.3/拍)与回退(0.3/拍固定)不对称 → I 上漂到 90 持续超供**,后半程
|
||||
大摆(J3 p2p=4.1 FAIL)。V0.1.8 改全区域对称慢速(GAIN=BLEED=0.15):
|
||||
积分不漂了(35~42 窄幅)、带内占有率 44%,**但 J3 仍 FAIL(p2p=4.0)**——
|
||||
csv 分析抓到 **-1.2C/0.5s 读数突跳 + 5s 后 +1.8C 回弹**(非故障级 NTC
|
||||
毛刺,真实热速率 <=0.2C/拍),毛刺触发 25% 猛拉 ~10s 直接制造 +2C 过冲。
|
||||
- **V0.1.9(待判定)**:① NTC 读数斜率限幅(HEATER_NTC_SLEW_C=0.5C/拍,
|
||||
超限沿限幅值过渡、连续 2 拍放行——毛刺对环路的冲击源头掐断);② 带内
|
||||
功率线性退闸(下缘 100% 维持量 -> 上缘 0%,过线前功率已趋零,蓄热滑行
|
||||
收窄)。已烧录 MATCH。
|
||||
- **NTC 硬件恶化为持续开路(阻塞判定)**:V0.1.9 判定测试全程 M1/M2 报
|
||||
OPEN(帧流正常、命令全 ok),加热安全关断(故障路径实测有效)。用户
|
||||
重焊后恢复(30.1C 稳定读数),进入 V0.1.10~V0.1.14 快速迭代:
|
||||
- **V0.1.10 去上限 + 提速**:测试脚本去掉 54C 自动熔断/J4 上限判据(用户
|
||||
明确:真机靠物理温度开关,55C 只是测试环境约束);RAMP 0.25→2.0(实测
|
||||
0.25 时斜坡本身就是瓶颈,9C 走 36s);温和带 15%→25%。判定:reach 49s
|
||||
但 J3 p2p=2.7 FAIL、首冲 +3.3。
|
||||
- **V0.1.11 NTC 5 点滑动中值**(ntc.c 采样侧):V0.1.10 稳态摆的根源确认为
|
||||
秒级读数坏值穿透斜率限幅(中值滤波只挡 <250ms 毛刺)。效果显著:
|
||||
稳态 p2p 4.0→**1.30**([49.40,50.70])。温和带 7→3C、25→35%。
|
||||
- **V0.1.12 近场 ASSIST 减速曲线**:分段速率分析钉死最后 2C 爬 141s 的瓶颈
|
||||
=近场推力真空(P 随 err 缩小消失、I 未成熟)。近场扩展区 [-0.3,3.0)
|
||||
合并退闸+缓爬:u = Ki*I + ASSIST 退闸曲线 + Kd,上限 NEAR_MAX_DUTY 50%。
|
||||
实时观测 p2p **0.31C**(达标水平);但判定窗两极值(47.1 塌陷 + 52.2
|
||||
顶格卡死)均为传感器事件:**-2.5C/4.5s 塌陷与顶格 10s,焊接点仍不稳**。
|
||||
- **V0.1.13 大偏差冻结**:滤波值与原始读数偏差 >1.5C(HEATER_NTC_JUMP_C)
|
||||
冻结不跟随,回弹自动恢复(真实冷却 ~0.05C/拍永不误触发)。判定 p2p
|
||||
1.60([49.00,50.60])——塌陷深谷消除。reach 32s(提速 7 倍)。
|
||||
- **V0.1.14 对称积分加倍**(GAIN=BLEED 0.15→0.30,I 充满 400s→200s,修复
|
||||
判定窗内驻留点偏低):p2p 1.80([49.20,51.00]),与 V0.1.13 同水平
|
||||
(差异在传感器事件噪声内)——**到达当前硬件条件下的平台期**。
|
||||
- **当前结论**:控制环本体(无事件段)p2p≈0.3~0.9 达标;残余 1.6~1.8 全部
|
||||
来自 NTC 焊接点秒级接触不良(塌陷/顶格事件,1.5C 以内的坏值仍会穿透
|
||||
防护)。**下一步是彻底重焊/更换 NTC 及其引线**,固件侧三重防护
|
||||
(中值+斜率限幅+大偏差冻结)已就位。到达速度 26s(30→50C,提速 9 倍)。
|
||||
测试/观测基建:test_run.py(无熔断版,J1/J2/J3±0.5/J4 无故障自动判定)、
|
||||
observe2.tcl+obs_last.py(**每次重编译必须 nm 重取符号地址**)、
|
||||
seg_rate.py(分段爬升速率)、log_tail.py(实时帧解析)。
|
||||
中间数据(obs_samples.txt/test_data.csv/test_events.log)已清理,
|
||||
由测试/观测脚本重新生成。
|
||||
- 方法论沉淀:单状态热模型仿真始终未复现真机极限环(双热容建不进单
|
||||
状态),本轮所有定位直接以 DAPLink 观测器 + COM5 判定脚本为准;
|
||||
观测符号地址 V0.1.4 起稳定(s_duty=0x20000459 / s_integral=0x20000454 /
|
||||
s_ramp_target=0x2000045c / s_target=0x20000460 / s_enable=0x20000464)。
|
||||
- **50℃→40℃ 阶跃验证脚本**(`temp_test_com.py`,工作区修改未提交):M2S500
|
||||
全曲线采样,输出超调/settle 时间/稳态误差/降温段统计(结果以实跑为准)。
|
||||
- **电源保持 PA11 实装**(`pwr_hold.h/pwr_hold.c`):上电最先拉高 PA11 维持
|
||||
@@ -122,8 +298,12 @@
|
||||
- [ ] **适配器更换**:换可靠的 USB-RS485 适配器(当前自动方向切换间歇丢响应)。
|
||||
- [x] **加热驱动 + PID 闭环**(PB12 慢 PWM 1s/10% 步进 + PID 500ms 周期,
|
||||
安全保护齐全;实测 30℃ 闭环通过:30s 爬升、超调 1.4℃、60s 收敛中)。
|
||||
- [ ] **PID 参数整定**:初值 Kp=8/Ki=0.05/Kd=0(board_config.h 宏),按真实
|
||||
工况温度(80~120℃)与负载重新整定,抑制 ~7% 超调。
|
||||
- [ ] **NTC 彻底重焊/更换(当前头号事项)**:焊接点秒级接触不良已用
|
||||
DAPLink+COM5 数据链钉死(-2.5C/4.5s 塌陷、顶格卡死、曾持续开路),
|
||||
是 ±0.5C 规格最后的拦路虎;固件三重防护已就位(V0.1.14)。
|
||||
- [ ] **±0.5℃ 规格复判**:NTC 重焊后 `python test_run.py 1200`(J3=±0.5);
|
||||
当前稳态控制环本体 p2p 0.3~0.9C 已达标。真实工况温度(80~120℃)下
|
||||
需复测整定(Kp=8/Ki=0.15/Kd=30、ASSIST 35%、I_MAX400,board_config.h)。
|
||||
- [x] **专用 GUI 上位机 v0.2.0**:v0.1.0 已完成 COM5 真机全链路验证;v0.2.0 与
|
||||
实装固件对齐——去除"固件预留/仅软件状态"旧表述(协议模块预设说明、温控
|
||||
卡片标题/备注、开启加热确认弹窗改为真实控温警示、ack 提示),GUI README
|
||||
@@ -140,7 +320,7 @@
|
||||
"未连接串口"丢弃)。selftest 全过(新增 8 项回归用例);已打包冒烟并发布
|
||||
Gitea Release `FilmForge-GUI-v0.2.1`(无应用图标——`logos/filmforge.ico`
|
||||
仍缺位)。
|
||||
- **GUI v0.2.2~v0.2.4(已修复待真机确认,未发布)**:
|
||||
- **GUI v0.2.2~v0.2.13(递进修复/新功能,Gitea 发布待确认)**:
|
||||
- v0.2.2:串口线程 `read(256)` 在持续帧流下攒满 256 字节才返回(win32 字节
|
||||
间隔超时因帧间隔更短永不触发,100ms 上报 ≈2.5s 一批),曲线/命令/响应全部
|
||||
3 秒一卡、一股脑一大坨——真机日志确认每簇 ≈25 帧、间隔恒 3s。已改
|
||||
@@ -185,8 +365,50 @@
|
||||
_last_set 单槽位罕见误补偿(8s 自愈)、build_exe remove_stale 无提示强杀
|
||||
运行实例、M888 后固定 2s 重查可能早于固件就绪、_danger_reset 把去重忽略记成
|
||||
"已下发"。协议建议的 300ms×3 自动重发维持不做(设计决策,用户拍板)。
|
||||
- selftest 全过(累计新增 15 项回归用例,含窗口图标非空检查);exe 已本地
|
||||
打包冒烟,Gitea 发布待用户确认后执行。
|
||||
- v0.2.10:**曲线 Y 轴手动量程 + "暂无数据"提示不消失修复**——①"实时温度曲线"
|
||||
卡新增 Y 轴行:下限/上限输入框 + 自动勾选钮(默认勾选=随可见数据自适应;
|
||||
取消勾选=按输入框固定量程,非法输入回退最近一次自适应范围并经状态栏提示;
|
||||
首次切手动按当前自适应范围预填平滑接管;固定量程下曲线越界 setClipRect
|
||||
裁剪回绘图区、当前值标记超量程不画,实时读数卡仍可见)。模式+量程随
|
||||
`filmforge_gui.json` 持久化(y_axis 键,存值非法一律回自动)。
|
||||
②提示文字不消失根因(用户标注截图):`set_source` 仅在构建期调用一次且在
|
||||
那里缓存 `_has_data` 布尔——数据经同一列表引用追加后该布尔永不刷新 → 改为
|
||||
paintEvent 实时判定(图表本就持有引用,无需其他调用点适应)。selftest 新增
|
||||
15 项回归(切换往返/兜底预填/无效回退/量程落地/越界条带像素=0/新图数据到位
|
||||
提示像素=0;断言须取当前主题曲线色、上条带避开 y=9 行图例色块),总数 74
|
||||
全过;离屏整窗预览目检(手动/自动对照)布局正常。
|
||||
- v0.2.11:**Y 轴量程控制改右缘滑条(用户要求放右侧、滑条形式)**——移除
|
||||
v0.2.10 的下限/上限输入框行,"Y轴自动/手动"勾选钮移到暂停/清空行右端;
|
||||
TemperatureChart 右缘新增滑条栏(GUTTER_W 18px:轨道+量程带+双把手):
|
||||
上把手=上限、下把手=下限、把手中带拖拽=整段平移(跨度保持)、点击轨道
|
||||
空白把对应边界直接设到该处;把手在**参考域**(全量数据范围 ±15%,`_y_ref`)
|
||||
上映射像素,auto 态把手半透明、首次任意拖拽即切手动(`yManualEngaged`
|
||||
同步按钮态+配置),松手量化 0.1℃ 经 `yRangeChanged` 记设置(最小间距
|
||||
0.5℃),按钮一键回自动;越界裁剪沿 v0.2.10 不变。坑两条(勿重蹈):
|
||||
①拖拽中的界值必须取**实时显示范围**而非 `_cached_layout` 绘制缓存——
|
||||
连续两次拖拽之间没有重绘,缓存是陈旧范围(实测 pan 把量程整段拖跳);
|
||||
②selftest 固件用真实 `filmforge_gui.json` 时"初始自动"断言会被用户存的
|
||||
手动模式击穿——改指向临时目录设置文件彻底隔离。
|
||||
selftest 重写第 8 段共 81 项全过(把手命中/拖拽切手动/量化/最小间距/
|
||||
平移保跨度/裁剪条带/提示消失);离屏手动/自动滑条目检通过。
|
||||
- v0.2.12:**滑条域定值 -20~120℃ + 标题行并排(用户截图标注 3 处)**——
|
||||
①滑条参考域从"全量数据 ±15%"改为**固定 -20~120℃**(Y_RAIL_LO/HI 宏):
|
||||
把手像素位置直接对应 ℃ 读数,空数据/冷机不再出现 -5.0 这类随数据漂移的
|
||||
误导把手位;命中/拖拽/钳位全部按此域,140℃ 域上 1px≈0.53℃(精度靠点击
|
||||
轨道空位直达边界;selftest 拖拽断言改为"目标值反算像素",不再依赖域宽);
|
||||
②暂停/清空并入卡片标题行(实时温度曲线 [暂停][清空]…[Y轴手动][2分钟]
|
||||
一行放齐,两组按钮不再各占一行悬在半空;顺带清掉 v0.2.11 重构残留的
|
||||
孤儿按钮组代码;此卡不走 self._card 通用头,内联构建支持多 trailing);
|
||||
③实测/目标图例移到绘图区左上(右侧被滑条把手/值标签占用)。selftest
|
||||
81 项全过;离屏目检(空态/手动拖后)通过。
|
||||
- v0.2.13:**把手值标签悬停门控(用户标注:常驻会遮挡曲线)**——上下限
|
||||
两个数值标签不再常驻,仅鼠标悬停在滑条栏(或拖拽中)才显示
|
||||
(`_y_hover` 翻转才重绘;leaveEvent 途中拖拽不退出悬停态,松手按位置
|
||||
重算)。selftest 新增 2 项(未悬停标签区像素=0 / 悬停出现标签),共 83
|
||||
项全过。exe 重新打包见下。
|
||||
- selftest 全过(v0.2.13 后共 83 项回归);**v0.2.13 exe 已重新打包并冒烟
|
||||
通过**(release/FilmForge.exe + FilmForge-GUI-v0.2.13-win64.zip,
|
||||
依次取代 v0.2.10/v0.2.11/v0.2.12 包),Gitea Release 待用户确认后执行。
|
||||
遗留见其 README"已知事项":USB-RS485 适配器更换。
|
||||
- [x] **GUI 慢性卡顿修复(根因已定位并修复 v0.2.8,待真机确认)**:
|
||||
根因/机理/实测数据见上方 v0.2.8 条目(`cursor.Start` 实例级枚举访问在
|
||||
|
||||
+32
-6
@@ -152,12 +152,38 @@ void usart1_irq_handler(void);
|
||||
#define HEATER_PWM_PERIOD_MS 2000U /* PWM 载波周期(2s,载波 0.5Hz) */
|
||||
#define HEATER_PWM_STEP_PCT 5U /* 占空比步进(每档脉宽 = 100ms,维持最小开关频率约束) */
|
||||
#define HEATER_PID_PERIOD_MS 500U /* PID 计算周期 */
|
||||
#define HEATER_PID_KP 10.0f /* PID 参数:超温前退功率更快(配合超温强制关断) */
|
||||
#define HEATER_PID_KI 0.05f
|
||||
#define HEATER_PID_KD 30.0f /* 微分阻尼:按误差变化率提前刹车(抑制爬升惯性) */
|
||||
#define HEATER_SLOWBAND_C 7.0f /* 温和带宽度:距目标小于此值时占空上限受限
|
||||
(低速爬完最后一段,减少蓄热惯性过冲) */
|
||||
#define HEATER_SLOWBAND_DUTY 25U /* 温和带内占空上限(%) */
|
||||
#define HEATER_PID_KP 8.0f /* PID 参数:带外下方 P 拉回增益(带内 P 退出) */
|
||||
#define HEATER_PID_KI 0.15f /* 输出映射:u 贡献 = KI*I;I 全区域对称慢速学习 */
|
||||
#define HEATER_PID_KD 30.0f /* 微分(仅带外下方起作用):爬升段提前退功率 */
|
||||
#define HEATER_PID_BAND_C 0.3f /* 作用分区带宽:带内恒功率(Ki*I),带外
|
||||
开关+P 修正——波动幅度被物理限制在
|
||||
带宽+滑行量 */
|
||||
#define HEATER_PID_INTEG_GAIN_IN 0.30f /* 带内/带外下方积分学习增益(对称):
|
||||
I += err*GAIN;0.15 时 I 充满需 ~400s,
|
||||
判定窗内驻留点偏低 0.5C(ASSIST 补缺口
|
||||
但驻留偏欠温),加倍后 ~200s 到位 */
|
||||
#define HEATER_PID_INTEG_BLEED 0.30f /* 带外上方每拍固定回退(=GAIN,对称) */
|
||||
#define HEATER_NTC_SLEW_C 0.5f /* NTC 读数斜率限幅(C/PID 拍):单拍变化超过
|
||||
此值判为读数毛刺(真实热速率 <=0.2C/拍),
|
||||
沿限幅值过渡、连续 2 拍超限放行真实值 */
|
||||
#define HEATER_NTC_JUMP_C 1.5f /* 秒级坏值冻结阈值:斜率超限持续且累计偏差超过
|
||||
此值时冻结滤波值(真机焊接点接触不良会产出
|
||||
-2.5C/4.5s 塌陷与顶格卡死,中值滤波挡不住) */
|
||||
#define HEATER_PID_I_MAX 400.0f /* 积分钳位上限:KI*I_MAX = 60% 占空(120C 模具
|
||||
维持功率余量);下限恒为 0 */
|
||||
#define HEATER_FAULT_RESTORE_MAX_S 3.0f /* NTC 短故障(去抖确认后恢复)无扰还原积分的
|
||||
时长上限;超过则从零重建(安全优先) */
|
||||
#define HEATER_RAMP_CPS 2.0f /* 内部设定值斜坡速率(℃/s):实测 0.25 时斜坡
|
||||
本身就是瓶颈(9C 走 36s,加热器只是"追"设定点);
|
||||
2.0 基本不设障,到达速度由加热功率与温和带
|
||||
上限决定。分区控制器爬升段不积分,快斜坡
|
||||
不会像旧连续 PID 那样预充积分导致过冲 */
|
||||
#define HEATER_SLOWBAND_C 3.0f /* 温和带宽度:缓爬段(原 7C 太宽,25% 上限下
|
||||
净升温仅 0.08C/s,爬升 234s 的主瓶颈) */
|
||||
#define HEATER_SLOWBAND_DUTY 35U /* 近场缓爬辅助满档(%):err=+SLOWBAND_C 处
|
||||
的推力,随 err 线性退闸(提供与积分成熟度
|
||||
无关的确定爬升推力,修复最后 2C 爬 141s) */
|
||||
#define HEATER_NEAR_MAX_DUTY 50U /* 近场扩展区推力上限(%):快而不过冲 */
|
||||
#define HEATER_OVERTEMP_C 150.0f /* 过温强制关断阈值(滞回保护) */
|
||||
#define HEATER_OVERTEMP_HYST 5.0f /* 过温恢复滞回 */
|
||||
|
||||
|
||||
+4
-3
@@ -12,9 +12,10 @@ void heat_init(void);
|
||||
|
||||
/**
|
||||
* @brief 加热控制任务(在主循环中周期调用,主循环周期 10ms)。
|
||||
* @details 内部包含:慢 PWM 载波(1s 周期、10% 步进,脉宽恒 >=100ms)、
|
||||
* PID 计算(500ms 周期,输出量化到占空档位)、安全保护
|
||||
* (传感器故障强制关断 / 过温滞回保护 / 未使能强制关断)。
|
||||
* @details 内部包含:慢 PWM 载波(2s 周期、5% 步进,脉宽恒 >=100ms)、
|
||||
* 目标斜坡 + 连续 PID(500ms 周期,全误差区间连续输出、微分基于
|
||||
* 温度变化率、积分窄带吸收+钳位)、安全保护(传感器故障去抖确认后
|
||||
* 强制关断 / 过温滞回保护 / 未使能强制关断)。
|
||||
*/
|
||||
void heat_task(void);
|
||||
|
||||
|
||||
+175
-68
@@ -1,16 +1,26 @@
|
||||
/**
|
||||
* @file heat.c
|
||||
* @brief 加热控制:慢 PWM 驱动(PB12)+ 分段 PID 控温闭环 + 安全保护。
|
||||
* @brief 加热控制:慢 PWM 驱动(PB12)+ 斜坡设定值 + 分段 PID 控温闭环 + 安全保护。
|
||||
* @details 硬件:PB12=HEAT_EN,高 = 加热开(2N7002K 电平转换 → VBE2412 PMOS → 24V)。
|
||||
* 开关频率约束(<=10Hz)由"2s 载波周期 + 5% 占空步进"保证——每档脉宽
|
||||
* 恒为 100ms 的整数倍(0.5Hz 载波、20 档占空分辨率)。
|
||||
* 控制为分段式:
|
||||
* err >= 温和带 全量 PID(满功率爬升);
|
||||
* 0 <= err < 温和带 正常 PID 但占空上限压到温和档(小功率缓升,
|
||||
* 减少进带蓄热,降低越过目标后的惯性过冲);
|
||||
* err < 0(超温) 强制关断 + 积分按负误差回退(执行器只能加热不能制冷)。
|
||||
* @note 安全保护:上电默认关闭;NTC 传感器故障(OPEN/SHRT)强制关断;
|
||||
* 过温(>= HEATER_OVERTEMP_C)滞回保护;未使能强制关断。
|
||||
* 控制结构(平滑逼近目标,抑制过冲/震荡):
|
||||
* 1. 目标斜坡:内部设定值以 HEATER_RAMP_CPS 限速滑向 M4 目标,
|
||||
* 设定值阶跃/跳变不直接进入误差;
|
||||
* 2. 全误差区间连续输出:err >= 温和带 P/D 全量;0 <= err < 温和带
|
||||
* P/D 钳到 HEATER_SLOWBAND_DUTY(积分不参与钳);err < 0(超过
|
||||
* 内部目标)P 负项直接抵扣积分后钳 0——P 参与使输出随超温幅度
|
||||
* 立即收敛,积分只按剩余需要维持(若 P 铰 0 积分独撑,过线后
|
||||
* 输出不随 err 变化,形成 sag->重建->过冲->排空自持极限环);
|
||||
* 3. 微分基于温度变化率(D on measurement,-Kd*ΔT/拍):设定值
|
||||
* 变化不产生微分踢;升温时提前退功率,回落越过目标时提前止跌;
|
||||
* 4. 积分钳位 [0, HEATER_PID_I_MAX],吸收仅限目标近旁窄带
|
||||
* (HEATER_PID_INTEG_ZONE_C)且 P/D 未被钳时(防爬升段
|
||||
* windup);err<0 且 u>0 时小量回退,积分向稳态收敛;
|
||||
* 稳态维持功率由积分承载。
|
||||
* @note 安全保护:上电默认关闭;NTC 传感器故障(OPEN/SHRT,连续 3 拍去抖确认,
|
||||
* 单拍毛刺不触发;短故障恢复无扰还原积分)强制关断;过温
|
||||
* (>= HEATER_OVERTEMP_C)滞回保护;未使能强制关断。
|
||||
*/
|
||||
|
||||
#include "heat.h"
|
||||
@@ -23,17 +33,27 @@
|
||||
/* 内部状态 */
|
||||
|
||||
static bool s_enable = false; /* 加热使能(M3) */
|
||||
static float s_target = 0.0f; /* 目标温度(M4) */
|
||||
static float s_target = 0.0f; /* 目标温度(M4,外部设定值) */
|
||||
static float s_ramp_target = 0.0f; /* 内部斜坡目标(以 HEATER_RAMP_CPS 滑向 s_target) */
|
||||
static uint8_t s_duty = 0; /* 量化占空档位 0~HEATER_PWM_STEPS(x STEP%) */
|
||||
static bool s_overtemp = false; /* 过温锁存(滞回恢复) */
|
||||
static float s_integral = 0.0f; /* PID 积分项 */
|
||||
static float s_prev_err = 0.0f; /* PID 上次误差 */
|
||||
static float s_integral = 0.0f; /* PID 积分累加(钳 [0, HEATER_PID_I_MAX]) */
|
||||
static float s_prev_temp = 0.0f; /* 上拍温度(微分 on measurement 用) */
|
||||
static float s_duty_acc = 0.0f; /* 占空抖动累加器(档位单位,<1) */
|
||||
static uint8_t s_fault_debounce = 0; /* NTC 故障去抖(连续故障拍数,255 封顶记时长) */
|
||||
static float s_fault_stash_I = 0.0f; /* 故障确认时刻积分暂存(短故障无扰还原用) */
|
||||
static float s_fault_stash_T = 0.0f; /* 故障前末次正常温度(还原条件校验用) */
|
||||
static float s_t_filt = 25.0f; /* 斜率限幅后的温度(读数毛刺防护) */
|
||||
static uint8_t s_slew_cnt = 0; /* 连续超斜率拍数(真实快速变化放行计数) */
|
||||
static uint16_t s_pwm_phase_ms = 0; /* PWM 载波相位 0..PERIOD-1 */
|
||||
static uint16_t s_pid_divider = 0; /* PID 周期分频(10ms 圈) */
|
||||
|
||||
/* 档位总数 = 100% / 步进% */
|
||||
#define HEATER_PWM_STEPS (100U / HEATER_PWM_STEP_PCT)
|
||||
|
||||
/* NTC 故障确认拍数(每拍 = 1 个 PID 周期 500ms;连续 3 拍 = 1.5s) */
|
||||
#define NTC_FAULT_CONFIRM_PID_STEPS 3U
|
||||
|
||||
/******************************************************************************/
|
||||
/* 底层输出 */
|
||||
|
||||
@@ -56,38 +76,74 @@ void heat_init(void) {
|
||||
/******************************************************************************/
|
||||
/* PID */
|
||||
|
||||
/**
|
||||
* @brief 复位 PID 积分与历史误差(使能切换/传感器故障时调用,防积分残留冲击)。
|
||||
*/
|
||||
static void pid_reset(void) {
|
||||
s_integral = 0.0f;
|
||||
s_prev_err = 0.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 积分试算值(err 一步积分;供抗饱和逻辑条件采纳)。
|
||||
*/
|
||||
static float integ_step(float err) {
|
||||
return s_integral + err;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 分段 PID 计算(500ms 周期):误差 -> 占空档位 s_duty。
|
||||
*/
|
||||
static void pid_compute(void) {
|
||||
float t = ntc_get_temperature();
|
||||
float t_raw = ntc_get_temperature();
|
||||
|
||||
/* 传感器故障(开路/短路):安全第一,强制关断并复位 PID */
|
||||
if (t <= -273.0f) {
|
||||
s_duty = 0;
|
||||
pid_reset();
|
||||
return;
|
||||
/* NTC 读数斜率限幅(V0.1.9)+ 大偏差冻结(V0.1.13):真实热速率
|
||||
<=0.2C/拍,单拍 |Δ|>0.5C 判为读数毛刺,沿限幅值过渡,连续 2 拍超限
|
||||
后——若累计偏差 > HEATER_NTC_JUMP_C(秒级塌陷/卡死事件,真机实测
|
||||
-2.5C/4.5s 塌陷与 +2.3C/10s 顶格,焊接点接触不良)则冻结滤波值
|
||||
不再跟随,直到读数回弹到偏差带内自动恢复。真实温度变化永远不会
|
||||
触发冻结(最大冷却 ~0.05C/拍)。过温方向冻结偏差最大 1.5C,安全。 */
|
||||
float t = t_raw;
|
||||
if (t_raw > -273.0f) {
|
||||
float d_raw = t_raw - s_t_filt;
|
||||
if (d_raw > HEATER_NTC_SLEW_C || d_raw < -HEATER_NTC_SLEW_C) {
|
||||
if (s_slew_cnt < 2U) {
|
||||
s_slew_cnt++;
|
||||
t = s_t_filt + ((d_raw > 0.0f) ? HEATER_NTC_SLEW_C : -HEATER_NTC_SLEW_C);
|
||||
} else if (d_raw > HEATER_NTC_JUMP_C || d_raw < -HEATER_NTC_JUMP_C) {
|
||||
t = s_t_filt; /* 秒级坏值:冻结 */
|
||||
} else {
|
||||
s_slew_cnt = 2U; /* 偏差带内:放行 */
|
||||
}
|
||||
} else {
|
||||
s_slew_cnt = 0U;
|
||||
}
|
||||
s_t_filt = t;
|
||||
} else {
|
||||
s_slew_cnt = 0U;
|
||||
}
|
||||
|
||||
/* 未使能:强制关断 */
|
||||
/* NTC 故障去抖:单拍毛刺(采样/EMI)保持输出不动;连续确认才安全关断。
|
||||
毛刺拍不更新任何 PID 状态(含 s_prev_temp),下拍用真实温差继续。
|
||||
确认沿暂存积分与故障前温度——短故障恢复时无扰还原(见恢复分支)。 */
|
||||
if (t <= -273.0f) {
|
||||
if (s_fault_debounce < 255U) {
|
||||
s_fault_debounce++;
|
||||
}
|
||||
if (s_fault_debounce >= NTC_FAULT_CONFIRM_PID_STEPS) {
|
||||
if (s_fault_debounce == NTC_FAULT_CONFIRM_PID_STEPS) {
|
||||
s_fault_stash_I = s_integral;
|
||||
s_fault_stash_T = s_prev_temp;
|
||||
}
|
||||
s_duty = 0;
|
||||
s_integral = 0.0f;
|
||||
s_duty_acc = 0.0f;
|
||||
s_prev_temp = t;
|
||||
}
|
||||
return;
|
||||
}
|
||||
/* 故障恢复:短故障(<=HEATER_FAULT_RESTORE_MAX_S)且温度未显著漂移
|
||||
(|ΔT|<2℃)时还原暂存积分——避免毛刺清零后稳态功率丢失引发
|
||||
sag->重建->过冲循环;长故障/温度已漂移则从零重建(安全优先) */
|
||||
if (s_fault_debounce >= NTC_FAULT_CONFIRM_PID_STEPS) {
|
||||
float fault_dur_s = (float)s_fault_debounce * (HEATER_PID_PERIOD_MS / 1000.0f);
|
||||
float drift = t - s_fault_stash_T;
|
||||
if (fault_dur_s <= HEATER_FAULT_RESTORE_MAX_S && drift < 2.0f && drift > -2.0f) {
|
||||
s_integral = s_fault_stash_I;
|
||||
}
|
||||
}
|
||||
s_fault_debounce = 0U;
|
||||
|
||||
/* 未使能:强制关断(每拍刷新微分基线,恢复时无假温差) */
|
||||
if (!s_enable) {
|
||||
s_duty = 0;
|
||||
pid_reset();
|
||||
s_integral = 0.0f;
|
||||
s_prev_temp = t;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -99,52 +155,88 @@ static void pid_compute(void) {
|
||||
}
|
||||
if (s_overtemp) {
|
||||
s_duty = 0;
|
||||
pid_reset();
|
||||
s_integral = 0.0f;
|
||||
s_duty_acc = 0.0f;
|
||||
s_prev_temp = t;
|
||||
return;
|
||||
}
|
||||
|
||||
float err = s_target - t;
|
||||
|
||||
/* 超温区间:执行器只能加热不能制冷,输出必须为 0;
|
||||
积分按负误差回退,为温度回落后的重新加热保留正确初始态 */
|
||||
if (err < 0.0f) {
|
||||
s_integral = integ_step(err);
|
||||
s_prev_err = err;
|
||||
s_duty = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
/* 位置式 PID(带抗积分饱和) */
|
||||
float u = HEATER_PID_KP * err
|
||||
+ HEATER_PID_KD * (err - s_prev_err)
|
||||
+ HEATER_PID_KI * integ_step(err);
|
||||
|
||||
if (u > 100.0f) {
|
||||
u = 100.0f;
|
||||
/* 饱和:err>=0 分支积分不吸收(防 windup) */
|
||||
/* 目标斜坡:内部设定值限速滑向 s_target(升/降同限速)。
|
||||
小步进(斜坡快于温升)全程落在温和带内缓升;大步进爬升段 ramp 超前、
|
||||
到站后误差随实际温度平滑收缩,抑制热滞后过冲。 */
|
||||
float diff = s_target - s_ramp_target;
|
||||
float ramp_step = HEATER_RAMP_CPS * (HEATER_PID_PERIOD_MS / 1000.0f);
|
||||
if (diff > ramp_step) {
|
||||
s_ramp_target += ramp_step;
|
||||
} else if (diff < -ramp_step) {
|
||||
s_ramp_target -= ramp_step;
|
||||
} else {
|
||||
s_integral = integ_step(err);
|
||||
s_ramp_target = s_target;
|
||||
}
|
||||
|
||||
/* 温和带:距目标不足 HEATER_SLOWBAND_C 时,比例/微分即时响应受限
|
||||
(小功率缓升,减少进带蓄热、降低惯性过冲);积分项不受限——
|
||||
稳态维持功率由积分缓慢顶到实际需要值 */
|
||||
if (err < HEATER_SLOWBAND_C) {
|
||||
float u_pd = HEATER_PID_KP * err + HEATER_PID_KD * (err - s_prev_err);
|
||||
if (u_pd > (float)HEATER_SLOWBAND_DUTY) {
|
||||
u_pd = (float)HEATER_SLOWBAND_DUTY;
|
||||
float err = s_ramp_target - t;
|
||||
float d_t = t - s_prev_temp; /* 本拍温度变化(D on measurement) */
|
||||
|
||||
/* 分区作用(V0.1.12):近场扩展区 [-BAND, SLOWBAND_C) 合并"带内退闸"
|
||||
与"带外下方"为一条连续减速曲线——u = Ki*I(维持功率,不衰减)+
|
||||
ASSIST 退闸曲线(err=+SLOWBAND_C 满档快爬、err=+BAND 处归零)+ Kd 减速。
|
||||
原结构近场推力真空(P 随 err 缩小消失、I 未成熟,最后 2C 爬 141s),
|
||||
ASSIST 提供与积分成熟度无关的确定推力。远场 err >= SLOWBAND_C:P/D
|
||||
全量 + I。过温缘 err < -BAND:关断 + I 限速回退。 */
|
||||
float u;
|
||||
if (err >= HEATER_SLOWBAND_C) {
|
||||
float u_pd = HEATER_PID_KP * err - HEATER_PID_KD * d_t;
|
||||
if (u_pd > 100.0f) {
|
||||
u_pd = 100.0f;
|
||||
}
|
||||
if (u_pd < 0.0f) {
|
||||
u_pd = 0.0f;
|
||||
}
|
||||
u = u_pd + HEATER_PID_KI * s_integral;
|
||||
if (u < 100.0f) {
|
||||
s_integral += err * HEATER_PID_INTEG_GAIN_IN;
|
||||
}
|
||||
if (u > 100.0f) {
|
||||
u = 100.0f;
|
||||
}
|
||||
} else if (err >= -HEATER_PID_BAND_C) {
|
||||
float assist = (float)HEATER_SLOWBAND_DUTY * (err + HEATER_PID_BAND_C)
|
||||
/ (HEATER_SLOWBAND_C + HEATER_PID_BAND_C);
|
||||
u = HEATER_PID_KI * s_integral + assist - HEATER_PID_KD * d_t;
|
||||
s_integral += err * HEATER_PID_INTEG_GAIN_IN;
|
||||
if (u > (float)HEATER_NEAR_MAX_DUTY) {
|
||||
u = (float)HEATER_NEAR_MAX_DUTY; /* 近场推力上限:快而不过冲 */
|
||||
}
|
||||
if (u < 0.0f) {
|
||||
u = 0.0f;
|
||||
}
|
||||
} else {
|
||||
s_integral -= HEATER_PID_INTEG_BLEED;
|
||||
u = 0.0f;
|
||||
}
|
||||
if (s_integral > HEATER_PID_I_MAX) {
|
||||
s_integral = HEATER_PID_I_MAX;
|
||||
}
|
||||
if (s_integral < 0.0f) {
|
||||
s_integral = 0.0f;
|
||||
}
|
||||
|
||||
/* 输出量化到档位(四舍五入) */
|
||||
s_duty = (uint8_t)((u + (float)HEATER_PWM_STEP_PCT / 2.0f) / (float)HEATER_PWM_STEP_PCT);
|
||||
s_prev_temp = t;
|
||||
|
||||
/* 输出 Bresenham 抖动量化:载波 2s = 4 个 PID 拍,档位余数按拍分摊,
|
||||
平均分辨率 5%/4 = 1.25%。稳态维持功率(实测 ~10-12%)恰落在整数档
|
||||
之间时,固定取整会迫使输出在相邻档间满幅切换(±1℃ 极限环残因);
|
||||
抖动后 PI 可停在真实需要的平均功率上。脉宽仍为 100ms 整数倍,
|
||||
开关频率仍 0.5Hz,硬件约束不变 */
|
||||
s_duty_acc += u / (float)HEATER_PWM_STEP_PCT;
|
||||
if (s_duty_acc >= (float)(HEATER_PWM_STEPS + 1)) {
|
||||
s_duty_acc = (float)(HEATER_PWM_STEPS + 1);
|
||||
}
|
||||
{
|
||||
uint8_t level = (uint8_t)s_duty_acc; /* u >= 0,截断即 floor */
|
||||
s_duty_acc -= (float)level;
|
||||
s_duty = level;
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
@@ -164,8 +256,10 @@ void heat_task(void) {
|
||||
if (s_pid_divider >= HEATER_PID_PERIOD_MS) {
|
||||
s_pid_divider = 0U;
|
||||
pid_compute();
|
||||
if (s_duty != prev_duty) {
|
||||
s_pwm_phase_ms = 0U; /* 占空变更:从新周期起点开始,响应即时 */
|
||||
if (s_duty > prev_duty) {
|
||||
s_pwm_phase_ms = 0U; /* 仅升档复位相位:立即起算新 ON 窗口;
|
||||
降档不复位——相位已过新窗口即刻关断,
|
||||
不再额外补发一段 ON 脉冲 */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,9 +277,22 @@ void heat_task(void) {
|
||||
void heat_set_enable(bool on) {
|
||||
if (s_enable == on) return; /* 幂等 */
|
||||
s_enable = on;
|
||||
if (!on) {
|
||||
if (on) {
|
||||
/* 从当前实际温度起坡:避免内部目标从 0 跳变造成功率冲击;
|
||||
微分基线/读数滤波基线同步取当前温度,首拍无假温差 */
|
||||
s_ramp_target = ntc_get_temperature();
|
||||
if (s_ramp_target < 0.0f) {
|
||||
s_ramp_target = 0.0f; /* 传感器故障标记值非法,退化为 0 */
|
||||
}
|
||||
s_prev_temp = s_ramp_target;
|
||||
s_t_filt = (s_ramp_target > 0.0f) ? s_ramp_target : 25.0f;
|
||||
s_slew_cnt = 0U;
|
||||
s_fault_debounce = 0U;
|
||||
} else {
|
||||
s_ramp_target = 0.0f;
|
||||
s_duty = 0;
|
||||
pid_reset();
|
||||
s_integral = 0.0f;
|
||||
s_duty_acc = 0.0f;
|
||||
heat_out(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,11 +97,43 @@ float ntc_read_temperature(void) {
|
||||
static float s_ntc_last_temp = -273.15f; /* 最新温度(初值 = 开路标记) */
|
||||
static uint16_t s_ntc_sample_divider = 0; /* 主循环 10ms 圈分频计数 */
|
||||
|
||||
/* 滑动中值滤波(V0.1.11):真机读数毛刺可持续 1.4s+(曾抓到 14 帧连续
|
||||
OPEN),会穿透控制环的斜率限幅并触发猛拉。最近 NTC_MEDIAN_N 个样本取
|
||||
中值——单点/短串毛刺被完全剔除,滞后仅 ~0.2s(远小于低通滤波)。
|
||||
故障标记值(-273/-274)参与排序:偶发 OPEN 被中值滤掉,连续 >=3 帧才
|
||||
成为中值(等效又一层去抖),故障判定语义不变。 */
|
||||
#define NTC_MEDIAN_N 5U
|
||||
static float s_ntc_win[NTC_MEDIAN_N];
|
||||
static uint8_t s_ntc_win_idx = 0;
|
||||
static uint8_t s_ntc_win_fill = 0;
|
||||
|
||||
void ntc_sample_task(void) {
|
||||
/* 主循环周期 10ms:按 NTC_SAMPLE_PERIOD_MS 分频触发一次采样 */
|
||||
if (++s_ntc_sample_divider >= (NTC_SAMPLE_PERIOD_MS / 10U)) {
|
||||
s_ntc_sample_divider = 0;
|
||||
s_ntc_last_temp = ntc_read_temperature();
|
||||
|
||||
float v = ntc_read_temperature();
|
||||
s_ntc_win[s_ntc_win_idx] = v;
|
||||
s_ntc_win_idx = (uint8_t)((s_ntc_win_idx + 1U) % NTC_MEDIAN_N);
|
||||
if (s_ntc_win_fill < NTC_MEDIAN_N) {
|
||||
s_ntc_win_fill++;
|
||||
}
|
||||
|
||||
/* 插入排序后取中值(5 点,开销可忽略) */
|
||||
float tmp[NTC_MEDIAN_N];
|
||||
for (uint8_t i = 0; i < s_ntc_win_fill; i++) {
|
||||
tmp[i] = s_ntc_win[i];
|
||||
}
|
||||
for (uint8_t i = 1; i < s_ntc_win_fill; i++) {
|
||||
float key = tmp[i];
|
||||
int8_t j = (int8_t)(i - 1);
|
||||
while (j >= 0 && tmp[j] > key) {
|
||||
tmp[j + 1] = tmp[j];
|
||||
j--;
|
||||
}
|
||||
tmp[j + 1] = key;
|
||||
}
|
||||
s_ntc_last_temp = tmp[s_ntc_win_fill / 2U];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
set(PROJECT_NAME "FilmForge_FW")
|
||||
set(BOARD_TYPE_CODE 41)
|
||||
set(VERSION_MAJOR 0)
|
||||
set(VERSION_MINOR 0)
|
||||
set(VERSION_PATCH 1)
|
||||
set(VERSION_MINOR 1)
|
||||
set(VERSION_PATCH 14)
|
||||
set(VERSION "V${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
|
||||
string(TIMESTAMP BUILD_DATE "%Y-%m-%d")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user