/* A2 验收主项:能在假总线上挂一个器件并读回预置值。 顺带把「器件失联 / 偶发 NAK」这两种后面 A15 要用的故障注入也测掉。 ---- R2 新增:假总线有了 `bus` 与一颗假 mux ------------------------------ 五片刀片**完全同构**(同址 0x40 / 0x20),真板上区分它们的唯一手段是底板 TCA9548A 的通道。假总线如果只按地址查表,五片就塌成一片 —— 于是「选错通道」这一类 R2 最危险的静默错误在宿主机上一次也复现不出来。 所以器件带一个 `bus`(-1 主干 / 0~7 支路),本文件最后两条用例测的就是它: **关着的支路一律 NAK**、**同时开两路会被记账**。 原有那几条用的仍是 `sim_i2c_add()`(挂主干),语义没变。 */ #include #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(); }