固件:R2 迁移 R-0 ~ R-6(底板 + 5 片同构刀片)
2026-09-16 完成、当时未提交,这里补提交(原样,不含 09-27 上板的修改)。 - 器件层 / 拓扑层整体从 R1 换到 R2:TCA9548A 分通道、五路同址 INA226 0x40 + 输入侧 0x43、 刀片 PCA9555(VBUS_EN 挪到 P0.4)、CH224A 只读驱动、风扇闭环 t_thermal - 新增 i2cbus(mux 原子事务)、slot(刀片 I/O 门面)、host/qtest.sh - 74HC595 链退役(代码与测试保留) - 判据阈值与时间常量未动;19 个 ctest 全绿,idf.py build 通过 Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
+129
-2
@@ -76,19 +76,45 @@ static void test_flash_format_is_frozen(void)
|
||||
TEST_ASSERT_EQUAL_INT(11, LOG_EV_FAN_STALL);
|
||||
TEST_ASSERT_EQUAL_INT(12, LOG_EV_REBOOT);
|
||||
TEST_ASSERT_EQUAL_INT(13, LOG_EV_CMD_REJECT);
|
||||
TEST_ASSERT_EQUAL_INT(14, LOG_EV_COUNT);
|
||||
/* ⭐ R2 新增的三条(计划 §5.8 / §5.9)。追加在末尾,前面 13 个一个都没动 ——
|
||||
所以 R1 那块板子上跑了半年的 flash 拿到 R2 固件上仍然读得对。
|
||||
⚠️ 这三行不是「补全」,它们是钉子:谁要是图省事把 MUX_LOST 插到
|
||||
OVER_VOLT 前面("安全事件排一起好看"),历史记录里的 3 就变成了 mux 失联,
|
||||
而读日志的工具、板子、测试全都不会报一个字。 */
|
||||
TEST_ASSERT_EQUAL_INT(14, LOG_EV_MUX_LOST);
|
||||
TEST_ASSERT_EQUAL_INT(15, LOG_EV_SLOT_EMPTY);
|
||||
TEST_ASSERT_EQUAL_INT(16, LOG_EV_PD_LIMIT);
|
||||
TEST_ASSERT_EQUAL_INT(17, LOG_EV_COUNT);
|
||||
}
|
||||
|
||||
/* §10.7 的「必记事件」有八类,A10 只产生其中两类。剩下的号先占住,
|
||||
免得 A12/A15/A16 各自往中间插一个,把 flash 里的旧记录挪了位。 */
|
||||
免得 A12/A15/A16 各自往中间插一个,把 flash 里的旧记录挪了位。
|
||||
|
||||
⭐ 这条用例的上界是 `LOG_EV_COUNT`,所以 R2 追加的 14/15/16 **自动**被它盖住:
|
||||
往枚举里加一条却忘了在 `evlog_ev_str()` 里加一行,这里立刻红。
|
||||
(`evlog_ev_str()` 的 switch 没有 `default`,`-Wswitch` 本来也会拦一道;
|
||||
但只要有人手贱补一个 `default: return "?"`,编译器那一道就没了 —— 这条还在。) */
|
||||
static void test_every_event_type_has_a_name(void)
|
||||
{
|
||||
const char *names[LOG_EV_COUNT];
|
||||
for (int i = 0; i < LOG_EV_COUNT; i++) {
|
||||
const char *s = evlog_ev_str((log_ev_t)i);
|
||||
TEST_ASSERT_NOT_NULL(s);
|
||||
TEST_ASSERT_TRUE_MESSAGE(s[0] != '?', "有事件类型没给名字");
|
||||
names[i] = s;
|
||||
}
|
||||
TEST_ASSERT_EQUAL_STRING("?", evlog_ev_str((log_ev_t)LOG_EV_COUNT));
|
||||
|
||||
/* 名字还得**两两不同**。加新类型时最省事的写法是复制上一行的 `case` ——
|
||||
复制完忘了改字符串,上面那个循环一个字都不会说。真板上的现象是
|
||||
日志里两种完全不同的事件打出同一行字(比如「刀片不在位」显示成
|
||||
「mux失联全断」),排查的人会照着那行字去查错的那条链。 */
|
||||
for (int i = 0; i < LOG_EV_COUNT; i++) {
|
||||
for (int j = i + 1; j < LOG_EV_COUNT; j++) {
|
||||
TEST_ASSERT_FALSE_MESSAGE(strcmp(names[i], names[j]) == 0,
|
||||
"两个事件类型共用了同一个名字");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* `safety_ev_t` 与 `log_ev_t` 是两套编号,中间是一个**显式的 switch**。
|
||||
@@ -108,6 +134,105 @@ static void test_the_safety_mapping_is_explicit(void)
|
||||
TEST_ASSERT_NOT_EQUAL((int)SAFETY_EV_PUSHED_UP, (int)LOG_EV_PUSHED_UP);
|
||||
}
|
||||
|
||||
/* ⭐ R2:把「显式 switch」这件事**用具体数字**钉死。
|
||||
|
||||
上面那条用例全用符号写(`LOG_EV_REVERSE`),它证明得了「对应关系没接错」,
|
||||
证明不了「这个函数不是一个偏移」—— 因为把 switch 换成
|
||||
`return (log_ev_t)(LOG_EV_OVER_VOLT + safety_ev - 1)` 之后,
|
||||
R1 那四条(1→3 2→4 3→5 4→6)的符号断言**全部照样通过**。
|
||||
|
||||
R2 追加的两条恰好是这个写法的照妖镜:`safety_ev_t` 里它们排在 5/6,
|
||||
而 `log_ev_t` 里它们是 14/15(前面还隔着 A12/A15/A16 占的七个号)。
|
||||
偏移写法会把它们算成 7 和 8 —— 也就是 `LOG_EV_EFUSE_FAULT` 与 `LOG_EV_PG_CHANGE`。
|
||||
**这个错在功能测试里完全看不见**:日志照写、条数不少、CRC 也对,
|
||||
只有事后读日志的人会看到「五路全断」那一刻记的是「eFuse故障」。
|
||||
|
||||
所以下面每一对都写成裸数字:两个枚举里任何一边动了号,这条都红。 */
|
||||
static void test_the_safety_mapping_is_a_table_not_an_offset(void)
|
||||
{
|
||||
/* 先把两边的号本身钉住 —— 不然「偏移对上了」可能是因为有人挪了 safety 那边。 */
|
||||
TEST_ASSERT_EQUAL_INT(0, SAFETY_EV_NONE);
|
||||
TEST_ASSERT_EQUAL_INT(1, SAFETY_EV_OVER_VOLT);
|
||||
TEST_ASSERT_EQUAL_INT(2, SAFETY_EV_UNDER_VOLT);
|
||||
TEST_ASSERT_EQUAL_INT(3, SAFETY_EV_REVERSE);
|
||||
TEST_ASSERT_EQUAL_INT(4, SAFETY_EV_PUSHED_UP);
|
||||
TEST_ASSERT_EQUAL_INT(5, SAFETY_EV_MUX_LOST);
|
||||
TEST_ASSERT_EQUAL_INT(6, SAFETY_EV_SLOT_EMPTY);
|
||||
|
||||
/* 逐对:safety 号 → flash 号,两边都是裸数字。 */
|
||||
TEST_ASSERT_EQUAL_INT(0, (int)evlog_from_safety(0)); /* NONE */
|
||||
TEST_ASSERT_EQUAL_INT(3, (int)evlog_from_safety(1)); /* OVER_VOLT */
|
||||
TEST_ASSERT_EQUAL_INT(4, (int)evlog_from_safety(2)); /* UNDER_VOLT */
|
||||
TEST_ASSERT_EQUAL_INT(5, (int)evlog_from_safety(3)); /* REVERSE */
|
||||
TEST_ASSERT_EQUAL_INT(6, (int)evlog_from_safety(4)); /* PUSHED_UP */
|
||||
TEST_ASSERT_EQUAL_INT(14, (int)evlog_from_safety(5)); /* MUX_LOST */
|
||||
TEST_ASSERT_EQUAL_INT(15, (int)evlog_from_safety(6)); /* SLOT_EMPTY */
|
||||
|
||||
/* 而且这张表**在数学上不可能**是一个偏移:前四对差 2,后两对差 9。
|
||||
写死这一条,是为了让「改成偏移」这件事在用例名上就说得清楚,
|
||||
而不是靠上面某一行恰好不满足。 */
|
||||
TEST_ASSERT_EQUAL_INT(2, (int)evlog_from_safety(1) - (int)SAFETY_EV_OVER_VOLT);
|
||||
TEST_ASSERT_EQUAL_INT(9, (int)evlog_from_safety(5) - (int)SAFETY_EV_MUX_LOST);
|
||||
|
||||
/* 越过表尾的号一律 NONE。偏移写法在这里会算出一个**越界但合法**的
|
||||
`log_ev_t`(7 → 记成 eFuse 故障,200 → 记成 202 这种根本没有的号)。 */
|
||||
TEST_ASSERT_EQUAL_INT(0, (int)evlog_from_safety(7));
|
||||
TEST_ASSERT_EQUAL_INT(0, (int)evlog_from_safety(200));
|
||||
TEST_ASSERT_EQUAL_INT(0, (int)evlog_from_safety(255));
|
||||
}
|
||||
|
||||
/* ⭐ R2 的两条新判据要真的落得了盘、读得回来。
|
||||
|
||||
这里刻意走**完整的一条链**:`safety_ev_fn` 回调 → RAM 环 → flash → 重启 → 读回。
|
||||
只断言 `evlog_from_safety()` 的返回值是不够的 —— `evlog_put_safety()` 里
|
||||
`r.rail = ev->rail` 这一行同样能出错,而 mux 失联是**整板级事件(rail = 0)**,
|
||||
正好落在「路号是 1~5」这个到处都在假设的前提之外:
|
||||
谁要是在入队或读回的路上顺手做一次 `rail - 1` 当下标、或者把 0 「修正」成 1,
|
||||
日志里五路全断那一刻就记成了「CH1 出事」,而板子、条数、CRC 全都正常。 */
|
||||
static void test_the_r2_events_round_trip_through_flash(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, evlog_init());
|
||||
const safety_ev_fn sink = evlog_put_safety; /* 按 A10 的出口类型接 */
|
||||
t_safety_set_event_sink(sink, NULL);
|
||||
|
||||
/* mux 失联:**整板级**,rail = 0,五路全断那一刻的输入侧读数。 */
|
||||
const safety_event_t mux = {
|
||||
.t_ms = 50000, .rail = 0, .type = SAFETY_EV_MUX_LOST,
|
||||
.bus_mv = 12050, .current_ua = 4300000,
|
||||
};
|
||||
/* 刀片不在位:**单路**,rail = 5(那一路正好是 PRSNT# 不顺排的 IO7)。 */
|
||||
const safety_event_t empty = {
|
||||
.t_ms = 50007, .rail = 5, .type = SAFETY_EV_SLOT_EMPTY,
|
||||
.bus_mv = 0, .current_ua = 0,
|
||||
};
|
||||
sim_time_advance_ms(88888); /* 记账在断电之后:时间戳不许换成「现在」 */
|
||||
sink(NULL, &mux);
|
||||
sink(NULL, &empty);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT32(3, evlog_flush(0)); /* BOOT + 这两条 */
|
||||
|
||||
/* 断电重启:这两条必须是从 **flash** 里读回来的,不是还留在 RAM 里。 */
|
||||
sim_hal_reboot();
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, evlog_init());
|
||||
TEST_ASSERT_EQUAL_UINT32_MESSAGE(0, sim_store_erases(), "重启把日志擦了");
|
||||
|
||||
const evlog_rec_t a = read_one(1);
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(14, a.type, "mux 失联没按 14 号落盘");
|
||||
TEST_ASSERT_EQUAL_UINT8_MESSAGE(0, a.rail, "整板级事件被安到了某一路头上");
|
||||
TEST_ASSERT_EQUAL_UINT32(50000, a.t_ms);
|
||||
TEST_ASSERT_EQUAL_INT32(12050, a.bus_mv);
|
||||
TEST_ASSERT_EQUAL_INT32(4300000, a.current_ua);
|
||||
|
||||
const evlog_rec_t b = read_one(2);
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(15, b.type, "刀片不在位没按 15 号落盘");
|
||||
TEST_ASSERT_EQUAL_UINT8(5, b.rail);
|
||||
TEST_ASSERT_EQUAL_UINT32(50007, b.t_ms);
|
||||
|
||||
/* 读日志的人看到的是名字,不是数字 —— 顺带确认这两条打得出人话来。 */
|
||||
TEST_ASSERT_EQUAL_STRING("mux失联全断", evlog_ev_str((log_ev_t)a.type));
|
||||
TEST_ASSERT_EQUAL_STRING("刀片不在位", evlog_ev_str((log_ev_t)b.type));
|
||||
}
|
||||
|
||||
/* A10 的事件出口是 `safety_ev_fn`。A11 要能**原样**接上去(HANDOFF 里
|
||||
「只接 sink,不动判据」那条),所以这里就按那个函数指针类型调一次。 */
|
||||
static void test_the_sink_plugs_into_the_safety_layer_unchanged(void)
|
||||
@@ -622,6 +747,8 @@ int main(void)
|
||||
RUN_TEST(test_flash_format_is_frozen);
|
||||
RUN_TEST(test_every_event_type_has_a_name);
|
||||
RUN_TEST(test_the_safety_mapping_is_explicit);
|
||||
RUN_TEST(test_the_safety_mapping_is_a_table_not_an_offset);
|
||||
RUN_TEST(test_the_r2_events_round_trip_through_flash);
|
||||
RUN_TEST(test_the_sink_plugs_into_the_safety_layer_unchanged);
|
||||
RUN_TEST(test_capacity_meets_the_requirement);
|
||||
RUN_TEST(test_putting_events_never_touches_flash);
|
||||
|
||||
在新工单中引用
屏蔽一个用户