From c9aa41685f8684af65263a4adc700562ff40c08b Mon Sep 17 00:00:00 2001 From: hulk Date: Sat, 10 Oct 2026 14:49:24 +0800 Subject: [PATCH] Heater: segmented PID (full-power / slow-band / overtemp-cutoff) + 2s/5pct PWM --- AGENTS.md | 11 +++- GUI/README.md | 1 - GUI/app_fluent.py | 25 +++++++- GUI/logos/filmforge.ico | Bin 0 -> 23945 bytes GUI/logos/filmforge.png | Bin 0 -> 10387 bytes GUI/logos/filmforge.svg | 6 ++ GUI/selftest_gui.py | 3 + Inc/board_config.h | 8 ++- Inc/heat.h | 2 +- Src/heat.c | 129 +++++++++++++++++++++------------------- 10 files changed, 114 insertions(+), 71 deletions(-) create mode 100644 GUI/logos/filmforge.ico create mode 100644 GUI/logos/filmforge.png create mode 100644 GUI/logos/filmforge.svg diff --git a/AGENTS.md b/AGENTS.md index 8341baf..38e05c8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -155,9 +155,14 @@ - v0.2.6:对齐修复——双列改放同一滚动容器(整页统一滚动):原先两列各自包 QScrollArea 独立滚动,窗口化下底边错位(全屏正常);现两列高度锁定一致, 不足一屏各自拉伸到底、超出一屏整页滚动。 - - selftest 全过(累计新增 14 项回归用例);exe 已本地打包冒烟,Gitea 发布 - 待用户确认后执行。 - 遗留见其 README"已知事项":应用图标、USB-RS485 适配器更换。 + - v0.2.7:图标接入(logos/ 下 ico/png/svg 已就位)——exe 嵌入图标 + (build_exe.py --icon,此前缺文件自动跳过)、窗口标题栏/任务栏/弹窗图标 + (app+window 两级 setWindowIcon,_icon_path() 兼容源码态与 _MEIPASS 打包态)、 + Windows 任务栏独立标识 AppUserModelID "hulk.FilmForgeGUI"。README"无应用 + 图标"遗留项关闭。 + - selftest 全过(累计新增 15 项回归用例,含窗口图标非空检查);exe 已本地 + 打包冒烟,Gitea 发布待用户确认后执行。 + 遗留见其 README"已知事项":USB-RS485 适配器更换。 - [ ] **LED 引脚确认**:FilmForge 自制板 LED 引脚未定(当前沿用模板默认 PA15, 仅为构建占位),按原理图修改。 - [ ] **delay_10us 校准**:M4@168MHz 系数为估算值(/300000),需实测修正。 diff --git a/GUI/README.md b/GUI/README.md index cb91e76..1275958 100644 --- a/GUI/README.md +++ b/GUI/README.md @@ -87,4 +87,3 @@ python publish_gitea.py release/FilmForge-GUI-v<版本>-win64.zip --replace --in (设备单向推送),不受影响。 - 加热(M3S1)为真实 PID 闭环:开启后模具真实升温(NTC 故障/过温 150℃ 固件自动 断电保护,上电默认关闭);当前占空比无查询命令,从温度曲线间接观察。 -- 打包暂无应用图标(`logos/filmforge.ico` 缺位,build_exe.py 自动跳过)。 diff --git a/GUI/app_fluent.py b/GUI/app_fluent.py index 8d3fbfd..ce253bb 100644 --- a/GUI/app_fluent.py +++ b/GUI/app_fluent.py @@ -46,7 +46,7 @@ if getattr(sys, "frozen", False) and os.name == "nt": pass from PySide6.QtCore import QPointF, QRectF, Qt, QTimer -from PySide6.QtGui import QColor, QFont, QPainter, QPainterPath, QPen, QPalette +from PySide6.QtGui import QColor, QFont, QIcon, QPainter, QPainterPath, QPen, QPalette from PySide6.QtWidgets import ( QApplication, QHBoxLayout, QLabel, QMainWindow, QMessageBox, QPlainTextEdit, QScrollArea, QSplitter, QVBoxLayout, QWidget, @@ -60,7 +60,7 @@ import config import filmforge_rs485 as ff APP_NAME = "FilmForge" -APP_VERSION = "0.2.6" +APP_VERSION = "0.2.7" REPO_URL = "https://gitea.hulk.wang/hulk/FilmForge" MONO = QFont("Consolas", 10) CHIP_IDLE = "#666666" @@ -364,6 +364,9 @@ class App(QMainWindow): self._system_palette = app.palette() if app else QPalette() self._system_style = app.styleSheet() if app else "" self.setWindowTitle(f"{APP_NAME} v{APP_VERSION} — 热压模具温控监视台") + icon = _icon_path() # 标题栏图标(run_gui 另设 app 级 + 任务栏标识) + if os.path.exists(icon): + self.setWindowIcon(QIcon(icon)) self.resize(1440, 920) self.setMinimumSize(1150, 720) @@ -1326,6 +1329,15 @@ def _single_instance(): return True +def _icon_path(): + """窗口图标路径: 打包版取 _MEIPASS(build_exe.py --add-data 内嵌), 源码版取 logos/。""" + bundled = os.path.join(getattr(sys, "_MEIPASS", ""), "filmforge.ico") + if os.path.exists(bundled): + return bundled + return os.path.join(os.path.dirname(os.path.abspath(__file__)), + "logos", "filmforge.ico") + + def run_gui(auto_close=0): if not _single_instance(): app = QApplication.instance() or QApplication(sys.argv) @@ -1333,6 +1345,15 @@ def run_gui(auto_close=0): return app = QApplication.instance() or QApplication(sys.argv) app.setApplicationName(APP_NAME) + if os.name == "nt": + try: # 任务栏独立标识: 缺省时任务栏可能沿用宿主默认图标并错误分组 + ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID( + "hulk.FilmForge.GUI") + except Exception: + pass + icon = _icon_path() + if os.path.exists(icon): + app.setWindowIcon(QIcon(icon)) # 主窗/弹窗/任务栏统一图标 setTheme(Theme.AUTO) window = App(auto_close) window.show() diff --git a/GUI/logos/filmforge.ico b/GUI/logos/filmforge.ico new file mode 100644 index 0000000000000000000000000000000000000000..59789dc2e0e53b69910236453594b9aae2cef36c GIT binary patch literal 23945 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

s~eXb$)YOf2JXAcUXRXuNCTN zup1{GSD{6oU**u6x(F2%(3J&P&&ALB>EIRbW`!CKCe}c~RD8V{dQ0$B=)v|Zz{N4` zEWgZ73A5_1Nk_8W48d)klalfR5Dws~`k#+H0FCc*eM9kDhU^=L>Oq-QQj+d#jv6_m z#82qAcE51Cqd$v%b?nY#E}L8;k@))c*@U)d#R#BYqXlyhh6Q9FV8O> z<*U#|YS6V&eiYQV2XK(Ay>2dzPeW>TF;S7GvwCj1*b#q3f6Bl9*hRSqG0$lCJn2eN z1m26ZGJT_so4yGqkHsF+HIHI@4FBvP!{6_3jP&>Qy?pfwqBFp54$>iEogJ}gp#P0E zCIz$ALd@rP{Oq`C_fu%~H&Uhm1{q~tO>0MxblGXAk}nB_xBbmAbo2070Q`Gi=Wy5N zLQU)#C6QYv=QDM&gX;7@u1XYlE7R}oCaZmssdfzYa4&%6fy?hjZkvNAL2%2e;;!4y zUNd)sj;w@Pe2Pr95hHJ?rpkVi#y-QUp~DZV2SQoyX%~4F*wapu;QDWFJO}Ik)%z)^ z&ivP}`$#rsmP>?sdj(2Ipjx1DWS)t_|=D&O;cJNWqZOo6Gf9D4yYPT<;(U-Gkar%I z&Q|f*2tkszpI|2|%%Da__VejxsMkUOZ5Ao6~OHN`oh^+nyNjkM;!VGV>uOPNhnXA8I7#_=_fJ0F*Yn~)=86i z|JwbC;!}b6@`S%r_&tXcBIY)*?b!6AR1Nnpa4~>SEb!+&f_a|F?lu-$-Ia!H&>9i}16U zhvI5UtjR-nl)*pVR#vU53ETx`Gcfn5D=r2}DV{CYY5CuP54M}*m4+Rkj~tELT$P@( zel*S)>_`kwFkfnuu!>=Fl}BhBe@kWEM=6{O@jioR<`pF=&?g0W;vwthh1c~`*WMTs zVbG`qHD};AVWa*RJ8V2>5!zRBE$po_40pRvN_O;}v5;~!`^2%`f8<(Y!KR+2-wL-M zhh=jjH_y}TGw--B_8gigFq}sy{Jq=u;3tQEGYrlQ3=U7^?r;5*k4%NYsB4ZSS((R4 zPuke4z>K8JG{-9|FPP+VEz)X#+>lLI$o=Gs!^68!k1h#FDtGUt&eM+n)Ckl4h-b;t zCthYILZg)JK6fgaw1%jU&!z{L!YtP6f5PlsMQk)eZ>XkkX*3(Y{xsIFg~G4G&a}xG zGz;T~W2zT=;@5r3T4acIOe(T_;%)Y23D855aFu3Z`5*d^IQj~>Pz1i634<=b3}{5N zj@;+aW7yia3C>LNySCWQl5-+i!^&b#7Y*)RG!#8@J z%6I!;xcTquCziGoZs8f9BJ}w>%w485KlJt6bvJCTAlS-y-ibu}gG=kl3>=^QO9+!! zLe8w)H&#om1LiU;FH@jjN7s%f`*_w_t!Iy$0c8e*l}bledinQas{6r0kDy56^lXLz zuP(ZueDX@*y(x`9H9cD%+1CORGc$+3-s_tkv`40|ch!g!7gbs4g<|eF=2qRN_1lSV zaK(y+W70F`+`DG(g@#@@cW!PMpHEycAw+;aD{lzPS2&VbVsNXlOz8SBZDQu_{fN|L zG6qC>9UPgI9e1af&u(_ps_vj{PNw#;BPPCZThoPpW$f067Gu(H%VPT<7c{+ zn>EyrTzvCDSboULUk_762rjsEAWke9E$}ueb>|@i3WAhZ{hD2z)i`wE$MSD;`b{7<0K zC$qaFPxHrbd?Q(t%Nlsi6Lsa#$&?x9MSz>>ahepIt zUeiN@G#Dhm;IJLwrKmk2fRBC1PpD;z!AV1Y7FIm;*um?BbT{Mg(VxAVGfHF1K!C76 zJJG44my^d3GsH333C2`6rqO-LhmYDzyyUe_59!E%$guj$V6qKJ?q zU=&8-_@1N7-b`4OO9G3HHKO)RWJrKPxZXw=hW`dbVYGxTpoH6bjffZNk-%M?B@z&T z{5b;4QAN+|Nj4oH7>a?2VEz9yCP0H(4s#)DMR!izZ_8$7le9Xj5s}gs;4R>x=K$wl zK4HqsudBtP+~e?&M)<75K){8wqQ$Pq5ehOO0goLaqmm32jy$&(u2Jffzy^52wU?73D+79SJH2F>uAfl*2ixTx6arD7P}AMd{Nou!~_`kmNd*E+z86om|EmZ3C|Eb~^ zh*2ZFqFYY=|6V~@07U>*(A>KQ*gL8Yc3%FFhQaY3M?W9C&5~g$(Wr%xm1D5E8`$%i z8Ns$))(mQpGB;pHylv!Y6zAJ|8Uxh95$Um0VsIKVv6cilVLk#xJu{a{hji>tq2+Dk z7Bh@#UJnM*ERJNJLOu59(Q+5f*smVPY#r=3jI^6!KbygwEj&^ zvB`LfUc6|#wK!zrci?JgmoD_tcA#hh=T9i*I@^_S?G@Ph;`wu;I9*Lm%_qi=j*fTl zcEDw7mdncrTO+z!T4b%=;H*lYy&}1#@g6cVGHyP;tb$F&!5Zf|73sr zCY;YheTxC=bxOK~OHZA3FJw9 zXC&2{qBd=#i)Bm3CC-*^ZbgAP_KJ$NTO)q>4iy0^MnPO-W8-|j!odl&YG!Ue;=8v{ zUM{VudGX@KRyn+PifRnyq49`ItIX^rX;U0l#n*JC(uels$=N;IiHV7&@-@{AovZ@M zoL|Ara%)W@OiWCHpUcb3ch;s%JSIMd3-EJsDc8Q@;^LyXM#(5>y0bb_TU#5b)X~{# z;=NM4xiG-2>1Xo{sW(qJ%PivvUs1E(UH|7+R<*y{I5KCL#fCJKZ>MEuYWeKKaxPOX zt|j+`t?5`;_SZYrrlzKN`qNTVBP5)R)z#lE5RCTQ=L~(be3KXw6&3Z7N7rqpLvbk| zD^7;!_YV$I?nJwd|2$SJFXc8rJyKOwL15aGsUP)uPeq_2Y4>q<)!{A#(#75Srl#_h zU(5?9(TNi$n8hEcuTQy^$?Z;mkz?Wb;Y}p(GxV^ADlILIWIvF9r_7+}fxezzMD8^t zy}w*z5cA~0gUmo$+pg61)rm&>gNn_5`^w5n9ctv{v^3M1Vt;u|^OSXKYz6*sZ{sqn z%}bP8X4M>(rJw(+KCW%)Ap3&qXPXuDeI_~D&dyHs%19WOny1&FJ*&C;`g%6MeGQ@C zJz0i+g?0`xh>VKrV0SKuKI~_0tBkbgIxE(PWfG~HiAN1zF ze}A%;fSiItIr#L~u`~r{BHYBz_BO9!(e1qS`T2Rrp;G-zXxMX(k4jBYKwzo3b1q!y zL9bxGC*Hu;*0xaOOfo4pK;g+v*zEI4L2a}1@l*t(!u$LCvOE81Yx`GMSF=kdPro6; zrD}XBkSTK?@hM&u3wisNpN~(wsmNhK+|SR?g%D>~;g{9X(E;IqthL%$KDJ?CVDA}? zETfcLW^NHA{P*wQeGhgcjY3`AhsxJlr22g)Q{sFL>hSsc`Bsph-L^(a#w64%+mq2w zQF{K_MrGN?6_SF2>Uu$q6T7R80%h(?j&6#}6iTSddaU|IpwDMnpHjeq$+t*LdeO^+ zn3x!Y!MLX7hlI3BjCg?;A>^^RdHdO!7*f{q$MDA@+5FJAg08a6Uui+DN0$k_% zY_~laQ0;i`+&PG5%^Xh6HyMBTTeWqKHU?cvy*SkrMbM#h^JaskTYpJ!sq365Z*gYke15C!-kb@(KgXmhH#ZlD zlLugHYipaHo(`bp;y0<3mXOfR`$wmK1R9?E-y>z>y_;hEzoLpLW|#bIw_E=4;4c0*0>;IWO(^n?l}8Citj{j0Zb1)Aj{y(-^| z3-m`}0sCZ0zPho&D(CC%=~-%_pHsP;J{$HzoDBQMb*?9?wAA2G_d{f){z#p%x3_n) zYK(!FR?^KGVTl?&4z&3F@7vNYos6MoS#YYS!#AiU681g3k-@5kRM_5Ly@o`4JG;r| zXn-u;x|7eIK4lOv=G8P)R8%Z;Tj+`rfPA~h%g?X6s++|9yT8yrTGTdiv9%on?_h7M z@y{3=e*0AxVLTu!zeFJHxk}hLpvKqiEHg7R)fmaIdhDq3VAC+t8B)@*Vnc_1V%QIV zAWYLx1xPmAata*B&v?Z->6}A*qT(et{xZEUO2Pk%7cX7Bc#MXo*a-m`pK?u0zd&QF z79**B!)B!NVC8Qey(Xb){kyZmFLboEgU`qqxKcn>X;^T};3-W?;^5%8?%MuN5#7r* zucMAM2~FgFb4sA+AM=cJ3G5)hek47Qj@~8oJ}hjiDN+Q`VR?C(gz7XAF|neO5;dFb z{zSOR;X-^0fAMR$*3cSZ>%6C~rS(C_DO%$cdY_M9b(e09kK?F zT>mueKc`dN*CGm(0uui-RS? zai-&r$uzRtrV0xFs%-pM{(0gLwkRklUi1umuQjKqU%Jm8$+}tWG!h#cnsDE;$Z;s@ z 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 AG5`Po literal 0 HcmV?d00001 diff --git a/GUI/logos/filmforge.svg b/GUI/logos/filmforge.svg new file mode 100644 index 0000000..1e8a63a --- /dev/null +++ b/GUI/logos/filmforge.svg @@ -0,0 +1,6 @@ + + + + FILM + Forge + diff --git a/GUI/selftest_gui.py b/GUI/selftest_gui.py index d7a46a1..8c0fdc1 100644 --- a/GUI/selftest_gui.py +++ b/GUI/selftest_gui.py @@ -40,6 +40,9 @@ window = app_fluent.App(0) window.show() app.processEvents() +# 0) 图标链路: logos/filmforge.ico 存在时窗口图标必须已加载 +check('窗口图标已设置', not window.windowIcon().isNull(), True) + # 1) 空数据渲染不抛异常(占位文案) img = QImage(window.chart.size(), QImage.Format_ARGB32) window.chart.render(img) diff --git a/Inc/board_config.h b/Inc/board_config.h index 72a5a7e..a844f4c 100644 --- a/Inc/board_config.h +++ b/Inc/board_config.h @@ -149,13 +149,15 @@ void usart1_irq_handler(void); #define HEATER_EN_RCU RCU_GPIOB #define HEATER_EN_PORT GPIOB #define HEATER_EN_PIN GPIO_PIN_12 -#define HEATER_PWM_PERIOD_MS 1000U /* PWM 载波周期(1s,恒 1Hz 开关频率) */ -#define HEATER_PWM_STEP_PCT 10U /* 占空比步进(每档脉宽 = 100ms) */ +#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_RAMP_CPS 0.8f /* 目标斜坡速率(℃/s):内部设定值缓升,抑制热滞后过冲 */ +#define HEATER_SLOWBAND_C 7.0f /* 温和带宽度:距目标小于此值时占空上限受限 + (低速爬完最后一段,减少蓄热惯性过冲) */ +#define HEATER_SLOWBAND_DUTY 25U /* 温和带内占空上限(%) */ #define HEATER_OVERTEMP_C 150.0f /* 过温强制关断阈值(滞回保护) */ #define HEATER_OVERTEMP_HYST 5.0f /* 过温恢复滞回 */ diff --git a/Inc/heat.h b/Inc/heat.h index 4d0c88d..27af12e 100644 --- a/Inc/heat.h +++ b/Inc/heat.h @@ -40,7 +40,7 @@ void heat_set_target(float temp_c); float heat_get_target(void); /** - * @brief 查询当前量化占空比档位(0~10,对应 0~100%,10% 步进)。 + * @brief 查询当前量化占空比档位(0~20,对应 0~100%,5% 步进)。 */ uint8_t heat_get_duty(void); diff --git a/Src/heat.c b/Src/heat.c index 2ce1a7c..70a7301 100644 --- a/Src/heat.c +++ b/Src/heat.c @@ -1,10 +1,14 @@ /** * @file heat.c - * @brief 加热控制:慢 PWM 驱动(PB12)+ PID 控温闭环 + 安全保护。 + * @brief 加热控制:慢 PWM 驱动(PB12)+ 分段 PID 控温闭环 + 安全保护。 * @details 硬件:PB12=HEAT_EN,高 = 加热开(2N7002K 电平转换 → VBE2412 PMOS → 24V)。 - * 开关频率约束(<=10Hz)由"1s 载波周期 + 10% 占空步进"保证——每档脉宽 - * 恒为 100ms 的整数倍。PID 以 500ms 周期计算(NTC 后台采样 100ms), - * 输出量化到 0~10 档占空。 + * 开关频率约束(<=10Hz)由"2s 载波周期 + 5% 占空步进"保证——每档脉宽 + * 恒为 100ms 的整数倍(0.5Hz 载波、20 档占空分辨率)。 + * 控制为分段式: + * err >= 温和带 全量 PID(满功率爬升); + * 0 <= err < 温和带 正常 PID 但占空上限压到温和档(小功率缓升, + * 减少进带蓄热,降低越过目标后的惯性过冲); + * err < 0(超温) 强制关断 + 积分按负误差回退(执行器只能加热不能制冷)。 * @note 安全保护:上电默认关闭;NTC 传感器故障(OPEN/SHRT)强制关断; * 过温(>= HEATER_OVERTEMP_C)滞回保护;未使能强制关断。 */ @@ -19,20 +23,20 @@ /* 内部状态 */ static bool s_enable = false; /* 加热使能(M3) */ -static float s_target = 0.0f; /* 目标温度(M4,外部设定值) */ -static float s_ramp_target = 0.0f; /* 内部斜坡目标(以 HEATER_RAMP_CPS 滑向 s_target) */ -static uint8_t s_duty10 = 0; /* 量化占空档位 0~10(x10%) */ +static float s_target = 0.0f; /* 目标温度(M4) */ +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 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) + /******************************************************************************/ /* 底层输出 */ -static void heat_out(bool on); - static void heat_out(bool on) { if (on) { gpio_bit_set(HEATER_EN_PORT, HEATER_EN_PIN); @@ -61,21 +65,28 @@ static void pid_reset(void) { } /** - * @brief PID 计算(500ms 周期):误差 -> 占空档位 s_duty10(0~10)。 + * @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(); /* 传感器故障(开路/短路):安全第一,强制关断并复位 PID */ if (t <= -273.0f) { - s_duty10 = 0; + s_duty = 0; pid_reset(); return; } /* 未使能:强制关断 */ if (!s_enable) { - s_duty10 = 0; + s_duty = 0; pid_reset(); return; } @@ -87,57 +98,60 @@ static void pid_compute(void) { s_overtemp = false; } if (s_overtemp) { - s_duty10 = 0; + s_duty = 0; pid_reset(); return; } - /* 目标斜坡:内部设定值以受限速率滑向 s_target。 - 满功率爬升段 ramp 超前于实际温升(不受影响);接近目标后 ramp 先到站停住, - 误差随实际温度逼近而平滑收缩 -> 功率缓退,抑制热传导滞后造成的过冲。 */ - float diff = s_target - s_ramp_target; - float step = HEATER_RAMP_CPS * (HEATER_PID_PERIOD_MS / 1000.0f); - if (diff > step) { - s_ramp_target += step; - } else if (diff < -step) { - s_ramp_target -= step; - } else { - s_ramp_target = s_target; + float err = s_target - t; + + /* 超温区间:执行器只能加热不能制冷,输出必须为 0; + 积分按负误差回退,为温度回落后的重新加热保留正确初始态 */ + if (err < 0.0f) { + s_integral = integ_step(err); + s_prev_err = err; + s_duty = 0; + return; } - /* 位置式 PID(带抗积分饱和):输出饱和时积分仅沿退饱和方向更新, - 防止升温段积分顶满后长时间维持 100% 输出(此前实测目标 50℃ 冲到 59℃) */ - float err = s_ramp_target - t; - float u_noi = HEATER_PID_KP * err - + HEATER_PID_KD * (err - s_prev_err); /* 比例 + 微分 */ - float integ_new = s_integral + err; - /* 超温区间(实际温度高于内部目标):加热器只能加热、不能制冷, - 输出必须为 0(此前积分正残量会算出小占空在超调段续灌加热); - 积分按负误差同步回退,为温度回落后的重新加热保留正确初始态 */ - if (err < 0.0f) { - s_integral = integ_new; - s_prev_err = err; - s_duty10 = 0; + /* 位置式 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) */ } else { - /* 误差为正(未达/低于内部目标):正常 PID + 抗积分饱和 */ - float u = u_noi + HEATER_PID_KI * integ_new; + s_integral = integ_step(err); + } + + /* 温和带:距目标不足 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; + } + if (u_pd < 0.0f) { + u_pd = 0.0f; + } + u = u_pd + HEATER_PID_KI * s_integral; if (u > 100.0f) { u = 100.0f; - /* 超上限:仅误差为负才吸收——err>=0 分支不会发生,保持积分不动 */ - } else { - s_integral = integ_new; } - /* 输出量化到 10% 档位(四舍五入) */ - s_duty10 = (uint8_t)((u + (float)HEATER_PWM_STEP_PCT / 2.0f) / (float)HEATER_PWM_STEP_PCT); } - s_prev_err = err; + + /* 输出量化到档位(四舍五入) */ + s_duty = (uint8_t)((u + (float)HEATER_PWM_STEP_PCT / 2.0f) / (float)HEATER_PWM_STEP_PCT); } /******************************************************************************/ /* 公共接口 */ void heat_task(void) { - uint8_t prev_duty = s_duty10; + uint8_t prev_duty = s_duty; /* PWM 载波相位推进(10ms tick) */ s_pwm_phase_ms += 10U; @@ -150,16 +164,16 @@ void heat_task(void) { if (s_pid_divider >= HEATER_PID_PERIOD_MS) { s_pid_divider = 0U; pid_compute(); - if (s_duty10 != prev_duty) { + if (s_duty != prev_duty) { s_pwm_phase_ms = 0U; /* 占空变更:从新周期起点开始,响应即时 */ } } - /* 按占空档位输出:0 档常关、10 档常开、其余按相位斩波 */ - uint16_t on_ms = (uint16_t)s_duty10 * (HEATER_PWM_PERIOD_MS / 10U); - if (s_duty10 == 0U) { + /* 按占空档位输出:0 档常关、满档常开、其余按相位斩波 */ + uint16_t on_ms = (uint16_t)s_duty * (HEATER_PWM_PERIOD_MS / HEATER_PWM_STEPS); + if (s_duty == 0U) { heat_out(false); - } else if (s_duty10 >= 10U) { + } else if (s_duty >= HEATER_PWM_STEPS) { heat_out(true); } else { heat_out(s_pwm_phase_ms < on_ms); @@ -169,15 +183,8 @@ void heat_task(void) { void heat_set_enable(bool on) { if (s_enable == on) return; /* 幂等 */ s_enable = on; - if (on) { - /* 从当前实际温度起坡,避免内部目标从 0 跳变造成功率冲击 */ - s_ramp_target = ntc_get_temperature(); - if (s_ramp_target < 0.0f) { - s_ramp_target = 0.0f; /* 传感器故障标记值非法,退化为 0 */ - } - } else { - s_ramp_target = 0.0f; - s_duty10 = 0; + if (!on) { + s_duty = 0; pid_reset(); heat_out(false); } @@ -196,5 +203,5 @@ float heat_get_target(void) { } uint8_t heat_get_duty(void) { - return s_duty10; + return s_duty; }