A11:事件日志(环形缓冲 + 落盘)

事件从 evlog_put*() 进一个 256 条的 RAM 环(临界区里两条指令,**不阻塞、
不碰 flash**),由最低优先级的 t_log 攒够 16 条或最老那条等满 1 s 时批量写进
新的 `evlog` 分区(17 扇区 × 4 KB = **4335 条**)。A10 的事件出口原样接上,
判据那一层一个字没动。

八条实现约定(详见需求文档 §10.7):

· 落盘不用 NVS 也不用 SPIFFS,是一块裸 data 分区 —— NVS 的页式 GC 会把
  「写一条事件」变成一次不可预测的搬移。HAL 因此多出第六样 hal_store;
· 17 个扇区不是 16:每扇区要拿一个 16 B 的槽当扇区头,4 KB 只装 255 条,
  16 扇区 = 4080,差 16 条不到 §10.7 要求的 4096;
· 序号 = flash 里的位置(epoch × 255 + 槽号),不是自增计数器 —— 重启接着数
  不用存计数器;代价是没落盘的事件还没有序号,A12 读之前要先 flush;
· 入队走关中断的临界区,**不是第三把锁**:入队方是最高优先级的 t_safety,
  停在一把 t_log 持着的锁上等于让最低优先级的任务决定巡检节拍;
· 丢事件可以,悄悄丢不行 —— 缓冲满了盖最旧的,但补一条 LOG_EV_LOST 记着丢了几条;
· 每条记录带 CRC-8(见下),写失败就放弃该扇区剩余部分,被放弃的那段在序号
  空间里是个洞,读回时给一条空记录占位(跳过的话上位机游标会卡住);
· 每次上电记一条 LOG_EV_BOOT:时间戳上电从 0 计,没有它两次上电的记录会穿插。

事件类型的数字是 flash 格式的一部分,只许往末尾加;safety_ev_t → log_ev_t
是显式 switch 不是加偏移。§10.7 里还没人产生的六类号已占住(A12/A15/A16 去填)。

27 个用例;23 条注错逐个跑过,21 条变红。第一版校验和用异或,漏掉的正是最常见
的那条:payload 全 0 的记录被掉电切在第 8 个字节上时,后 8 个字节留在 0xFF,
而 8 个 0xFF 的异或恰好是 0 —— 半条的校验和与整条一模一样,扫描把半条当成
一条真事件。换成 CRC-8 之后拦住。另外两个只在这里才踩得到的坑:「掉电重启」
不能用 sim_hal_reset()(那是换新板子,flash 一起擦,测试永远绿),为此加了
sim_hal_reboot();「读到洞就跳过」这个错被测试里 static 数组的零初始化掩盖过
一次,缓冲要先填成非零。

Co-Authored-By: Claude Opus 5 <[email protected]>
这个提交包含在:
zhou yang
2026-09-07 09:20:02 +08:00
共同撰写人 Claude Opus 5
父节点 273c53347e
当前提交 18d3d69856
共修改 21 个文件,包含 2025 行新增和 103 行删除
@@ -54,6 +54,27 @@ bool hal_lock_held(hal_lock_id_t id);
/* 累计等锁失败次数(超时)。遥测里报出去,用来看总线是不是排队了。 */
uint32_t hal_lock_timeouts(hal_lock_id_t id);
/* ---- 临界区(A11)——**这不是第三把锁** ----------------------------------
事件环形缓冲的入队要在几条指令里改完两个下标,而入队方与出队方是
优先级两端的两个任务:`t_safety`(最高,而且是在**断电之后**调的)
与 `t_log`(最低)。这里不能用互斥锁,理由有两条,都不是风格问题:
· **不许阻塞。** t_safety.h 写着「接进来的东西不许阻塞」——
入队要是可能停在一把 t_log 持着的锁上,巡检的节拍就被最低优先级的任务
决定了。优先级继承能救回来,但那是「让阻塞变短」,不是「不阻塞」。
· **§10.3 的共享资源只有两个**(I²C 与 595 链),这条架构结论不该被
一个环形缓冲的下标改写。
所以给的是关中断的临界区:不阻塞、没有持有者、不参与「不许嵌套两把锁」
那条规则,也不出现在 `hal_lock_id_t` 里。
⚠️ **里面只许改内存。** 关着中断去等锁、睡、跑 I²C 事务,都是错的
—— sim 里 `sim_crit_blocking()` 会计数,`test_evlog.c` 拿它当断言。
可以嵌套(按层数计),但没有理由这么写。 */
void hal_crit_enter(void);
void hal_crit_exit(void);
#ifdef __cplusplus
}
#endif