文件
zhou yang和Claude Opus 5.5 b8ad69681f 固件: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]>
2026-09-27 10:26:24 +08:00

238 行
10 KiB
C

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
/* A2 验收主项:能在假总线上挂一个器件并读回预置值。
顺带把「器件失联 / 偶发 NAK」这两种后面 A15 要用的故障注入也测掉。
---- R2 新增:假总线有了 `bus` 与一颗假 mux ------------------------------
五片刀片**完全同构**(同址 0x40 / 0x20),真板上区分它们的唯一手段是底板
TCA9548A 的通道。假总线如果只按地址查表,五片就塌成一片 ——
于是「选错通道」这一类 R2 最危险的静默错误在宿主机上一次也复现不出来。
所以器件带一个 `bus`(-1 主干 / 0~7 支路),本文件最后两条用例测的就是它:
**关着的支路一律 NAK**、**同时开两路会被记账**。
原有那几条用的仍是 `sim_i2c_add()`(挂主干),语义没变。 */
#include <string.h>
#include "sim_hal.h"
#include "unity.h"
#define INA226_RAIL 0x40 /* R2:五片刀片上的那片,五片同址(计划 §5.2) */
#define INA226_INPUT 0x43 /* R2:底板输入侧 U13,挂**主干** */
#define PCA9555 0x20
#define MUX 0x70 /* TCA9548A(底板 U35) */
#define MUX_RST_PIN 40 /* `MUX_RST#` 接的 GPIO,本文件用不到,给个真值免得误导 */
#define INA226_CH1 INA226_RAIL /* 原有用例沿用的名字 */
void setUp(void) { sim_hal_reset(); }
void tearDown(void) {}
static void init_bus(void)
{
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_init(NULL));
}
static void test_use_before_init_is_state_error(void)
{
sim_i2c_add(INA226_CH1, 2);
uint8_t rd[2];
TEST_ASSERT_EQUAL_INT(HAL_ERR_STATE, hal_i2c_read(INA226_CH1, rd, sizeof(rd)));
}
/* ⭐ A2 的验收判据本身 */
static void test_read_back_preset_register(void)
{
init_bus();
sim_i2c_add(INA226_CH1, 2);
sim_i2c_set_reg(INA226_CH1, 0x02, 0x1234); /* INA226 的 Bus Voltage 寄存器 */
const uint8_t ptr = 0x02;
uint8_t rd[2] = { 0, 0 };
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_write_read(INA226_CH1, &ptr, 1, rd, sizeof(rd)));
TEST_ASSERT_EQUAL_HEX8(0x12, rd[0]); /* 大端 */
TEST_ASSERT_EQUAL_HEX8(0x34, rd[1]);
TEST_ASSERT_EQUAL_UINT32(1, sim_i2c_dev(INA226_CH1)->n_read);
}
static void test_write_stores_big_endian_and_logs_bytes(void)
{
init_bus();
sim_i2c_add(INA226_CH1, 2);
const uint8_t wr[3] = { 0x05, 0x12, 0x34 };
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_write(INA226_CH1, wr, sizeof(wr)));
TEST_ASSERT_EQUAL_HEX16(0x1234, sim_i2c_get_reg(INA226_CH1, 0x05));
const sim_i2c_dev_t *d = sim_i2c_dev(INA226_CH1);
TEST_ASSERT_EQUAL_UINT8(3, d->last_write_len);
TEST_ASSERT_EQUAL_HEX8_ARRAY(wr, d->last_write, 3);
}
/* 寄存器指针自增:一次事务连读两个寄存器,真器件就是这个行为。 */
static void test_pointer_auto_increments(void)
{
init_bus();
sim_i2c_add(INA226_CH1, 2);
sim_i2c_set_reg(INA226_CH1, 0x01, 0xAAAA);
sim_i2c_set_reg(INA226_CH1, 0x02, 0xBBBB);
const uint8_t ptr = 0x01;
uint8_t rd[4];
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_write_read(INA226_CH1, &ptr, 1, rd, sizeof(rd)));
TEST_ASSERT_EQUAL_HEX8(0xAA, rd[0]);
TEST_ASSERT_EQUAL_HEX8(0xBB, rd[2]);
}
/* PCA9555 是 8 位寄存器;同一套 HAL 要能同时伺候两种宽度。 */
static void test_byte_wide_device(void)
{
init_bus();
sim_i2c_add(PCA9555, 1);
const uint8_t wr[3] = { 0x06, 0xFF, 0x0F }; /* 配置寄存器 Port0/Port1 */
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_write(PCA9555, wr, sizeof(wr)));
TEST_ASSERT_EQUAL_HEX16(0x00FF, sim_i2c_get_reg(PCA9555, 0x06));
TEST_ASSERT_EQUAL_HEX16(0x000F, sim_i2c_get_reg(PCA9555, 0x07));
}
static void test_absent_device_naks(void)
{
init_bus();
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, hal_i2c_probe(0x77)); /* 总线上没这个地址 */
sim_i2c_add(INA226_CH1, 2);
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_probe(INA226_CH1));
sim_i2c_set_present(INA226_CH1, false); /* 器件失联(A15 降级矩阵) */
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, hal_i2c_probe(INA226_CH1));
}
static void test_transient_nak_then_recovers(void)
{
init_bus();
sim_i2c_add(INA226_CH1, 2);
sim_i2c_inject_nak(INA226_CH1, 2); /* 接下来两次事务失败 */
uint8_t rd[2];
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, hal_i2c_read(INA226_CH1, rd, sizeof(rd)));
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, hal_i2c_read(INA226_CH1, rd, sizeof(rd)));
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_read(INA226_CH1, rd, sizeof(rd)));
/* NAK 掉的事务不该被算成一次成功访问 */
TEST_ASSERT_EQUAL_UINT32(1, sim_i2c_dev(INA226_CH1)->n_read);
}
/* 半个寄存器的长度:真器件上是未定义行为,这里必须当场报错而不是悄悄凑合。 */
static void test_partial_register_length_rejected(void)
{
init_bus();
sim_i2c_add(INA226_CH1, 2);
uint8_t rd[1];
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, hal_i2c_read(INA226_CH1, rd, 1));
const uint8_t wr[2] = { 0x05, 0x12 };
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, hal_i2c_write(INA226_CH1, wr, sizeof(wr)));
}
static void test_address_out_of_range(void)
{
init_bus();
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, hal_i2c_probe(0x80));
}
/* ---- R2:mux 支路 ---------------------------------------------------------
控制字是**掩码不是编号**,而且写 mux 是**一个裸字节**(不带寄存器号)。
这两件事都在 tca9548a.h 的文件头里;这里直接按线上的样子发。 */
static void select_mask(uint8_t mask)
{
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_write(MUX, &mask, 1));
}
/* ⭐ 通道**关着**的时候,挂在那条支路上的器件一律 NAK —— 真器件就是这个行为:
支路被断开,从机根本收不到地址。
⚠️ 假总线要是不做这一层(只按地址查表),五片刀片就塌成一片:
「忘了选通道」「选错通道」「选通道失败却照读」这三种错在宿主机上**全都是绿的**,
而它们在真板上的现象是「读到的是另一台手机的电压电流」——
量纲、范围、变化趋势全部正常,没有任何一层会报错。 */
static void test_device_behind_a_closed_channel_naks(void)
{
init_bus();
sim_i2c_add(INA226_INPUT, 2); /* 主干上那片,永远看得见 */
sim_i2c_add_ch(2, INA226_RAIL, 2); /* 槽 3 的刀片 */
sim_i2c_add_ch(3, INA226_RAIL, 2); /* 槽 4 的刀片,**同一个地址** */
sim_i2c_add_mux(MUX, MUX_RST_PIN);
sim_i2c_set_reg_ch(2, INA226_RAIL, 0x02, 0x1111);
sim_i2c_set_reg_ch(3, INA226_RAIL, 0x02, 0x2222);
/* 上电全关:两片刀片谁也不应答,而主干那片照常。 */
TEST_ASSERT_EQUAL_INT(-1, sim_i2c_mux_ch());
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, hal_i2c_probe(INA226_RAIL));
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_probe(INA226_INPUT));
/* 选中槽 3:同一个地址,读回来的必须是**那一片**的寄存器。 */
select_mask(1u << 2);
TEST_ASSERT_EQUAL_INT(2, sim_i2c_mux_ch());
const uint8_t ptr = 0x02;
uint8_t rd[2] = { 0, 0 };
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_write_read(INA226_RAIL, &ptr, 1, rd, sizeof(rd)));
TEST_ASSERT_EQUAL_HEX16(0x1111, (uint16_t)((rd[0] << 8) | rd[1]));
/* 换成槽 4:地址一个字没改,读回来的却是另一片 —— 这就是「选错通道」的全貌。 */
select_mask(1u << 3);
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_write_read(INA226_RAIL, &ptr, 1, rd, sizeof(rd)));
TEST_ASSERT_EQUAL_HEX16(0x2222, (uint16_t)((rd[0] << 8) | rd[1]));
/* 槽 3 那片这时**一次都没有**再被访问过。 */
TEST_ASSERT_EQUAL_UINT32(1, sim_i2c_dev_ch(2, INA226_RAIL)->n_read);
/* 关回全断:两片都不见了,主干那片还在。 */
select_mask(0);
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, hal_i2c_probe(INA226_RAIL));
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_probe(INA226_INPUT));
/* 全程只开过一路,不许有「两片一起应答」。 */
TEST_ASSERT_EQUAL_UINT32(0, sim_i2c_ambiguous());
}
/* ⭐ 同一个地址同时看得见两片 = **通道开重了**(控制字是掩码,写 0x03 不会报错)。
真板上那是两片一起应答,主机读回来的是两片的**线与** ——
数值在量程里、随负载变化、还特别稳,谁也看不出这是两台手机的数据与在一起了。
所以这个计数必须恒为 0;它非 0 的那一刻,就是所有电流读数开始悄悄偏小的那一刻。 */
static void test_two_channels_open_at_once_is_counted_as_ambiguous(void)
{
init_bus();
sim_i2c_add_ch(0, INA226_RAIL, 2);
sim_i2c_add_ch(1, INA226_RAIL, 2);
sim_i2c_add_mux(MUX, MUX_RST_PIN);
select_mask(1u << 0); /* 规规矩矩只开一路 */
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_probe(INA226_RAIL));
TEST_ASSERT_EQUAL_UINT32(0, sim_i2c_ambiguous());
select_mask(0x03); /* 两路一起开:写下去**不报错**,器件也不抗议 */
TEST_ASSERT_EQUAL_INT_MESSAGE(-2, sim_i2c_mux_ch(), "多于一路不是一种用法,是 bug");
TEST_ASSERT_EQUAL_HEX8(0x03, sim_i2c_mux_mask());
/* 事务照样「成功」—— 这正是它难查的地方。 */
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_probe(INA226_RAIL));
TEST_ASSERT_EQUAL_UINT32(1, sim_i2c_ambiguous());
uint8_t rd[2];
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_read(INA226_RAIL, rd, sizeof(rd)));
TEST_ASSERT_EQUAL_UINT32_MESSAGE(2, sim_i2c_ambiguous(), "每一次事务都记一笔");
/* 收回成一路,计数就不再涨(它是「发生过几次」,不是一个状态位)。 */
select_mask(1u << 1);
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_probe(INA226_RAIL));
TEST_ASSERT_EQUAL_UINT32(2, sim_i2c_ambiguous());
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_use_before_init_is_state_error);
RUN_TEST(test_read_back_preset_register);
RUN_TEST(test_write_stores_big_endian_and_logs_bytes);
RUN_TEST(test_pointer_auto_increments);
RUN_TEST(test_byte_wide_device);
RUN_TEST(test_absent_device_naks);
RUN_TEST(test_transient_nak_then_recovers);
RUN_TEST(test_partial_register_length_rejected);
RUN_TEST(test_address_out_of_range);
RUN_TEST(test_device_behind_a_closed_channel_naks);
RUN_TEST(test_two_channels_open_at_once_is_counted_as_ambiguous);
return UNITY_END();
}