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]>
这个提交包含在:
@@ -14,16 +14,23 @@
|
||||
#include "hal_i2c_acct.h"
|
||||
#include "hal_lock.h"
|
||||
#include "hal_pwm.h"
|
||||
#include "hal_store.h"
|
||||
#include "hal_time.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* 四路 sim 全部恢复出厂:器件表清空、引脚未配置、PWM 未配置、时钟归零、
|
||||
init 标志清掉。每个测试的 setUp() 里调它。 */
|
||||
/* 各路 sim 全部恢复出厂:器件表清空、引脚未配置、PWM 未配置、时钟归零、
|
||||
init 标志清掉,**掉电不丢区整块擦成 0xFF**。每个测试的 setUp() 里调它。
|
||||
语义是「换了一块新板子」。 */
|
||||
void sim_hal_reset(void);
|
||||
|
||||
/* **掉电重启**:除了掉电不丢区里的内容,其余与 `sim_hal_reset()` 完全一样。
|
||||
§10.7 的验收「掉电重启后能读回」就是靠这两个的区别 ——
|
||||
拿 reset 去测重启,测的是「刚擦干净的 flash 读不出东西」,永远绿。 */
|
||||
void sim_hal_reboot(void);
|
||||
|
||||
/* ---------------- I²C ---------------- */
|
||||
|
||||
#define SIM_I2C_MAX_DEV 16
|
||||
@@ -172,6 +179,34 @@ void sim_lock_require(hal_lock_id_t id, bool on);
|
||||
uint32_t sim_lock_violations(hal_lock_id_t id);
|
||||
void sim_lock_check(hal_lock_id_t id); /* 给 sim 各路自己调,测试一般用不到 */
|
||||
|
||||
/* ---------------- 掉电不丢区(NOR flash 那块) ---------------- */
|
||||
|
||||
/* 假 flash 的几何。与 partitions.csv 里 `evlog` 分区的大小是同一个数,
|
||||
`test_evlog.c` 拿 `EVLOG_SECTORS` 把两边钉在一起。 */
|
||||
#define SIM_STORE_SECTOR_BYTES 4096
|
||||
#define SIM_STORE_SECTORS 17
|
||||
|
||||
/* 故障注入:第 nth 次写整条失败(一个字节都不落)。0 = 清除。 */
|
||||
void sim_store_fail_next_write(int nth);
|
||||
|
||||
/* **掉电打断一次写**:下一次写只落前 bytes 个字节,然后返回 HAL_ERR_BUS。
|
||||
这是每条记录都要带校验字节的理由 —— 半条记录在 flash 里长得和一条真记录一样。 */
|
||||
void sim_store_tear_next_write(uint32_t bytes);
|
||||
|
||||
uint32_t sim_store_writes(void); /* 成功或半成功的写事务次数 */
|
||||
uint32_t sim_store_erases(void); /* 擦过几个扇区 —— 寿命就花在这上面 */
|
||||
uint32_t sim_store_bytes(void); /* 真正落进去的字节数 */
|
||||
uint8_t sim_store_byte(uint32_t off); /* 越界返回 0xFF */
|
||||
|
||||
/* ---------------- 临界区(不是第三把锁,见 hal_lock.h) ---------------- */
|
||||
|
||||
int sim_crit_depth(void); /* 当前嵌套层数。测试结束时必须是 0 */
|
||||
uint32_t sim_crit_enters(void); /* 进过几次 —— 断言「入队真的是在临界区里做的」*/
|
||||
|
||||
/* 在临界区里干了会阻塞的事(等锁 / 睡)的次数。**必须恒为 0**。 */
|
||||
uint32_t sim_crit_blocking(void);
|
||||
void sim_crit_blocking_note(void); /* 给 sim 各路自己调 */
|
||||
|
||||
/* ---------------- 时钟 ---------------- */
|
||||
|
||||
void sim_time_set_us(uint64_t us);
|
||||
|
||||
在新工单中引用
屏蔽一个用户