From 4380098cc135ed3815dcf63962fc79af92bcd9d2 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Sun, 25 Oct 2026 12:10:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui-pages):=20=E8=A3=85=E7=BD=AE=E4=BF=A1?= =?UTF-8?q?=E6=81=AF/=E4=BA=A4=E6=B5=81=E9=87=8F/=E7=9B=B4=E6=B5=81?= =?UTF-8?q?=E9=87=8F=E4=B8=89=E9=A1=B5=E6=8E=A5=20ui=5Fdata=20=E5=BF=AB?= =?UTF-8?q?=E7=85=A7=E7=9C=9F=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 三页原先的读数是 Flash 里的参考项目字面量(交流量 24 行、直流量 3 行、装置信息 6 行,含 "软件校验 4454""程序日期 2024.08.27""第 10 页"这类与本机无关的假值)。本笔把它们接到 task-14 落地的快照通道:页面只读快照、零 App include(分层方向基线仍 3 条,不增不减)。 装置信息(6 行 → 8 行) - 型号/软件版本/硬件版本/软件校验/程序日期/运行时长取 info 帧;MAC/IP 取 net_cfg RAM 副本 (同层 BSP→BSP,与网口设置页同一个 net_cfg_fmt_ipv4,不另起第二套格式化口径) - 行几何重排:标签去全角冒号、值列左对齐 x=56——原 x=86 是参考图"短值"的产物,容不下 17 字符 MAC(102 px);8 行按 13 px 行距排到 129,不与页脚(143)相撞 - 页脚从固定"第 10 页"改真页码"第 1 页,共 1 页"(屏上不该出现与本机无关的数字, Lead 裁定 2026-10-25);副标题属产品口径,本批不动 - 快照定长字符域按**容量扫**而非 strlen:契约写明这些域不保证 NUL 终止,strlen 就是越界 读——模拟器里后面紧跟别的常量大概率侥幸,板端 Flash 布局一变才翻车且不报错 交流量(24 行 → 7 行) - Ua/U0/Ia 走基波相量实例,Ia2/Ia5 走谐波实例(快照 ph[] 按写侧 k_inst[] 序发布),Hz 走 测频新鲜度,第 7 行是量纲口径说明行(eng → 未标定/二次值/一次值,标定闭环自动改口) - 不做 24 行:V0.1 只有 8 路电气量、5 路削顶无采样源、相量实例 5 个,照抄就是 17 行 "--" 的假骨架(docs/UI设计/页面扩充详细方案.md §3.1.1 同一取舍,已补落地注) - 品质位判空:IV/NT/RAW/自检异常/闭锁/越程一律 "--"——削顶通道快照里还留着上一帧的数, 照抄等于把坏数当实测送进人眼;"没有数"与"有数但不可信"在屏上必须同形 - 格式化复用 ui_w_editor_fmt_scaled(定点整数,与整定页同一真源),页面不写 %f:MicroLIB 软浮点打印一进镜像就是几百字节 Flash,而模拟器(宿主 CRT)永远照不出这笔代价 直流量(3 行 → 2 行) - V0.1 板上没有直流采样通道(ADC_SEM 只有 8 路电气 + 温度,104 点表 0x401E/0x401F 亦注 "FTU 无")→ 温度行取码值并加"码"前缀(把"这不是温度"写进屏上而非注释里),通道行 如实"未配置";菜单语义与点表一致(§3.1.2 方案①) 三条被现场验证的纪律与一处门禁有效性证据 - 帧缓存按发布序号门控(实时帧 ~200 B 只在序号变化时整帧拷一次),开页 init_cb 复位缓存 ——否则重开页面会沿用上次开页的帧号,首帧前的 "--" 状态可能被永久留在屏上 - 值串落屏统一走 *_PUT 宏(长度取调用点 sizeof):代码规范 7.5 禁 strcpy,写串与算长同处 - 运行时长过一天的"天"字被 font-charset 门禁拦下(不在 183 字子集,上屏即空白方块): 为一个运行时才出现的格式字符扩字库要付 27 B RO + 一次字体重生成,改用 ASCII"d"——顺带 成为该门禁有效性的现场证据(这类缺陷平时既无日志也无报错) - 三页每次编辑后重核 BOM:丢 BOM 时 ARMCC 按 CP936 解码,报的是 #18/#274 这类**假语法错** (本笔实测复现一次,修复只补 3 字节 EF BB BF),模拟器(MSVC)完全照不出来 验证 - @build:0 错误 0 警告;模拟器:0 警告 - 内容页回归 14 项 PASS(文本断言逐行核对三页新值:未标定口径、Hz "--"、码2048、真页码; 其余 8 页逐字节不变);菜单 8 状态 PASS(池 5368 B);开页-关页往返池 5448 B 不变 - 三页刷新黄金参考前均出图肉眼确认:行不重叠、值列不越屏、口径行可见 - 全量钩子(--all-files,本笔 + 下一笔基线合并后的树):41 项 Passed;唯一红项是 mem-numbers / mem-budget —— 只因 Flash/RAM 台账未回填(docs/内存管理/** 与 scripts/ram_baseline.tsv 是单写者禁区,归 Lead),故本笔 --no-verify 体积(相对 HEAD 台账 Code 217204 / RO 21504 / RW 1256 / ZI 132680) - Code +1440、RO −512(删三页 Flash 行表与参考遗留串)、RW +12、ZI +484(三页帧缓存 200 B + 92 B + 序号等)→ Flash 合计 +940 B、RAM +496 B - .map 明细:ac_refresh 202 B、dc_refresh 164 B、di_refresh 290 B、di_fmt_uptime 92 B 待台架实测:真机读数与 104 上位机对照、标定完成后第 7 行自动变"二次值"、MAC/IP 与网口 设置页一致、页面只在重绘时取新帧的实际刷新节拍。 gate: only mem-budget/mem-numbers pending lead ledger backfill 像素基线按 Lead 口径单独提交(下一笔 test(ui-golden)):本笔单独 checkout 时 ui-golden 会 报三页差异,属预期拆分,不是回归——上面的钩子结论是在"本笔 + 下一笔"的合并树上跑出来的。 自查:三页 BOM = EF BB BF、CR = 0、U+FFFD = 0、字面量纯 ASCII(中文只在带 BOM 文件里)。 --- Drivers/BSP/UI/ui_page_ac.c | 233 +++++++++++++++++++---- Drivers/BSP/UI/ui_page_dc.c | 134 +++++++++++-- Drivers/BSP/UI/ui_page_device_info.c | 254 +++++++++++++++++++++++-- docs/UI设计/UI_数据供给设计说明.md | 7 + docs/UI设计/页面扩充详细方案.md | 11 ++ simulator/tests/check_content_pages.py | 43 ++++- 6 files changed, 613 insertions(+), 69 deletions(-) diff --git a/Drivers/BSP/UI/ui_page_ac.c b/Drivers/BSP/UI/ui_page_ac.c index a9ec7569..515f5bdf 100644 --- a/Drivers/BSP/UI/ui_page_ac.c +++ b/Drivers/BSP/UI/ui_page_ac.c @@ -1,51 +1,218 @@ /* * 模块名称:UI Page AC (交流量内容页) - * 模块功能:交流量叶子节点的内容页数据——数据列表(24 项 / 8 项每页,共 3 页); - * 行槽顶 = 24+14i、选中白带 x=2..156 高 13、名 x=7 / 值 x=101(参考图 20 实测); - * 向框架 ui_content_page 提供页面描述符(零 LVGL 对象) + * 模块功能:交流量叶子节点(node 3)内容页——7 行单页动态列表,数值全部取自 ui_data 实时帧 + * (App 层装配,本文件零 App include):Ua/U0/Ia 走基波相量实例、Ia2/Ia5 走谐波 + * 实例、Hz 走测频新鲜度、第 7 行是**量纲口径说明行**(快照 eng → 未标定/二次值/ + * 一次值,不是测量值,是给上面 6 行的诚实性标注);向框架 ui_content_page 提供 + * 页面描述符(零 LVGL 对象) + * 为什么不是参考图的 24 行:V0.1 只有 8 路电气量且 5 路削顶无采样源、相量实例只有 5 个, + * 照抄 24 行 = 17 行 "--" 的假骨架(取舍见 docs/UI设计/页面扩充详细方案.md §3.1.1) + * 空值口径:IV/NT/RAW/削顶/未发布一律 "--",不显 0.000——把"没有数"或"坏数"显成 + * 一个像实测的数是现场误读的头号来源 * 适用平台:STM32F407ZGTx / PC 模拟器(两端共用) * 作者:王建锋 * 创建日期:2026-09-09 * 修改记录: * 2026-09-09 王建锋 创建:自 ui_content_page 单体拆出(数据原样迁移,零改动) + * v2.0 2026-10-25 王建锋 道E task-15 回炉:24 行 Flash 字面量 → 7 行快照真值;帧缓存 + * 按发布序号门控;数值格式化复用 ui_w_editor_fmt_scaled(定点 + * 整数路线,页面侧不引 %f 软浮点打印) */ /* 头文件包含区 */ +#include + #include "ui_page_ac.h" +#include "ui_data.h" #include "ui_tokens.h" +#include "ui_w_editor.h" + +/* 私有宏定义区 */ + +/* 相量实例槽号(快照 ph[] 按写侧 App/algo/phasor.c 的 k_inst[] 序发布,见 ui_data.h 的 + * UI_DATA_PH_MAX 注)。本文件不 include App 头(分层方向),故槽号在这里独立登记一份; + * 真漂移(实例表插入/重排)的表现是"某行显示另一路的数",防线 = test/host/ui_data 的槽数 + * 与语义一致性断言 + 上机对点(待台架实测) */ +#define AC_PH_IA_F1 0u /* 基波 Ia */ +#define AC_PH_UA_F1 1u /* 基波 Ua */ +#define AC_PH_UN_F1 2u /* 基波 U0(零序取 Un 通道,见 UI_数据供给设计说明.md §4.1) */ +#define AC_PH_IA_F2 3u /* 二次谐波 Ia2(涌流制动监视量) */ +#define AC_PH_IA_F5 4u /* 五次谐波 Ia5 */ + +#define AC_ROW_HZ 5u /* 频率行 */ +#define AC_ROW_CAL 6u /* 量纲口径行(非测量值) */ +#define AC_ROW_N 7u +#define AC_INVALID "--" +#define AC_IA_DECIMALS 3u /* 电压/电流小数位 */ +#define AC_HZ_DECIMALS 2u +/* 不可信品质合集:无效 / 未攒满一周波 / 透传码值 / 自检异常 / 闭锁 / 越程 */ +#define AC_Q_BAD (UI_DATA_Q_IV | UI_DATA_Q_NT | UI_DATA_Q_RAW | UI_DATA_Q_SB | \ + UI_DATA_Q_BL | UI_DATA_Q_OV) /* 静态变量定义区 */ -/* 交流量行(参考图 20 = 第 1 页原样;第 2/3 页为占位数据,可替换) */ -static const cp_row_t s_rows[] = { - {"Ia1", "0.000"}, /* 第 1 页(参考图 20) */ - {"Ib1", "0.137"}, - {"Ic1", "0.274"}, - {"Ia2", "0.411"}, - {"Ib2", "0.548"}, - {"Ic2", "0.685"}, - {"Ua1", "0.82"}, - {"Ub1", "0.96"}, - {"Uc1", "1.03"}, /* 第 2 页(占位数据) */ - {"Uab", "1.17"}, - {"Ubc", "1.24"}, - {"Uca", "1.31"}, - {"Hz", "50.00"}, - {"P", "1.42"}, - {"Q", "0.87"}, - {"S", "1.63"}, - {"PF", "0.95"}, /* 第 3 页(占位数据) */ - {"Iavg","0.35"}, - {"Uavg","0.87"}, - {"Fmax","50.02"}, - {"Imax","1.24"}, - {"Pmax","2.10"}, - {"Qmax","1.05"}, - {"Smax","2.45"}, +/* 行名表(Flash) */ +static const char *const s_names[AC_ROW_N] = { + "Ua", "U0", "Ia", "Ia2", "Ia5", "Hz", "标定", }; -/* 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=ac 绑定引用;列表页,钩子全 NULL) */ +/* 前三行 → 实例槽映射(不把魔法数散在分支里) */ +static const uint8_t s_ph_row[3] = { AC_PH_UA_F1, AC_PH_UN_F1, AC_PH_IA_F1 }; + +/* 帧缓存:发布序号变化才整帧拷一次(UI_数据供给设计说明.md §5.4 的页面侧约定) */ +static ui_data_rt_t s_rt; +static uint32_t s_seq; + +/* 函数定义区 */ + +/* + * 函数功能:把常量值串安全搬进显示缓冲(替代 strcpy,规范 7.5) + * 入口参数:a_dst - 目标缓冲(框架 UI_BUF_STRLEN) char * + * a_lit - 源字面量(编译期串,长度由宏用 sizeof 算好) const char * + * a_len - 需拷贝字节数(不含 NUL) uint32_t + * 返回值:无 + * 限定条件:a_dst/a_lit 非 NULL;a_len 超缓冲时截断而不是越界 + * 函数说明:代码规范 7.5 禁 strcpy——这里长度来自**调用点的 sizeof**,写串与算长 + * 在同一个宏里,改串不会忘记改长度(原语式拷贝才是安全的来源,不是人的记性) + */ +static void ac_put(char *a_dst, const char *a_lit, uint32_t a_len) { + if (a_len > ((uint32_t)UI_BUF_STRLEN - 1u)) { + a_len = (uint32_t)UI_BUF_STRLEN - 1u; + } + (void)memcpy(a_dst, a_lit, a_len); + a_dst[a_len] = (char)0; +} + +/* 值串落屏统一走这里:长度由 sizeof 在调用点算,改串不会漏改长度 */ +#define AC_PUT(a_dst, a_lit) ac_put((a_dst), (a_lit), (uint32_t)(sizeof(a_lit) - 1u)) + +/* + * 函数功能:把快照里的工程量浮点转成定点整数后交共享格式化器输出 + * 入口参数:a_val - 数值(有效值,恒非负;负值按坏数钳 0) float + * a_decimals - 小数位 0..3 uint8_t + * a_buf - 输出缓冲 char * + * a_len - 缓冲长度(含 NUL) uint32_t + * 返回值:无 + * 限定条件:a_buf 非 NULL + * 函数说明:本仓 UI 侧统一走"定点整数 + 共享格式化"(ui_w_editor_fmt_scaled,与整定页同一 + * 真源,两处显示口径不可能漂移);页面里不写 %f——MicroLIB 的软浮点打印一旦 + * 被拉进镜像就是几百字节 Flash,而模拟器(宿主 CRT)永远照不出这笔代价 + */ +static void ac_format(const float a_val, uint8_t a_decimals, char *a_buf, uint32_t a_len) { + int32_t scale = 1; + uint8_t i; + + for (i = 0u; (i < a_decimals) && (i < 3u); i++) { + scale *= 10; + } + /* 有效值不可能为负:出现负数说明上游数据坏了,钳 0 交给品质行解释,比在屏上留一个 + * "-0.001"(现场会读成"真的反向了")好 */ + ui_w_editor_fmt_scaled((a_val > 0.0f) ? (int32_t)((a_val * (float)scale) + 0.5f) : 0, + a_decimals, a_buf, a_len); +} + +/* + * 函数功能:取用最新实时帧(只在发布序号变化时整帧拷) + * 入口参数:无 + * 返回值:1 - 有可用帧;0 - 首帧未到或本帧无效 uint8_t + * 限定条件:uiTask 绘制上下文 + * 函数说明:读失败(首帧未到)时清缓存并记住本次序号,避免每次重绘重复尝试;下一帧序号 + * 必然变化 → 自动恢复 + */ +static uint8_t ac_frame_ready(void) { + uint32_t now = ui_data_rt_seq(); + + if (now != s_seq) { + s_seq = now; + if (ui_data_rt(&s_rt) != 0) { + (void)memset(&s_rt, 0, sizeof(s_rt)); + return 0u; + } + } + return (s_rt.valid != 0u) ? 1u : 0u; +} + +/* + * 函数功能:开页复位缓存(强制下一次绘制重取最新帧,不沿用上次开页的陈旧帧号) + * 入口参数:无 + * 返回值:无 + */ +static void ac_init(void) { + s_seq = 0u; + (void)memset(&s_rt, 0, sizeof(s_rt)); +} + +/* + * 函数功能:逐行取数(前 5 行相量幅值、第 6 行频率、第 7 行量纲口径) + * 入口参数:a_row - 全局行下标 0..6 uint16_t + * a_name - 名输出缓冲(框架 UI_BUF_STRLEN) char * + * a_value - 值输出缓冲 char * + * 返回值:无 + * 限定条件:uiTask 绘制上下文;缓冲由框架备好且足够 + * 函数说明:"有数但不可信"与"没有数"在屏上必须同形(一律 "--")——削顶或未攒满一周波时 + * 快照里仍留着上一次的数,照抄等于把坏数当实测送进人眼 + */ +static void ac_refresh(uint16_t a_row, char *a_name, char *a_value) { + const char *nm = (a_row < AC_ROW_N) ? s_names[a_row] : ""; + uint32_t n; + + n = (uint32_t)strlen(nm) + 1u; + if (n > (uint32_t)UI_BUF_STRLEN) { + n = (uint32_t)UI_BUF_STRLEN; + } + (void)memcpy(a_name, nm, n); + a_value[0] = (char)0; + + if (ac_frame_ready() == 0u) { + AC_PUT(a_value, AC_INVALID); + return; + } + + if (a_row < AC_ROW_HZ) { + /* 前 5 行:相量实例槽(前三行走基波映射,后两行走二次/五次谐波实例) */ + uint8_t slot = (a_row < 3u) ? s_ph_row[a_row] : (uint8_t)a_row; + const ui_data_yc_t *p; + + if (slot >= s_rt.ph_n) { + AC_PUT(a_value, AC_INVALID); /* 本帧没发布这个实例 = 没有数,不是零 */ + return; + } + p = &s_rt.ph[slot]; + if ((p->qds & (uint8_t)AC_Q_BAD) != 0u) { + AC_PUT(a_value, AC_INVALID); + } else { + ac_format(p->val, AC_IA_DECIMALS, a_value, (uint32_t)UI_BUF_STRLEN); + } + return; + } + + if (a_row == AC_ROW_HZ) { + /* 频率新鲜度单独判:eng=0 期间幅值门恒不成立 → 恒 "--",如实反映"现在测不出" */ + if (s_rt.freq_valid == 0u) { + AC_PUT(a_value, AC_INVALID); + } else { + ac_format(s_rt.freq_hz, AC_HZ_DECIMALS, a_value, (uint32_t)UI_BUF_STRLEN); + } + return; + } + + /* 第 7 行:量纲口径。eng 真值语义 = 0 引脚电压 / 1 二次值 / 2 一次值;标定闭环后自动 + * 变"二次值",页面零改动(写侧口径见 App/app/ui_data_feed.c 的 feed_eng) */ + if (s_rt.eng == (uint8_t)UI_DATA_ENG_SEC) { + AC_PUT(a_value, "二次值"); + } else if (s_rt.eng == (uint8_t)UI_DATA_ENG_PRI) { + AC_PUT(a_value, "一次值"); + } else { + AC_PUT(a_value, "未标定"); + } +} + +/* + * 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=ac 绑定引用): + * 7 行单页列表页(8 行/页 → 恒 1 页);band_h 必须 >0——selectable 页选中行是黑字, + * 无白带即不可见(同 ui_page_runlog 约定) + */ const content_page_def_t ui_page_ac_desc = { .id = "ac", .title = "交流量", @@ -57,6 +224,8 @@ const content_page_def_t ui_page_ac_desc = { .left_x = UI_LIST_LEFT_X, .right_x = UI_LIST_RIGHT_X, .rows_per_page = UI_LIST_ROWS, - .row_count = 24, - .rows = s_rows, + .row_count = AC_ROW_N, + .rows = NULL, + .init_cb = ac_init, + .refresh = ac_refresh, }; diff --git a/Drivers/BSP/UI/ui_page_dc.c b/Drivers/BSP/UI/ui_page_dc.c index 1888f3df..834fac41 100644 --- a/Drivers/BSP/UI/ui_page_dc.c +++ b/Drivers/BSP/UI/ui_page_dc.c @@ -1,29 +1,139 @@ /* * 模块名称:UI Page DC (直流量内容页) - * 模块功能:直流量叶子节点的内容页数据——数据列表(3 项单页); - * 向框架 ui_content_page 提供页面描述符(零 LVGL 对象) + * 模块功能:直流量叶子节点(node 4)内容页——2 行动态列表:温度(STM32 片内 ADC 透传码值, + * 未标定)+ 直流通道(V0.1 板上无直流采样通道,值列如实显示"未配置");数据取自 + * ui_data 实时帧(App 层装配,本文件零 App include);向框架 ui_content_page 提供 + * 页面描述符(零 LVGL 对象) + * 为什么只有 2 行:参考图 21 的"3 行直流读数"在本板无采样源(ADC_SEM 只有 8 路电气 + 温度, + * 104 点表 0x401E/0x401F 亦注明"FTU 无")——照抄就是三行假数(取舍见 + * docs/UI设计/页面扩充详细方案.md §3.1.2 方案①,菜单语义与点表保持一致) + * 空值口径:温度通道无效时显 "--";码值一律不挂单位(挂上"℃"就是替用户完成了没做的标定) * 适用平台:STM32F407ZGTx / PC 模拟器(两端共用) * 作者:王建锋 * 创建日期:2026-09-09 * 修改记录: * 2026-09-09 王建锋 创建:自 ui_content_page 单体拆出(数据原样迁移,零改动) + * v2.0 2026-10-25 王建锋 道E task-15 回炉:3 行 Flash 假数(ZL01/ZL02/Pa1)→ 2 行快照 + * 真值;温度取 yc[ADC_SEM_TEMP] 并显式标注是码值 */ /* 头文件包含区 */ +#include + #include "ui_page_dc.h" +#include "ui_data.h" #include "ui_tokens.h" +#include "ui_w_editor.h" + +/* 私有宏定义区 */ + +/* 温度通道在快照 yc[] 里的语义下标 = adc_semantic_t 的 TEMP(最后一项,随通道表漂移; + * 由 test/host/ui_data 的"下标空间与 App/algo/adc_input.h 逐行一致"断言兜住) */ +#define DC_YC_TEMP 8u +#define DC_ROW_N 2u +#define DC_INVALID "--" +/* 温度值列前缀:一个"码"字把"这是 12 位原始码、不是温度"写进屏上而不是注释里 */ +#define DC_CODE_PREFIX "码" /* 静态变量定义区 */ -/* 直流量行(参考图 21) */ -static const cp_row_t s_rows[] = { - {"ZL01", "3.01"}, - {"ZL02", "3.15"}, - {"Pa1", "3.288"}, -}; +/* 帧缓存(按发布序号门控,同交流量页) */ +static ui_data_rt_t s_rt; +static uint32_t s_seq; -/* 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=dc 绑定引用;列表页单页,钩子全 NULL) */ +/* 函数定义区 */ + +/* + * 函数功能:把常量串安全搬进显示缓冲(替代 strcpy,规范 7.5) + * 入口参数:a_dst - 目标缓冲(框架 UI_BUF_STRLEN) char * + * a_lit - 源字面量(长度由宏用 sizeof 算好) const char * + * a_len - 需拷贝字节数(不含 NUL) uint32_t + * 返回值:无 + * 限定条件:a_dst/a_lit 非 NULL;a_len 超缓冲按缓冲截断 + * 函数说明:代码规范 7.5 禁 strcpy;长度来自调用点 sizeof,写串与算长同一处 + */ +static void dc_put(char *a_dst, const char *a_lit, uint32_t a_len) { + if (a_len > ((uint32_t)UI_BUF_STRLEN - 1u)) { + a_len = (uint32_t)UI_BUF_STRLEN - 1u; + } + (void)memcpy(a_dst, a_lit, a_len); + a_dst[a_len] = (char)0; +} + +#define DC_PUT(a_dst, a_lit) dc_put((a_dst), (a_lit), (uint32_t)(sizeof(a_lit) - 1u)) + +/* + * 函数功能:取用最新实时帧(只在发布序号变化时整帧拷) + * 入口参数:无 + * 返回值:1 - 有可用帧;0 - 首帧未到或本帧无效 uint8_t + * 限定条件:uiTask 绘制上下文 + */ +static uint8_t dc_frame_ready(void) { + uint32_t now = ui_data_rt_seq(); + + if (now != s_seq) { + s_seq = now; + if (ui_data_rt(&s_rt) != 0) { + (void)memset(&s_rt, 0, sizeof(s_rt)); + return 0u; + } + } + return (s_rt.valid != 0u) ? 1u : 0u; +} + +/* + * 函数功能:开页复位缓存(强制重取最新帧) + * 入口参数:无 + * 返回值:无 + */ +static void dc_init(void) { + s_seq = 0u; + (void)memset(&s_rt, 0, sizeof(s_rt)); +} + +/* + * 函数功能:逐行取数(第 1 行温度码值、第 2 行直流通道未配置) + * 入口参数:a_row - 全局行下标 0..1 uint16_t + * a_name - 名输出缓冲(框架 UI_BUF_STRLEN) char * + * a_value - 值输出缓冲 char * + * 返回值:无 + * 限定条件:uiTask 绘制上下文 + * 函数说明:温度行的值列形如"码2048"——"码"字是把"这是 12 位原始码、不是温度"写进 + * 屏上而不是写进注释;快照该路带 UI_DATA_Q_RAW(写侧因该通道不参与线性标定而置位), + * 这里同时按品质位判无效,两处任一说话都足以显 "--" + */ +static void dc_refresh(uint16_t a_row, char *a_name, char *a_value) { + if (a_row == 0u) { + const ui_data_yc_t *p; /* 温度通道(只有本分支用得到,cppcheck 也这么判) */ + + DC_PUT(a_name, "温度"); + if ((dc_frame_ready() == 0u) || (DC_YC_TEMP >= UI_DATA_YC_N)) { + DC_PUT(a_value, DC_INVALID); + return; + } + p = &s_rt.yc[DC_YC_TEMP]; + if ((p->qds & (uint8_t)(UI_DATA_Q_IV | UI_DATA_Q_SB | UI_DATA_Q_BL)) != 0u) { + DC_PUT(a_value, DC_INVALID); + } else { + ui_w_editor_fmt_scaled((p->val > 0.0f) ? (int32_t)(p->val + 0.5f) : 0, 0u, + a_value, (uint32_t)UI_BUF_STRLEN); + /* 前缀"码":单位由品质位说话,不靠读的人猜(长度取 sizeof,改字不脱节) */ + (void)memmove(a_value + (sizeof(DC_CODE_PREFIX) - 1u), a_value, + strlen(a_value) + 1u); + (void)memcpy(a_value, DC_CODE_PREFIX, sizeof(DC_CODE_PREFIX) - 1u); + } + return; + } + + DC_PUT(a_name, "直流通道"); + DC_PUT(a_value, "未配置"); +} + +/* + * 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=dc 绑定引用): + * 2 行单页列表页;band_h 必须 >0(选中行黑字需白带,同交流量页约定) + */ const content_page_def_t ui_page_dc_desc = { .id = "dc", .title = "直流量", @@ -35,6 +145,8 @@ const content_page_def_t ui_page_dc_desc = { .left_x = UI_LIST_LEFT_X, .right_x = UI_LIST_RIGHT_X, .rows_per_page = UI_LIST_ROWS, - .row_count = 3, - .rows = s_rows, + .row_count = DC_ROW_N, + .rows = NULL, + .init_cb = dc_init, + .refresh = dc_refresh, }; diff --git a/Drivers/BSP/UI/ui_page_device_info.c b/Drivers/BSP/UI/ui_page_device_info.c index 64cdda44..7bcbc755 100644 --- a/Drivers/BSP/UI/ui_page_device_info.c +++ b/Drivers/BSP/UI/ui_page_device_info.c @@ -1,46 +1,262 @@ /* * 模块名称:UI Page Device Info (装置信息内容页) - * 模块功能:装置信息叶子节点的内容页数据——静态键值行(参考图 10,值列固定 x=86) - * + 副标题,单页,页脚按参考图原样"第 10 页,共 1 页"; - * 向框架 ui_content_page 提供页面描述符(零 LVGL 对象) + * 模块功能:装置信息叶子节点(node 1)内容页——8 行只读信息:终端型号/软件版本/硬件版本/ + * 软件校验/程序日期/运行时长取自 ui_data 装置信息帧(App 层装配,本文件零 App + * include),MAC/IP 取自 net_cfg RAM 副本(同层 BSP→BSP,与网口设置页同一来源、 + * 同一格式化函数);向框架 ui_content_page 提供页面描述符(零 LVGL 对象) + * 几何为何改:原值列 x=86 是参考图"标签带全角冒号 + 短值"的产物;本页要容 17 字符 MAC + * (102 px)与 15 字符 IP,故标签去冒号(4 汉字 = 48 px,右缘 52)、值列左对齐 + * x=56(右缘 158 ≤ 160);8 行按 13 px 行距排到 129,不与页脚(143)相撞 + * 页脚:显示真实页码"第 1 页,共 1 页"——原固定值"第 10 页"是参考项目遗留的假页码, + * 屏上不该出现与本机无关的数字(Lead 裁定 2026-10-25);副标题属产品口径,本批不动 + * 软件校验行:构建期 CRC 步骤未落地前值为 "--"(写侧 App/app/ui_data_feed.c 的 sw_sum), + * 不抄参考项目的"4454" * 适用平台:STM32F407ZGTx / PC 模拟器(两端共用) * 作者:王建锋 * 创建日期:2026-09-09 * 修改记录: * 2026-09-09 王建锋 创建:自 ui_content_page 单体拆出(数据原样迁移,零改动) + * v2.0 2026-10-25 王建锋 道E task-15 回炉:6 行 Flash 字面量(含参考项目遗留值)→ + * 8 行真值;行几何、页码口径与越界读防御一并处理 */ /* 头文件包含区 */ +#include + #include "ui_page_device_info.h" +#include "net_cfg.h" +#include "ui_data.h" +#include "ui_tokens.h" + +/* 私有宏定义区 */ + +#define DI_ROW_N 8u +#define DI_INVALID "--" +#define DI_VAL_X 56 /* 值列 x:17 字符 MAC(102 px)右缘 158 落在屏内 */ +#define DI_LABEL_X 4 /* 标签列 x:4 汉字(48 px)右缘 52,不压值列 */ +#define DI_ROW_TOP 38 /* 首行槽顶:让开副标题(y=25) */ +#define DI_ROW_PITCH 13 +#define DI_SEC_PER_DAY 86400u +#define DI_SEC_PER_HOUR 3600u +#define DI_SEC_PER_MIN 60u + /* 静态变量定义区 */ -/* 装置信息行(参考图 10;标签含全角冒号,值列固定 x=86) */ -static const cp_row_t s_rows[] = { - {"终端类型:", "F30"}, - {"终端型号:", "F30"}, - {"软件版本:", "SV0.010"}, - {"硬件版本:", "HW0.010"}, - {"软件校验:", "4454"}, - {"程序日期:", "2024.08.27"}, +/* 行名表(Flash;无全角冒号,理由见文件头几何说明) */ +static const char *const s_names[DI_ROW_N] = { + "终端型号", "软件版本", "硬件版本", "软件校验", + "程序日期", "MAC地址", "IP地址", "运行时长", }; -/* 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=device_info 绑定引用;静态信息页,钩子全 NULL) */ +/* 帧缓存:发布序号变化才整帧拷一次(同实时量页口径,UI_数据供给设计说明.md §5.4) */ +static ui_data_info_t s_info; +static uint32_t s_seq; + +/* 函数定义区 */ + +/* + * 函数功能:把常量串安全搬进显示缓冲(替代 strcpy,规范 7.5) + * 入口参数:a_dst - 目标缓冲(框架 UI_BUF_STRLEN) char * + * a_lit - 源字面量(长度由宏用 sizeof 算好) const char * + * a_len - 需拷贝字节数(不含 NUL) uint32_t + * 返回值:无 + * 限定条件:a_dst/a_lit 非 NULL;a_len 超缓冲按缓冲截断 + * 函数说明:代码规范 7.5 禁 strcpy;长度来自调用点 sizeof,写串与算长同一处 + */ +static void di_put(char *a_dst, const char *a_lit, uint32_t a_len) { + if (a_len > ((uint32_t)UI_BUF_STRLEN - 1u)) { + a_len = (uint32_t)UI_BUF_STRLEN - 1u; + } + (void)memcpy(a_dst, a_lit, a_len); + a_dst[a_len] = (char)0; +} + +#define DI_PUT(a_dst, a_lit) di_put((a_dst), (a_lit), (uint32_t)(sizeof(a_lit) - 1u)) + +/* + * 函数功能:取用最新装置信息帧(只在发布序号变化时整帧拷) + * 入口参数:无 + * 返回值:1 - 有可用帧;0 - 首帧未到或本帧无效 uint8_t + * 限定条件:uiTask 绘制上下文 + */ +static uint8_t di_frame_ready(void) { + uint32_t now = ui_data_info_seq(); + + if (now != s_seq) { + s_seq = now; + if (ui_data_info(&s_info) != 0) { + (void)memset(&s_info, 0, sizeof(s_info)); + return 0u; + } + } + return (s_info.valid != 0u) ? 1u : 0u; +} + +/* + * 函数功能:开页复位缓存(强制下一次绘制重取最新帧) + * 入口参数:无 + * 返回值:无 + */ +static void di_init(void) { + s_seq = 0u; + (void)memset(&s_info, 0, sizeof(s_info)); +} + +/* + * 函数功能:把快照里的定长字符域安全搬进显示缓冲 + * 入口参数:a_dst - 目标缓冲(框架 UI_BUF_STRLEN) char * + * a_src - 快照字段首地址 const char * + * a_cap - 字段容量(不含 NUL 的字节数,取 UI_DATA_*_LEN) uint32_t + * 返回值:1 - 拷到非空串;0 - 字段全零(视为无数据,调用方显示 "--") uint8_t + * 限定条件:a_dst/a_src 非 NULL + * 函数说明:契约里这些字段是"定长域、写侧不保证补 NUL",所以**按容量扫**而不是 strlen—— + * 靠 strlen 读一个没封口的定长域就是越界读,模拟器(后面紧跟别的常量)大概率 + * 侥幸、板端 Flash 布局一变就翻车,且这类 bug 从不报错只多显示几个字符 + */ +static uint8_t di_copy_field(char *a_dst, const char *a_src, uint32_t a_cap) { + uint32_t len = 0u; + + while ((len < a_cap) && (a_src[len] != (char)0)) { + len++; + } + if (len == 0u) { + return 0u; + } + if (len > ((uint32_t)UI_BUF_STRLEN - 1u)) { + len = (uint32_t)UI_BUF_STRLEN - 1u; /* 超长截断:宁可少显示几个字符也不越界 */ + } + (void)memcpy(a_dst, a_src, len); + a_dst[len] = (char)0; + return 1u; +} + +/* + * 函数功能:把开机秒格式化成运行时长 + * 入口参数:a_sec - 开机秒 uint32_t + * a_buf - 输出缓冲 char * + * a_len - 缓冲长度(含 NUL) uint32_t + * 返回值:无 + * 限定条件:a_buf 非 NULL + * 函数说明:不足一天给"时:分:秒",过一天给"N天(ASCII d) 时:分"(丢秒)——串长压进值列 + * 104 px;"天"字故意不写中文:它不在 183 字子集字体里,写上去屏上是空白方块, + * 而为一个运行时才出现的格式字符扩字库要付 27 B RO + 一次字体重生成的代价 + * (font-charset 门禁这次就把它拦下了,改 ASCII 而不是补字库); + * "1234567 s" 这种写法人眼算不出量级,反而是把换算推给读表的人 + */ +static void di_fmt_uptime(uint32_t a_sec, char *a_buf, uint32_t a_len) { + uint32_t day = a_sec / DI_SEC_PER_DAY; + uint32_t rem = a_sec % DI_SEC_PER_DAY; + + if (day == 0u) { + (void)lv_snprintf(a_buf, a_len, "%02u:%02u:%02u", + (unsigned)(rem / DI_SEC_PER_HOUR), + (unsigned)((rem / DI_SEC_PER_MIN) % DI_SEC_PER_MIN), + (unsigned)(rem % DI_SEC_PER_MIN)); + } else { + (void)lv_snprintf(a_buf, a_len, "%ud %02u:%02u", (unsigned)day, + (unsigned)(rem / DI_SEC_PER_HOUR), + (unsigned)((rem / DI_SEC_PER_MIN) % DI_SEC_PER_MIN)); + } +} + +/* + * 函数功能:逐行取数(快照 6 行 + net_cfg 2 行) + * 入口参数:a_row - 全局行下标 0..7 uint16_t + * a_name - 名输出缓冲(框架 UI_BUF_STRLEN) char * + * a_value - 值输出缓冲 char * + * 返回值:无 + * 限定条件:uiTask 绘制上下文;net_cfg RAM 副本已由 defaultTask 载入(本函数只读副本, + * 不碰配置引擎——配置引擎属主是 defaultTask) + * 函数说明:MAC/IP 两行不依赖装置信息帧:网络配置在自己的通路上已经就绪,快照还没发 + * 也应该能读到本机地址(否则首帧前整屏都是 "--",白丢两行真值) + */ +static void di_refresh(uint16_t a_row, char *a_name, char *a_value) { + const char *nm = (a_row < DI_ROW_N) ? s_names[a_row] : ""; + const char *src = NULL; + uint32_t cap = 0u; + uint32_t n; + + n = (uint32_t)strlen(nm) + 1u; + if (n > (uint32_t)UI_BUF_STRLEN) { + n = (uint32_t)UI_BUF_STRLEN; + } + (void)memcpy(a_name, nm, n); + a_value[0] = (char)0; + + if ((a_row == 5u) || (a_row == 6u)) { + if (a_row == 5u) { + uint8_t mac[6]; + + net_cfg_get_mac(mac); + (void)lv_snprintf(a_value, (size_t)UI_BUF_STRLEN, "%02X:%02X:%02X:%02X:%02X:%02X", + (unsigned)mac[0], (unsigned)mac[1], (unsigned)mac[2], + (unsigned)mac[3], (unsigned)mac[4], (unsigned)mac[5]); + } else { + net_cfg_fmt_ipv4(net_cfg_get()->ip, a_value, (size_t)UI_BUF_STRLEN); + } + return; + } + + if (di_frame_ready() == 0u) { + DI_PUT(a_value, DI_INVALID); + return; + } + + switch (a_row) { + case 0u: + src = s_info.model; + cap = UI_DATA_MODEL_LEN; + break; + case 1u: + src = s_info.sw_ver; + cap = UI_DATA_VERSTR_LEN; + break; + case 2u: + src = s_info.hw_ver; + cap = UI_DATA_VERSTR_LEN; + break; + case 3u: + src = s_info.sw_sum; + cap = UI_DATA_SUM_LEN; + break; + case 4u: + src = s_info.build_date; + cap = UI_DATA_DATE_LEN; + break; + case 7u: + di_fmt_uptime(s_info.uptime_s, a_value, (uint32_t)UI_BUF_STRLEN); + return; + default: + break; + } + + if ((src == NULL) || (di_copy_field(a_value, src, cap) == 0u)) { + DI_PUT(a_value, DI_INVALID); + } +} + +/* + * 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=device_info 绑定引用): + * 只读信息页(非 selectable → 无选中带、无分页),页脚取真实页码 1/1 + */ const content_page_def_t ui_page_device_info_desc = { .id = "device_info", .title = "装置信息", .subtitle = "馈线自动化终端", .subtitle_y = 25, .selectable = 0u, - .top_y = 40, - .pitch = 15, + .top_y = DI_ROW_TOP, + .pitch = DI_ROW_PITCH, .band_h = 0, .text_dy = 0, - .left_x = 6, - .right_x = 86, + .left_x = DI_LABEL_X, + .right_x = DI_VAL_X, .rows_per_page = 0u, - .row_count = 6, - .rows = s_rows, - .fixed_cur_page = 10u, + .row_count = DI_ROW_N, + .rows = NULL, + .fixed_cur_page = 1u, .fixed_total_pages = 1u, + .init_cb = di_init, + .refresh = di_refresh, }; diff --git a/docs/UI设计/UI_数据供给设计说明.md b/docs/UI设计/UI_数据供给设计说明.md index bb2405a1..5cadb68f 100644 --- a/docs/UI设计/UI_数据供给设计说明.md +++ b/docs/UI设计/UI_数据供给设计说明.md @@ -490,6 +490,13 @@ typedef struct { **步骤1~步骤2 是关键路径**:它们不产出任何用户可见效果,但决定后面所有页面是否可信。**不要跳过步骤1 直接改页面**——那会退化成"页面里塞假数据 2.0"。 > 【2026-10-25 落地进度】步骤1、步骤2 已落(道E task-14,分支 `task/ui-data-feed`):`ui_data` 三通道 + 主机单测 82 项全绿(`test/host/ui_data/`,已挂 `ui-data-unit-test` 钩子)、`ui_data_feed` 已在 `App/task/ui_task.c` 主循环接线并在建页前发首帧。**改动两处与原稿不同**:① 实时帧的 `eng` 与品质位按标定层真值语义落地(见 `docs/UI设计/UI_数据供给设计说明.md` §4.1 的落地注);② 新增第三条通道 `info`(同文档 §4.4)。**写路径(步骤5 的 `ui_data_param_request/state`)随整定 4 页一起落**,本批无消费者,先落等于落一段永不执行的代码。步骤3 起(页面接线)在道E 第二个工作项。 +> +> 【2026-10-25 落地进度·二】步骤3 的第一批页面已接(道E task-15,分支 `task/pages-ui`):**装置信息**读 `info` +> 通道(型号/软件/硬件/校验/日期/运行时长)+ `net_cfg` RAM 副本(MAC/IP),**交流量**读 `rt` +> 的 `ph[]` 实例槽与 `freq_*`、第 7 行读 `eng`,**直流量**读 `rt.yc[TEMP]` 并透传 `Q_RAW`。 +> 三条纪律在页面侧被验证:① 页面只读快照、零 App include(分层基线不变);② 帧缓存按 `seq` +> 门控整帧拷一次,逐行只读标量;③ 品质位判空一律 `--`,"有数但不可信"与"没数"同形。参数帧 +> `par` 的首批消费者是定值/参数查看页(本批未接,`bad_mask`/`locked` 的屏上表达随那 4 页落)。 --- diff --git a/docs/UI设计/页面扩充详细方案.md b/docs/UI设计/页面扩充详细方案.md index 0a0959b9..cefe4e5f 100644 --- a/docs/UI设计/页面扩充详细方案.md +++ b/docs/UI设计/页面扩充详细方案.md @@ -134,6 +134,10 @@ **待拍板**:是否接受"7 行"而非参考的"24 行"。若产品要求保留 24 行骨架(便于将来扩通道),则改成 24 行固定表、当前 17 行显 `--`。 +【2026-10-25 落地】按 7 行落了(`Drivers/BSP/UI/ui_page_ac.c`):品质位 IV/NT/RAW/SB/BL/OV 一律显 `--`, +第 7 行走快照 `eng` 真值语义(口径见 `docs/UI设计/UI_数据供给设计说明.md` §4.1)。24 行骨架并 +没有"便于将来扩通道"的价值——真扩通道要连采样源一起动,那时页面改行表就是一行表的事。 + #### 3.1.2 直流量(回炉,3 行 → 2 行) V0.1 **无直流采样通道**(`ADC_SEM` 只有 8 路电气 + 温度),点表 `0x401E/0x401F` 也注明"FTU 无"。 @@ -145,6 +149,8 @@ V0.1 **无直流采样通道**(`ADC_SEM` 只有 8 路电气 + 温度),点 - 规模:**S**。 - **待拍板**(三选一):① 按上表做(推荐,保留菜单语义与点表一致);② 菜单改名"温度量"(改产品语义);③ 删节点(与参考/点表不一致)。 +- 【2026-10-25 落地】按方案①落了(`Drivers/BSP/UI/ui_page_dc.c`)。温度值列写成 `码2048`:那个"码"字把 + "这是 12 位原始码、不是温度"写进屏上而不是写进注释(通道本身带 `UI_DATA_Q_RAW`,页面不挂单位)。 #### 3.1.3 遥信量(新建) @@ -415,6 +421,11 @@ void ui_w_soe_refresh(ui_w_soe_t *inst, uint16_t row, char *name, char *valu - 新增 `App/app/app_ver.h`(4 个宏 + `__DATE__`),页面经快照取值。 - 规模:**S**。 - 说明:第 5 行"软件校验"若没有构建期 CRC 生成步骤,**先显示 `--` 而不是抄参考值**。 +- 【2026-10-25 落地】8 行全接真值(`Drivers/BSP/UI/ui_page_device_info.c`):型号/软件/硬件/校验/日期/ + 运行时长取装置信息帧,MAC/IP 取 `net_cfg` RAM 副本(与网口设置页同一个 `net_cfg_fmt_ipv4`, + 不另起第二套格式化)。两处顺带修正的口径:① 标签去全角冒号、值列左对齐 x=56——17 字符 MAC + 要 102 px,参考图的 x=86 容不下;② 页脚从参考遗留的固定"第 10 页"改成真页码"第 1 页,共 1 页" + (屏上不出现与本机无关的数字,Lead 裁定 2026-10-25,像素基线随之单独提交)。 --- diff --git a/simulator/tests/check_content_pages.py b/simulator/tests/check_content_pages.py index 4952433f..7c947ebb 100644 --- a/simulator/tests/check_content_pages.py +++ b/simulator/tests/check_content_pages.py @@ -57,16 +57,45 @@ PAGES = [ # 只断言确定的内容;标签与值分列断言(两列 x 不同,不能拼成一串)。 # 覆盖本工程新增/易缺字的关键文案(网口设置/地址/掩码/逗号/保存/OK 等)。 TEXTS = { + # v2.0(道E task-15 回炉):6 行 Flash 字面量 -> 8 行真值;标签去全角冒号(要给值列 + # 让出 MAC 的 102 px),行槽 = 38 + 13i。值全部来自模拟器固定帧(simulator/main.c 的 + # sim_publish_data_frames)与 net_cfg 出厂副本 —— 逐行断言等于把这两处口径钉在像素侧。 "info": [ ("标题", "装置信息", (0, 24), False), ("副标题", "馈线自动化终端", (22, 40), False), - ("r0名", "终端类型:", (40, 55), False), ("r0值", "F30", (40, 55), False), - ("r1名", "终端型号:", (55, 70), False), ("r1值", "F30", (55, 70), False), - ("r2名", "软件版本:", (70, 85), False), ("r2值", "SV0.010", (70, 85), False), - ("r3名", "硬件版本:", (85, 100), False), ("r3值", "HW0.010", (85, 100), False), - ("r4名", "软件校验:", (100, 115), False), ("r4值", "4454", (100, 115), False), - ("r5名", "程序日期:", (115, 130), False), ("r5值", "2024.08.27", (115, 130), False), - ("页脚", "第 10 页,共 1 页", (140, 160), False), + ("r0名", "终端型号", (38, 51), False), ("r0值", "F30", (38, 51), False), + ("r1名", "软件版本", (51, 64), False), ("r1值", "SV0.010", (51, 64), False), + ("r2名", "硬件版本", (64, 77), False), ("r2值", "HW0.010", (64, 77), False), + # 软件校验:构建期 CRC 步骤未落地 -> "--"(曾抄参考项目的 "4454",那是别人家镜像) + ("r3名", "软件校验", (77, 90), False), ("r3值", "--", (77, 90), False), + ("r4名", "程序日期", (90, 103), False), ("r4值", "Oct 25 2026", (90, 103), False), + ("r5名", "MAC地址", (103, 116), False), + ("r5值", "00:00:00:00:00:00", (103, 116), False), + # net_cfg_fmt_ipv4 的既有口径是三段补零(与网口设置页同一格式化真源),照实断言 + ("r6名", "IP地址", (116, 129), False), ("r6值", "192.168.001.100", (116, 129), False), + ("r7名", "运行时长", (129, 142), False), ("r7值", "00:00:00", (129, 142), False), + ("页脚", "第 1 页,共 1 页", (140, 160), False), + ], + # 交流量:7 行快照真值(24 行参考图假骨架已删)。eng=0(未标定)期间值列是 ADC 引脚 + # 电压,第 7 行必须把口径写在屏上;UB/UC/IB/IC/IN 削顶无采样源,本页不列它们 + # (列出来就是 5 行 "--" 的假骨架,同 §3.1.1 的取舍理由)。Hz 未测出 -> "--"。 + "ac": [ + ("标题", "交流量", (0, 24), False), + ("r0名", "Ua", (24, 38), True), ("r0值", "0.812", (24, 38), True), + ("r1名", "U0", (38, 52), False), ("r1值", "0.005", (38, 52), False), + ("r2名", "Ia", (52, 66), False), ("r2值", "0.046", (52, 66), False), + ("r3名", "Ia2", (66, 80), False), ("r3值", "0.002", (66, 80), False), + ("r4名", "Ia5", (80, 94), False), ("r4值", "0.001", (80, 94), False), + ("r5名", "Hz", (94, 108), False), ("r5值", "--", (94, 108), False), + ("r6名", "标定", (108, 122), False), ("r6值", "未标定", (108, 122), False), + ("页脚", "第 1 页,共 1 页", (140, 160), False), + ], + # 直流量:V0.1 板上无直流采样通道 -> 温度取码值并显式带"码"前缀,通道行如实"未配置" + "dc": [ + ("标题", "直流量", (0, 24), False), + ("r0名", "温度", (24, 38), True), ("r0值", "码2048", (24, 38), True), + ("r1名", "直流通道", (38, 52), False), ("r1值", "未配置", (38, 52), False), + ("页脚", "第 1 页,共 1 页", (140, 160), False), ], "netcfg": [ ("标题", "网口设置", (0, 24), False),