diff --git a/AGENTS.md b/AGENTS.md index 9deaf60..8341baf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -152,6 +152,9 @@ 截图确认);改为纯阶梯(变更时刻垂直落变+水平延伸),数据本身无误。 - v0.2.5:排版调整——串口连接卡片从右列移到左列顶部,左列图表卡/右列日志卡 弹性拉伸填充,两列底边对齐。 + - v0.2.6:对齐修复——双列改放同一滚动容器(整页统一滚动):原先两列各自包 + QScrollArea 独立滚动,窗口化下底边错位(全屏正常);现两列高度锁定一致, + 不足一屏各自拉伸到底、超出一屏整页滚动。 - selftest 全过(累计新增 14 项回归用例);exe 已本地打包冒烟,Gitea 发布 待用户确认后执行。 遗留见其 README"已知事项":应用图标、USB-RS485 适配器更换。 diff --git a/GUI/app_fluent.py b/GUI/app_fluent.py index dce1cce..8d3fbfd 100644 --- a/GUI/app_fluent.py +++ b/GUI/app_fluent.py @@ -60,7 +60,7 @@ import config import filmforge_rs485 as ff APP_NAME = "FilmForge" -APP_VERSION = "0.2.5" +APP_VERSION = "0.2.6" REPO_URL = "https://gitea.hulk.wang/hulk/FilmForge" MONO = QFont("Consolas", 10) CHIP_IDLE = "#666666" @@ -470,27 +470,34 @@ class App(QMainWindow): header.addWidget(self._button("关于", self._about)) outer.addLayout(header) - split = QSplitter(Qt.Horizontal, root) + # 双列放进同一滚动容器: 两列高度锁定一致——不足一屏时各自拉伸到底边, + # 超出一屏时整页一起滚动; 各列独立滚动会让底边在窗口化下错位(实测) + split = QSplitter(Qt.Horizontal) split.setChildrenCollapsible(False) - split.addWidget(self._scroll_column(self._build_left)) - split.addWidget(self._scroll_column(self._build_right)) + split.addWidget(self._column_host(self._build_left)) + split.addWidget(self._column_host(self._build_right)) split.setStretchFactor(0, 3) split.setStretchFactor(1, 2) - outer.addWidget(split, 1) + page = QWidget() + page_lay = QHBoxLayout(page) + page_lay.setContentsMargins(0, 0, 0, 0) + page_lay.addWidget(split) + scroll = QScrollArea() + scroll.setWidgetResizable(True) + scroll.setFrameShape(QScrollArea.NoFrame) + scroll.setWidget(page) + outer.addWidget(scroll, 1) self.setCentralWidget(root) - def _scroll_column(self, builder): + def _column_host(self, builder): + """构建一列的宿主 QWidget(不再各自包 QScrollArea, 整页统一滚动)""" host = QWidget() layout = QVBoxLayout(host) layout.setContentsMargins(2, 2, 8, 2) layout.setSpacing(10) builder(layout) layout.addStretch(1) - scroll = QScrollArea() - scroll.setWidgetResizable(True) - scroll.setFrameShape(QScrollArea.NoFrame) - scroll.setWidget(host) - return scroll + return host # ---------------- 左列: 串口 + 监视 ---------------- def _build_serial_card(self, layout): diff --git a/Inc/board_config.h b/Inc/board_config.h index 20a98a5..72a5a7e 100644 --- a/Inc/board_config.h +++ b/Inc/board_config.h @@ -152,7 +152,7 @@ void usart1_irq_handler(void); #define HEATER_PWM_PERIOD_MS 1000U /* PWM 载波周期(1s,恒 1Hz 开关频率) */ #define HEATER_PWM_STEP_PCT 10U /* 占空比步进(每档脉宽 = 100ms) */ #define HEATER_PID_PERIOD_MS 500U /* PID 计算周期 */ -#define HEATER_PID_KP 4.0f /* PID 参数:小 Kp 换取低过冲(接近目标时减速) */ +#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):内部设定值缓升,抑制热滞后过冲 */ diff --git a/Src/heat.c b/Src/heat.c index e33c46c..2ce1a7c 100644 --- a/Src/heat.c +++ b/Src/heat.c @@ -111,21 +111,26 @@ static void pid_compute(void) { float u_noi = HEATER_PID_KP * err + HEATER_PID_KD * (err - s_prev_err); /* 比例 + 微分 */ float integ_new = s_integral + err; - float u = u_noi + HEATER_PID_KI * integ_new; - - 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; /* 超下限:仅误差为正(升温方向)才吸收 */ + /* 超温区间(实际温度高于内部目标):加热器只能加热、不能制冷, + 输出必须为 0(此前积分正残量会算出小占空在超调段续灌加热); + 积分按负误差同步回退,为温度回落后的重新加热保留正确初始态 */ + if (err < 0.0f) { + s_integral = integ_new; + s_prev_err = err; + s_duty10 = 0; } else { - s_integral = integ_new; /* 未饱和:正常积分 */ + /* 误差为正(未达/低于内部目标):正常 PID + 抗积分饱和 */ + float u = u_noi + HEATER_PID_KI * integ_new; + 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; - - /* 输出量化到 10% 档位(四舍五入) */ - s_duty10 = (uint8_t)((u + (float)HEATER_PWM_STEP_PCT / 2.0f) / (float)HEATER_PWM_STEP_PCT); } /******************************************************************************/