feat(ui+记录): 整定写路径与四类页落地,五类记录写入方补齐
UI 面:ui_data 增加深度 4 参数写队列(请求 168 B、队列 .bss 672 B)与状态机 OK/ERR_RANGE/ERR_LOCKED/ERR_BUSY,ui_data_feed 出队后按注入 ops 走「闭锁 → 整批 预检 → 逐项 prot_set_set_sealed」,只提交改动位图;par 帧尾追加 cal_block (口径 = UA/UN/IA 三路 ADC_CAL_F_VALID,与 protect_task 判据同构,不用 rt.eng 反推,V0.1 下 eng 恒 0)、UI_DATA_VER 升到 2。参数/定值/控制字/软压板四叶绑定 内容页(UI_PAGE_MAP_COUNT 12→16,复用四个查看模块,零新增 .c),编辑态按 is_editable 分派,LOCK(写不进去)与「未标定」(仍可改值)两源两通道两词。 记录面:syslog_request_clear/state/apply 三段式删除 API(UI 只发请求,落地在 defaultTask 的 drain 尾部且必须后置,卷锁一律 try,幂等只认 FR_NO_FILE); 事故/告警/启动/操作/遥控五类各有一个真实写入点,记录类型 = 日志 ID 本身 (一类一个 /log 严格 8.3 文件;64B 定长体没有类型字段位,塞字段会砸掉既有读者)。 为守 protectTask 872 B 静态最坏链另拆 syslog_post_quiet()(无 DBG 的投递核, 丢弃留痕挪到 drain 侧差值补发),syslog_post 对外语义逐字不变。 为什么两道合成一笔而不是各一笔(实测,不是省事):simulator/main.c 同时消费 两道新增 API(整定 pump 与 logtest 清除用例),按道切文件会让中间提交编不过; RAM 受管快照与体积同码凭证描述的是合并后的 .map,拆开必落在「台账与 .map 不同 码」这条红线上。pre-commit 跑钩子前会 stash 未 staged 部分,这类耦合没法绕。 实测:@build 0 错误 0 警告;Code 228,260 / RO 24,960 / RW 1,440 / ZI 135,976 B; RW_IRAM1 79,992 B = 61.0%、RW_CCM 87.9%(阈值 92%);九个任务静态最坏链全 PASS, protectTask 872 B / 57% 改前后逐字节不变(记录投递没垫栈)。主机单测 ui_data 82→ 164、sys_log_mcu 106→137、prot_elm 47→60、iec104 141→154、iec104_serv 28→34, 既有断言零删改;48 个钩子 --all-files 全绿;编号门禁可判定 86.34%(基线 86.0%)、 不可判定 1,153 处低于上限 1,166;doc-links 无法判定 0;UI 黄金 18 页扩到 27 页 连跑两次逐字节一致;字库 diff 空。 顺手修掉三处既有缺陷(都是本轮第一个消费者才暴露):sim_syslog.c 的 s_paths 行宽 16 而 "sim_selftest.log" 正好 16 字符没有 NUL 位(stat/remove 读越过数组尾,此前 靠后面 .rodata 碰巧为 0);ui_w_setlist 二态行 ENTER 不回绕(控制字与软压板出厂值 全在最后一项,按了像没反应而叠层里已是越界值);生成物 ui_menu_tab.h 与 gen_menu.py 模板漂移(上一笔改了模板没重跑生成器),现随生成器输出对齐。 未收口(不算完成):保存后重启值保持与主站 C_RD 读回两条只能上板,台架窗口执行; LOCK 屏显与越界反显的页面侧在模拟器造不出来(模拟器不跑定值自检、无 NAND), 已由生成侧单测与代码注释点名差距;同类页数与钩子数陈述残留另立待办。
This commit is contained in:
+228
-2
@@ -10,11 +10,23 @@
|
||||
* 发布者,保护收在本模块内部(双槽 + 每槽 seq + 单字节活动下标,照
|
||||
* App/algo/phasor.c 双区先例),页面代码不受影响
|
||||
* (判据 docs/UI设计/UI_数据供给设计说明.md §5.1)
|
||||
* 写路径(定值整定请求队列)同理:生产者是页面"保存"按键钩子、消费者是
|
||||
* App 侧节拍里的 ui_data_param_drain(),两者同在 uiTask → 环形队列照旧
|
||||
* 无锁;若将来出现第二个投递上下文,唯一要做的是给 request/drain 两侧
|
||||
* 各加一把互斥(队列本身是单字 head/cnt 推进,页面侧不会看到半成品)
|
||||
* 模块功能补充:除三通道快照外,本模块还持有定值写路径的请求队列与状态字
|
||||
* (ui_data_param_request/state/drain)——本层只做"排队 + 状态机",
|
||||
* 不判断值域、不碰 prot_set、不碰文件系统(值域与写入事实经
|
||||
* ui_data_param_ops_t 由 App 侧注入,理由见 ui_data.h 写路径节)
|
||||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-10-25
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-25 王建锋 创建(道E task-14)
|
||||
* v1.1 2026-10-26 DTU 项目组 道F task-1:新增定值写路径环形队列(深度 4、满返 -1)
|
||||
* 与状态机(IDLE/WAIT/OK/ERR_RANGE/ERR_LOCKED/ERR_BUSY),
|
||||
* 执行经注入函数表回调 App 侧;参数帧新增 cal_block
|
||||
* 字段随 publish 原样入存(本层不解释含义)
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
@@ -39,15 +51,42 @@ static uint32_t s_rt_cnt;
|
||||
static uint32_t s_par_cnt;
|
||||
static uint32_t s_info_cnt;
|
||||
|
||||
/* ── 写路径:定值整定请求队列(深度 UI_DATA_PARAM_QD 的静态环形数组)──────── */
|
||||
|
||||
/*
|
||||
* mask 合法位集合 = 低 UI_DATA_SET_N 位(40 项 → 0x000000FFFFFFFFFF)。
|
||||
* 用"全 1 右移"而不是"1 左移 40 位再减 1":前者在 UI_DATA_SET_N == 64 时仍是
|
||||
* 良定义(移 0 位),后者会退化成立即未定义行为(移位数 == 位宽)。
|
||||
* 项数一旦超过 64,下面的编译期守卫当场拦下——位图放不下就是表示法该换,
|
||||
* 绝不能静默把高 24 项变成"投不进去也不报错"
|
||||
*/
|
||||
#define UI_DATA_PARAM_MASK_VALID ((uint64_t)(~(uint64_t)0u) >> (64u - UI_DATA_SET_N))
|
||||
|
||||
typedef char ui_data_chk_mask_t[((uint32_t)UI_DATA_SET_N <= 64u) ? 1 : -1];
|
||||
|
||||
/* 环形队列:head = 出队下标,cnt = 在队条数(0..UI_DATA_PARAM_QD)
|
||||
* 为什么用 head+cnt 而不是 head+tail:cnt 直接就是"还有几条没落定",
|
||||
* state() 判 WAIT 不必再做 (tail - head) 取模减法,少一处可算错的地方 */
|
||||
static ui_data_param_req_t s_pq[UI_DATA_PARAM_QD];
|
||||
static uint8_t s_pq_head;
|
||||
static uint8_t s_pq_cnt;
|
||||
|
||||
/* 写路径状态字与越界位图(页面只读;int/uint64 单字读写在 M4 上不撕裂) */
|
||||
static int s_pq_state = UI_DATA_PARAM_ST_IDLE;
|
||||
static uint64_t s_pq_err_mask;
|
||||
|
||||
/* 函数定义区 */
|
||||
|
||||
/*
|
||||
* 函数功能:快照层初始化——三通道清零(valid=0、seq=0),页面即按"无首帧"渲染
|
||||
* 函数功能:快照层初始化——三通道清零(valid=0、seq=0)+ 写路径队列复位,
|
||||
* 页面即按"无首帧/未投递"渲染
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:内核启动前可调(纯内存操作,零 OS 依赖);可重复调用(幂等)
|
||||
* 函数说明:静态存储上电本已清零,本函数的意义是运行期复位(例如"恢复出厂后
|
||||
* 重开 UI"这类后续路径),以及让宿主侧测试能随时造出"首帧之前"的状态
|
||||
* 重开 UI"这类后续路径),以及让宿主侧测试能随时造出"首帧之前"的状态。
|
||||
* 写路径一并丢弃在队请求是**有意**的:旧请求带的是旧配置下的编辑叠层,
|
||||
* 复位后还把它落进 prot_set,等于拿陈旧草稿覆盖新装载的定值
|
||||
*/
|
||||
void ui_data_init(void) {
|
||||
(void)memset(&s_rt, 0, sizeof(s_rt));
|
||||
@@ -56,6 +95,11 @@ void ui_data_init(void) {
|
||||
s_rt_cnt = 0u;
|
||||
s_par_cnt = 0u;
|
||||
s_info_cnt = 0u;
|
||||
(void)memset(s_pq, 0, sizeof(s_pq));
|
||||
s_pq_head = 0u;
|
||||
s_pq_cnt = 0u;
|
||||
s_pq_state = UI_DATA_PARAM_ST_IDLE;
|
||||
s_pq_err_mask = 0u;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -194,3 +238,185 @@ uint32_t ui_data_par_seq(void) {
|
||||
uint32_t ui_data_info_seq(void) {
|
||||
return s_info.seq;
|
||||
}
|
||||
/*
|
||||
* 函数功能:投递一条定值写入请求(页面"保存"行的唯一出口——只投递,不执行)
|
||||
* 入口参数:a_req - 请求(本层整条拷入环形队列,调用方可复用其栈/叠层空间)
|
||||
* const ui_data_param_req_t *
|
||||
* 返回值:0 - 已入队(状态转 WAIT);或 mask 为 0 的空请求(无改动可保存,
|
||||
* 状态直接转 OK,不占槽位)
|
||||
* -1 - 队列满(状态转 ERR_BUSY,在队请求一条不动);
|
||||
* -2 - 参数无效:NULL 或 mask 含 UI_DATA_SET_N 以上的位
|
||||
* (状态与队列都不动——把调用方的编程错误落成屏上"错误"会误导现场) int
|
||||
* 限定条件:uiTask 上下文(页面按键钩子);与 drain 同上下文串行 → 无锁
|
||||
* 函数说明:本层不校验值域(越界与否属执行侧事实,落在 drain 的整批预检),
|
||||
* 不 include App 头、不碰 prot_set、不碰 FatFs(UI 直写定值会把校验和
|
||||
* 打成中间态、直写盘会撞 defaultTask 单写者纪律——契约 §6.1)
|
||||
* 空请求按成功处理是刻意的:保存行 ENTER 时"一个都没改"是正常操作,
|
||||
* 报 ERR 会让用户以为设备有问题
|
||||
*/
|
||||
int ui_data_param_request(const ui_data_param_req_t *a_req) {
|
||||
uint8_t slot;
|
||||
|
||||
if (a_req == NULL) {
|
||||
return -2;
|
||||
}
|
||||
if ((a_req->mask & ~UI_DATA_PARAM_MASK_VALID) != 0u) {
|
||||
return -2;
|
||||
}
|
||||
if (a_req->mask == 0u) {
|
||||
s_pq_state = UI_DATA_PARAM_ST_OK;
|
||||
s_pq_err_mask = 0u;
|
||||
return 0;
|
||||
}
|
||||
if (s_pq_cnt >= (uint8_t)UI_DATA_PARAM_QD) {
|
||||
s_pq_state = UI_DATA_PARAM_ST_ERR_BUSY;
|
||||
return -1;
|
||||
}
|
||||
|
||||
slot = (uint8_t)(((uint32_t)s_pq_head + (uint32_t)s_pq_cnt) % UI_DATA_PARAM_QD);
|
||||
(void)memcpy(&s_pq[slot], a_req, sizeof(s_pq[slot]));
|
||||
s_pq_cnt += 1u;
|
||||
s_pq_state = UI_DATA_PARAM_ST_WAIT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取写路径状态(页面保存行显示)
|
||||
* 入口参数:无
|
||||
* 返回值:UI_DATA_PARAM_ST_IDLE / _WAIT / _OK / _ERR_RANGE / _ERR_LOCKED /
|
||||
* _ERR_BUSY 之一 int
|
||||
* 限定条件:无(只读单字,任意上下文安全)
|
||||
* 函数说明:本函数就是 s_pq_state 的直读,不做"pending 优先"之类的二次加工——
|
||||
* 三条写路径(投递成功→WAIT、投递被拒→ERR_BUSY、drain 落定→终态或
|
||||
* 残留则 WAIT)已把顺序语义收在写侧,读侧再判一次等于两处解释同一件事
|
||||
*/
|
||||
int ui_data_param_state(void) {
|
||||
return s_pq_state;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取最近一条请求的越界项位图(页面高亮越界行)
|
||||
* 入口参数:无
|
||||
* 返回值:bit i = 1 → 第 i 项越界;最近终态不是 ERR_RANGE 时恒 0 uint64_t
|
||||
* 限定条件:无(只读单字)
|
||||
* 函数说明:与 ui_data_par_t.bad_mask 两条来源不同(本函数 = 本次提交被预检挡下
|
||||
* 的项;bad_mask = prot_set 整表装载/解析留下的越界位图),页面在
|
||||
* ERR_RANGE 那一帧用本函数,混用会把上一次装载的越界显示成本次的
|
||||
*/
|
||||
uint64_t ui_data_param_err_mask(void) {
|
||||
return s_pq_err_mask;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:整批预检一条请求(位图内置位的项逐个问执行侧 check())
|
||||
* 入口参数:a_ops - 执行侧函数表(调用方已核非 NULL)
|
||||
* a_req - 待检请求
|
||||
* 返回值:越界项位图(bit i = 第 i 项越界;0 = 本批全部在界内) uint64_t
|
||||
* 限定条件:只调 check(),不写任何东西("预检"与"落值"分成两个函数,
|
||||
* 正是为了让 all-or-nothing 有地方落脚:整批扫完才决定写不写)
|
||||
* 函数说明:一次扫全 40 位而不是"遇到第一个越界就返回"——err_mask 要给页面
|
||||
* **全部**越界行反显,只报第一行等于让用户改完一行再撞下一行
|
||||
*/
|
||||
static uint64_t ui_data_param_scan(const ui_data_param_ops_t *a_ops,
|
||||
const ui_data_param_req_t *a_req) {
|
||||
uint64_t bad = 0u;
|
||||
uint64_t bit = 1u;
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0u; i < (uint8_t)UI_DATA_SET_N; i++) {
|
||||
if ((a_req->mask & bit) != 0u) {
|
||||
if (a_ops->check(i, a_req->q10[i]) != 0) {
|
||||
bad |= bit;
|
||||
}
|
||||
}
|
||||
bit <<= 1u;
|
||||
}
|
||||
return bad;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:逐项落值一条请求(调用前 ui_data_param_scan() 已确认整批在界内)
|
||||
* 入口参数:a_ops - 执行侧函数表
|
||||
* a_req - 待写请求
|
||||
* 返回值:0 - 位图内全部写入;-1 - 某项被执行侧拒绝(*p_bad 输出该项) int
|
||||
* 限定条件:apply() 的副作用由执行侧定义(App 实现 = prot_set_set_sealed,
|
||||
* 每项自带置脏);本函数不回滚已写项——prot_set 没有回滚接口
|
||||
* 函数说明:走到这里还失败 = 执行侧与预检不一致(两个真源漂移),理论不可达;
|
||||
* 如实报失败项比谎称"整批未写"更诚实,故**可能已有前几项生效**
|
||||
*/
|
||||
static int ui_data_param_write(const ui_data_param_ops_t *a_ops,
|
||||
const ui_data_param_req_t *a_req,
|
||||
uint64_t *p_bad) {
|
||||
uint64_t bit = 1u;
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0u; i < (uint8_t)UI_DATA_SET_N; i++) {
|
||||
if ((a_req->mask & bit) != 0u) {
|
||||
if (a_ops->apply(i, a_req->q10[i]) != 0) {
|
||||
*p_bad = bit;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
bit <<= 1u;
|
||||
}
|
||||
*p_bad = 0u;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:处理一条定值写入请求(App 装配层节拍入口:出队 → 闭锁 → 预检 → 落定)
|
||||
* 入口参数:a_ops - 执行侧函数表(locked/check/apply 三者都不得为 NULL)
|
||||
* const ui_data_param_ops_t *
|
||||
* 返回值:1 - 处理并提交了一条(状态已转终态,或队列仍有残留则转 WAIT);
|
||||
* 0 - 队列空(状态与队列都不动);
|
||||
* -1 - 参数无效(a_ops 或其成员为 NULL,状态与队列都不动) int
|
||||
* 限定条件:uiTask 上下文(App/app/ui_data_feed.c 的 tick 每轮一次);每轮最多
|
||||
* 处理一条——整定是低频人工事件,一轮一条已够,且把最坏耗时钉在
|
||||
* 2 × 40 次回调(预检一遍 + 写入一遍),不随队列堆积抖动
|
||||
* 函数说明:四步顺序即判据(契约 §6.2),判定本体拆成 scan/write 两个静态函数
|
||||
* (见其函数说明)——
|
||||
* ① 取队首;② locked() → ERR_LOCKED 并**丢弃本条**(闭锁是业务态,不做
|
||||
* "排队等解锁",否则屏上 WAIT 无限期挂着,用户以为设备卡死);
|
||||
* ③ scan() 任一项越界 → ERR_RANGE 且**一字不写**(all-or-nothing,与
|
||||
* prot_set_load_raw 同纪律:半张表落进值区就是"看着保存成功、其实混着
|
||||
* 新旧两套定值");④ write() 全成 → OK。
|
||||
* ⚠ 顺序不可调:先判闭锁再判越界——闭锁态下 check() 的语义仍成立,但
|
||||
* 页面要的是"定值闭锁"这个更高优先级的原因(契约 §6.3 表序)
|
||||
*/
|
||||
int ui_data_param_drain(const ui_data_param_ops_t *a_ops) {
|
||||
const ui_data_param_req_t *p_req;
|
||||
uint64_t bad;
|
||||
int settled;
|
||||
|
||||
if ((a_ops == NULL) || (a_ops->locked == NULL) ||
|
||||
(a_ops->check == NULL) || (a_ops->apply == NULL)) {
|
||||
return -1;
|
||||
}
|
||||
if (s_pq_cnt == 0u) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
p_req = &s_pq[s_pq_head];
|
||||
bad = 0u;
|
||||
|
||||
if (a_ops->locked() != 0u) {
|
||||
settled = UI_DATA_PARAM_ST_ERR_LOCKED;
|
||||
} else {
|
||||
settled = UI_DATA_PARAM_ST_OK;
|
||||
bad = ui_data_param_scan(a_ops, p_req);
|
||||
if (bad != 0u) {
|
||||
settled = UI_DATA_PARAM_ST_ERR_RANGE; /* 预检挡下:一字未写 */
|
||||
} else {
|
||||
if (ui_data_param_write(a_ops, p_req, &bad) != 0) {
|
||||
settled = UI_DATA_PARAM_ST_ERR_RANGE; /* 执行侧拒写:见函数说明 */
|
||||
}
|
||||
/* 两关都过 → settled 保持 OK */
|
||||
}
|
||||
}
|
||||
|
||||
s_pq_err_mask = bad;
|
||||
s_pq_head = (uint8_t)(((uint32_t)s_pq_head + 1u) % UI_DATA_PARAM_QD);
|
||||
s_pq_cnt -= 1u;
|
||||
s_pq_state = (s_pq_cnt != 0u) ? UI_DATA_PARAM_ST_WAIT : settled;
|
||||
return 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user