固件: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]>
这个提交包含在:
+154
-42
@@ -1,10 +1,28 @@
|
||||
/* A3:INA226 驱动 + 六片地址表。
|
||||
两个验收主项:①六个地址就是 0x40/41/44/45/42/43;②通道号 ≠ 地址低位。 */
|
||||
/* A3:INA226 驱动 + 地址表(**R2 版**,计划 §5.2)。
|
||||
|
||||
两个验收主项:
|
||||
① 总线上只有**两个** INA226 地址:刀片那五片全是 `0x40`,底板输入侧 `U13` 是 `0x43`;
|
||||
② ⭐ **五路同址不是 bug** —— 区分是哪一路的是 **mux 通道**,不是地址。
|
||||
|
||||
---- R1 → R2 换掉了什么 --------------------------------------------------
|
||||
R1 是「六片六个地址、一条总线」,于是那时的验收主项是「通道号 ≠ 地址低位」,
|
||||
拦的是有人写出 `0x40 + rail`。R2 把五路做成五片完全同构的刀片(D8),
|
||||
地址 strap 全部焊死在 0x40,**`0x40 + rail` 这行代码现在写出来会当场读不到器件**,
|
||||
一上板就暴露。真正接替它的坑是通道:选错通道读到的是**另一台手机**的电压电流,
|
||||
量纲、范围、变化趋势全都正常,没有任何一层会报错。
|
||||
所以那条用例在这里被换成了「五路地址必须全相同」,
|
||||
而「五路通道必须两两不同」那一半在 `test_rail_cfg.c` 里。 */
|
||||
#include "ina226.h"
|
||||
#include "sim_hal.h"
|
||||
#include "unity.h"
|
||||
|
||||
void setUp(void) { sim_hal_reset(); }
|
||||
void setUp(void)
|
||||
{
|
||||
sim_hal_reset();
|
||||
for (uint8_t a = 0x40; a <= 0x4F; a++) { /* 对调登记是驱动里的静态表,不清会串到别的用例 */
|
||||
ina226_set_reversed(a, false);
|
||||
}
|
||||
}
|
||||
void tearDown(void) {}
|
||||
|
||||
/* 挂一片假 INA226 并预置 ID,返回地址。 */
|
||||
@@ -19,41 +37,58 @@ static uint8_t mount(uint8_t addr)
|
||||
|
||||
/* ---- 地址表 -------------------------------------------------------------- */
|
||||
|
||||
/* ⭐ 验收主项一:设计文档 §8.2 的六个地址,逐个钉死。 */
|
||||
static void test_addr_table_is_the_one_from_8_2(void)
|
||||
/* ⭐ 验收主项一:总线上出现过的 INA226 地址只有两个,逐个钉死。
|
||||
出处:刀片 `U3` 的 pin1/pin2(A1/A0)都接 GND ⇒ 0x40;底板 `U13` pin1=GND、
|
||||
pin2=SCL ⇒ 0x43(`.claude/r2/_blade_pcb_nets.json` / `_base_pcb_nets.json`)。 */
|
||||
static void test_addr_list_is_the_two_from_5_2(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_HEX8(0x40, ina226_rail_addr(1));
|
||||
TEST_ASSERT_EQUAL_HEX8(0x41, ina226_rail_addr(2));
|
||||
TEST_ASSERT_EQUAL_HEX8(0x44, ina226_rail_addr(3));
|
||||
TEST_ASSERT_EQUAL_HEX8(0x45, ina226_rail_addr(4));
|
||||
TEST_ASSERT_EQUAL_HEX8(0x42, ina226_rail_addr(5));
|
||||
static const uint8_t want[INA226_ADDR_COUNT] = { 0x40, 0x43 };
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(2, INA226_ADDR_COUNT, "R2 只有两个地址,不是六个");
|
||||
TEST_ASSERT_EQUAL_HEX8_ARRAY(want, ina226_addr_list, INA226_ADDR_COUNT);
|
||||
TEST_ASSERT_EQUAL_HEX8(0x40, INA226_ADDR_RAIL);
|
||||
TEST_ASSERT_EQUAL_HEX8(0x43, INA226_ADDR_INPUT);
|
||||
|
||||
static const uint8_t want[INA226_COUNT] = { 0x40, 0x41, 0x44, 0x45, 0x42, 0x43 };
|
||||
TEST_ASSERT_EQUAL_HEX8_ARRAY(want, ina226_all_addr, INA226_COUNT);
|
||||
/* **物理片数仍然是 6**。两个数不一样,正是 R2 的全部意思 ——
|
||||
把 `INA226_COUNT` 当成「扫描要扫几个地址」用,就会去扫 0x44/0x45 那种
|
||||
R2 板上根本不存在的地址,然后得出「四片 INA226 失联」的结论。 */
|
||||
TEST_ASSERT_EQUAL_INT(6, INA226_COUNT);
|
||||
TEST_ASSERT_EQUAL_INT(5, INA226_RAIL_COUNT);
|
||||
}
|
||||
|
||||
/* ⭐ 验收主项二:`0x40 + ch` 这行代码一定会被写出来(需求 §10.4),
|
||||
这条测试就是拦它的 —— 五路里有三路对不上,把表改成算式必红。 */
|
||||
static void test_channel_index_is_not_the_address_low_bits(void)
|
||||
/* ⭐ 验收主项二:**五路的地址全是 0x40 —— 这不是 bug。**
|
||||
五片刀片完全同构(D8),地址 strap 焊死;区分是哪一路的是
|
||||
`rail_cfg(rail)->mux_ch`,光有地址是选不中任何一路的。
|
||||
|
||||
⚠️ 写错了会怎样:把这张表改回「每路一个地址」(R1 的记忆),
|
||||
CH2~CH5 一上板就读不到器件 —— 这一半会当场暴露,不可怕。
|
||||
可怕的是反过来:既然五路同址,任何「拿到地址就直接读」的代码
|
||||
读的都是**上一次选中的那一路**,五路数字全部正常、还很稳,只是有几路是同一份。
|
||||
这条用例只能钉住地址这一半,另一半(通道两两不同)在 test_rail_cfg.c 里。 */
|
||||
static void test_all_five_rails_share_one_address(void)
|
||||
{
|
||||
int mismatches = 0;
|
||||
for (int rail = 1; rail <= INA226_RAIL_COUNT; rail++) {
|
||||
uint8_t naive = (uint8_t)(0x40 + (rail - 1));
|
||||
if (ina226_rail_addr(rail) != naive) {
|
||||
mismatches++;
|
||||
TEST_ASSERT_EQUAL_HEX8_MESSAGE(INA226_ADDR_RAIL, ina226_rail_addr(rail),
|
||||
"五路同址是设计(D8),不是笔误");
|
||||
}
|
||||
/* 输入侧那片**必须**不同址:它挂主干,和刀片那片同时可见。 */
|
||||
TEST_ASSERT_NOT_EQUAL(INA226_ADDR_RAIL, INA226_ADDR_INPUT);
|
||||
|
||||
/* R1 那行 `0x40 + rail` 现在对 CH1 仍然「碰巧对」,对其余四路全错 ——
|
||||
写下来是为了让「拿 CH1 试一下没问题」这条老路在这里就断掉。 */
|
||||
int naive_ok = 0;
|
||||
for (int rail = 1; rail <= INA226_RAIL_COUNT; rail++) {
|
||||
if (ina226_rail_addr(rail) == (uint8_t)(0x40 + rail - 1)) {
|
||||
naive_ok++;
|
||||
}
|
||||
}
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(3, mismatches, "CH3/CH4/CH5 三路必须与顺排不同");
|
||||
|
||||
/* 反过来也钉一遍:拿地址低位算回通道号同样会算错。 */
|
||||
TEST_ASSERT_NOT_EQUAL(0x42, ina226_rail_addr(3)); /* CH3 是 0x44,不是 0x42 */
|
||||
TEST_ASSERT_NOT_EQUAL(0x44, ina226_rail_addr(5)); /* CH5 是 0x42,不是 0x44 */
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(1, naive_ok, "只有 CH1 碰巧和 0x40+ch 对得上");
|
||||
}
|
||||
|
||||
/* 撞车检查的对象在 R2 里换了一批:主干上的 DAC63204(0x4A/0x4B) 与 TCA9548A(0x70),
|
||||
加上刀片支路上的 PCA9555(0x20)。**0x48 的 LM75 不在里面** —— R2 没有载板。 */
|
||||
static void test_addr_table_has_no_collisions(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(ina226_addr_table_ok()); /* 含与 DAC/PCA9555/LM75 的撞车检查 */
|
||||
TEST_ASSERT_TRUE(ina226_addr_table_ok());
|
||||
}
|
||||
|
||||
/* ---- 认件 ---------------------------------------------------------------- */
|
||||
@@ -66,28 +101,37 @@ static void test_probe_accepts_real_ina226(void)
|
||||
/* 应答了但 ID 不对 = 贴错件或地址跳线焊错,必须和「没这个器件」区分开。 */
|
||||
static void test_probe_rejects_wrong_die_id(void)
|
||||
{
|
||||
uint8_t a = mount(0x41);
|
||||
uint8_t a = mount(INA226_ADDR_RAIL);
|
||||
sim_i2c_set_reg(a, INA226_REG_DIE_ID, 0x1234);
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_BUS, ina226_probe(a));
|
||||
}
|
||||
|
||||
static void test_probe_absent_device_naks(void)
|
||||
{
|
||||
mount(0x40);
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, ina226_probe(0x44)); /* 没挂 */
|
||||
mount(INA226_ADDR_RAIL);
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, ina226_probe(INA226_ADDR_INPUT)); /* 没挂 */
|
||||
sim_i2c_set_present(0x40, false);
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, ina226_probe(0x40)); /* 失联 */
|
||||
}
|
||||
|
||||
/* 需求 §12.1 步 2:六片全应答。 */
|
||||
static void test_all_six_answer_scan(void)
|
||||
/* 需求 §12.1 步 2 的 R2 版:扫描只会扫到**两个**地址。
|
||||
⚠️ 这里挂的两片都在主干上,因为本文件测的是驱动、不带 mux;
|
||||
真板上 0x40 那片在支路后面,**只有对应通道开着的时候才应答** ——
|
||||
所以「扫一圈看到几片 INA226」在 R2 上回答不了「五路都在不在」。
|
||||
那件事要靠逐通道扫(i2cbus + slot),不在本文件的范围里。 */
|
||||
static void test_only_two_addresses_answer_a_plain_scan(void)
|
||||
{
|
||||
for (int i = 0; i < INA226_COUNT; i++) {
|
||||
mount(ina226_all_addr[i]);
|
||||
for (int i = 0; i < INA226_ADDR_COUNT; i++) {
|
||||
mount(ina226_addr_list[i]);
|
||||
}
|
||||
for (int i = 0; i < INA226_COUNT; i++) {
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_probe(ina226_all_addr[i]));
|
||||
for (int i = 0; i < INA226_ADDR_COUNT; i++) {
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_probe(ina226_addr_list[i]));
|
||||
}
|
||||
/* R1 的那四个地址在 R2 板上根本不存在 —— 照着老表扫会得出「四片失联」。 */
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, ina226_probe(0x41));
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, ina226_probe(0x42));
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, ina226_probe(0x44));
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, ina226_probe(0x45));
|
||||
}
|
||||
|
||||
static void test_bus_not_initialised_propagates(void)
|
||||
@@ -101,7 +145,7 @@ static void test_bus_not_initialised_propagates(void)
|
||||
|
||||
static void test_init_writes_config_and_calibration(void)
|
||||
{
|
||||
uint8_t a = mount(0x45);
|
||||
uint8_t a = mount(INA226_ADDR_INPUT);
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_init(a));
|
||||
TEST_ASSERT_EQUAL_HEX16(INA226_CONFIG, sim_i2c_get_reg(a, INA226_REG_CONFIG));
|
||||
TEST_ASSERT_EQUAL_HEX16(2048, sim_i2c_get_reg(a, INA226_REG_CALIB));
|
||||
@@ -130,7 +174,7 @@ static void test_check_catches_lost_calibration(void)
|
||||
/* 满载一路:4.010 V / 4 A / 16 W。5 mΩ + 0.5 mA LSB 下分流码与电流码同值。 */
|
||||
static void test_readings_at_full_load(void)
|
||||
{
|
||||
uint8_t a = mount(0x44);
|
||||
uint8_t a = mount(INA226_ADDR_RAIL);
|
||||
sim_i2c_set_reg(a, INA226_REG_BUS_V, 3208); /* ×1.25 mV */
|
||||
sim_i2c_set_reg(a, INA226_REG_SHUNT_V, 8000); /* ×2.5 µV = 20 mV */
|
||||
sim_i2c_set_reg(a, INA226_REG_CURRENT, 8000); /* ×0.5 mA */
|
||||
@@ -148,7 +192,7 @@ static void test_readings_at_full_load(void)
|
||||
而判据是「电流为负」,于是保护永远不触发。 */
|
||||
static void test_reverse_current_is_negative(void)
|
||||
{
|
||||
uint8_t a = mount(0x42);
|
||||
uint8_t a = mount(INA226_ADDR_RAIL);
|
||||
sim_i2c_set_reg(a, INA226_REG_CURRENT, (uint16_t)(int16_t)-120); /* −60 mA */
|
||||
sim_i2c_set_reg(a, INA226_REG_SHUNT_V, (uint16_t)(int16_t)-120); /* −300 µV */
|
||||
|
||||
@@ -177,7 +221,7 @@ static void test_bus_voltage_rounding(void)
|
||||
靠自增省事的写法在这里会绿,到板上四个量全读成同一个。 */
|
||||
static void test_pointer_is_rewritten_before_every_read(void)
|
||||
{
|
||||
uint8_t a = mount(0x41);
|
||||
uint8_t a = mount(INA226_ADDR_RAIL);
|
||||
ina226_sample_t s = { 0, 0, 0, 0 };
|
||||
sim_i2c_dev(a)->n_write = 0;
|
||||
sim_i2c_dev(a)->n_read = 0;
|
||||
@@ -204,7 +248,7 @@ static void test_read_vi_uses_two_transactions(void)
|
||||
/* 器件中途失联:错误必须一路传上去,不能返回一个看着正常的 0。 */
|
||||
static void test_read_propagates_nak(void)
|
||||
{
|
||||
uint8_t a = mount(0x45);
|
||||
uint8_t a = mount(INA226_ADDR_INPUT);
|
||||
sim_i2c_inject_nak(a, -1);
|
||||
ina226_vi_t vi = { 123, 456 };
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, ina226_read_vi(a, &vi));
|
||||
@@ -218,16 +262,81 @@ static void test_null_output_rejected(void)
|
||||
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, ina226_read_bus_mv(0x40, NULL));
|
||||
}
|
||||
|
||||
/* ---- Kelvin 对调接的片子(R2 底板 U13)--------------------------------- */
|
||||
|
||||
/* ⭐ R2 底板 U13 的 IN+ 接下游:正常供电时芯片读出负值。登记后驱动取反,
|
||||
调用方看到正值 —— 否则 SW-8 会把正常供电当成反灌。母线电压 / 功率是无符号量,不动。 */
|
||||
static void test_reversed_chip_reads_positive_in_normal_flow(void)
|
||||
{
|
||||
uint8_t a = mount(INA226_ADDR_INPUT);
|
||||
ina226_set_reversed(a, true);
|
||||
TEST_ASSERT_TRUE(ina226_is_reversed(a));
|
||||
sim_i2c_set_reg(a, INA226_REG_BUS_V, 9600); /* 12.000 V */
|
||||
sim_i2c_set_reg(a, INA226_REG_SHUNT_V, (uint16_t)(int16_t)-8000); /* 芯片读 −20 mV */
|
||||
sim_i2c_set_reg(a, INA226_REG_CURRENT, (uint16_t)(int16_t)-8000); /* 芯片读 −4 A */
|
||||
sim_i2c_set_reg(a, INA226_REG_POWER, 3840); /* 48 W,恒正 */
|
||||
|
||||
ina226_sample_t s = { 0, 0, 0, 0 };
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_read_all(a, &s));
|
||||
TEST_ASSERT_EQUAL_INT32(12000, s.bus_mv);
|
||||
TEST_ASSERT_EQUAL_INT32(20000, s.shunt_uv);
|
||||
TEST_ASSERT_EQUAL_INT32(4000000, s.current_ua);
|
||||
TEST_ASSERT_EQUAL_INT32(48000, s.power_mw);
|
||||
|
||||
ina226_vi_t vi = { 0, 0 };
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_read_vi(a, &vi));
|
||||
TEST_ASSERT_EQUAL_INT32(4000000, vi.current_ua);
|
||||
}
|
||||
|
||||
/* 对调片子上真反灌:芯片读正值 → 驱动给出负值,SW-8 照样触发。
|
||||
没登记的片子(CH 路)不受影响。 */
|
||||
static void test_reversed_only_affects_that_chip(void)
|
||||
{
|
||||
uint8_t in = mount(INA226_ADDR_INPUT);
|
||||
uint8_t ch = mount(0x40);
|
||||
ina226_set_reversed(in, true);
|
||||
sim_i2c_set_reg(in, INA226_REG_CURRENT, 120); /* 芯片读 +60 mA = 反灌 */
|
||||
sim_i2c_set_reg(ch, INA226_REG_CURRENT, 120);
|
||||
|
||||
int32_t ua_in = 0, ua_ch = 0;
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_read_current_ua(in, &ua_in));
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_read_current_ua(ch, &ua_ch));
|
||||
TEST_ASSERT_EQUAL_INT32(-60000, ua_in);
|
||||
TEST_ASSERT_EQUAL_INT32(60000, ua_ch);
|
||||
TEST_ASSERT_FALSE(ina226_is_reversed(ch));
|
||||
}
|
||||
|
||||
/* 取消登记恢复原样;地址越界(不是 INA226 的 0x40~0x4F)的调用被忽略、不越界写。 */
|
||||
static void test_reversed_can_be_cleared_and_ignores_bad_addr(void)
|
||||
{
|
||||
uint8_t a = mount(INA226_ADDR_RAIL);
|
||||
ina226_set_reversed(a, true);
|
||||
ina226_set_reversed(a, false);
|
||||
TEST_ASSERT_FALSE(ina226_is_reversed(a));
|
||||
sim_i2c_set_reg(a, INA226_REG_CURRENT, (uint16_t)(int16_t)-120);
|
||||
int32_t ua = 0;
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_read_current_ua(a, &ua));
|
||||
TEST_ASSERT_EQUAL_INT32(-60000, ua);
|
||||
|
||||
ina226_set_reversed(0x20, true); /* PCA9555 的地址 */
|
||||
ina226_set_reversed(0x50, true);
|
||||
TEST_ASSERT_FALSE(ina226_is_reversed(0x20));
|
||||
TEST_ASSERT_FALSE(ina226_is_reversed(0x50));
|
||||
for (uint8_t b = 0x40; b <= 0x4F; b++) {
|
||||
TEST_ASSERT_FALSE(ina226_is_reversed(b));
|
||||
}
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_addr_table_is_the_one_from_8_2);
|
||||
RUN_TEST(test_channel_index_is_not_the_address_low_bits);
|
||||
RUN_TEST(test_addr_list_is_the_two_from_5_2);
|
||||
RUN_TEST(test_all_five_rails_share_one_address);
|
||||
RUN_TEST(test_addr_table_has_no_collisions);
|
||||
RUN_TEST(test_probe_accepts_real_ina226);
|
||||
RUN_TEST(test_probe_rejects_wrong_die_id);
|
||||
RUN_TEST(test_probe_absent_device_naks);
|
||||
RUN_TEST(test_all_six_answer_scan);
|
||||
RUN_TEST(test_only_two_addresses_answer_a_plain_scan);
|
||||
RUN_TEST(test_bus_not_initialised_propagates);
|
||||
RUN_TEST(test_init_writes_config_and_calibration);
|
||||
RUN_TEST(test_check_catches_lost_calibration);
|
||||
@@ -238,5 +347,8 @@ int main(void)
|
||||
RUN_TEST(test_read_vi_uses_two_transactions);
|
||||
RUN_TEST(test_read_propagates_nak);
|
||||
RUN_TEST(test_null_output_rejected);
|
||||
RUN_TEST(test_reversed_chip_reads_positive_in_normal_flow);
|
||||
RUN_TEST(test_reversed_only_affects_that_chip);
|
||||
RUN_TEST(test_reversed_can_be_cleared_and_ignores_bad_addr);
|
||||
return UNITY_END();
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户