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]>
80 行
3.6 KiB
C
80 行
3.6 KiB
C
/* TCA9548A —— 底板上的 I²C 八路多路器(U35,0x70),R2 新增(方案 D8)。
|
||
|
||
它的存在让五片刀片可以**完全同构**:每片上都是 0x40 的 INA226 + 0x20 的
|
||
PCA9555,零 strap 电阻、五片可互换。代价是**每一次访问刀片都先得选通道**。
|
||
|
||
---- 这颗器件的三件反直觉的事 --------------------------------------------
|
||
|
||
1. **它没有寄存器地址。** 整个器件只有一个 8 位控制寄存器,写就是「发一个
|
||
字节」、读就是「收一个字节」,**不带寄存器指针**。所以这里用的是
|
||
`hal_i2c_write(addr, &byte, 1)` 而不是常见的「写 reg + 写 val」。
|
||
按后者写会把通道选成 `reg` 那个字节的值 —— 而且**不会报错**。
|
||
|
||
2. **控制字是掩码不是编号。** bit n = 1 表示通道 n 接通,**可以同时开多路**。
|
||
同时开两路的后果是两片同地址的 INA226 一起应答,读回来的是两片的**线与**
|
||
(量纲正常、数值偏小、还很稳)。所以本驱动只接受「恰好一路」或「全关」,
|
||
多路那种用法在这块板上永远是 bug。
|
||
|
||
3. **`RESET#` 只复位它自己**(把通道全关),不影响挂在支路上的器件。
|
||
复位脉冲很短(datasheet 最小 6 ns),但复位之后**通道是全关的** ——
|
||
所以调用方的通道影子必须一起作废,见 `i2cbus.h`。
|
||
|
||
⚠️ 本驱动**不做加锁,也不管影子**。「选通道 + 事务」的原子性是 `i2cbus`
|
||
那一层的事(计划 §4.3)—— 混在这里会让驱动没法单独测。 */
|
||
#ifndef TCA9548A_H
|
||
#define TCA9548A_H
|
||
|
||
#include <stdbool.h>
|
||
#include <stdint.h>
|
||
|
||
#include "hal_err.h"
|
||
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
/* A0 = A1 = A2 = GND(U35 的 pin1 / pin2 / pin21,已按 PCB 焊盘网络核过)。 */
|
||
#define TCA9548A_ADDR 0x70
|
||
|
||
#define TCA9548A_CH_COUNT 8
|
||
|
||
/* 全关。`tca9548a_select()` 传这个值 = 断开所有支路,只剩主干可见
|
||
(主干上是 DAC63204 ×2 + 输入侧 INA226 + CH224A 以外的东西)。 */
|
||
#define TCA9548A_CH_NONE (-1)
|
||
|
||
/* 通道 → 去哪(`.claude/r2/_base_pcb_nets.json` 的 `U35` 逐脚读回,2026-09-16)。
|
||
⚠️ 槽 1~5 这次**确实是顺排的**,但通道号仍然从 `rail_cfg` 那张表里查 ——
|
||
顺排是当下的事实,不是不变量。 */
|
||
#define TCA9548A_CH_SLOT1 0
|
||
#define TCA9548A_CH_SLOT5 4
|
||
#define TCA9548A_CH_PD 5 /* SDA6/SCL6 → U36 CH224A(PD 受电芯片) */
|
||
/* 通道 6 / 7 悬空。 */
|
||
|
||
/* 0x70 不与 INA226(0x40/0x43) / DAC63204(0x4A/0x4B) / PCA9555(0x20) 撞车。
|
||
启动自检调一次。 */
|
||
bool tca9548a_addr_ok(void);
|
||
|
||
/* 选通道。`ch` 是 0~7 或 `TCA9548A_CH_NONE`;其它值 → HAL_ERR_ARG。
|
||
**一次只开一路**(见文件头第 2 条)。 */
|
||
hal_err_t tca9548a_select(uint8_t addr, int ch);
|
||
|
||
/* 读回控制字(一个字节,不带寄存器指针)。
|
||
用来验证「选通道真的生效了」—— 写下去不报错但没生效是这颗器件最难查的失效
|
||
(比如上拉太弱、支路被某个器件拉死)。 */
|
||
hal_err_t tca9548a_read_mask(uint8_t addr, uint8_t *mask);
|
||
|
||
/* 认件:全关之后读回控制字,必须是 0。
|
||
不应答 → HAL_ERR_NAK;应答了但读回不是 0 → HAL_ERR_BUS(地址上有东西,
|
||
但它不是一颗听话的 TCA9548A)。 */
|
||
hal_err_t tca9548a_probe(uint8_t addr);
|
||
|
||
/* 掩码 → 通道号。恰好一位才返回 0~7;全 0 返回 `TCA9548A_CH_NONE`;
|
||
**多于一位返回 -2**(见文件头第 2 条:那是 bug,不是一种用法)。 */
|
||
int tca9548a_mask_to_ch(uint8_t mask);
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
|
||
#endif /* TCA9548A_H */
|