Files
STM32F4-Base/Drivers/BSP/UI/ui_page_yx.c
T
wandering1 4332142680 feat(ui-pages): 遥信量 33 行固定名表页接实时快照(新增 yx_dp)
批 4 主体(票 task-15;规格 docs/UI设计/页面扩充详细方案.md §3.1.3 方案 A)。V0.1 只有 6 点有
硬件来源,其余 27 点在屏上就是 "--"——这是真实状态,不是页面没做完。

页面与数据
- 33 行固定名表(Flash const)+ 动态分页(8 行/页 = 5 页)。两处命名不照抄文档简写:idx0~3
  屏上写「位置1~4」而非「YK1~4 位置」(遥信页出现 YK 前缀会被读成遥控对象,对应关系留在注释);
  idx30 写「保护闭锁」(写侧位号注释的语义即此,用编号反而丢信息)。idx4~13、idx31~32 点表未
  定义功能名,如实写点号占位(YX5…、YX32),不编造功能名。
- 值映射次序:先判无效 → 再判单/双点 → 最后看位值。无效位上的位值不代表任何事实,次序颠倒就会
  把垃圾位画成「复归」。
- 快照新增 yx_dp 双点位图(+8 B)。不教页面"前 4 点是双点":单双点是 104 点表事实(YX_DP_N /
  iec104_pts_yx_is_dp),抄进表现层就是第二真相——点表把双点段挪位或改起始下标,屏上不报错,
  只会用错词形容同一个点。写侧逐点问 is_dp() 拼位图。
