Files
hulk 8900240c65 Heater: PID v0.1.14 - banded controller + sensor glitch guards
Rebuild PID as three-zone banded controller: far field P/D full power,
2026-10-15 20:55:02 +08:00

429 lines
36 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# FilmForge — AGENTS.md
## 项目约定
- **用途**:自制热压模具温控固件——塑型 PC 膜,负责加热驱动与测温(NTC),
对外走 RS485 数据口。基于 GD32F4xx 工程模板
(`https://gitea.hulk.wang/hulk/gd32f4xx_template_cmake_vscode`,源自
celalkismiroglu/gd32e23x-demo-suites-cmake 体系)。
- **命名**:仓库 `FilmForge`,固件工程 `FilmForge_FW`(产物自动命名
`FilmForge_FW_V<版本>_APP_<日期>.bin`),协议板卡类型码 `BOARD_TYPE_CODE 41`。
- **目标芯片**:GD32F405RET6(设备宏 `GD32F405`,IRC16M PLL 168MHz——板上无外部
晶振;硬件 FPU fpv4-sp-d16 / hard ABI;512K Flash,链接脚本 xE)。
- **串口角色与 PC 端映射**:
- debug 串口 = USART1(PA2 TX / PA3 RX,AF7,纯串口)→ PC 端 **COM4**;
printf 重定向到 USART1(`DEBUG_UART` 宏),`DEBUG_MODE` 打开后输出。
- 数据口 RS485 = USART0(PA9 TX / PA10 RX,AF7)+ DE 引脚 PA8(GPIO 手动切换
收发)→ PC 端 **COM5**(115200-8-N-1)。拉通阶段主循环回显。
- 注意 GD 外设编号比 STM32 小 1:PA9/PA10=USART0、PA2/PA3=USART1。
- **NTC 测温**:PC5(ADC0_IN15),硬件 10K 上拉、NTC 接 GND 侧。B 值公式换算,
`NTC_B_VALUE` 宏(3950/3435 未定,默认 3950,确认后修改);`R_PULL/NTC_R25`
均为宏,换料只改宏。
- **语言/工具链**:C(gnu11)+ ARM GCC(xpack 15.2.1 已验证)、CMake ≥ 3.20 +
Ninja、VSCode + CMake Tools + cortex-debug;工具链路径经 `GD32_GCC_BIN`。
- **目录约定**(沿用模板):
- `Build/` —— 构建树与产物(gitignore);
- `SDK/` —— CMSIS、标准外设库 V3.3.3、SEGGER_RTT、USB 库(`GD32_ENABLE_USB`
开关,默认 OFF,当前未用);
- `LD/` —— 链接脚本(默认 xE 512K);
- `Inc/`+`Src/` —— 应用层,板级引脚/开关集中在 `Inc/board_config.h`;
- `OpenOCD/` —— 工程内会话配置(GD-LINK/CMSIS-DAP + stm32f4x);
- `doc/` —— 芯片文档(数据手册/用户手册/固件库指南/AN,已齐备);
- `GUI/` —— 温控上位机(PySide6-Fluent,详见其 `README.md`,运行态
`filmforge_gui.json`/`logs/`/`build/`/`release/` 由 `GUI/.gitignore` 挡)。
- **编译方法**:
```bash
cmake --preset Debug && cmake --build Build/Debug
cmake --preset Release && cmake --build Build/Release
```
- **Git/网络经验**:origin = HTTPS(`https://gitea.hulk.wang/hulk/FilmForge.git`),
该实例 HTTPS/SSH 直传大包均不稳,已在本仓库配置
`http.proxy http://127.0.0.1:6789` + `http.sslVerify=false`(仓库级),
代理端口变化时用 `git config http.proxy http://127.0.0.1:<新端口>` 更新。
- **printf 浮点**:`--specs=nano.specs` 默认裁剪 `%f`(输出空白),已在
`cmake/project.cmake` 启用 `-u _printf_float`(F405 flash 充裕,ROM +8~10KB 无感)。
- **在线排查寄存器地址速查**(GD32F4xx):ADC0=0x40012000(RDATA@0x4C)、
USART0=0x40011000、USART1=0x40004400、GPIOA=0x40020000(OCTL@0x14)、
RCU:CTL0=0x40023800 / **PLL=0x40023804** / **CFG0=0x40023808**。
## 开发进度
### 已完成
- 模板就位(GD32F405RET6 / IRC16M 168MHz / 硬件 FPU / 上板验证过的模板体系)。
- **debug 串口**:USART1 PA2/PA3 @115200,printf 重定向 + RBNE 中断收环形缓冲
(模板已有,COM4 观测)。
- **RS485 数据口驱动**(`rs485.h/rs485.c`):USART0 PA9/PA10 AF7 + DE PA8 GPIO
手动切换;独立接收环形缓冲(预留一格策略 + PRIMASK 临界区);阻塞发送
(DE→TBE→TC→DE,防尾字节截断);`rs485_rx_clear()` 清接收缓冲。
- **NTC 测温驱动**(`ntc.h/ntc.c`):PC5 模拟输入 + ADC0 常规通道(软件触发单次
转换 + 校准),16 次采样均值 + 分压反解阻值 + B 值公式换算摄氏度
(含开路/短路电源轨检测)。
- `debug_uart_write()` 助手(uart.c):debug 口阻塞单字节发送。
- **命令协议框架移植**(`command.h/command.c`,自 E23x 移植):D5 帧协议
(D5 BOARD_TYPE LEN [cmd] CRC 累加和校验)+ B5 响应;数据源改为 RS485 收缓冲
(`rs485_rx_*`),响应经 `rs485_send` 发出(DE 自动切换);帧状态机 + CRC +
参数解析工具(uint_to_str/parse_uint_dec/parse_timer_parameter/find_parameter_value)
全套保留;通用命令骨架:**M888 软复位 / M999 版本号 / M9999 进 OTA**;
业务命令(加热/控温)待定义,switch 中扩展(TODO 注释有格式示例)。
- **命令协议 v1.0 定稿**(权威定义见根目录 `protocols.md`,帧 hex 全部脚本生成):
- M1 查温度 / M2 查上报状态 / M2S<ms> 上报间隔+开关(M2S0 停)/ M3 加热开关
(M3S1/M3S0/M3 查询)/ M4 目标温度(M4S<temp>/M4 查询)/ M888 / M999 / M9999
- **加热驱动 + PID 闭环实装**(`heat.h/heat.c`,PB12=HEAT_EN):
- 执行器:慢 PWM(载波 1s、占空 10% 步进 → 脉宽恒 >=100ms、开关频率恒 1Hz,
满足 2N7002K→VBE2412 PMOS 驱动电路 <=10Hz 约束)
- PID:500ms 周期(NTC 采样 100ms),位置式 + 积分限幅 + 输出量化 10% 档;
参数 Kp=8/Ki=0.05/Kd=0(初值,宏在 board_config.h,待整定)
- 安全:上电默认关;NTC 故障(OPEN/SHRT)强制关断;过温 150℃ 滞回保护;
未使能关断;使能切换/故障时复位 PID 积分
- 实测 30℃ 闭环(NTC 贴 PTC 鳍片):30s 爬升、超调 1.4℃(7%)、60s 收敛中
- M3/M4 已从软件桩切到 heat 模块(协议帧不变)
- 协议要点:全幂等(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 维持
PMOS 自锁供电(先于一切外设初始化);`pwr_hold_off()` 预留软关机(置低即失电)。
- **专用 GUI 上位机 v0.1.0 完成**(`GUI/`,参照 DLPilot 分层:协议模块纯标准库 +
PySide6-Fluent 界面 + 自检 + PyInstaller):
- 文件角色:`filmforge_rs485.py`(帧/解析/串口线程 FF485)、`app_fluent.py`
(双栏仪表盘 + **自绘温度曲线**,未引第三方图表库)、`config.py`(设置记忆
filmforge_gui.json)、`filmforge_gui.py`(入口+CLI `--port/--cmd/--seconds-report`)、
`selftest_rs485.py`/`selftest_gui.py`(协议向量 / 离屏渲染+派发回归)、
`build_exe.py` → `release/FilmForge.exe`+发布 zip、`publish_gitea.py`
(tag `FilmForge-GUI-vX.Y.Z`)
- 功能:实测温度曲线+目标温度阶梯线同图(闭环监视)、M2 上报帧直采 / 上报关闭时
上位机自动轮询 M1 补点、NTC OPEN/SHRT 断口+故障标记、时间窗/暂停/窗口统计、
目标(M4S 0~200)/加热(M3S1 确认)/上报(M2S 100~60000)控制、连接自动同步
版本/上报/加热/目标、M888/M9999 二次确认+复位后重查、收发日志、CSV 记录
- **COM5 真机全链路验证通过**:M999=v41.0.0.1、M1/M2/M3/M4 查询、M2S500 上报
3s 收 6 帧、M2S0 停止;GUI 端到端(串口线程→队列→派发→曲线/状态芯片)实测通过
- 关键教训(勿重蹈,详见 `GUI/README.md`):①应答派发必须是 **FIFO 队列**(半双工
应答按发送序返回;单 pending 槽在连接序列 150ms 连发下被乱序击穿,实测 M2 的
OFF 被 M3 槽吃掉、M4 目标值混入曲线);②时间戳在串口线程读取点打(SerialMate
教训);③M4 目标应答路由窗口取条目生命周期 1.2s(适配器换向延迟实测可 >0.5s,
上报开启时才收紧 0.5s);④Fluent 控件样式走 setCustomStyleSheet(DLPilot 同款坑)
- 发布策略:GUI 产物(release/)暂不入库,本地 `python build_exe.py` 生成;发布走
`publish_gitea.py` 上传 zip(tag `FilmForge-GUI-vX.Y.Z`,与固件 tag 区分)
- **SerialMate 串口工具问题已定位**(见
`E:\Hulk-Coding\HW-Debug-Tools\SerialMate\ISSUE_rx_timestamp_and_lag.md`):
时间戳在 GUI 消费时打导致失真 + 接收日志无限增长致卡顿;待该工具修复后
再做长时间曲线联调。
- 工程改名:`FilmForge_FW`、`BOARD_TYPE_CODE 41`。
### 计划 / TODO(已提出尚未实现)
- [x] **RS485 拉通上板验证**:命令收发/响应/上报实测通过(固件端零丢字节,
响应 10ms 级实时);遗留为 USB-RS485 适配器方向切换丢包(换适配器解决)
与 SerialMate 显示层问题(见 ISSUE 文档)。
- [x] **NTC 测温上板验证**:NTC 支路修复后读数正常(23.6~25.9℃ 稳定)。
- [ ] **B 值确认**:温度计对照实测定 `NTC_B_VALUE`(3950/3435 二选一)。
- [ ] **适配器更换**:换可靠的 USB-RS485 适配器(当前自动方向切换间歇丢响应)。
- [x] **加热驱动 + PID 闭环**(PB12 慢 PWM 1s/10% 步进 + PID 500ms 周期,
安全保护齐全;实测 30℃ 闭环通过:30s 爬升、超调 1.4℃、60s 收敛中)。
- [ ] **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
与 publish_gitea 发布说明模板同步,protocols.md M3 表项去"预留";
监控逻辑简化:温度监控统一走 M2 周期上报(移除"M1 自动轮询"双通道及
poll 配置项,M1 仅手动单点查询),目标温度滑条范围 0~200→0~120(协议
上限 200 仍可手输)。**v0.2.1 真机回归修复**(COM5 实测暴露):①M4 查询
应答与周期上报帧同为 ASCII 浮点被误占(目标 chip 显示成实测值)→ 查目标
改应答驱动"暂停上报 M2S0→M4→自动恢复 M2S<ms>"链;②设定 ok 延迟 >1.2s
超时清队后状态 chip 与设备脱节(实测 M2S100 ok 迟到、chip 停 OFF)→ 8s
内晚到 ok 按发送时计划补偿应用;③断开按钮偶发不切换、断开后日志仍刷 →
连接按钮改意图驱动(点击即翻转、2s 未建立判丢失回退),已连接/已断开/
丢失全部入日志,连接同步序列每步发送前确认在线(修复序列整组被
"未连接串口"丢弃)。selftest 全过(新增 8 项回归用例);已打包冒烟并发布
Gitea Release `FilmForge-GUI-v0.2.1`(无应用图标——`logos/filmforge.ico`
仍缺位)。
- **GUI v0.2.2~v0.2.13(递进修复/新功能,Gitea 发布待确认)**:
- v0.2.2:串口线程 `read(256)` 在持续帧流下攒满 256 字节才返回(win32 字节
间隔超时因帧间隔更短永不触发,100ms 上报 ≈2.5s 一批),曲线/命令/响应全部
3 秒一卡、一股脑一大坨——真机日志确认每簇 ≈25 帧、间隔恒 3s。已改
`read(in_waiting or 1)`,真机复测曲线实时、命令即发即回。
- v0.2.3:发送入口幂等去重——同命令未决时忽略重复发送(真机连点"停止"12 次
挤爆 8 条 pending 队列、"丢弃积压命令"刷屏),状态栏提示"应答中"。
- v0.2.4:目标阶梯线渲染 bug(v0.1.0 起潜伏)——`_target_points` 对最后一次
目标变更只画终点,从旧值斜拉直线到图表右缘(实测 57→41 被画成 2 分钟斜坡,
截图确认);改为纯阶梯(变更时刻垂直落变+水平延伸),数据本身无误。
- v0.2.5:排版调整——串口连接卡片从右列移到左列顶部,左列图表卡/右列日志卡
弹性拉伸填充,两列底边对齐。
- v0.2.6:对齐修复——双列改放同一滚动容器(整页统一滚动):原先两列各自包
QScrollArea 独立滚动,窗口化下底边错位(全屏正常);现两列高度锁定一致,
不足一屏各自拉伸到底、超出一屏整页滚动。
- v0.2.7:图标接入(logos/ 下 ico/png/svg 已就位)——exe 嵌入图标
(build_exe.py --icon,此前缺文件自动跳过)、窗口标题栏/任务栏/弹窗图标
(app+window 两级 setWindowIcon,_icon_path() 兼容源码态与 _MEIPASS 打包态)、
Windows 任务栏独立标识 AppUserModelID "hulk.FilmForgeGUI"。README"无应用
图标"遗留项关闭。
- v0.2.8:**慢性卡顿修复**——`_log()` 手工 cursor 裁剪(`cursor.Start`/
`BlockUnderCursor` 实例级枚举访问)在 PySide6 6.11 上必然抛 AttributeError,
日志超 500 行(100ms 上报 ≈50s)后每个 100ms tick 异常中止 `_tick` 后半段:
应答超时清理失效(pending 永不清 → "停止"点击被幂等去重永久拦截,实拍状态栏
"M2S0 应答中,已忽略重复发送")、RX 队列积压 ratchet(帧处理滞后 54ms→均值
1037ms/峰值 2701ms,日志/chip/曲线显示滞后数秒,"停止 5s 后才见命令发出"实为
日志显示积压,TX 本身 5~166ms 即出)、日志无限增长、CPU +30%。改原生
`setMaximumBlockCount(500)` 环形裁剪;selftest 新增"500 行封顶/封顶后可追加/
最新行在末尾"3 条回归。离屏 A/B 实测(GUI/_diag_latency_buggy.py,假固件
100ms 上报+定时点击):buggy 组 5.5 分钟异常 2241 次、修复组全程漂移 ≤15ms/
零异常/零积压。v0.2.7 exe(2026-10-10 打包)内嵌同版本 PySide6 6.11.2,
同带此 bug,需重新打包生效。
- v0.2.9:**整体审查修复 5 项**——①CSV 写失败(磁盘满/记录盘被拔)自动停记录
并提示,不再沿 _tick 帧处理路径抛 OSError 中止主循环(与 v0.2.8 日志裁剪同款
失效管道);②故障标记 faults 封顶 1000 条(曾是唯一无上限缓冲:NTC 抖动时
10 条/s 无限增长,且 _draw_faults 每 paint 全量扫描);③串口写超时单独捕获
SerialTimeoutException——只丢该条命令、链路保留,不再按 OSError 当"设备拔出"
关端口断链(适配器停滞曾迫使手动重连);④日志自动滚动仅在贴底时跟随(上报
运行时可上翻历史,不再被拽回底部);⑤冻结视图切片右缘同样二分(暂停时长不再
增加 render 成本)。selftest 新增 7 项回归(封顶/滚动跟随/CSV 失败自停/冻结
切片)共 59 项全过。整体审查报告(2026-10-12)其余 P3 遗留:selftest 未覆盖
_tick 主循环(超时清理/丢失判定)、"全部"窗口全量切片有 MAX_SAMPLES 兜底、
_last_set 单槽位罕见误补偿(8s 自愈)、build_exe remove_stale 无提示强杀
运行实例、M888 后固定 2s 重查可能早于固件就绪、_danger_reset 把去重忽略记成
"已下发"。协议建议的 300ms×3 自动重发维持不做(设计决策,用户拍板)。
- 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` 实例级枚举访问在
PySide6 6.11 必抛 → tick 异常中止 → 应答超时清理失效 + RX 积压 ratchet +
日志无限增长)。复现/验证脚本:`GUI/_diag_latency.py`(修复基线)/
`GUI/_diag_latency_buggy.py`(带 bug 对照,假固件 100ms 上报+定时点击)。
遗留:待真机长时间运行确认 + 重新打包 exe。
- [ ] **LED 引脚确认**:FilmForge 自制板 LED 引脚未定(当前沿用模板默认 PA15,
仅为构建占位),按原理图修改。
- [ ] **delay_10us 校准**:M4@168MHz 系数为估算值(/300000),需实测修正。
## 备注
- 本机凭据/发布约定见用户级 `C:\Users\Lenovo\.zcode\AGENTS.md`。
- 模板工程位置:`E:\Hulk-Coding\gd32f4xx_template_cmake_vscode`(模板对标工程,
Gitea: gd32f4xx_template_cmake_vscode)。
- E23x 工程位置:`E:\Hulk-Coding\gd32e23x_template_cmake_vscode`(协议/驱动参考)。