feat(UI): 遥测死区与系数 2 页——注入表第四张、三态用脏位、WAIT 与 OK 各出一张黄金

这两页是「UI补齐」里唯一带**持久化语义**的一档,所以整笔的设计重心不在画面上,而在**页面如何诚实表达
「已受理」与「已落盘」**——这一点由 setpage-scout 在开工前的只读查实钉下来:cfg_file_save() 全仓唯一调用
者是 run_par_store_save(),而 default_task.c:246 **每轮无条件**调它、函数首行 s_dirty==0 就直接返回
⇒
落盘动力本来就在 defaultTask,不在 104 会话、也不在事件位。因此本笔走 A2(UI 不当授权写者),不需要
新事件位、不需要新同步口。

页面三态判据 = run_par_dirty_peek():ENTER / WAIT(脏位仍在 = 已受理未落盘)/ OK(脏位归零 = 整档已外科
保存成功)/ 写被拒 = ERR。两条限制写在页面注释里没藏:脏位是**整档全局**的(一趟落 [run_par] 9 键 +
[adc_cal] 两段,归零只证明「这一档落盘了」,不证明「我这一项单独落盘」);他人随后再改会重新置脏 ⇒ 沿用
clear/clock 页的结果锁存,把「我这次动作的结果」与「档位此刻的状态」分开。

**为什么黄金必须出两张**(这条是本轮最有价值的防假证据规则):模拟器假件**保留脏位**、全仓唯一的
s_yt_dirty=0 写点是 sim_yt_pump()(setter 只置不清),于是 WAIT 与 OK 各出一帧——两组同键序、唯一差别是
EXTRA 里的 --yt-nopump。若假件同步清脏位,真机的 WAIT 态永远不会被像素回归覆盖,黄金就成了「只钉住最
好看那一帧」。TC-UIDATA-028o 另钉住 ui_data 层零缓存。

形状:新增 ui_data 第四张注入表(八字段整表校验、拒残表),实现落在装配层 App/app/ui_data_feed.c,
页面零 App include(分层方向 + 模拟器不链 run_par 两条老理由都成立);共用视图模型 ui_w_ytview 承载两页。
合法域一律由执行侧出口给(run_par_db_range / RUN_PAR_K_MAX_UV),页面不抄第二真源——系数页 v1 只用粗守
并在注释写明两层守卫互不掩盖,µV/码 ×[0.5,2.0] 那条真带的出口器归 task-43,**没有**在页面里抄常量。

取舍与遗留(如实):系数行 d=0 整 µV(1e9 装不下两位小数,注释写了「要小数改哪、不收紧假上限」);b 覆盖
无口未做;徽标只画系数页;行名因「率」缺字改为 Hz死区(font-charset 机器先抓到的,不是我目测判的)。

实测:@build 0 错误 0 警告(Code=233,900 / RW-data=1,536);ui_data 单测 277/277、run_par
67/67 零回归;
内容页黄金 47 页全过、**存量 40 张逐字节零漂移**(git 里只有 7 张新增);menu_rows / 池水位 / font-charset
/
bom / mojibake / nested-comment / cppcheck / coding-standard / layer-deps /
bsp-err 全 0 新增;编号五项式
refs 8,778 / dec 7,622 / und **1,156**(上限 1,166)/ 比值 86.83%(基线 86.0% 之上)/ 错误差值
0,注释零裸形
字母+数字。generated-fresh 与 registration-check 本地 PASS;新增 6 只源文件按规矩**双端注册同笔**(叶子
+
registry 19→21 + uvprojx + CMakeLists),uvprojx 这次属「真的增删源文件」才入库。