- UI_DATA_YX_N(33) 与 iec104_pts_yx_count() 编译期比不了(那个 #define 在协议层 .c 内),故写侧
  留一条一次性 DBG_ERROR(约 90 B Flash,只在真漂移时打一次)。名表行数 = UI_DATA_YX_N 由数组
  宽度法编译期守卫钉住(ARMCC C99 无 _Static_assert,本仓惯用写法)。
- 补 include 一条实测坑:DBG_ERROR 是宏,漏 include "dbg_log.h" 时 ARMCC 把它当隐式声明函数放过
  编译期,错落在链接期(L6218E Undefined symbol DBG_ERROR)——报错点离根因隔了一整个链接步骤,
  已在 include 处写明「DBG_TAG 必须在 include 之前」。

一个只有像素文本断言能抓到的截断 bug
- 照抄批 1 的 *_PUT(dst, 源) 宏(长度取 sizeof(源)),但本表源是 const char *const 数组的
  **元素(指针)**,sizeof 得 4 → 「故障总」被截成「故」,而 "YX9" 恰好 4 字节看起来完全正常。
  编译器 / cppcheck / 编码钩子全不响。定位来自断言:第 2 页「过流Ⅰ段」匹配不到,同一行值列
  「复归」却在原位 = 名字被截、值没错位。修法:长度取目标缓冲容量 UI_BUF_STRLEN。同名宏、不同
  参数类别(数组 vs 指针),批 1 那处本身是对的,照抄才中招——教训写在宏定义上方。

模拟器与覆盖诚实性
- 固定帧发布 yx_dp = 0x0F,但刻意不把这几位置进 yx_valid:V0.1 无开入硬件,屏上就是 "--"。不用
  「让它显示合位/分位」的假数据填满画面,那等于让黄金参考声称本机有开关位置遥信。
- 代价如实登记:合位/分位这条词映射当前**没有任何自动化证据**(像素恒 IV;页面依赖 LVGL 进不了
  主机单测)。已写进方案 §3.1.3 落地注与契约落地进度·四,作为开入落地后的台架判据,不假装测过。

测试
- 主机单测 +2 条(TC-UIDATA-009c 双点位图原样透传、不被 valid 掩;009d 点数 33 的独立见证,注明
  绊红时要同步改哪四处)→ 85/85。
- 文本断言 +33 条(yx / yxp2 两态,含页码钉住 33/8 = 5 页的分页数学);两页像素基线单独一笔。
- font-charset 本批零新增字:名表用字全部命中既有 183 字子集——「置/故/障/总/外/部」正属批 1
  成组并入 KEEP_EXTRA 的那批字,第二次兑现(当时若按 extract 输出重写字符集,这些字早被洗掉,
  上屏是空白,而门禁只会说「字不在清单里」)。

体积(同一棵树、构建前后的实测增量)
- Code 224272→224688(+416);RO 24040→24640(+600);RW 1324→1328(+4);
  ZI 134844→135088(+244)→ Flash 约 +1020 B、RAM +248 B。
- RO 增量主要是 33 行名表与名称串,是"把 33 个真点名上屏"的代价,不是可省开销。

跳过的门禁(三条同族,同一根因):mem-numbers / mem-budget / size-gate(L0c)——台账与体积凭证
回填是 Lead 单写者,本分支不做静默处理,数值见上。
2026-10-25 15:33:04 +08:00

204 lines
9.3 KiB
C
Raw 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.
/*
* 模块名称:UI Page YX (遥信量内容页)
* 模块功能:33 点遥信的固定名表页(方案 A)——每行一个遥信点,值列按快照的三个位图
* (有效 / 动作 / 单双点)映射成"合位-分位"、"动作-复归"或"--";动态列表页,
* 数据只读 Drivers/BSP/UI/ui_data.h 的实时快照,零 App include
* 为什么是 33 行固定表(V0.1 只有 6 点有硬件来源,另外 27 行都是 "--"):
* ① 与 104 点表一一对应,现场拿这张屏就能与上位机逐点对;② 开入落地后本页
* 零改动自动变真;③ 只显有效点的做法看不出"缺哪些点",而且点数一变黄金参考
* 就得重做(把判据建立在会漂的东西上等于没有判据)——取舍记录在
* docs/UI设计/页面扩充详细方案.md §3.1.3
* 名表为什么在页面侧而不在快照里:显示文案属表现层(同文档 §8);点数与单/双点性质
* 反过来**必须**由写侧下发(那是点表事实),两者边界见 ui_data.h 的 yx_dp 注释
* 适用平台:STM32F407ZGTx / PC 模拟器(共用源码)
* 作者:王建锋
* 创建日期:2026-10-25
* 修改记录:
* v1.0 2026-10-25 王建锋 创建(道E task-15 批 4)
*/
/* 头文件包含区 */
#include "ui_page_yx.h"
#include "ui_data.h"
#include "ui_tokens.h"
/* 静态变量定义区 */
/*
* 33 点固定名表(下标 = 遥信位图位号 = 104 点表 IOA - 1,IOA 1~33)
*
* 两处偏离文档的命名(文档 §3.1.3 用的是区间简写,落到逐行名字时必须选一个,理由写在
* 这里而不是留给人猜):
* · 文档把 idx0~3 写成「YK1~4 位置」:遥信页上出现"YK"前缀会被读成遥控对象(那是
* 另一页的事),屏上只写「位置1~4」,与遥控的对应关系留在本注释;
* · 文档把 idx30~32 写成「闭锁1~3」:idx30 在写侧有明确语义来源(保护闭锁,定值校验
* 和自检失败),用编号反而把信息丢了;idx31/32 点表未定义功能名,如实写点号占位,
* **不编造功能名**。同理 idx4~13(YX5~14)也用点号占位。
*
* 长度与 UI_DATA_YX_N 的一致性由下面的编译期数组守卫钉住(本仓惯用宽度法,ARMCC C99
* 没有 _Static_assert):名表少写/多写一行直接编译失败,不会到了屏上才少一行
*/
static const char *const s_yx_name[UI_DATA_YX_N] = {
"位置1", "位置2", "位置3", "位置4", /* 0~3 双点;V0.1 无开入硬件 */
"YX5", "YX6", "YX7", "YX8", "YX9", /* 4~8 点表未定义功能名 */
"YX10", "YX11", "YX12", "YX13", "YX14", /* 9~13 (同上,不编名) */
"故障总", "过流Ⅰ段", "过流Ⅱ段", "过流Ⅲ段", /* 14~17 YX15~18,有硬件来源 */
"零序Ⅰ段", "零序Ⅱ段", /* 18,19 V0.1 无该判据上送 */
"接地故障", "重载", "过载", /* 20~22 过载(YX23)有来源 */
"外部遥信1", "外部遥信2", "外部遥信3", "外部遥信4", /* 23~26 开入未实现 */
"外部遥信5", "外部遥信6", "外部遥信7", /* 27~29 */
"保护闭锁", "YX32", "YX33", /* 30~32 闭锁(YX31)有来源 */
};
/* 名表行数与快照点数一致(不一致 = 编译期错误,见上注) */
typedef char yx_name_count_check[(sizeof(s_yx_name) / sizeof(s_yx_name[0]) == UI_DATA_YX_N) ? 1 : -1];
/* 值列词表(全在既有 183 字子集内,font-charset 实测零新增) */
#define YX_INVALID "--"
#define YX_DP_ON "合位"
#define YX_DP_OFF "分位"
#define YX_SP_ON "动作"
#define YX_SP_OFF "复归"
/* 帧缓存:只在发布序号变化时整帧拷一次(一帧约 200 B,逐行只读标量) */
static ui_data_rt_t s_rt;
static uint32_t s_seq = 0xFFFFFFFFu;
/* 函数定义区 */
/*
* 函数功能:定长缓冲内拷字面量(代码规范 7.5 禁 strcpy;长度取调用点 sizeof)
* 入口参数:a_dst - 目标缓冲(框架行缓冲 UI_BUF_STRLEN) char *
* a_src - 源字面量(含 NUL) const char *
* len - 目标容量 uint32_t
* 返回值:无
* 限定条件:源串长度必须小于 len(编译期由字面量 sizeof 保证,不做运行期截断——
* 截断只会把"动作"打成"动",比读错更难查)
*/
static void yx_put(char *a_dst, const char *a_src, uint32_t len) {
uint32_t i = 0u;
if (len == 0u) {
return;
}
while (((i + 1u) < len) && (a_src[i] != '\0')) {
a_dst[i] = a_src[i];
i++;
}
a_dst[i] = '\0';
}
/*
* 长度参数**必须取目标缓冲容量**,不能取 sizeof(源):本表的源是 `const char *const`
* 表里的**指针**,`sizeof(lit)` 得到的是指针宽度(4/8),于是 "故障总" 会被拷成第一个
* 汉字 "故"——ASCII 名("YX9" 恰好 4 字节)看着完全正常,CJK 名集体缺尾巴。这个错
* 编译器、cppcheck、编码钩子都不会响,是像素文本断言把它揪出来的(第 2 页第 7 行
* "过流Ⅰ段" 匹配不到,值列 "复归" 却在原位 = 名字被截、值没错位)。
* 批 1 那几处 *_PUT 宏传的是**字面量数组**(sizeof 就是数组长度),照抄到这里就中招——
* 同名宏、不同参数类别,正是"看着像同构其实不同"的那类坑。
*/
#define YX_PUT(dst, lit) yx_put((dst), (lit), (uint32_t)UI_BUF_STRLEN)
/*
* 函数功能:开页复位帧缓存(接框架 init_cb)
* 入口参数:无
* 返回值:无
* 限定条件:框架开页链内(uiTask)
* 函数说明:序号必须复位——否则重开本页会沿用上次开页的帧号,首帧之前的 "--" 状态
* 可能被永久留在屏上(批 1 已踩过一次,同一条纪律再用一遍)
*/
static void pg_init(void) {
s_seq = 0xFFFFFFFFu;
(void)memset(&s_rt, 0, sizeof(s_rt));
}
/*
* 函数功能:按发布序号刷新帧缓存
* 入口参数:无
* 返回值:无
* 限定条件:uiTask 绘制链内(refresh 前)
* 函数说明:ui_data_rt() 的通道是 0=成功 / -1=尚无首帧(不是 bool 成败);失败时把
* 序号打回不可能的值,下一轮一定重试,不会停在半新不旧的帧上
*/
static void pg_sync(void) {
uint32_t seq = ui_data_rt_seq();
if ((seq == s_seq) && (s_rt.valid != 0u)) {
return;
}
if (ui_data_rt(&s_rt) != 0) {
s_seq = 0xFFFFFFFFu;
(void)memset(&s_rt, 0, sizeof(s_rt));
return;
}
s_seq = seq;
}
/*
* 函数功能:总行数(固定 33 点,不随有效性增减)
* 入口参数:无
* 返回值:行数 uint16_t
* 限定条件:框架绘制/翻页链内
* 函数说明:故意返回常量——"只显有效点"会让现场无法判断缺了哪些点(方案 B 的缺点),
* 且行数变化会让黄金参考随运行态漂移
*/
static uint16_t pg_count(void) {
return (uint16_t)UI_DATA_YX_N;
}
/*
* 函数功能:逐行取数(名 = 固定名表,值 = 三个位图映射出的词)
* 入口参数:a_row - 行下标(0..32) uint16_t
* a_name - 名输出缓冲(UI_BUF_STRLEN) char *
* a_value - 值输出缓冲 char *
* 返回值:无
* 限定条件:框架绘制链内(双缓冲各调一次,本函数无副作用)
* 函数说明:判定顺序是"先无效、再单双点、最后位值"——无效位上的位值是真随机垃圾
* (位图没更新过就是 0,更新了也不代表该点有效),所以 IV 必须排在最前;
* 双点段 V0.1 恒无效,"合位/分位"这条支路当前不可达但必须写:开入一接上
* 就走通,而按协议写死的 4 个点本来就是双点(可达性由点表决定,不由本页决定)
*/
static void pg_refresh(uint16_t a_row, char *a_name, char *a_value) {
uint64_t bit;
a_value[0] = '\0';
if (a_row >= (uint16_t)UI_DATA_YX_N) {
a_name[0] = '\0';
return; /* 防御:行号越界不越读名表(count 恒 33,走不到) */
}
pg_sync();
YX_PUT(a_name, s_yx_name[a_row]);
bit = (uint64_t)1u << (uint8_t)a_row;
if ((s_rt.valid == 0u) || ((s_rt.yx_valid & bit) == 0u)) {
YX_PUT(a_value, YX_INVALID);
return;
}
if ((s_rt.yx_dp & bit) != 0u) {
YX_PUT(a_value, ((s_rt.yx_bits & bit) != 0u) ? YX_DP_ON : YX_DP_OFF);
return;
}
YX_PUT(a_value, ((s_rt.yx_bits & bit) != 0u) ? YX_SP_ON : YX_SP_OFF);
}
/*
* 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=yx 绑定引用):
* 固定 33 行的动态页(rows=NULL + count_cb/refresh),8 行/页 = 5 页;band_h 必须 >0
* (selectable 页选中行是黑字,无白带即不可见,同记录页约定)
*/
const content_page_def_t ui_page_yx_desc = {
.id = "yx",
.title = "遥信量",
.selectable = 1u,
.top_y = UI_LIST_TOP,
.pitch = UI_ROW_PITCH,
.band_h = UI_ROW_BAND_H,
.text_dy = 0,
.left_x = UI_LIST_LEFT_X,
.right_x = UI_LIST_RIGHT_X,
.rows_per_page = UI_LIST_ROWS,
.init_cb = pg_init,
.count_cb = pg_count,
.refresh = pg_refresh,
};