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]>
214 行
7.9 KiB
C
214 行
7.9 KiB
C
/* INA226 驱动。只经 hal_i2c_*,不碰任何平台头文件。 */
|
||
#include "ina226.h"
|
||
|
||
#include <assert.h>
|
||
#include <stddef.h>
|
||
|
||
#include "hal_i2c.h"
|
||
|
||
/* R2:五片刀片完全同构 ⇒ 五路同址。仍然写成表而不是直接 `return 0x40`,
|
||
理由和 mux 通道那张表一样:**同址是当下的事实,不是不变量**。
|
||
哪天某一片改了 strap,改这里一处,`rail_selftest()` 会立刻发现两边分家。 */
|
||
static const uint8_t s_rail_addr[INA226_RAIL_COUNT] = {
|
||
INA226_ADDR_RAIL, INA226_ADDR_RAIL, INA226_ADDR_RAIL, INA226_ADDR_RAIL, INA226_ADDR_RAIL,
|
||
};
|
||
|
||
const uint8_t ina226_addr_list[INA226_ADDR_COUNT] = { INA226_ADDR_RAIL, INA226_ADDR_INPUT };
|
||
|
||
uint8_t ina226_rail_addr(int rail)
|
||
{
|
||
assert(rail >= 1 && rail <= INA226_RAIL_COUNT); /* 越界是代码写错,不许悄悄返回一个地址 */
|
||
return s_rail_addr[rail - 1];
|
||
}
|
||
|
||
bool ina226_addr_table_ok(void)
|
||
{
|
||
/* 会与之撞车的其它器件:主干上的 DAC63204 / TCA9548A,刀片支路上的 PCA9555。
|
||
⚠️ 0x48(R1 载板上的 LM75)**不在这张表里** —— R2 没有载板,那片不存在。 */
|
||
static const uint8_t others[] = { 0x4A, 0x4B, 0x70, 0x20 };
|
||
|
||
for (int i = 0; i < INA226_ADDR_COUNT; i++) {
|
||
if (ina226_addr_list[i] > 0x7f) {
|
||
return false;
|
||
}
|
||
for (int j = i + 1; j < INA226_ADDR_COUNT; j++) {
|
||
if (ina226_addr_list[i] == ina226_addr_list[j]) {
|
||
return false; /* 刀片那片与输入侧那片必须不同址:它们同时可见 */
|
||
}
|
||
}
|
||
for (size_t k = 0; k < sizeof(others); k++) {
|
||
if (ina226_addr_list[i] == others[k]) {
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
/* 五路那张表里的每一个都必须在扫描表里 —— 分家的那天就是错一路的那天。 */
|
||
for (int i = 0; i < INA226_RAIL_COUNT; i++) {
|
||
if (s_rail_addr[i] != ina226_addr_list[0]) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/* ---- 寄存器读写 ---------------------------------------------------------
|
||
⚠️ 每次读之前都重写一遍寄存器指针,**不依赖指针自增**:INA226 的指针按 datasheet
|
||
是停在最后写入值上的(未在实物上验过,B1 上板时确认)。而 sim 的假器件是**会**自增的,
|
||
所以「靠自增一次读完 0x01~0x04」在宿主机上能绿,到板上会把四个量读成同一个。 */
|
||
|
||
static hal_err_t read_reg(uint8_t addr, uint8_t reg, uint16_t *raw)
|
||
{
|
||
uint8_t rd[2];
|
||
hal_err_t err = hal_i2c_write_read(addr, ®, 1, rd, sizeof(rd));
|
||
if (err != HAL_OK) {
|
||
return err;
|
||
}
|
||
*raw = (uint16_t)(((uint16_t)rd[0] << 8) | rd[1]); /* 大端 */
|
||
return HAL_OK;
|
||
}
|
||
|
||
static hal_err_t write_reg(uint8_t addr, uint8_t reg, uint16_t val)
|
||
{
|
||
const uint8_t wr[3] = { reg, (uint8_t)(val >> 8), (uint8_t)(val & 0xff) };
|
||
return hal_i2c_write(addr, wr, sizeof(wr));
|
||
}
|
||
|
||
static hal_err_t write_verify(uint8_t addr, uint8_t reg, uint16_t val)
|
||
{
|
||
hal_err_t err = write_reg(addr, reg, val);
|
||
if (err != HAL_OK) {
|
||
return err;
|
||
}
|
||
uint16_t back = 0;
|
||
err = read_reg(addr, reg, &back);
|
||
if (err != HAL_OK) {
|
||
return err;
|
||
}
|
||
return back == val ? HAL_OK : HAL_ERR_BUS;
|
||
}
|
||
|
||
/* ---- 换算 ---------------------------------------------------------------- */
|
||
|
||
static int32_t div_round(int32_t num, int32_t den) /* 四舍五入,负数远离零 */
|
||
{
|
||
return num >= 0 ? (num + den / 2) / den : (num - den / 2) / den;
|
||
}
|
||
|
||
static int32_t s16(uint16_t raw) { return (int32_t)(int16_t)raw; }
|
||
|
||
static int32_t bus_mv_of(uint16_t raw) { return div_round((int32_t)raw * 5, 4); } /* 1.25 mV */
|
||
static int32_t shunt_uv_of(uint16_t raw) { return div_round(s16(raw) * 5, 2); } /* 2.5 µV */
|
||
static int32_t current_ua_of(uint16_t raw) { return s16(raw) * INA226_CURRENT_LSB_UA; }
|
||
static int32_t power_mw_of(uint16_t raw) { return div_round((int32_t)raw * 25, 2); } /* 12.5 mW */
|
||
|
||
/* ---- 对外 ---------------------------------------------------------------- */
|
||
|
||
hal_err_t ina226_probe(uint8_t addr)
|
||
{
|
||
uint16_t mfg = 0;
|
||
hal_err_t err = read_reg(addr, INA226_REG_MFG_ID, &mfg);
|
||
if (err != HAL_OK) {
|
||
return err;
|
||
}
|
||
uint16_t die = 0;
|
||
err = read_reg(addr, INA226_REG_DIE_ID, &die);
|
||
if (err != HAL_OK) {
|
||
return err;
|
||
}
|
||
/* 应答了但不是 INA226:地址跳线焊错 / 贴错件。不能当「没这个器件」混过去。 */
|
||
return (mfg == INA226_MFG_ID && die == INA226_DIE_ID) ? HAL_OK : HAL_ERR_BUS;
|
||
}
|
||
|
||
hal_err_t ina226_init(uint8_t addr)
|
||
{
|
||
hal_err_t err = write_verify(addr, INA226_REG_CONFIG, INA226_CONFIG);
|
||
if (err != HAL_OK) {
|
||
return err;
|
||
}
|
||
return write_verify(addr, INA226_REG_CALIB, INA226_CALIB);
|
||
}
|
||
|
||
hal_err_t ina226_check(uint8_t addr)
|
||
{
|
||
uint16_t cal = 0;
|
||
hal_err_t err = read_reg(addr, INA226_REG_CALIB, &cal);
|
||
if (err != HAL_OK) {
|
||
return err;
|
||
}
|
||
return cal == INA226_CALIB ? HAL_OK : HAL_ERR_BUS;
|
||
}
|
||
|
||
/* ---- Kelvin 对调接的片子 --------------------------------------------------
|
||
IN+ / IN− 在板上对调接(IN+ 接分流电阻下游)的片子,分流电压 / 电流读出来是负的。
|
||
驱动在这里取反,调用方看到的永远是「正 = 正常供电方向」—— SW-8 反灌判据靠这个符号。
|
||
母线电压、功率寄存器是无符号量,不受影响。
|
||
R1 全部正接(默认);R2 底板的输入片 U13(0x43)为了让两条 Kelvin 在 PCB 上不交叉
|
||
而对调(2026-09-11),R2 固件启动时要 ina226_set_reversed(INA226_ADDR_INPUT, true)。 */
|
||
static uint16_t s_reversed; /* bit (addr − 0x40);INA226 的地址只有 0x40~0x4F */
|
||
|
||
void ina226_set_reversed(uint8_t addr, bool reversed)
|
||
{
|
||
if (addr < 0x40 || addr > 0x4F) {
|
||
return;
|
||
}
|
||
uint16_t bit = (uint16_t)(1u << (addr - 0x40));
|
||
s_reversed = reversed ? (uint16_t)(s_reversed | bit) : (uint16_t)(s_reversed & ~bit);
|
||
}
|
||
|
||
bool ina226_is_reversed(uint8_t addr)
|
||
{
|
||
return addr >= 0x40 && addr <= 0x4F && (s_reversed & (1u << (addr - 0x40))) != 0;
|
||
}
|
||
|
||
/* pol_ = 1:有符号量(分流电压 / 电流),按登记取反 */
|
||
#define READ_ONE(fn, conv, reg, pol_) \
|
||
hal_err_t fn(uint8_t addr, int32_t *out) \
|
||
{ \
|
||
if (out == NULL) { \
|
||
return HAL_ERR_ARG; \
|
||
} \
|
||
uint16_t raw = 0; \
|
||
hal_err_t err = read_reg(addr, (reg), &raw); \
|
||
if (err != HAL_OK) { \
|
||
return err; \
|
||
} \
|
||
int32_t v = conv(raw); \
|
||
*out = ((pol_) && ina226_is_reversed(addr)) ? -v : v; \
|
||
return HAL_OK; \
|
||
}
|
||
|
||
READ_ONE(ina226_read_bus_mv, bus_mv_of, INA226_REG_BUS_V, 0)
|
||
READ_ONE(ina226_read_shunt_uv, shunt_uv_of, INA226_REG_SHUNT_V, 1)
|
||
READ_ONE(ina226_read_current_ua, current_ua_of, INA226_REG_CURRENT, 1)
|
||
READ_ONE(ina226_read_power_mw, power_mw_of, INA226_REG_POWER, 0)
|
||
|
||
hal_err_t ina226_read_vi(uint8_t addr, ina226_vi_t *out)
|
||
{
|
||
if (out == NULL) {
|
||
return HAL_ERR_ARG;
|
||
}
|
||
hal_err_t err = ina226_read_bus_mv(addr, &out->bus_mv);
|
||
if (err != HAL_OK) {
|
||
return err;
|
||
}
|
||
return ina226_read_current_ua(addr, &out->current_ua);
|
||
}
|
||
|
||
hal_err_t ina226_read_all(uint8_t addr, ina226_sample_t *out)
|
||
{
|
||
if (out == NULL) {
|
||
return HAL_ERR_ARG;
|
||
}
|
||
hal_err_t err = ina226_read_bus_mv(addr, &out->bus_mv);
|
||
if (err == HAL_OK) {
|
||
err = ina226_read_shunt_uv(addr, &out->shunt_uv);
|
||
}
|
||
if (err == HAL_OK) {
|
||
err = ina226_read_current_ua(addr, &out->current_ua);
|
||
}
|
||
if (err == HAL_OK) {
|
||
err = ina226_read_power_mw(addr, &out->power_mw);
|
||
}
|
||
return err;
|
||
}
|