移交三条不混进本笔:① 全量 --selftest 现 exit 1 是 task-41 硬闸在模拟器上也拒 reset/clear(既有红,快检档
不受影响),归台架道定夺;② 内容页 40→47 的全仓文档计数改写由 Lead 派;③ run_par_deadband_set 的限定
条件语措辞(「iec104Task 上下文」已被装配层裁定覆盖)归算法层下一笔。
This commit is contained in:
2026-10-27 05:07:18 +08:00
parent 1d39571114
commit 48a8ef0564
27 changed files with 1682 additions and 11 deletions
+150 -1
View File
@@ -24,6 +24,10 @@
* (定值写路径出队 → 闭锁/越界预检 → set_sealed,
* 见 s_param_ops);② 参数帧尾填 cal_block(未标定
* 闭锁徽标,三路口径同 protect_task.c,不用 eng)
* v1.3 2026-10-27 DTU 项目组 道F task-7:遥测整定注入表(ui_data_yt_ops_t)
* 装配层实现 + 首轮懒绑(时序与通道四绑定等价,
* 见 feed_yt_bind_once 函数头);行数等值由编译期
* 守卫钉,量纲分档与合法域分档同源同序
* v1.2 2026-10-26 DTU 项目组 道F task-16:删除本地镜像 feed_cal_block(),
* 两个调用点(填帧 / 门控比较)改调
* adc_cal_prot_blocked()——同一份三路判定此前在
@@ -55,7 +59,8 @@
/* 同层(App)数据源 */
#include "adc_input.h" /* ADC_SEM_COUNT / adc_input_raw(TEMP 码值) */
#include "adc_cal.h" /* adc_cal_flags:VALID/CLIPPED 三态可用性;adc_cal_prot_blocked:未标定闭锁单点 */
#include "adc_cal.h" /* adc_cal_flags:VALID/CLIPPED 三态可用性;adc_cal_prot_blocked:未标定闭锁单点;adc_cal_k_uv:系数当前生效值(与 104 回显同视图) */
#include "run_par.h" /* 道F task-7 遥测整定注入表:死区/合法域/系数 setter/脏位(真源全在本模块与 adc_cal,feed 只转达) */
#include "phasor.h" /* 基波/谐波有效值(fAmSQ) */
#include "freq.h" /* 测频读数与新鲜度 */
#include "prot_elm.h" /* 元件动作态(遥信位图来源) */
@@ -476,6 +481,143 @@ static const ui_data_param_ops_t s_param_ops = {
feed_param_apply
};
/*
* ======================= 道F task-7:遥测整定注入表(ui_data_yt_ops_t)装配
*
* 两页唯一的执行侧转达处。四条装配纪律:
* ① 数值与合法域**都**现给执行侧(run_par_deadband / run_par_db_range /
* adc_cal_k_uv / RUN_PAR_K_MAX_UV)——本文件不出现任何范围数字字面量;
* 系数真带(µV/码执行段裁)没有公开访问器,也**不在此补一份**(task-43 收口),
* 真带拒的值经 set 原样返 -1,页面反显 + 保存行 ERR,两层守卫互不掩盖。
* ② 写走语义 setter(写值与置脏同一函数内发生,run_par_cal_k_set 文件头契约);
* 本层不存在"只置脏不写值"或"只写值不置脏"的组合,UI 永远够不到裸脏位。
* ③ 跨任务写口径:真机默认档 = iec104Task 的 C_SE;本文件的调用在 uiTask,
* 这**正是** Lead 裁定(task-7 派发②)批准的"装配层转达"整定路径——与 104
* 走同一批 setter、同一 int32 单字原子契约,不新开工;run_par.h 里死区旧
* 接口"iec104Task 上下文"的限定条件语写于该裁定之前,语义已被裁定覆盖
* (系数两支的限定条件已明写装配层入口,措辞同步归算法层下一笔)。
* ④ 落盘不靠本表:写成功置脏 → defaultTask 每轮无条件 run_par_store_save()
* (查实:唯一 cfg_file_save 调用者,首行"无脏立即返回";104 的 peek→事件位
* 只是加速器不是必经链)——所以"OK = 盘上真落了"在真机与模拟器(pump 清脏)
* 两端口径一致,黄金参考拍的 WAIT/OK 不是表演。
*/
/* 跨层行数等值守卫:BSP 侧契约值 ↔ 执行侧真值。本文件是全仓第一处两份头
* 同时可见的编译单元——BSP 物理上无法自证(看不到 App 头),守卫落这里 */
typedef char feed_yt_db_n_check_t[(UI_DATA_YT_DB_N == (uint8_t)RUN_PAR_DB_N) ? 1 : -1];
typedef char feed_yt_ch_n_check_t[(UI_DATA_YT_CH_N == (uint8_t)RUN_PAR_CH_N) ? 1 : -1];
/*
* 函数功能:死区点数(执行侧真值直通)
* 入口参数:无
* 返回值:RUN_PAR_DB_N uint8_t
* 限定条件:uiTask(注入表回调)
* 函数说明:行数从这一头出、BSP 契约常量在文件头编译期守卫对表——两边不等
* 编不过,等值不靠注释维持
*/
static uint8_t feed_yt_db_n(void) {
return (uint8_t)RUN_PAR_DB_N;
}
/*
* 函数功能:读第 idx 行死区现状(值 + 粗守域现给;量纲按点型分档)
* 入口参数:idx - 0..8(0..7 电气语义通道序,8 频率) uint8_t
* a_out - 行现状 ui_data_yt_row_t *
* 返回值:0 - 已写出;-1 - 越界 / 指针空(run_par_db_range 自判,本层不重复判) int
* 函数说明:unit 的分档(前 4 电压、中 4 电流、末 1 频率)**必须**与
* run_par_db_range 的档位分档同序——两处真源都没导出量纲,唯一防线是
* run_par.h 地址映射表(0x8030~0x8038 = UA..IN/FREQ)与本表同序这条
* 写死的对表纪律 + 黄金像素(档位漂了屏上会呈现"0~50"配 "A" 的肉眼矛盾);
* decimals 取 3 = 死区在引擎里的存储分辨率(毫单位 int32,换算单点
* s_milli_to_eng 在 run_par.c,本层不重除)
*/
static int feed_yt_db_row(uint8_t a_idx, ui_data_yt_row_t *a_out) {
float lo;
float hi;
if (run_par_db_range(a_idx, &lo, &hi) != 0) {
return -1;
}
a_out->val = run_par_deadband(a_idx);
a_out->lo = lo;
a_out->hi = hi;
a_out->decimals = 3u;
a_out->unit = (a_idx < 4u) ? UI_DATA_YT_UNIT_V
: ((a_idx < 8u) ? UI_DATA_YT_UNIT_A : UI_DATA_YT_UNIT_HZ);
return 0;
}
/* 函数功能:写第 idx 行死区(直通语义 setter;返回值原样上抛,页面据此记账) int */
static int feed_yt_db_set(uint8_t a_idx, float a_eng) {
return run_par_deadband_set(a_idx, a_eng);
}
static uint8_t feed_yt_cal_n(void) {
return (uint8_t)RUN_PAR_CH_N;
}
/*
* 函数功能:读第 idx 路系数现状(k = adc_cal **当前生效值**,µV/码)
* 返回值:0 - 已写出;-1 - idx 越界或指针空 int
* 函数说明:显示值与 104 C_RD 回显同视图(run_par_104_read 用的就是 adc_cal_k_uv:
* 文件坏值被真带回退 ROM 时,屏与主站看到同一份 ROM——"页面值按比例
* 变化"判据因此量的就是执行段真正吃的数);
* lo/hi 只有粗守一层(0..RUN_PAR_K_MAX_UV,上限来自 run_par 唯一出口),
* 真带在执行段裁——两层守卫的注释纪律见 ui_w_ytview 头注;
* decimals 取 0 是**显示域容量约束**不是精度偏好:粗守上限 1e9 在
* ui_w_setlist 的 int32 显示域里只够零位小数(带 2 位则上限装不下),
* 屏上输入按整 µV 量化、写口仍收 float;要小数位得收紧粗守或改组件
* 值通道,都不在本批,也不许在此抄一份更小的假上限
*/
static int feed_yt_cal_row(uint8_t a_idx, ui_data_yt_row_t *a_out) {
if ((a_out == NULL) || (a_idx >= (uint8_t)RUN_PAR_CH_N)) {
return -1;
}
a_out->val = adc_cal_k_uv((adc_semantic_t)a_idx);
a_out->lo = 0.0f; /* 下限 0 由写口拒 k<=0 兜住,这里只给输入框边界 */
a_out->hi = RUN_PAR_K_MAX_UV;
a_out->decimals = 0u;
a_out->unit = UI_DATA_YT_UNIT_UV;
return 0;
}
/* 函数功能:写第 idx 路系数 k(越界防御后再进语义 setter——setter 形参是枚举,
* 编译器帮不上越界忙;返回码直通) int */
static int feed_yt_cal_k_set(uint8_t a_idx, float a_uv) {
if (a_idx >= (uint8_t)RUN_PAR_CH_N) {
return -1;
}
return run_par_cal_k_set((adc_semantic_t)a_idx, a_uv);
}
/* 函数功能:未标定闭锁徽标源(task-16 单点访问器,与判据停走同一个判定) uint8_t */
static uint8_t feed_yt_cal_blocked(void) {
return (adc_cal_prot_blocked() != 0) ? 1u : 0u;
}
/* 函数功能:整档落盘脏位(保存行三态判据;与 104 留痕同一个 peek,不另发明) uint8_t */
static uint8_t feed_yt_pending(void) {
return run_par_dirty_peek();
}
/* 遥测整定注入表(八件齐全;ui_data_yt_bind 拒残表,缺件就是装配错) */
static const ui_data_yt_ops_t s_yt_ops = {
feed_yt_db_n,
feed_yt_db_row,
feed_yt_db_set,
feed_yt_cal_n,
feed_yt_cal_row,
feed_yt_cal_k_set,
feed_yt_cal_blocked,
feed_yt_pending
};
/* 首轮懒绑一次:ui_data_feed_tick() 的首个调用点在 App/task/ui_task.c 里先于
* ui_menu_init()(首帧发布那次),与通道四 ui_data_maint_bind 的绑定时序等价;
* 不在 ui_task.c 直接绑是为了本批写域收在派发单列出的文件里。残表是真故障,
* 必须能在启动日志里看见,不能静默(通道四同口径) */
static uint8_t s_yt_bound;
/*
* 函数功能:组装参数快照(40 项定值 Q10 + 校验和 + 封存态 + 越界位图 + 未标定闭锁)
@@ -558,6 +700,13 @@ void ui_data_feed_tick(void) {
uint32_t sum;
uint8_t due_rt;
if (s_yt_bound == 0u) {
s_yt_bound = 1u; /* 先置位再绑:失败也不每轮重试刷日志(90 B/条) */
if (ui_data_yt_bind(&s_yt_ops) != 0) {
DBG_ERROR("UI yt bind: FAIL");
}
}
now = osKernelGetTickCount();
if (s_map_done == 0u) {
+147
View File
@@ -879,3 +879,150 @@ int ui_data_clock_format(const ui_data_clock_t *p_in, char *a_buf, uint32_t a_le
*p = '\0';
return 0;
}
/* ── 通道五:遥测整定注入(死区 / 系数)──────────────────────────────────── */
/*
* 注入的遥测整定执行侧函数表(NULL = 未注入)。
* 与 s_maint 同一条生命周期约定:注入方(App/app/ui_data_feed.c 与
* simulator/main.c)给的都文件级 static const 表,本层只存指针不拷内容;
* 撤销窗口只在宿主测试里存在。
*
* ⚠ 本层**不持有任何遥测整定的值**:九条死区与八路系数的真源在
* App/algo/run_par.c 与 App/algo/adc_cal.c,落盘动力在 defaultTask 的每轮
* 无条件 run_par_store_save()(首行 if (s_dirty == 0u) return 0;,道F task-7
* 查实结论:落盘链不靠 104 会话、也不靠事件位)。本层若自己存一份,就正是
* 卡片判据"不新增第二份真源"要拦的那件事。页面能问的只有两样:现状(*_row)
* 与"我这次动作落没落"(pending)。
*/
static const ui_data_yt_ops_t *s_yt;
/*
* 函数功能:注入遥测整定执行侧函数表(启动期一次)
* 入口参数:ops - 函数表;八个成员指针都不得为 NULL;NULL = 撤销注入
* 返回值:0 - 已注入(或已撤销);-1 - ops 非空但某成员为 NULL(存量不动) int
* 限定条件:uiTask 启动期,先于 ui_menu_init();本层无锁,与快照层同一并发前提
* 函数说明:整表校验的理由与 ui_data_maint_bind() 逐字相同——半途接错一条会让
* 页面在"按下才崩"的位置崩,装配期就拒掉残表才是省事。残表被拒后**存量
* 不动**(TC-UIDATA-027 钉住这条:好表在用时被残表覆盖会静默把已通的口
* 打断)
*/
int ui_data_yt_bind(const ui_data_yt_ops_t *a_ops) {
if (a_ops == NULL) {
s_yt = NULL; /* 撤销注入:页面随即按"整定能力不可用"渲染 */
return 0;
}
if ((a_ops->db_n == NULL) || (a_ops->db_row == NULL) ||
(a_ops->db_set == NULL) || (a_ops->cal_n == NULL) ||
(a_ops->cal_row == NULL) || (a_ops->cal_k_set == NULL) ||
(a_ops->cal_blocked == NULL) || (a_ops->pending == NULL)) {
return -1;
}
s_yt = a_ops;
return 0;
}
/*
* 函数功能:死区点数
* 返回值:点数;未注入返回 0(页面据此渲染空表,而不是拿编译期常量当"真源说
* 有几行"——行数归执行侧,见 ui_data.h 通道五表注) uint8_t
* 限定条件:页面绘制路径可直调(一次函数指针转发,零 I/O、零锁)
*/
uint8_t ui_data_yt_db_n(void) {
if (s_yt == NULL) {
return 0u;
}
return s_yt->db_n();
}
/*
* 函数功能:读第 idx 行死区现状(工程量 + 粗守域 + 量纲 + 显示小数位)
* 入口参数:idx - 0..8(0..7 = 电气语义通道序,8 = 频率) uint8_t
* a_out - 行现状 ui_data_yt_row_t *
* 返回值:0 - 已写出;-1 - 未注入 / 参数空 / 执行侧无此行(页面这行退化显示,
* 不猜、不拿上一行数值顶替) int
* 限定条件:页面绘制与按键链内(uiTask)
* 函数说明:出参在本层不预清——执行侧契约是"返 0 才整份写",页面返非 0 时
* 不得读该结构,这条口径由 TC-UIDATA-028 钉
*/
int ui_data_yt_db_row(uint8_t a_idx, ui_data_yt_row_t *a_out) {
if ((s_yt == NULL) || (a_out == NULL)) {
return -1;
}
return s_yt->db_row(a_idx, a_out);
}
/*
* 函数功能:写第 idx 行死区(工程量)——两页唯一的死区写出口
* 返回值:0 - 副本已写且已置脏;-1 - 未注入或执行侧拒(副本未动、不置脏) int
* 限定条件:uiTask 按键链内(编辑提交)
* 函数说明:未注入与执行侧拒绝**同码返回**是刻意的:两条腿屏上都是"值没变 +
* 保存行 ERR",页面不需要、也不该分辨"是没接上还是被拒"——那是装配期
* 故障与工况故障的区分,归日志不归屏幕
*/
int ui_data_yt_db_set(uint8_t a_idx, float a_eng) {
if (s_yt == NULL) {
return -1;
}
return s_yt->db_set(a_idx, a_eng);
}
/* 函数功能:系数覆盖点数;未注入返回 0(理由同 ui_data_yt_db_n) uint8_t */
uint8_t ui_data_yt_cal_n(void) {
if (s_yt == NULL) {
return 0u;
}
return s_yt->cal_n();
}
/* 函数功能:读第 idx 路系数现状;返回值与不猜口径同 ui_data_yt_db_row int */
int ui_data_yt_cal_row(uint8_t a_idx, ui_data_yt_row_t *a_out) {
if ((s_yt == NULL) || (a_out == NULL)) {
return -1;
}
return s_yt->cal_row(a_idx, a_out);
}
/*
* 函数功能:写第 idx 路系数 k(µV/码)——两页唯一的系数写出口
* 返回值:0 - 已写并置脏;-1 - 未注入或拒(副本未动、不置脏) int
* 限定条件:uiTask 按键链内
* 函数说明:粗守(上限 RUN_PAR_K_MAX_UV)与真带(执行段 ×[0.5,2.0])两层守卫
* **互不掩盖**:屏上输入框只能挡误写,真带裁在 adc_cal;本层不加第三层
*/
int ui_data_yt_cal_k_set(uint8_t a_idx, float a_uv) {
if (s_yt == NULL) {
return -1;
}
return s_yt->cal_k_set(a_idx, a_uv);
}
/*
* 函数功能:未标定闭锁徽标源
* 返回值:1 - 闭锁中;0 - 正常**或未注入** uint8_t
* 限定条件:页面绘制路径可直调
* 函数说明:未注入按 0 是"宁可不画"而不是"报个徽标保平安"——徽标是断言,
* 没有真源时的断言就是假断言;此时页面上写值本来也全被拒(写口返 -1),
* 信息不缺这条
*/
uint8_t ui_data_yt_cal_blocked(void) {
if (s_yt == NULL) {
return 0u;
}
return s_yt->cal_blocked();
}
/*
* 函数功能:整档落盘脏位探查(保存行三态的唯一判据源)
* 返回值:1 - 有已整定未落盘改动;0 - 干净或未注入 uint8_t
* 限定条件:页面绘制路径可直调(每帧一次转发,零 I/O)
* 函数说明:本层**不加任何边沿记忆**——"我这次动作的结果"要锁存是页面的事
* (脏位整档全局、他人再改会重新置脏,两条口径见 ui_data.h pending
* 成员注);在这里做锁存会把两页的状态耦合成一份
*/
uint8_t ui_data_yt_pending(void) {
if (s_yt == NULL) {
return 0u;
}
return s_yt->pending();
}
+155
View File
@@ -679,6 +679,161 @@ int ui_data_clock_parse(const char *a_ts, ui_data_clock_t *p_out);
*/
int ui_data_clock_format(const ui_data_clock_t *p_in, char *a_buf, uint32_t a_len);
/* ── 通道五:遥测整定注入(死区 / 系数,道F task-7)────────────────────────
*
* 为什么又是一张注入表而不是页面直调 run_par / adc_cal:与通道四完全同一组理由
* ——① 分层方向(Drivers/BSP 不得反向 include App 头,layer-deps 拦提交);
* ② 模拟器既不链 App/algo/run_par.c 也不链 App/algo/adc_cal.c,页面里用一次
* 真源函数就是"MCU 编得过、模拟器链接期爆"(通道四表注已记过一次的同族坑);
* ③ 合法域与"写值 + 置脏"必须原子发生在执行侧(run_par_cal_k_set 的文件头契约:
* 拆开等于"屏上已改、盘上永不落、且没有任何错误返回"),本表只转达。
* ⚠ 本表刻意不含任何范围数值的拷贝:lo/hi 由执行侧逐行现给(合法域唯一出口
* run_par_db_range,系数粗守上限唯一出口 RUN_PAR_K_MAX_UV),页面抄一份数字
* 就是第二真源——µV 真带(执行段裁)没有公开访问器,也**不许**页面自己补一份。
*/
/* 死区点数:下标 0..7 = 电气语义通道序(UA..IN),8 = 频率(与 104 地址映射表
* 0x8030~0x8038 同序);系数覆盖点数:电气 8 路(语义枚举前缀段)。
* 与 App/algo/run_par.h 的 RUN_PAR_DB_N / RUN_PAR_CH_N 等值——BSP 看不到 App 头,
* 等值由装配层编译期守卫(App/app/ui_data_feed.c)与 TC-UIDATA-027 同时钉住 */
#define UI_DATA_YT_DB_N 9u
#define UI_DATA_YT_CH_N 8u
/* 量纲代码:unit 是"这行数值本身的物理量纲"(诚实性字段,同 rt 帧 eng 的思路),
* 归数据源管;把它显示成什么字符串("V"/"Hz"/"uV")才是表现层的事 */
#define UI_DATA_YT_UNIT_V 0u /* 电压死区,V */
#define UI_DATA_YT_UNIT_A 1u /* 电流死区,A */
#define UI_DATA_YT_UNIT_HZ 2u /* 频率死区,Hz */
#define UI_DATA_YT_UNIT_UV 3u /* 系数 k,µV/码(屏上串用 ASCII "uV") */
/* 一行遥测整定项的完整现状(执行侧现给,页面只读) */
typedef struct {
float val; /* 当前工程量:死区 V/A/Hz;系数 = adc_cal 当前生效值(µV/码,
与 104 回显同视图:文件坏值被合法带哨兵回退 ROM 时显 ROM) */
float lo; /* 粗守下限(含)——合法域唯一出口给,页面不得自造 */
float hi; /* 粗守上限(含)——同上;真带在执行段另裁,两层互不掩盖 */
uint8_t unit; /* UI_DATA_YT_UNIT_* */
uint8_t decimals; /* 显示小数位(**显示域选择**,不是数据源精度,归装配层定) */
} ui_data_yt_row_t;
/*
* 执行侧函数表(常驻注入;形状与 ui_data_maint_ops_t 同款:整表校验、拒残表)
*
* 与定值整定(通道 param 的位图批量 + 工作叠层)**故意不同形**:死区/系数写的是
* 运行参数档([run_par]/[adc_cal]),语义 setter 本身就是"写 RAM 副本 + 置脏"
* 的原子出口、单行即提交;落盘确认是独立一问(pending),所以本表没有 commit
* 一口——保存行按键读的就是 pending()。
*/
typedef struct {
/*
* 函数功能:死区点数(执行侧真值;未注入时本层返回 0)
* 返回值:点数 uint8_t
*/
uint8_t (*db_n)(void);
/*
* 函数功能:读第 idx 行死区现状(工程量 + 粗守域 + 量纲 + 显示小数位)
* 返回值:0 - 已写出;-1 - idx 越界 / 指针空 / 执行侧无此行 int
*/
int (*db_row)(uint8_t a_idx, ui_data_yt_row_t *a_out);
/*
* 函数功能:写第 idx 行死区(工程量)——真机 = run_par_deadband_set()
* 返回值:0 - 副本已写且已置脏;-1 - 粗守或执行侧拒(副本未动、不置脏) int
*/
int (*db_set)(uint8_t a_idx, float a_eng);
/* 函数功能:系数覆盖点数(执行侧真值) 返回值:点数 uint8_t */
uint8_t (*cal_n)(void);
/* 函数功能:读第 idx 路系数现状(同 db_row,工程量域为 µV/码) int */
int (*cal_row)(uint8_t a_idx, ui_data_yt_row_t *a_out);
/*
* 函数功能:写第 idx 路系数 k(µV/码)——真机 = run_par_cal_k_set()
* 返回值:0 - 已写并置脏;-1 - 粗守或执行段真带拒(副本未动、不置脏) int
*/
int (*cal_k_set)(uint8_t a_idx, float a_uv);
/*
* 函数功能:未标定闭锁徽标源——真机 = adc_cal_prot_blocked()(task-16 单点
* 访问器,与判据停走同一个判定;这里**不**用 rt 帧 eng 反推)
* 返回值:1 - 未标定闭锁中;0 - 正常 uint8_t
*/
uint8_t (*cal_blocked)(void);
/*
* 函数功能:整档落盘脏位探查——真机 = run_par_dirty_peek()
* 返回值:1 - 有已整定未落盘改动;0 - 干净 uint8_t
* ⚠ 两条口径必须与页面注释同步(道F task-7 Lead 裁定):
* ① 脏位是**整档全局**的(一趟落 [run_par] 九键 + [adc_cal] 两段),归零
* 证明"这一档落盘了",不证明"我这一项单独落盘";
* ② 他人(104 C_SE)随后再改会重新置脏 ⇒ 页面必须结果锁存,把"我这次动作
* 的结果"与"档位此刻的状态"分开(clear/clock 页同一条先例)。
*/
uint8_t (*pending)(void);
} ui_data_yt_ops_t;
/*
* 函数功能:注入遥测整定执行侧函数表(启动期一次;形状/语义逐字对齐 ui_data_maint_bind)
* 入口参数:ops - 函数表(八个成员指针都不得为 NULL);NULL = 撤销注入(只在宿主测试存在)
* 返回值:0 - 已注入(或已撤销);-1 - ops 非空但成员残缺(存量不动,页面按
* "整定能力不可用"渲染,不崩不猜) int
* 限定条件:uiTask 启动期、先于 ui_menu_init()(真机在装配层 ui_data_feed_tick()
* 首轮懒绑一次——该调用点在 ui_menu_init 之前,时序与通道四绑定等价)
*/
int ui_data_yt_bind(const ui_data_yt_ops_t *a_ops);
/*
* 函数功能:死区点数
* 入口参数:无
* 返回值:点数;未注入返回 0 uint8_t
*/
uint8_t ui_data_yt_db_n(void);
/*
* 函数功能:读第 idx 行死区现状
* 入口参数:idx - 行下标 uint8_t;a_out - 行现状(返 0 才被整份写出) ui_data_yt_row_t *
* 返回值:0 - 已写出;-1 - 未注入 / 参数空 / 执行侧无此行 int
*/
int ui_data_yt_db_row(uint8_t a_idx, ui_data_yt_row_t *a_out);
/*
* 函数功能:写第 idx 行死区(工程量)
* 入口参数:idx - 行下标 uint8_t;a_eng - 工程量(V/A/Hz) float
* 返回值:0 - 已写并置脏;-1 - 未注入或执行侧拒(未注入与拒绝同码:页面都按
* ERR 显示,装配故障与工况故障的区分归日志不归屏幕) int
*/
int ui_data_yt_db_set(uint8_t a_idx, float a_eng);
/*
* 函数功能:系数覆盖点数
* 入口参数:无
* 返回值:点数;未注入返回 0 uint8_t
*/
uint8_t ui_data_yt_cal_n(void);
/*
* 函数功能:读第 idx 路系数现状(µV/码域)
* 入口参数:idx - 电气语义通道序 uint8_t;a_out - 行现状 ui_data_yt_row_t *
* 返回值:0 - 已写出;-1 - 未注入 / 参数空 / 执行侧无此行 int
*/
int ui_data_yt_cal_row(uint8_t a_idx, ui_data_yt_row_t *a_out);
/*
* 函数功能:写第 idx 路系数 k(µV/码)
* 入口参数:idx - 电气语义通道序 uint8_t;a_uv - 新系数(µV/码) float
* 返回值:0 - 已写并置脏;-1 - 未注入或拒(粗守/真带,副本未动) int
*/
int ui_data_yt_cal_k_set(uint8_t a_idx, float a_uv);
/*
* 函数功能:未标定闭锁徽标源
* 入口参数:无
* 返回值:1 - 闭锁中;0 - 正常或未注入(宁可不画,不假报) uint8_t
*/
uint8_t ui_data_yt_cal_blocked(void);
/*
* 函数功能:整档落盘脏位探查(保存行三态判据)
* 入口参数:无
* 返回值:1 - 有已整定未落盘改动;0 - 干净或未注入 uint8_t
*/
uint8_t ui_data_yt_pending(void);
#ifdef __cplusplus
}
#endif
+118
View File
@@ -0,0 +1,118 @@
/*
* 模块名称:UI Page calib (遥测系数内容页)
* 模块功能:三遥设置 → 遥测系数 叶子(#P 受密码保护)的内容页——8 行数值表,
* 每页 8 行自动分页;本文件零 App include:行表现状(值/粗守域/量纲/
* 小数位)逐行来自 ui_data.h 通道五注入表,写与落盘确认全经
* ui_w_ytview 视图模型(状态机/反显/徽标在那一侧,两页共用一份)——
* 本文件只是标准骨架几何令牌接进框架的薄跳板,改一次口径两页一致
* 两态一页:123 修改权限 → 可编辑(编辑器 + 保存行 = 落盘确认);000 查看权限
* → 只读(attach 内不绑 commit → 无保存行、key_cb 整页交回框架,
* 行为与"没有 key_cb"逐字等价,整定四页同一条分派纪律)
* 徽标说明:只读/可编辑两态都画"未标定"(cal_blocked 经注入表现读)——徽标说的
* 是数据可信度,与本次权限无关;它**不**禁任何编辑(与定值 LOCK 分工:
* LOCK 才写不进去,未标定只是"改了也不会动作",ui_w_setlist.h 三词三源)
* 适用平台:STM32F407ZGTx / PC 模拟器(共用源码)
* 作者:DTU 项目组
* 创建日期:2026-10-27
* 修改记录:
* v1.0 2026-10-27 DTU 项目组 创建(道F task-7;判据 docs/任务规划/UI补齐.md
* §2 第 4 行「改死区能改变 104 上送门限 / 改 k 后
* 页面值按比例变化 / 只走既有落盘链不加第二真源」)
*/
/* 头文件包含区 */
#include "ui_page_calib.h"
#include "ui_tokens.h"
#include "ui_w_setlist.h"
#include "ui_w_ytview.h"
/* 静态变量定义区 */
/* 本页定值表实例(组件状态独占一份,勿跨页共用——ui_w_setlist 实例化约定) */
static ui_w_setlist_t s_sl;
/* 函数定义区 */
/*
* 函数功能:开页绑视图(行表现状逐行现取 + 两态分派都在视图模型内;接 init_cb)
* 入口参数:无
* 返回值:无
* 限定条件:框架开页链内(本次进入权限已装载,时序依据见 ui_page_view_param.c
* 同位置注释:框架先装权限再调 init_cb)
*/
static void pg_init(void) {
ui_w_ytview_attach_cal(&s_sl);
}
/*
* 函数功能:总行数(数据行现给 + 可编辑态才有保存行;接 count_cb)
* 入口参数:无
* 返回值:总行数 uint16_t
* 限定条件:框架绘制/翻页链内
*/
static uint16_t pg_count(void) {
return ui_w_setlist_count(&s_sl);
}
/*
* 函数功能:逐行取数(接 refresh;行现状在视图模型里现取,本层无缓存)
* 入口参数:a_row / a_name / a_value - 契约见 ui_content_page.h
* 返回值:无
* 限定条件:框架绘制链内
*/
static void pg_refresh(uint16_t a_row, char *a_name, char *a_value) {
ui_w_setlist_refresh(&s_sl, a_row, a_name, a_value);
}
/*
* 函数功能:自绘层(编辑光标反显 + 写被拒行反显 + 未标定徽标)
* 入口参数:a_layer / a_clip - 契约见 ui_content_page.h
* 返回值:无
* 限定条件:框架绘制链内,内置区块画完之后
*/
static void pg_draw(lv_layer_t *a_layer, const lv_area_t *a_clip) {
ui_w_setlist_draw(&s_sl, a_layer, a_clip);
ui_w_ytview_draw_cal_badge(a_layer, a_clip);
}
/*
* 函数功能:按键转发(接 key_cb)
* 入口参数:a_key - LVGL 键值 lv_key_t
* 返回值:true - 本页已消费;false - 交回框架(↑↓ 移选、← 关页)
* 限定条件:描述符 edit_capable=1 + key_intercept=1(编辑中 ←→ 是移数字光标,
* 必须先给页面,否则框架把 ← 当关页——整定四页同款实证)
* 函数说明:第一条门 = 只读进入(未绑 commit)整页交回框架,与"本页没有
* key_cb"逐字等价(黄金参考只读态为证);其余分发规则全在组件
*/
static bool pg_key(lv_key_t a_key) {
if (s_sl.commit == NULL) {
return false;
}
return (ui_w_setlist_key(&s_sl, a_key) != 0u);
}
/*
* 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=calib 绑定引用)
* 列表页骨架 + 可编辑 + ← 拦截;band_h 必须 >0(选中行黑字白底反显,同四整定页);
* row_count 给静态上限(8 数据行 + 保存行),行数运行时以注入表现报为准
*/
const content_page_def_t ui_page_calib_desc = {
.id = "calib",
.title = "遥测系数",
.selectable = 1u,
.edit_capable = 1u,
.key_intercept = 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,
.row_count = 9u,
.init_cb = pg_init,
.count_cb = pg_count,
.refresh = pg_refresh,
.draw_cb = pg_draw,
.key_cb = pg_key,
};
+25
View File
@@ -0,0 +1,25 @@
/*
* 模块名称:UI Page calib (遥测系数内容页对外声明)
* 模块功能:供生成物 ui_page_registry.c 引用的页面描述符声明
* 适用平台:STM32F407ZGTx / PC 模拟器(共用源码)
* 作者:DTU 项目组
* 创建日期:2026-10-27
* 修改记录:
* v1.0 2026-10-27 DTU 项目组 创建(道F task-7)
*/
#ifndef UI_PAGE_CALIB_H
#define UI_PAGE_CALIB_H
#ifdef __cplusplus
extern "C" {
#endif
#include "ui_content_page.h"
extern const content_page_def_t ui_page_calib_desc;
#ifdef __cplusplus
}
#endif
#endif /* UI_PAGE_CALIB_H */
+117
View File
@@ -0,0 +1,117 @@
/*
* 模块名称:UI Page deadband (遥测死区内容页)
* 模块功能:三遥设置 → 遥测死区 叶子(#P 受密码保护)的内容页——9 行数值表,
* 每页 8 行自动分页;本文件零 App include:行表现状(值/粗守域/量纲/
* 小数位)逐行来自 ui_data.h 通道五注入表,写与落盘确认全经
* ui_w_ytview 视图模型(状态机/反显/徽标在那一侧,两页共用一份)——
* 本文件只是标准骨架几何令牌接进框架的薄跳板,改一次口径两页一致
* 两态一页:123 修改权限 → 可编辑(编辑器 + 保存行 = 落盘确认);000 查看权限
* → 只读(attach 内不绑 commit → 无保存行、key_cb 整页交回框架,
* 行为与"没有 key_cb"逐字等价,整定四页同一条分派纪律)
* 徽标说明:本页**不画**"未标定"——死区数值来自 [run_par] 档、与两点标定无关,
* 挂不相干的告警等于稀释真告警(ui_w_ytview.h 同注)
* 适用平台:STM32F407ZGTx / PC 模拟器(共用源码)
* 作者:DTU 项目组
* 创建日期:2026-10-27
* 修改记录:
* v1.0 2026-10-27 DTU 项目组 创建(道F task-7;判据 docs/任务规划/UI补齐.md
* §2 第 4 行「改死区能改变 104 上送门限 / 改 k 后
* 页面值按比例变化 / 只走既有落盘链不加第二真源」)
*/
/* 头文件包含区 */
#include "ui_page_deadband.h"
#include "ui_tokens.h"
#include "ui_w_setlist.h"
#include "ui_w_ytview.h"
/* 静态变量定义区 */
/* 本页定值表实例(组件状态独占一份,勿跨页共用——ui_w_setlist 实例化约定) */
static ui_w_setlist_t s_sl;
/* 函数定义区 */
/*
* 函数功能:开页绑视图(行表现状逐行现取 + 两态分派都在视图模型内;接 init_cb)
* 入口参数:无
* 返回值:无
* 限定条件:框架开页链内(本次进入权限已装载,时序依据见 ui_page_view_param.c
* 同位置注释:框架先装权限再调 init_cb)
*/
static void pg_init(void) {
ui_w_ytview_attach_db(&s_sl);
}
/*
* 函数功能:总行数(数据行现给 + 可编辑态才有保存行;接 count_cb)
* 入口参数:无
* 返回值:总行数 uint16_t
* 限定条件:框架绘制/翻页链内
*/
static uint16_t pg_count(void) {
return ui_w_setlist_count(&s_sl);
}
/*
* 函数功能:逐行取数(接 refresh;行现状在视图模型里现取,本层无缓存)
* 入口参数:a_row / a_name / a_value - 契约见 ui_content_page.h
* 返回值:无
* 限定条件:框架绘制链内
*/
static void pg_refresh(uint16_t a_row, char *a_name, char *a_value) {
ui_w_setlist_refresh(&s_sl, a_row, a_name, a_value);
}
/*
* 函数功能:自绘层(编辑光标反显 + 写被拒行反显)
* 入口参数:a_layer / a_clip - 契约见 ui_content_page.h
* 返回值:无
* 限定条件:框架绘制链内,内置区块画完之后
*/
static void pg_draw(lv_layer_t *a_layer, const lv_area_t *a_clip) {
ui_w_setlist_draw(&s_sl, a_layer, a_clip);
}
/*
* 函数功能:按键转发(接 key_cb)
* 入口参数:a_key - LVGL 键值 lv_key_t
* 返回值:true - 本页已消费;false - 交回框架(↑↓ 移选、← 关页)
* 限定条件:描述符 edit_capable=1 + key_intercept=1(编辑中 ←→ 是移数字光标,
* 必须先给页面,否则框架把 ← 当关页——整定四页同款实证)
* 函数说明:第一条门 = 只读进入(未绑 commit)整页交回框架,与"本页没有
* key_cb"逐字等价(黄金参考只读态为证);其余分发规则全在组件
*/
static bool pg_key(lv_key_t a_key) {
if (s_sl.commit == NULL) {
return false;
}
return (ui_w_setlist_key(&s_sl, a_key) != 0u);
}
/*
* 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=deadband 绑定引用)
* 列表页骨架 + 可编辑 + ← 拦截;band_h 必须 >0(选中行黑字白底反显,同四整定页);
* row_count 给静态上限(9 数据行 + 保存行),行数运行时以注入表现报为准
*/
const content_page_def_t ui_page_deadband_desc = {
.id = "deadband",
.title = "遥测死区",
.selectable = 1u,
.edit_capable = 1u,
.key_intercept = 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,
.row_count = 10u,
.init_cb = pg_init,
.count_cb = pg_count,
.refresh = pg_refresh,
.draw_cb = pg_draw,
.key_cb = pg_key,
};
+25
View File
@@ -0,0 +1,25 @@
/*
* 模块名称:UI Page deadband (遥测死区内容页对外声明)
* 模块功能:供生成物 ui_page_registry.c 引用的页面描述符声明
* 适用平台:STM32F407ZGTx / PC 模拟器(共用源码)
* 作者:DTU 项目组
* 创建日期:2026-10-27
* 修改记录:
* v1.0 2026-10-27 DTU 项目组 创建(道F task-7)
*/
#ifndef UI_PAGE_DEADBAND_H
#define UI_PAGE_DEADBAND_H
#ifdef __cplusplus
extern "C" {
#endif
#include "ui_content_page.h"
extern const content_page_def_t ui_page_deadband_desc;
#ifdef __cplusplus
}
#endif
#endif /* UI_PAGE_DEADBAND_H */
+4
View File
@@ -10,9 +10,11 @@
#include "ui_page_registry.h"
#include "ui_page_ac.h"
#include "ui_page_calib.h"
#include "ui_page_clear.h"
#include "ui_page_clock.h"
#include "ui_page_dc.h"
#include "ui_page_deadband.h"
#include "ui_page_device_info.h"
#include "ui_page_net_cfg.h"
#include "ui_page_password.h"
@@ -39,6 +41,8 @@ const ui_page_entry_t g_ui_page_map[UI_PAGE_MAP_COUNT] = {
{ 14u, &ui_page_view_set_desc }, /* node 14 定值 */
{ 15u, &ui_page_view_word_desc }, /* node 15 控制字 */
{ 16u, &ui_page_view_plate_desc }, /* node 16 软压板 */
{ 18u, &ui_page_deadband_desc }, /* node 18 遥测死区 */
{ 19u, &ui_page_calib_desc }, /* node 19 遥测系数 */
{ 24u, &ui_page_clock_desc }, /* node 24 时钟设置 */
{ 25u, &ui_page_reset_desc }, /* node 25 强制复归 */
{ 27u, &ui_page_clear_desc }, /* node 27 清除记录 */
+1 -1
View File
@@ -25,7 +25,7 @@ typedef struct {
const content_page_def_t *desc; /* 页面描述符(Flash const) */
} ui_page_entry_t;
#define UI_PAGE_MAP_COUNT 19u /* 绑定了内容页的叶子数 */
#define UI_PAGE_MAP_COUNT 21u /* 绑定了内容页的叶子数 */
extern const ui_page_entry_t g_ui_page_map[UI_PAGE_MAP_COUNT];
+374
View File
@@ -0,0 +1,374 @@
/*
* 模块名称:UI 遥测整定视图模型实现(ui_w_ytview)
* 模块功能:死区/系数两张页的行表填充(注入表现状 → 显示域)、逐行写转发、
* 落盘确认状态机、越界行反显与未标定徽标——对外形状与纪律见配对头文件
* (两态分派 / 无叠层 / 保存行=确认 / 两层守卫互不掩盖 四条都在头注)
* 编辑即生效的取舍(如实记在这里,评审时不靠口头解释):定值四页有"叠层 + 整表
* 封存 + ESC 弃稿",本两页没有——[run_par]/[adc_cal] 的语义 setter 就是
* 执行侧写入口(写值与置脏不可拆),页面再造一份叠层只会造出"屏显值与
* RAM 副本不一致"的第二个视图。后果是**按下 ENTER 提交那一行即刻生效**、
* 退出页面不回滚;"哪些已生效"由每行的显示值本身说明(读的就是真源),
* "落没落盘"由保存行说明。v1 认可这条语义(Lead 裁定 task-7 派发单),
* 要"草稿/弃稿"得改注入表形状(叠层 + 批量提交),不在本批
* 状态机(每视图独立一份,两页互不干扰):
* ENTER --保存行按键--> 脏位在 ? WAIT : OK(无改动/已落 = OK,与定值写
* 路径"空请求不消费队列位、直接 OK"同口径);WAIT 期间脏位归零 → 锁存
* OK(此后他人再改重新置脏**不回退**显示——"我这次动作的结果"与"档位
* 此刻的状态"分开,clear/clock 页先例);任一写被拒(执行侧返 -1)→
* 粘滞 ERR + 该行反显,直到下次写成功或再按保存行重新评估
* 适用平台:STM32F407ZGTx / PC 模拟器(共用源码)
* 作者:DTU 项目组
* 创建日期:2026-10-27
* 修改记录:
* v1.0 2026-10-27 DTU 项目组 创建(道F task-7)
*/
/* 头文件包含区 */
#include <string.h>
#include "ui_w_ytview.h"
#include "ui_data.h"
#include "ui_tokens.h"
/* 私有宏定义区 */
/* 保存行四态的内部编码(对外映射成 ui_w_setlist_st_t,不直接外露——枚举值序
* 被各页钉着,这里换底层编码不影响组件) */
#define YT_ST_ENTER 0u /* 初始 / 已作废:值列 ENTER */
#define YT_ST_WAIT 1u /* 按过保存、脏位仍在:WAIT */
#define YT_ST_OK 2u /* 脏位归零(或按时无脏):OK,锁存 */
#define YT_ST_ERR 3u /* 最近一次写被执行侧拒:粘滞 ERR */
/* 徽标右界与文案:右界 141 的理由、测宽传 strlen 的版本坑,均沿用整定四页
* 徽标的既有实测结论(ui_w_setview.c 的 draw_cal_badge 头注),不重新发明 */
#define YT_BADGE_RIGHT_X 141
#define YT_CAL_BADGE "未标定"
/* 私有变量定义区 */
/* 行名表(Flash):下标即执行侧 idx,注释是唯一的对表处——
* 死区 0..7 = 电气语义通道 UA..IN、8 = 频率(104 地址映射 0x8030~ 同序);
* 系数 0..7 = 电气语义通道 UA..IN(adc_semantic_t 前缀段) */
static const char *const k_db_name[UI_DATA_YT_DB_N] = {
"UA死区", "UB死区", "UC死区", "UN死区",
"IA死区", "IB死区", "IC死区", "IN死区",
"Hz死区" /* 频率死区行的上屏名用 Hz(本仓既有写法,交流量页 Hz 行同词):
* "率"不在 183 字子集里,font-charset 机器先抓出来——改文案不改字库;
* 这一行的小数位数与量纲由执行侧现给(0.5 Hz / decimals 3 / UNIT_HZ),
* 名字里的 Hz 只是让现场知道该行量纲,不参与取数 */
};
static const char *const k_cal_name[UI_DATA_YT_CH_N] = {
"UA系数", "UB系数", "UC系数", "UN系数",
"IA系数", "IB系数", "IC系数", "IN系数"
};
/* 量纲代码 → 屏上串:µV 用 ASCII "uV"(字库无希腊小写 mu,缺字即白框——
* font-charset 只保汉字子集,ASCII 字形里本来也没有 µ) */
static const char *const k_unit_str[UI_DATA_YT_UNIT_UV + 1u] = {
"V", "A", "Hz", "uV"
};
/* 显示域倍率(decimals 契约 0..3,与 ui_w_setlist 行表同口径) */
static const int32_t k_p10[4] = { 1, 10, 100, 1000 };
typedef struct {
ui_w_setlist_row_t rows[UI_DATA_YT_DB_N]; /* 按较大的死区表定容;系数表只占前 8 槽 */
uint16_t badmap; /* 写被拒行反显位图(≤16 行) */
uint8_t st; /* YT_ST_*,锁存的保存结果 */
uint8_t n; /* 本次开页实际建成的行数 */
} yt_view_t;
static yt_view_t s_db;
static yt_view_t s_cal;
/* 函数定义区 */
/*
* 函数功能:工程量 → 显示域整数(四舍五入,半程进位朝远离 0 侧)
* 入口参数:v - 工程量 float;d - 小数位 0..3 uint8_t
* 返回值:显示域整数 int32_t
* 限定条件:v 与行 min/max 同来自执行侧,粗守上限量级(系数 1e9 µV)在 d=0 时
* 仍落在 int32 内——这是系数行显示小数位取 0 的硬理由(见装配层建表处),
* 不是排版偏好
*/
static int32_t yt_scale(float a_v, uint8_t a_d) {
float p = (float)k_p10[a_d];
float s = a_v * p;
return (int32_t)((s >= 0.0f) ? (s + 0.5f) : (s - 0.5f));
}
/*
* 函数功能:逐行从注入表建行表(名/量纲/小数位/min/max 全部现给,本模块零范围字面量)
* 入口参数:v - 目标视图 yt_view_t *;want - 执行侧报出的行数 uint8_t;
* names - 行名表 const char *const *;rowfn - 对应注入口的读函数
* 返回值:无
* 限定条件:attach 链内(开页)
* 函数说明:读到第 i 行失败就停在 i 行(**不补空行、不跳过**):半张表比空表更
* 危险——用户会以为下面的行"没有这回事"。行数超过行槽数组上限的部分
* 同样不显示(正常不可能发生:want 已由调用方钳到契约值,这里双保险)
*/
static void yt_fill(yt_view_t *a_v, uint8_t a_want, const char *const *a_names,
int (*a_rowfn)(uint8_t, ui_data_yt_row_t *)) {
uint8_t i;
a_v->n = 0u;
for (i = 0u; (i < a_want) && (i < (uint8_t)UI_DATA_YT_DB_N); i++) {
ui_data_yt_row_t r;
uint8_t d;
if (a_rowfn(i, &r) != 0) {
break;
}
d = (r.decimals <= 3u) ? r.decimals : 3u; /* 组件契约 0..3,越界钳住而不是读脏倍率 */
a_v->rows[a_v->n].name = a_names[i];
a_v->rows[a_v->n].id = i;
a_v->rows[a_v->n].kind = UI_W_SETLIST_KIND_NUM;
a_v->rows[a_v->n].decimals = d;
a_v->rows[a_v->n].min = yt_scale(r.lo, d);
a_v->rows[a_v->n].max = yt_scale(r.hi, d);
a_v->rows[a_v->n].opts = NULL;
a_v->rows[a_v->n].unit = (r.unit <= UI_DATA_YT_UNIT_UV)
? k_unit_str[r.unit] : NULL;
a_v->n++;
}
}
/*
* 函数功能:写结果记账(成功/被拒两条腿共一个入口,防两页写两遍规则写漏)
* 入口参数:v - 视图 yt_view_t *;id - 行下标 uint8_t;rc - 注入口返回值 int
* 返回值:无
* 限定条件:set 回调链内(uiTask 按键)
* 函数说明:拒绝 → 该行反显置位 + 粘滞 ERR;成功 → 只清**这一行**的反显(别的
* 行还欠着被执行侧拒的账,不陪它一起销)+ 作废上次保存结果(改了任何
* 一位,上一次的 OK/ERR 都不该再挂屏,clock 页同一条纪律)
*/
static void yt_note_write(yt_view_t *a_v, uint8_t a_id, int a_rc) {
if (a_rc != 0) {
a_v->badmap = (uint16_t)(a_v->badmap | (uint16_t)(1u << a_id));
a_v->st = YT_ST_ERR;
} else {
a_v->badmap = (uint16_t)(a_v->badmap & (uint16_t)~(1u << a_id));
a_v->st = YT_ST_ENTER;
}
}
/*
* 函数功能:保存行按键(不是"投递整表"——值早在逐行 ENTER 时写进副本并置脏,
* 这里只做一次落盘确认的起点)
* 入口参数:v - 视图 yt_view_t *
* 返回值:无
* 限定条件:可编辑态、保存行 ENTER;uiTask 按键链
* 函数说明:先清越界反显再评估(按保存 = "我知道刚才被拒了,看新一轮结果");
* 脏位仍在 → WAIT 开始锁存观察;无脏 → 直接 OK——两种都如实:真机
* defaultTask 每 50 ms 无条件落一遍,WAIT 窗口毫秒级,**模拟器假件刻意
* 不自动清脏**(由无头 pump 清),否则 WAIT 态永远不会被像素回归覆盖,
* 黄金就成了只钉住最好看那一帧(Lead 裁定④的原文理由)
*/
static void yt_commit(yt_view_t *a_v) {
a_v->badmap = 0u;
a_v->st = (ui_data_yt_pending() != 0u) ? YT_ST_WAIT : YT_ST_OK;
}
/*
* 函数功能:保存行值列回读(组件每次绘制调一次;WAIT → OK 的边沿在这里收尾)
* 入口参数:v - 视图 yt_view_t *
* 返回值:组件态枚举 ui_w_setlist_st_t
* 限定条件:绘制链内;只读视图组件不会调到这里(未绑 state)
* 函数说明:锁存语义 = 只前进不回退:WAIT 期间看到脏位归零就落 OK;此后他人
* (104 选择段)再改重新置脏也**不**把 OK 拽回 WAIT——用户按保存那次
* 动作确实落盘了,档位的新脏是别人的动作,不归本次显示(ui_data.h
* pending 成员注的两条口径,第②条落在这三行代码上)
*/
static ui_w_setlist_st_t yt_state(yt_view_t *a_v) {
if ((a_v->st == YT_ST_WAIT) && (ui_data_yt_pending() == 0u)) {
a_v->st = YT_ST_OK;
}
switch (a_v->st) {
case YT_ST_WAIT:
return UI_W_SETLIST_ST_WAIT;
case YT_ST_OK:
return UI_W_SETLIST_ST_OK;
case YT_ST_ERR:
return UI_W_SETLIST_ST_FAIL;
default:
return UI_W_SETLIST_ST_IDLE;
}
}
/* 函数功能:某行是否需要越界反显(写被拒的账挂在这行值上,同整定页越界反显
* 的视觉语言) 返回值:true = 反显 bool */
static bool yt_bad(const yt_view_t *a_v, uint16_t a_row) {
if (a_row >= 16u) {
return false;
}
return ((a_v->badmap >> a_row) & 0x1u) != 0u;
}
/* ── 死区视图的回调组(id = 执行侧 idx 直通) ────────────────────────────── */
static int32_t yt_db_get(uint8_t a_id) {
ui_data_yt_row_t r;
if ((a_id >= s_db.n) || (ui_data_yt_db_row(a_id, &r) != 0)) {
return 0; /* 取不到现状的行显示 0 而不是旧缓存:本组件没有缓存可回退,
0 配 "--" 之外的解释都不诚实;attach 时读失败该行根本不存在 */
}
return yt_scale(r.val, r.decimals);
}
static void yt_db_write(uint8_t a_id, int32_t a_val) {
float eng;
if (a_id >= s_db.n) {
return;
}
eng = (float)a_val / (float)k_p10[s_db.rows[a_id].decimals];
yt_note_write(&s_db, a_id, ui_data_yt_db_set(a_id, eng));
}
static void yt_db_commit(void) { yt_commit(&s_db); }
static ui_w_setlist_st_t yt_db_state(void) { return yt_state(&s_db); }
static bool yt_db_bad(uint16_t a_row) { return yt_bad(&s_db, a_row); }
/* ── 系数视图的回调组 ────────────────────────────────────────────────────── */
/*
* 函数功能:读系数行当前值(显示域;口径同 yt_db_get,现状现取零缓存)
* 入口参数:a_id - 行下标(= 执行侧语义通道序) uint8_t
* 返回值:工程量 × 10^decimals int32_t
* 限定条件:组件取数回调链内(绘制)
*/
static int32_t yt_cal_get(uint8_t a_id) {
ui_data_yt_row_t r;
if ((a_id >= s_cal.n) || (ui_data_yt_cal_row(a_id, &r) != 0)) {
return 0;
}
return yt_scale(r.val, r.decimals);
}
/*
* 函数功能:写一行系数(显示域 → µV/码工程量后转注入表;结果记账同 yt_db_write)
* 入口参数:a_id - 行下标 uint8_t;a_val - 显示域新值 int32_t
* 返回值:无(拒绝经 yt_note_write 上屏:行反显 + 保存行 ERR)
* 限定条件:组件编辑提交回调链内(可编辑态)
*/
static void yt_cal_write(uint8_t a_id, int32_t a_val) {
float uv;
if (a_id >= s_cal.n) {
return;
}
uv = (float)a_val / (float)k_p10[s_cal.rows[a_id].decimals];
yt_note_write(&s_cal, a_id, ui_data_yt_cal_k_set(a_id, uv));
}
static void yt_cal_commit(void) { yt_commit(&s_cal); }
static ui_w_setlist_st_t yt_cal_state(void) { return yt_state(&s_cal); }
static bool yt_cal_bad(uint16_t a_row) { return yt_bad(&s_cal, a_row); }
/* ── 对外 attach 与徽标 ──────────────────────────────────────────────────── */
/*
* 函数功能:死区视图绑表(两态在同一函数里分派一次,页面只调这一个口)
* 入口参数:a_inst - 页面持有的定值表实例 ui_w_setlist_t *
* 返回值:无
* 限定条件:页面 init_cb 内(本次进入权限已装载)
* 函数说明:行数**执行侧现给**(ui_data_yt_db_n):未注入 = 0 行 = 空表页,
* 一个像素都不画假数据;只读进入不绑 commit/state——组件因此既不画
* 保存行也放开不了编辑(set 契约"必填",绑的就是真写口,只读态组件
* 保证不会调它,与整定四页同一绑法同一理由)
*/
void ui_w_ytview_attach_db(ui_w_setlist_t *a_inst) {
uint8_t want;
if (a_inst == NULL) {
return;
}
want = ui_data_yt_db_n();
if (want > (uint8_t)UI_DATA_YT_DB_N) {
want = (uint8_t)UI_DATA_YT_DB_N;
}
s_db.badmap = 0u;
s_db.st = YT_ST_ENTER;
yt_fill(&s_db, want, k_db_name, ui_data_yt_db_row);
(void)memset(a_inst, 0, sizeof(*a_inst));
a_inst->rows = s_db.rows;
a_inst->count = s_db.n;
a_inst->get = yt_db_get;
a_inst->set = yt_db_write;
a_inst->bad = yt_db_bad;
if (ui_content_page_is_editable() != false) {
a_inst->commit = yt_db_commit;
a_inst->state = yt_db_state;
}
ui_w_setlist_reset(a_inst);
}
/*
* 函数功能:系数视图绑表(形状与 attach_db 逐字同构;两视图各持一份状态机,
* 死区页的 WAIT 不会串门到系数页)
* 入口参数:a_inst - 页面持有的定值表实例 ui_w_setlist_t *
* 返回值:无
* 限定条件:页面 init_cb 内
*/
void ui_w_ytview_attach_cal(ui_w_setlist_t *a_inst) {
uint8_t want;
if (a_inst == NULL) {
return;
}
want = ui_data_yt_cal_n();
if (want > (uint8_t)UI_DATA_YT_CH_N) {
want = (uint8_t)UI_DATA_YT_CH_N;
}
s_cal.badmap = 0u;
s_cal.st = YT_ST_ENTER;
yt_fill(&s_cal, want, k_cal_name, ui_data_yt_cal_row);
(void)memset(a_inst, 0, sizeof(*a_inst));
a_inst->rows = s_cal.rows;
a_inst->count = s_cal.n;
a_inst->get = yt_cal_get;
a_inst->set = yt_cal_write;
a_inst->bad = yt_cal_bad;
if (ui_content_page_is_editable() != false) {
a_inst->commit = yt_cal_commit;
a_inst->state = yt_cal_state;
}
ui_w_setlist_reset(a_inst);
}
/*
* 函数功能:系数页"未标定"徽标(头文件里写清了死区页为何不调、右界为何取 141)
* 入口参数:a_layer - 绘制层 lv_layer_t *
* a_clip - 重绘区 const lv_area_t *
* 返回值:无
* 限定条件:系数页 draw_cb 内;cal_blocked() 为假时一个像素都不落
*/
void ui_w_ytview_draw_cal_badge(lv_layer_t *a_layer, const lv_area_t *a_clip) {
lv_area_t area;
lv_draw_label_dsc_t ld;
const char *txt = YT_CAL_BADGE;
if (ui_data_yt_cal_blocked() == 0u) {
return;
}
area.x2 = YT_BADGE_RIGHT_X;
area.y1 = UI_TITLE_Y;
area.y2 = UI_TITLE_Y + UI_TEXT_BAND_H;
/* 该版本 lv_text_get_width:长度必须传 strlen(传 -1/0 死循环或返 0) */
area.x1 = YT_BADGE_RIGHT_X
- (int32_t)lv_text_get_width(txt, (uint32_t)strlen(txt), LV_FONT_DEFAULT, 0);
if ((area.x1 > a_clip->x2) || (a_clip->x1 > area.x2) ||
(area.y1 > a_clip->y2) || (a_clip->y1 > area.y2)) {
return;
}
lv_draw_label_dsc_init(&ld);
ld.base.layer = a_layer;
ld.text = txt;
ld.text_static = 1u;
ld.color = UI_COL_FG;
ld.ofs_x = 0;
ld.ofs_y = 0;
lv_draw_label(a_layer, &ld, &area);
}
+83
View File
@@ -0,0 +1,83 @@
/*
* 模块名称:UI 遥测整定视图模型(ui_w_ytview)
* 模块功能:两张遥测整定页(遥测死区 9 行 / 遥测系数 8 行)的**共用视图模型**——
* 行表现状从 ui_data.h 通道五注入表逐行现取(工程量 + 粗守域 + 量纲 +
* 显示小数位),换算成 ui_w_setlist 的显示域(工程量 × 10^decimals),
* 并把"写一行 / 落盘确认 / 越界反显 / 未标定徽标"四件事收在一处,供两张
* 页面薄跳板(ui_page_deadband / ui_page_calib)共用——两页共用一份状态机
* 与换算,与 ui_w_setview"行表只有一份,界内界外就不会两套口径"同一条理由
* 与整定四页(ui_w_setview 的写方向)**故意不同形**,三处差别都写在 .c 的函数头:
* ① 无工作叠层:死区/系数的真源本身就是 RAM 副本,语义 setter 是执行侧
* 写入口(写值与置脏同一函数内发生),"编辑即生效、ESC 不回滚"是本档
* 语义(与定值"攒一批整表封存"不同,取舍见 .c 文件头);
* ② 保存行不是"投递整表",而是**落盘确认**:值在逐行 ENTER 时已经写进
* 副本并置脏,保存行按键只锁存"我这次动作的结果"(脏位归零 = 整档已
* 外科保存成功,判据 = ui_data_yt_pending(),与 104 留痕同一条
* "先 peek 再算成功",不另发明);
* ③ 没有 LOCK 态:本档不受定值校验和闭锁管辖;"未标定"(cal_blocked)
* 只画徽标、不禁编辑——改系数恰恰是走向标定的动作,禁了就是把路堵死
* 值域诚实性:行 min/max **逐行来自执行侧现给**(合法域唯一出口 run_par_db_range /
* 粗守上限 RUN_PAR_K_MAX_UV)——本模块不出现任何一个范围数字字面量,
* 抄一份就是卡片判据要拦的第二真源;µV 真带(执行段裁)没有公开访问器,
* 本模块**不**补一份假的(task-43 收口前屏上只有粗守这一层,真带拒的值
* 经 set 返回 -1 → 行反显 + 保存行 ERR,两层守卫互不掩盖)
* 分层:只依赖 Drivers/BSP/UI 同层(ui_data.h 注入表 + ui_w_setlist/ui_w_editor),
* 零 App include(layer-deps + 模拟器链接期两条老理由,见 ui_data.h 通道五表注)
* 适用平台:STM32F407ZGTx / PC 模拟器(共用源码)
* 作者:DTU 项目组
* 创建日期:2026-10-27
* 修改记录:
* v1.0 2026-10-27 DTU 项目组 创建(道F task-7;判据 docs/任务规划/UI补齐.md
* §2 第 4 行;Lead 裁定:注入表八口形状、结果锁存、
* 粗守 + 注释两层守卫、真带访问器归 task-43)
*/
#ifndef UI_W_YTVIEW_H
#define UI_W_YTVIEW_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "ui_w_setlist.h" /* ui_w_setlist_t + lvgl(经 ui_content_page.h) */
/*
* 函数功能:把**死区**视图绑到定值表实例(行表现状逐行现取;两态在内部按
* ui_content_page_is_editable() 分派——只读不绑 commit/state,无保存行)
* 入口参数:a_inst - 页面持有的实例(static,一页一份) ui_w_setlist_t *
* 返回值:无
* 限定条件:页面 init_cb 内调用(uiTask 上下文,此时本次进入的权限已装载)
* 函数说明:每次开页都重填行表并清历史结果(st/badmap)——上一次开页的 WAIT/OK
* 与越界反显都不该带到这一次来(同整定四页"开页清稿"纪律)
*/
void ui_w_ytview_attach_db(ui_w_setlist_t *a_inst);
/*
* 函数功能:把**系数**视图绑到定值表实例(形状/分派/清稿与 attach_db 同一条路)
* 入口参数:a_inst - 页面持有的实例 ui_w_setlist_t *
* 返回值:无
* 限定条件:页面 init_cb 内调用
*/
void ui_w_ytview_attach_cal(ui_w_setlist_t *a_inst);
/*
* 函数功能:系数页标题右侧画"未标定"徽标(cal_blocked 的屏上表达)
* 入口参数:a_layer - 绘制层 lv_layer_t *
* a_clip - 重绘区 const lv_area_t *
* 返回值:无
* 限定条件:系数页 draw_cb 内(框架内置区块之后);只在 cal_blocked() 为真时落墨
* 函数说明:**死区页刻意不调它**——死区数值来自 [run_par] 档、与两点标定无关,
* 在那页画"未标定"是把不相关的告警挂到不相干的屏上(徽标是断言)。
* 几何右界取 141:与整定四页徽标同一实测理由(标题带装饰右腿占 x 约
* 147..158);本标题"遥测系数"4 字到 x 约 104,与徽标左缘仅 1 px 间隙,
* 会被读成一句"遥测系数未标定"——该读法语义成立(本页说的就是系数、
* 而系数此刻确实没标定过),像素黄金目检时确认过不再挪
*/
void ui_w_ytview_draw_cal_badge(lv_layer_t *a_layer, const lv_area_t *a_clip);
#ifdef __cplusplus
}
#endif
#endif /* UI_W_YTVIEW_H */
+15
View File
@@ -3571,6 +3571,11 @@
<FileType>1</FileType>
<FilePath>..\Drivers\BSP\UI\ui_w_soe.c</FilePath>
</File>
<File>
<FileName>ui_w_ytview.c</FileName>
<FileType>1</FileType>
<FilePath>..\Drivers\BSP\UI\ui_w_ytview.c</FilePath>
</File>
<File>
<FileName>ui_w_setview.c</FileName>
<FileType>1</FileType>
@@ -3621,6 +3626,16 @@
<FileType>1</FileType>
<FilePath>..\Drivers\BSP\UI\ui_page_reset.c</FilePath>
</File>
<File>
<FileName>ui_page_calib.c</FileName>
<FileType>1</FileType>
<FilePath>..\Drivers\BSP\UI\ui_page_calib.c</FilePath>
</File>
<File>
<FileName>ui_page_deadband.c</FileName>
<FileType>1</FileType>
<FilePath>..\Drivers\BSP\UI\ui_page_deadband.c</FilePath>
</File>
</Files>
</Group>
<Group>
@@ -47,10 +47,10 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1`
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
| 区域 | 用量 | 容量 | 占比 |
|---|---|---|---|
| ER_IROM1 | 257,272 B (251.24 KB) | 1,048,576 B (1024 KB) | 24.5% |
| RW_IRAM1 | 80,312 B (78.43 KB) | 131,072 B (128 KB) | 61.3% |
| ER_IROM1 | 260,004 B (253.91 KB) | 1,048,576 B (1024 KB) | 24.8% |
| RW_IRAM1 | 80,920 B (79.02 KB) | 131,072 B (128 KB) | 61.7% |
| RW_CCM | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% |
| **Flash (ROM 合计)** | **258,792 B (252.7 KB)** | 1,048,576 B (1024 KB) | **24.7%** |
| **Flash (ROM 合计)** | **261,540 B (255.4 KB)** | 1,048,576 B (1024 KB) | **24.9%** |
<!-- MEM_SNAPSHOT:END -->
各区的容量与常量来源:
@@ -470,6 +470,57 @@ FreeRTOS 堆不变(`free=17248 / min_ever=9312`)。验证链:`@build` 0/0
**留作候选、本轮刻意未做**:① **共享静态行缓冲池**(`dbg_console_line_claim()/release()` 发还栈上数组,改为从 2 个静态槽领取)——可再削每条日志帧的全部 152 B(全链再省 ~1.3 KB),但把"日志缓冲在调用者栈上"改成"跨任务共享 + 抢占失败即丢行",**改变日志并发语义**,需配套主机侧单测才动;② `timeSyncTask` 静态余量已正好等于 1024 B ISR 底线,**该链上再加任何调用都必须先抬栈**(已在 `Src/freertos.c` 栈注释里写明)。
#### 4.0.7 configASSERT↔控制台幻影环:三树实测与记账口径(2026-10-26 道G task-38 出证)
**机制**:`configASSERT` 在 FreeRTOS port/tasks/queue 内部可达(map 中 `refers to
vApplicationAssertFailed` 共 103 行),而 `vApplicationAssertFailed` 钩子(`Src/freertos.c`,
2026-09-29 起)经 `dbg_console_write_raw` 打印 `[ERR] ASSERT FAILED`;同时控制台裸发/入环
路径一度也走 `taskENTER_CRITICAL()`(→ `vPortEnter/ExitCritical` → `configASSERT`)——
两个方向互可达 ⇒ 调用图成**幻影环**。armlink 对环的展开策略(截断,或 `In Cycle` 单趟
记账)**随环的形状变动**:三棵树里 port.o、FreeRTOSConfig.h 与 configASSERT 宏逐字节相同
(本节按树核对),静态记账却差出最高 80 B——这就是「幻影环只是记账」的完整证明。
**三树实测**(每行钉双指纹:产物路径 + htm 内 Linker run 时刻;量数一律用**该树自己的**
`scripts/stack_report.py` 从该树根跑起,重跑脚本不算新测量):
| 树(提交态) | 产物指纹 | `vPortExitCritical` 子树 D | 最坏链含断言族 | defaultTask / netTask / iec104Task |
|---|---|---|---|---|
| bench-21(77779d49) | `_wt\bench-21-77779d49\MDK-ARM\STM32F407-Demo\STM32F407-Demo.htm`,Linker run 16:27:18 | **32**(展开 `⇒vApplicationAssertFailed⇒dbg_console_write_raw⇒con_tx_bounded`,钩子 D24) | 138 个函数条目的最坏子树经过钩子(浅链 +16~24 B,例 `net_get_generation` D72) | 3032 / 1912 / 1456 |
| bench-23(1d4b6a17,task-23 整行入环) | `_wt\bench-23-1d4b6a17\…\STM32F407-Demo.htm`,Linker run 19:43:40 | 8(截断) | 0 | 3040 / 1920 / 1464(+8 = `dbg_console_drain` 局部变量,task-23 真账) |
| 主干在飞(task-26 钩子 begin/end 接线,未提交) | 主干 .htm,Linker run 20:39:43 ——**该产物已被 22:52 覆盖,本行数字是会话内逐帧存档**(差分表见 task-38 卡片与 45086cf5 提交正文) | 8,但 `con_tx_release` 子树 D152(`⇒vPortExitCritical⇒vApplicationAssertFailed⇒dbg_console_tx_end (Cycle)`) | 3 条任务链各 **+80 B**;iec104Task 1544 → margin 1016 < 1024 = **FAIL** | 3120 / 2000 / 1544 |
| 主干现状(b24b2b5f,PRIMASK 落地后) | `MDK-ARM\STM32F407-Demo\STM32F407-Demo.htm`,Linker run 22:52:12 | 8(钩子未在其下展开) | **9/9 条最坏链干净** | 3040 / 1920 / 1464(margin 2080/1920/1096,全 PASS) |
**① 现状点名(22:52 树)**:断言→控制台这条边**仍然活着**(map refers 103 行未减,钩子
`[Called By]` 仍挂着 port/tasks/queue 全家),只是 armlink 当前不在临界区函数下展开它——
状态是「**休眠**」不是「解耦」。模块级记账底数还在:**37 个函数、14 个模块**的最坏子树
经过 `vPortExitCritical`(每个吃 8 B 底账)——`net_socket`×12、`net_pending`×9、`tasks`×3、
`task_watchdog`×2、`port`×2、`adc_input_poll` / `tpafe5160_snapshot` / `sys_clock_get` /
`osEventFlagsGet` / `xEventGroupClearBits` / `prvIsQueueEmpty` / `vListInsertEnd` /
`fire_event` / `s_epoch_ms` 各 1。**不止 console_log 一家**:凡走内核临界区 API 的模块都在
这张账本上,环形状再变时先咬这些链。
**② 量化表("把环断掉"能回收多少)**:现状每链 **0 B**——断言已不在任何最坏链,没有可
回收的存量;历史兑现值即上表:浅链 +16~24 B(b21 时代)→ 全图截断(b23 起)→ 深链
+80 B×3(20:39 实证)。环形状再波动一次的潜在摆动带 ≈ **−80 ~ +144 B/链**(弹药深度 =
`dbg_console_write_raw` 现测 D128 + 钩子自身帧 0)。
⚠ **反向风险(钉死,原话引用为准):若将来 `configASSERT` 被定义成空宏或被裁剪,本表
每链会"回收"约 80~90 B——那是记账变化,不是固件变好;谁把它写成优化收益就是在骗人**
(真实代价是内核误用全部静默)。要"正式断环"而不牺牲取证,正确方向是让断言钩子的
发送口不再回流临界区(task-26 的 PRIMASK 窗口已是这个方向),**不是**把宏裁空。
**③ 口径:硬线 vs 实际余量**:静态最坏链是**保守建模**——`Error_Handler`(own 152 + 环
记账 208 = D360)与 `[ERR]→NVIC_SystemReset` 这类终点路径,真实运行不会把环走满;它
也不算 ISR 上下文保存(约 100 B)与间接调用(§4.0.6 盲区条同源)。所以静态链适合当
**硬线**(可复现、无噪声、机器可判:margin ≥ max(1024 B, cfg×30%)),不适合当实际余量;
实际余量看 §4.0.2 的串口 `[STACK]` 水位。台架判据「栈水位不劣于上一轮」必须加容差或
改拿本节的静态硬线(「栈水位判据加容差或改静态最坏链当硬线」卡 = task-32,与本节和
§4.0.2 互指)。
**取证纪律(本节方法的可复用模板)**:凡跨树/跨构建引用数字,必须同时钉**产物路径 +
Linker run 时刻**——commit hash 不是稳定身份(重写正文即换 hash),tree hash + 父提交
才是;测量脚本必须从目标树根用该树自己的 `scripts/`(`REPO_ROOT` 跟随 `__file__`,
拿主树脚本进别的树量 = 量的还是主树产物,2026-10-26 实测踩实)。
#### 阶段 0 验收
- 串口可见 `[STACK]` 报告:每任务 `peak_used/free_min` + 堆 `free/min_ever`
+4 -4
View File
@@ -15,18 +15,18 @@
<!-- MEM_BUDGET:BEGIN generated by scripts/ram_baseline.py -->
| 区域 | 用量 | 容量 | 占比 | 门禁(≤92%) |
|---|---|---|---|---|
| ER_IROM1 (Flash) | 257,272 B | 1,048,576 B | 24.5% | 参考 |
| ER_IROM1 (Flash) | 260,004 B | 1,048,576 B | 24.8% | 参考 |
| RW_CCM | 57,376 B | 65,280 B | 87.9% | PASS |
| UNINIT_CCM | 48 B | 256 B | 18.8% | 参考 |
| RW_IRAM1 | 80,312 B | 131,072 B | 61.3% | PASS |
| RW_IRAM1 | 80,920 B | 131,072 B | 61.7% | PASS |
| 子系统(静态 RW+ZI;除 RTOS 堆在 CCM 外均在 SRAM1) | 现值 | 配额 | 状态 |
|---|---|---|---|
| 网络 | 13,702 B | 12 KB | ⚠ 超 1.4 KB |
| 存储 | 7,072 B | 12 KB | OK |
| UI | 33,533 B | 16 KB | ⚠ 超 16.7 KB |
| UI | 34,085 B | 16 KB | ⚠ 超 17.3 KB |
| RTOS 堆 | 57,376 B | 56 KB | 堆=配额 |
| 应用 | 15,485 B | 8 KB | ⚠ 超 7.1 KB |
| 应用 | 15,537 B | 8 KB | ⚠ 超 7.2 KB |
| 其它 | 10,523 B | — | 参考 |
<!-- MEM_BUDGET:END -->
+1
View File
@@ -9,3 +9,4 @@ date code ro rw zi er_irom1 er_pct iram1 iram1_pct ccm ccm_pct top_rwzi
2026-10-24 213780 18860 1204 131852 232640 22.2 75632 57.7 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4128;sys_log_mcu.o=3944;default_task.o=2736
2026-10-25 224860 24640 1332 135100 249500 23.8 79008 60.3 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;lftpd.o=7884;ui_net_sink.o=6770;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4128;sys_log_mcu.o=3944;default_task.o=2736
2026-10-26 231712 25560 1520 136216 257272 24.5 80312 61.3 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;lftpd.o=7884;ui_net_sink.o=6770;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4132;sys_log_mcu.o=4003;default_task.o=2736
2026-10-27 233900 26104 1536 136808 260004 24.8 80920 61.7 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;lftpd.o=7884;ui_net_sink.o=6770;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4132;sys_log_mcu.o=4055;default_task.o=2736
1 date code ro rw zi er_irom1 er_pct iram1 iram1_pct ccm ccm_pct top_rwzi
9 2026-10-24 213780 18860 1204 131852 232640 22.2 75632 57.7 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4128;sys_log_mcu.o=3944;default_task.o=2736
10 2026-10-25 224860 24640 1332 135100 249500 23.8 79008 60.3 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;lftpd.o=7884;ui_net_sink.o=6770;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4128;sys_log_mcu.o=3944;default_task.o=2736
11 2026-10-26 231712 25560 1520 136216 257272 24.5 80312 61.3 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;lftpd.o=7884;ui_net_sink.o=6770;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4132;sys_log_mcu.o=4003;default_task.o=2736
12 2026-10-27 233900 26104 1536 136808 260004 24.8 80920 61.7 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;lftpd.o=7884;ui_net_sink.o=6770;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4132;sys_log_mcu.o=4055;default_task.o=2736
+3
View File
@@ -105,6 +105,7 @@ set(UI_SOURCES
${UI_DIR}/ui_data.c # 数据快照存侧(道E task-14;写侧 App/app/ui_data_feed.c 不进模拟器,见 scripts/check_registration.py)
${UI_DIR}/ui_w_soe.c # SOE 记录组件(道E task-14;reader 已在批 2 两端绑定)
${UI_DIR}/ui_w_setview.c # 定值查看视图模型(道E task-15 批 3:四张视图表 + Q10 换算 + 越界反显)
${UI_DIR}/ui_w_ytview.c # 遥测整定视图模型(道F task-7:死区/系数两页共用注入表视图 + 落盘确认状态机)
${UI_DIR}/ui_page_soe.c
${UI_DIR}/ui_page_view_param.c
${UI_DIR}/ui_page_view_plate.c
@@ -114,6 +115,8 @@ set(UI_SOURCES
${UI_DIR}/ui_page_clear.c
${UI_DIR}/ui_page_clock.c
${UI_DIR}/ui_page_reset.c
${UI_DIR}/ui_page_calib.c
${UI_DIR}/ui_page_deadband.c
)
# 运行/自检记录引擎(双端共享核心 + 平台存储后端;MCU 端编 sys_log_mcu.c,见 Keil 工程)
+148
View File
@@ -267,6 +267,143 @@ static const ui_data_maint_ops_t s_maint_ops = {
sim_maint_clear_req,
sim_maint_clear_state
};
/*
* ======================= 道F task-7:遥测整定注入表假件(镜像 ui_data_feed.c 形状)
*
* 初值/合法域 = App/algo/run_par.c 工厂 ROM 与 RUN_PAR_DB_MAX_* 档位的镜像
* (模拟器不链 run_par,真源抄不来只能镜像;变更纪律同 s_sim_log_map 那条:
* 执行侧改档不改这里,屏上会呈现"档位与量纲自相矛盾"的肉眼可见错,黄金像素兜底)。
* ⚠ 脏位口径是这一家的存在理由:**写成功只置脏、绝不同步清**,清脏的唯一去处是
* sim_yt_pump()(镜像 defaultTask 每轮无条件 run_par_store_save())。同步清 =
* WAIT 态在模拟器里永不出现,"落盘确认"三态就只剩最好看那一帧被黄金钉住
* (Lead 裁定④原文的理由,别顺手"优化"成同步)。
*/
static float s_yt_db[UI_DATA_YT_DB_N] = {
0.5f, 0.5f, 0.5f, 0.5f, /* 电压死区 0.5 V(工厂额定档) */
0.005f, 0.005f, 0.005f, 0.005f, /* 电流死区 0.005 A */
0.5f /* 频率死区 0.5 Hz(绝对档) */
};
static float s_yt_k[UI_DATA_YT_CH_N] = {
152.587890625f, 152.587890625f, 152.587890625f, 152.587890625f,
152.587890625f, 152.587890625f, 152.587890625f, 152.587890625f
}; /* = adc_cal ROM 的 TPAFE 系数(µV/码);显示小数位 0 的量化后果见真机侧装配注 */
static uint8_t s_yt_dirty; /* 镜像 run_par 的整档脏位(页面上 WAIT 的唯一来源) */
static int s_yt_nopump; /* --yt-nopump:拍 WAIT 态黄金专用(截图前不清脏) */
/* 死区粗守上限(工程量):镜像 RUN_PAR_DB_MAX_U/I/F 三档;分档与 feed 的 unit
* 分档必须同序(同一条对表纪律) */
/* 函数功能:假死区粗守上限(工程量档,镜像 RUN_PAR_DB_MAX_U/I/F) 返回值:上限 float */
static float sim_yt_db_hi(uint8_t a_idx) {
if (a_idx < 4u) {
return 50.0f;
}
if (a_idx < (UI_DATA_YT_DB_N - 1u)) {
return 1.0f;
}
if (a_idx == (UI_DATA_YT_DB_N - 1u)) {
return 5.0f;
}
return 0.0f;
}
/* 函数功能:假死区点数(镜像 RUN_PAR_DB_N) 返回值:点数 uint8_t */
static uint8_t sim_yt_db_n(void) {
return (uint8_t)UI_DATA_YT_DB_N;
}
/* 函数功能:假死区行现状(值现取自家数组、档位分档与 feed 同序) 返回值:0 / -1 int */
static int sim_yt_db_row(uint8_t a_idx, ui_data_yt_row_t *a_out) {
if ((a_out == NULL) || (a_idx >= UI_DATA_YT_DB_N)) {
return -1;
}
a_out->val = s_yt_db[a_idx];
a_out->lo = 0.0f;
a_out->hi = sim_yt_db_hi(a_idx);
a_out->decimals = 3u;
a_out->unit = (a_idx < 4u) ? UI_DATA_YT_UNIT_V
: ((a_idx < (UI_DATA_YT_DB_N - 1u)) ? UI_DATA_YT_UNIT_A : UI_DATA_YT_UNIT_HZ);
return 0;
}
/* 函数功能:假写死区——写守卫与执行侧同款语义:越界拒 = 值不动、**不置脏**
* (置了脏就是"屏上没改、却要落盘"的空承诺);接受 = 落值 + 置脏,
* 同一函数内发生。 返回值:0 / -1 int */
static int sim_yt_db_set(uint8_t a_idx, float a_eng) {
if ((a_idx >= UI_DATA_YT_DB_N) || (a_eng < 0.0f) || (a_eng > sim_yt_db_hi(a_idx))) {
return -1;
}
s_yt_db[a_idx] = a_eng;
s_yt_dirty = 1u;
return 0;
}
/* 函数功能:假系数点数(镜像 RUN_PAR_CH_N) 返回值:点数 uint8_t */
static uint8_t sim_yt_cal_n(void) {
return (uint8_t)UI_DATA_YT_CH_N;
}
/* 函数功能:假系数行现状(hi = RUN_PAR_K_MAX_UV 的镜像;见上注变更纪律) 返回值:0 / -1 int */
static int sim_yt_cal_row(uint8_t a_idx, ui_data_yt_row_t *a_out) {
if ((a_out == NULL) || (a_idx >= UI_DATA_YT_CH_N)) {
return -1;
}
a_out->val = s_yt_k[a_idx];
a_out->lo = 0.0f;
a_out->hi = 1000000000.0f; /* = RUN_PAR_K_MAX_UV 的镜像(粗守上限) */
a_out->decimals = 0u;
a_out->unit = UI_DATA_YT_UNIT_UV;
return 0;
}
/* 函数功能:假写系数 k(同 db_set 的"写值与置脏成对"语义) 返回值:0 / -1 int */
static int sim_yt_cal_k_set(uint8_t a_idx, float a_uv) {
if ((a_idx >= UI_DATA_YT_CH_N) || !(a_uv > 0.0f) || (a_uv > 1000000000.0f)) {
return -1; /* k<=0 与超粗守都拒;真带这一层模拟器不复刻(执行段的事) */
}
s_yt_k[a_idx] = a_uv;
s_yt_dirty = 1u;
return 0;
}
/* 函数功能:假未标定徽标源(恒已标定,理由见函数体注) 返回值:0 uint8_t */
static uint8_t sim_yt_cal_blocked(void) {
/* 模拟器恒"已标定":徽标腿的像素证据由整定四页黄金覆盖(同一注入式画法);
* 要拍"未标定 + 系数页"的组合另立带旗标的黄金态,本批不扩表 */
return 0u;
}
/* 函数功能:假整档脏位(页面上 WAIT 的唯一来源) 返回值:脏位 uint8_t */
static uint8_t sim_yt_pending(void) {
return s_yt_dirty;
}
static const ui_data_yt_ops_t s_yt_ops = {
sim_yt_db_n,
sim_yt_db_row,
sim_yt_db_set,
sim_yt_cal_n,
sim_yt_cal_row,
sim_yt_cal_k_set,
sim_yt_cal_blocked,
sim_yt_pending
};
/*
* 函数功能:落盘脏位的唯一清去处(镜像 defaultTask 每轮无条件 run_par_store_save)
* 入口参数:无
* 返回值:无
* 限定条件:主循环 / 无头注键尾各一处;--yt-nopump(拍 WAIT 黄金)时整函数空转
* 函数说明:真机"保存成功"的语义 = cfg_file_save 返 0 才清脏;模拟器无失败面,
* pump 即落盘——ERR 腿不复刻(拍不出来也别假拍,屏上 ERR 由写被拒触发,
* 那条模拟器能真拍:见黄金 cal_reject)
*/
static void sim_yt_pump(void) {
if (s_yt_nopump != 0) {
return;
}
s_yt_dirty = 0u;
}
/*
* 函数功能:SOE 记录假源——固定 3 条(镜像 App/task/ui_task.c 真机绑定的形态)
* 入口参数:无
@@ -584,6 +721,9 @@ static int run_headless(const char * keys, const char * path, int standby) {
printf("[sim] UI maint bind: FAIL\n");
}
ui_w_soe_bind(&s_soe_reader); /* task-15:SOE 记录页假源(形态镜像真机绑定) */
if (ui_data_yt_bind(&s_yt_ops) != 0) {
printf("[sim] UI yt bind: FAIL\n");
}
/* task-14 数据快照:镜像 ui_task.c 的 ui_data_init + 首帧发布(先于建页,
* 页面任何时刻打开都拿得到帧,不必等装配节拍) */
sim_publish_data_frames();
@@ -617,6 +757,7 @@ static int run_headless(const char * keys, const char * path, int standby) {
* 无头模式没有那个节拍,不在此当场排空,截图就会停在 WAIT(终态拍不到,
* OK/ERR/LOCK 三条判据的像素证据也就全都不成立) */
sim_param_pump();
sim_yt_pump(); /* 道F task-7:截图前清脏 = 拍 OK 黄金;--yt-nopump 停转 = 拍 WAIT 黄金(Lead 裁定④:两态都要有像素证据) */
/* --standby:注键后强制进入待机屏(截图前) */
if (standby != 0) {
@@ -1009,6 +1150,9 @@ int main(int argc, char **argv) {
if (strcmp(argv[i], "--noclock") == 0) {
s_noclock = 1; /* 把这台机器装成"墙钟无效"(RTC 异常/未对时) */
}
if (strcmp(argv[i], "--yt-nopump") == 0) {
s_yt_nopump = 1; /* 落盘 pump 停转:拍"已受理未落盘"(WAIT)黄金专用 */
}
}
/* 截图/自检模式:--screenshot / --keys / --standby / --cfgtest / --logtest */
@@ -1080,6 +1224,9 @@ int main(int argc, char **argv) {
printf("[sim] UI maint bind: FAIL\n");
}
ui_w_soe_bind(&s_soe_reader); /* task-15:SOE 记录页假源(形态镜像真机绑定) */
if (ui_data_yt_bind(&s_yt_ops) != 0) {
printf("[sim] UI yt bind: FAIL\n");
}
/* task-14 数据快照:镜像 ui_task.c 的 ui_data_init + 首帧发布(先于建页,
* 页面任何时刻打开都拿得到帧,不必等装配节拍) */
sim_publish_data_frames();
@@ -1128,6 +1275,7 @@ int main(int argc, char **argv) {
/* 落定定值整定请求(镜像真机 ui_data_feed_tick 里的 ui_data_param_drain:
* 出队 → prot_set 落值 → 重发参数帧);队列空时只是一次计数判断 */
sim_param_pump();
sim_yt_pump(); /* 道F task-7:窗口模式同样每轮无条件清脏(真机 50 ms 落盘的镜像) */
/* 配置文件轮询(2s 周期:外部文件变更 4s 沉降后自动重载) */
uint32_t now_ms = (uint32_t)SDL_GetTicks();
+69
View File
@@ -102,6 +102,25 @@ PAGES = [
("clear_2nd", "dddd eddde uruuruuue e"),
("clear_done", "dddd eddde uruuruuue ee"),
("clear_ro", "dddd eddde e"),
# ---- 道F task-7 遥测整定两页(根菜单第 4 项 = 3 下;子项 0/1 = 死区/系数)
# 键序读法与 task-3 三页同源:e 进子菜单后再 e 开叶子(#P → 先落密码页),
# "uruuruuue" = 修改口令 123(可编辑)。
# 死区 9 行 + 保存 = 10 行(2 页);系数 8 行 + 保存 = 9 行(2 页,保存行独在第 2 页)。
# ⚠ wait/ok 两张**必须成对存在**:脏位在模拟器由 sim_yt_pump 清(镜像真机
# defaultTask 每轮无条件落盘),wait 态靠 --yt-nopump 停 pump 拍下来——
# 只钉 OK 就是"只钉最好看那一帧"(Lead 裁定 task-7 派发④原文),
# 真机 WAIT 窗口毫秒级、人工永远补拍不到,只有这里替它留了像素证据。
("deadband", "ddd ee uruuruuue"), # 首屏(选中首行 UA死区 0.500V)
("deadband_wait", "ddd ee uruuruuue e uu e ddddddddd e"), # UA→0.502 提交 → 保存行 ENTER:WAIT(+--yt-nopump)
("deadband_ok", "ddd ee uruuruuue e uu e ddddddddd e"), # 同键序、pump 正常 → OK(脏位真被清)
("calib", "ddd e de uruuruuue"), # 首屏 8 行系数(153uV = ROM TPAFE 整 µV)
("calib_wait", "ddd e de uruuruuue e u e dddddddd e"), # UA→154 提交 → 保存行:WAIT(+--yt-nopump)
("calib_ok", "ddd e de uruuruuue e u e dddddddd e"), # 同键序、pump 正常 → OK
# 拒后**再**移到保存行(不按保存):一张图同时钉"粘滞 ERR 词"与"越界行反显"
# (r0 反显要等光标移开才单独可见——与选中白条重叠时像素分不出来,这是一开始
# 拍在首屏的图目检后改出来的键序);不按保存是因为按键会重新评估、ERR 会被
# 新一轮 WAIT/OK 覆盖——ERR 的像素证据只有这条窗口是真的
("calib_reject", "ddd e de uruuruuue e ddd l ddddd l d e dddddddd"),
]
# 文本级断言表:页名 -> [(名称, 期望串, y 带, 是否反显)]
@@ -192,6 +211,52 @@ TEXTS = {
("r0名", "运行记录", (24, 38), True),
("r6名", "遥控记录", (108, 122), False),
],
# ---- 道F task-7 遥测整定两页。行槽沿用列表页口径(24 + 14i),单位串由
# ui_w_setlist 直接贴数值尾("0.500V"无空格),µV 在屏上是 ASCII "uV"
"deadband": [
("标题", "遥测死区", (0, 24), False),
("r0名", "UA死区", (24, 38), True), ("r0值", "0.500V", (24, 38), True),
("r4名", "IA死区", (80, 94), False), ("r4值", "0.005A", (80, 94), False),
("r7名", "IN死区", (122, 136), False), ("r7值", "0.005A", (122, 136), False),
("页脚", "第 1 页,共 2 页", (140, 160), False),
],
"deadband_wait": [
("标题", "遥测死区", (0, 24), False),
# 第 2 页窗口:槽 0 = Hz死区行(跟随真源、本轮未动),槽 1 = 保存行(选中反显)
("r0名", "Hz死区", (24, 38), False), ("r0值", "0.500Hz", (24, 38), False),
("保存名", "保存", (38, 52), True), ("保存值", "WAIT", (38, 52), True),
("页脚", "第 2 页,共 2 页", (140, 160), False),
],
"deadband_ok": [
("标题", "遥测死区", (0, 24), False),
("r0名", "Hz死区", (24, 38), False),
("保存名", "保存", (38, 52), True), ("保存值", "OK", (38, 52), True),
("页脚", "第 2 页,共 2 页", (140, 160), False),
],
"calib": [
("标题", "遥测系数", (0, 24), False),
("r0名", "UA系数", (24, 38), True), ("r0值", "153uV", (24, 38), True),
("r7名", "IN系数", (122, 136), False), ("r7值", "153uV", (122, 136), False),
("页脚", "第 1 页,共 2 页", (140, 160), False),
],
"calib_wait": [
("标题", "遥测系数", (0, 24), False),
("保存名", "保存", (24, 38), True), ("保存值", "WAIT", (24, 38), True),
("页脚", "第 2 页,共 2 页", (140, 160), False),
],
"calib_ok": [
("标题", "遥测系数", (0, 24), False),
("保存名", "保存", (24, 38), True), ("保存值", "OK", (24, 38), True),
("页脚", "第 2 页,共 2 页", (140, 160), False),
],
"calib_reject": [
("标题", "遥测系数", (0, 24), False),
# 第 2 页(光标移到保存行、未按键):k=0 被执行侧拒 → **值纹丝不动**
# (副本未动、不置脏——"写值与置脏成对"的另一半判据)+ 保存行粘滞 ERR;
# r0 的越界反显在第 1 页像素里(文本断言不猜反显形状,逐字节比对钉住)
("保存名", "保存", (24, 38), True), ("保存值", "ERR", (24, 38), True),
("页脚", "第 2 页,共 2 页", (140, 160), False),
],
# v2.0(道E task-15 回炉):6 行 Flash 字面量 -> 8 行真值;标签去全角冒号(要给值列
# 让出 MAC 的 102 px),行槽 = 38 + 13i。值全部来自模拟器固定帧(simulator/main.c 的
# sim_publish_data_frames)与 net_cfg 出厂副本 —— 逐行断言等于把这两处口径钉在像素侧。
@@ -569,6 +634,10 @@ POOL_AFTER_ROUNDTRIP = 5448
# 不为它另造一套假数据:假的是"时钟有没有"这件事,页面与显示链全是真的。
EXTRA = {
"clock_noclock": ["--noclock"],
# 道F task-7:WAIT 态黄金 = 落盘 pump 停转(假件保留脏位,Lead 裁定④);
# OK 态同键序不加旗标,两图唯一差别就是"脏有没有被清"——对照即凭证
"deadband_wait": ["--yt-nopump"],
"calib_wait": ["--yt-nopump"],
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+187
View File
@@ -74,6 +74,11 @@
* test-cppcheck 抓过,真判据落在 TC-UIDATA-026s)
* TC-UIDATA-025 维护通道未注入防御:六口安全返回 + 残表整表拒绝且不覆盖好表
* TC-UIDATA-026 注入后转发言行:epoch 0 哨兵不下发、reset_req 自行投操作记录=0 次
* TC-UIDATA-027 遥测整定通道未注入防御:八个口一律安全返回、不碰出参;
* 残表整表拒绝且不覆盖好表(与 025 同一条纪律的第五张表复刻)
* TC-UIDATA-028 遥测整定注入后转发言行:行现状逐字段原样上抛、写口的 idx 与
* 工程量原样下传、返回码直通、pending 无状态直读(本层零缓存——
* 页面 OK/WAIT 的唯一依据必须是执行侧此刻的脏位)
* (谁触发谁投,防"远端 + 本地"双投)、clear 状态码直通不改码
*
* 通过:全部 TC PASS(main 返回 0)。
@@ -1297,6 +1302,184 @@ static void tc_maint_forwarding(void)
CHECK(s_stub_clear_state_calls == 2, "TC-UIDATA-026r state queried once per call");
}
/* ── 道F task-7:通道五(遥测整定注入表)桩 ─────────────────────────────── */
static uint8_t s_yt_db_calls;
static uint8_t s_yt_db_last_idx;
static float s_yt_db_last_val;
static int s_yt_db_set_rc;
static uint8_t s_yt_cal_calls;
static uint8_t s_yt_cal_last_idx;
static float s_yt_cal_last_uv;
static int s_yt_cal_set_rc;
static uint8_t s_yt_blocked;
static uint8_t s_yt_pending_ret;
static int s_yt_pending_calls;
static uint8_t yt_stub_db_n(void)
{
return UI_DATA_YT_DB_N;
}
static int yt_stub_db_row(uint8_t a_idx, ui_data_yt_row_t *a_out)
{
if (a_idx >= UI_DATA_YT_DB_N) {
return -1;
}
a_out->val = 0.125f + (float)a_idx;
a_out->lo = 0.0f;
a_out->hi = 50.0f;
a_out->unit = UI_DATA_YT_UNIT_V;
a_out->decimals = 3u;
return 0;
}
static int yt_stub_db_set(uint8_t a_idx, float a_eng)
{
s_yt_db_calls++;
s_yt_db_last_idx = a_idx;
s_yt_db_last_val = a_eng;
return s_yt_db_set_rc;
}
static uint8_t yt_stub_cal_n(void)
{
return UI_DATA_YT_CH_N;
}
static int yt_stub_cal_row(uint8_t a_idx, ui_data_yt_row_t *a_out)
{
if (a_idx >= UI_DATA_YT_CH_N) {
return -1;
}
a_out->val = 152.5f;
a_out->lo = 0.0f;
a_out->hi = 1000000000.0f;
a_out->unit = UI_DATA_YT_UNIT_UV;
a_out->decimals = 0u;
return 0;
}
static int yt_stub_cal_k_set(uint8_t a_idx, float a_uv)
{
s_yt_cal_calls++;
s_yt_cal_last_idx = a_idx;
s_yt_cal_last_uv = a_uv;
return s_yt_cal_set_rc;
}
static uint8_t yt_stub_blocked(void)
{
return s_yt_blocked;
}
static uint8_t yt_stub_pending(void)
{
s_yt_pending_calls++;
return s_yt_pending_ret;
}
static const ui_data_yt_ops_t s_yt_stub = {
yt_stub_db_n, yt_stub_db_row, yt_stub_db_set, yt_stub_cal_n,
yt_stub_cal_row, yt_stub_cal_k_set, yt_stub_blocked, yt_stub_pending
};
/* 残表:只缺最后一口(pending)——八口校验里最容易被"顺手少注一件"漏掉的那个 */
static const ui_data_yt_ops_t s_yt_broken = {
yt_stub_db_n, yt_stub_db_row, yt_stub_db_set, yt_stub_cal_n,
yt_stub_cal_row, yt_stub_cal_k_set, yt_stub_blocked, NULL
};
/*
* TC-UIDATA-027 未注入防御(025 的第五张表复刻):八个口一律安全返回;
* db_row/cal_row 未注入时**不得碰出参**(页面拿栈上未初始化结构当现状读,
* 比返回 -1 更坏——这里用预填哨兵钉住"一个字节都没写")。
*/
static void tc_yt_not_bound(void)
{
ui_data_yt_row_t r;
float sentinel = -777.0f;
(void)ui_data_yt_bind(NULL);
r.val = sentinel;
CHECK(ui_data_yt_db_n() == 0u, "TC-UIDATA-027a db_n not bound is 0");
CHECK(ui_data_yt_db_row(0u, &r) == -1, "TC-UIDATA-027b db_row not bound is -1");
CHECK(r.val == sentinel, "TC-UIDATA-027c db_row not bound wrote nothing");
r.val = sentinel;
CHECK(ui_data_yt_cal_row(0u, &r) == -1, "TC-UIDATA-027d cal_row not bound is -1");
CHECK(r.val == sentinel, "TC-UIDATA-027e cal_row not bound wrote nothing");
CHECK(ui_data_yt_cal_n() == 0u, "TC-UIDATA-027f cal_n not bound is 0");
CHECK(ui_data_yt_db_set(0u, 1.0f) == -1, "TC-UIDATA-027g db_set not bound is -1");
CHECK(ui_data_yt_cal_k_set(0u, 1.0f) == -1, "TC-UIDATA-027h cal_k_set not bound is -1");
CHECK(ui_data_yt_cal_blocked() == 0u, "TC-UIDATA-027i blocked not bound is 0 (never fabricate)");
CHECK(ui_data_yt_pending() == 0u, "TC-UIDATA-027j pending not bound is 0");
CHECK(ui_data_yt_db_row(0u, NULL) == -1, "TC-UIDATA-027k NULL out refused even bound-or-not");
CHECK(ui_data_yt_bind(&s_yt_broken) == -1, "TC-UIDATA-027l incomplete table refused");
CHECK(ui_data_yt_db_n() == 0u, "TC-UIDATA-027m refused table left channel unbound");
CHECK(ui_data_yt_bind(&s_yt_stub) == 0, "TC-UIDATA-027n complete table accepted");
CHECK(ui_data_yt_db_n() == UI_DATA_YT_DB_N, "TC-UIDATA-027o bound table forwards count");
CHECK(ui_data_yt_bind(&s_yt_broken) == -1, "TC-UIDATA-027p broken refused again");
CHECK(ui_data_yt_db_n() == UI_DATA_YT_DB_N, "TC-UIDATA-027q refusal keeps the good table active");
}
/*
* TC-UIDATA-028 转发言行(两张页面全部判据的地基):
* - 行现状逐字段原样上抛(val/lo/hi/unit/decimals 五样一个不许"顺手加工"——
* 合法域是执行侧事实,转发层改一个数就是第二真源)
* - 写口 idx 与工程量原样下传,执行侧返回码直通(0/-1 两态,页面按 ERR 显示)
* - pending 无状态直读:本层每问一次转发一次(calls 增长)、桩改什么口径立刻
* 变什么口径——页面锁存的合法性建立在"本层不缓存"之上
* - 越界 idx 由执行侧判(桩返 -1 直通),本层不抄一份行数守卫
*/
static void tc_yt_forwarding(void)
{
ui_data_yt_row_t r;
(void)ui_data_yt_bind(&s_yt_stub);
s_yt_db_calls = 0u; s_yt_cal_calls = 0u; s_yt_pending_calls = 0;
s_yt_db_set_rc = 0; s_yt_cal_set_rc = 0;
CHECK(ui_data_yt_db_row(2u, &r) == 0, "TC-UIDATA-028a db_row forwarded");
CHECK((r.val == (0.125f + 2.0f)) && (r.lo == 0.0f) && (r.hi == 50.0f) &&
(r.unit == UI_DATA_YT_UNIT_V) && (r.decimals == 3u),
"TC-UIDATA-028b row fields pass through verbatim (no second source of truth)");
CHECK(ui_data_yt_db_row(UI_DATA_YT_DB_N, &r) == -1, "TC-UIDATA-028c executor-side oob idx propagates");
CHECK((ui_data_yt_db_set(3u, 0.25f) == 0), "TC-UIDATA-028d db_set accepted");
CHECK((s_yt_db_calls == 1u) && (s_yt_db_last_idx == 3u) && (s_yt_db_last_val == 0.25f),
"TC-UIDATA-028e executor got exact idx and engineering value");
s_yt_db_set_rc = -1;
CHECK(ui_data_yt_db_set(3u, 0.25f) == -1, "TC-UIDATA-028f executor rejection propagates");
CHECK((ui_data_yt_cal_n() == UI_DATA_YT_CH_N) && (ui_data_yt_cal_row(7u, &r) == 0) &&
(r.unit == UI_DATA_YT_UNIT_UV) && (r.hi == 1000000000.0f) && (r.decimals == 0u),
"TC-UIDATA-028g cal row forwards (coarse-guard hi verbatim)");
s_yt_cal_set_rc = 0;
CHECK(ui_data_yt_cal_k_set(7u, 153.0f) == 0, "TC-UIDATA-028h cal_k_set accepted");
CHECK((s_yt_cal_calls == 1u) && (s_yt_cal_last_idx == 7u) && (s_yt_cal_last_uv == 153.0f),
"TC-UIDATA-028i cal executor got exact idx and uv value");
s_yt_cal_set_rc = -1;
CHECK(ui_data_yt_cal_k_set(7u, 0.0f) == -1, "TC-UIDATA-028j cal rejection propagates (k<=0 leg)");
s_yt_blocked = 1u;
CHECK(ui_data_yt_cal_blocked() == 1u, "TC-UIDATA-028k blocked badge forwarded");
s_yt_blocked = 0u;
CHECK(ui_data_yt_cal_blocked() == 0u, "TC-UIDATA-028l badge clear follows executor immediately");
s_yt_pending_ret = 1u;
CHECK(ui_data_yt_pending() == 1u, "TC-UIDATA-028m pending=dirty forwarded (WAIT leg)");
s_yt_pending_ret = 0u;
CHECK(ui_data_yt_pending() == 0u, "TC-UIDATA-028n pending follows to clean (OK leg, zero cache)");
CHECK(s_yt_pending_calls == 2, "TC-UIDATA-028o every question reaches executor (stateless layer)");
(void)ui_data_yt_bind(NULL);
CHECK(ui_data_yt_pending() == 0u, "TC-UIDATA-028p unbind restores safe defaults");
/* 未注入不得把问题漏给桩:计数不涨 = 撤销后桩彻底脱离(028o 只数了绑定期的
* 两次,这条把"撤销后一次都不到"补成闭环) */
CHECK(s_yt_pending_calls == 2, "TC-UIDATA-028q unbind stops reaching executor entirely");
}
/* ===================== main ===================== */
int main(void)
@@ -1351,6 +1534,10 @@ int main(void)
tc_maint_not_bound();
printf("-- maint channel: forwarding --\n");
tc_maint_forwarding();
printf("-- yt channel: not bound --\n");
tc_yt_not_bound();
printf("-- yt channel: forwarding --\n");
tc_yt_forwarding();
printf("\n=== UI Data Snapshot Tests: %d/%d PASSED (failed=%d) ===\n",
s_passed, s_total, s_failed);
+2 -2
View File
@@ -34,8 +34,8 @@
控制字 #page=view_word
软压板 #page=view_plate
三遥设置
遥测死区 #P
遥测系数 #P
遥测死区 #P #page=deadband
遥测系数 #P #page=calib
遥信类型 #P
遥信防抖 #P
双点遥信 #P