A3:INA226 驱动 + 六片地址表
地址表按设计文档 §8.2 钉死(0x40/41/44/45/42 + 输入侧 0x43),只走 ina226_rail_addr(),并有一条测试专门拦「ina_addr = 0x40 + ch」这一手 (CH3/CH4/CH5 三路全错,需求 §10.4)。 驱动只经 hal_i2c_*:认件(MFG/DIE ID)、写 Config 0x4327 + CAL 2048 并 回读校验、读 V/I/分流/功率。电流有符号,反灌为负(SW-8 判据)。 巡检走 read_vi(),每片两次事务,对应 §10.3 的 8% I²C 占用。 两条留给下次的坑:寄存器指针不依赖自增(sim 会自增、真件不会,靠自增 在宿主机上测不出来);CAL 丢了不报错只让 Current/Power 恒读 0,所以 有 ina226_check()。 Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QYuExfgyYa6DzRRcUso8M8
这个提交包含在:
@@ -0,0 +1,8 @@
|
||||
# 板上器件驱动(ESP-IDF 侧)。宿主机侧由 host/CMakeLists.txt 的 PHONERAIL_DRV_SRCS 收,
|
||||
# 新增一个驱动两边都要加 —— 漏一处 = 「宿主机绿了但上板编不过」,反之亦然。
|
||||
#
|
||||
# 驱动只准依赖 phonerail_hal,不准直接包含 IDF 的头。
|
||||
idf_component_register(
|
||||
SRCS "ina226.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES phonerail_hal)
|
||||
@@ -0,0 +1,184 @@
|
||||
/* INA226 驱动。只经 hal_i2c_*,不碰任何平台头文件。 */
|
||||
#include "ina226.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "hal_i2c.h"
|
||||
|
||||
/* 设计文档 §8.2:CH1..CH5。⚠️ 不是 0x40 起顺排,CH3/CH4/CH5 三路都不是。 */
|
||||
static const uint8_t s_rail_addr[INA226_RAIL_COUNT] = { 0x40, 0x41, 0x44, 0x45, 0x42 };
|
||||
|
||||
const uint8_t ina226_all_addr[INA226_COUNT] = { 0x40, 0x41, 0x44, 0x45, 0x42, 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)
|
||||
{
|
||||
/* 同一条 I²C 上的其它器件(§8.2):撞上任何一个都是硬件表被改坏了。 */
|
||||
static const uint8_t others[] = { 0x4A, 0x4B, 0x20, 0x48 };
|
||||
|
||||
for (int i = 0; i < INA226_COUNT; i++) {
|
||||
if (ina226_all_addr[i] > 0x7f) {
|
||||
return false;
|
||||
}
|
||||
for (int j = i + 1; j < INA226_COUNT; j++) {
|
||||
if (ina226_all_addr[i] == ina226_all_addr[j]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (size_t k = 0; k < sizeof(others); k++) {
|
||||
if (ina226_all_addr[i] == others[k]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* 前五个必须和 rail 表逐个一致 —— 两张表分家的那天就是错一路的那天。 */
|
||||
for (int i = 0; i < INA226_RAIL_COUNT; i++) {
|
||||
if (ina226_all_addr[i] != s_rail_addr[i]) {
|
||||
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;
|
||||
}
|
||||
|
||||
#define READ_ONE(fn, conv, reg) \
|
||||
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; \
|
||||
} \
|
||||
*out = conv(raw); \
|
||||
return HAL_OK; \
|
||||
}
|
||||
|
||||
READ_ONE(ina226_read_bus_mv, bus_mv_of, INA226_REG_BUS_V)
|
||||
READ_ONE(ina226_read_shunt_uv, shunt_uv_of, INA226_REG_SHUNT_V)
|
||||
READ_ONE(ina226_read_current_ua, current_ua_of, INA226_REG_CURRENT)
|
||||
READ_ONE(ina226_read_power_mw, power_mw_of, INA226_REG_POWER)
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/* INA226 —— 板上 6 片(CH1~CH5 输出各一片 + 输入侧一片),分流电阻全部 5 mΩ / 1%。
|
||||
地址表见设计文档 §8.2,**不是顺排的**,详见下面 ina226_rail_addr() 的警告。
|
||||
|
||||
本驱动只通过 hal_i2c_* 访问总线,宿主机上用 sim 的假器件即可全部测完。 */
|
||||
#ifndef INA226_H
|
||||
#define INA226_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "hal_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ---- 寄存器(全部 16 位、大端) ---------------------------------------- */
|
||||
|
||||
#define INA226_REG_CONFIG 0x00
|
||||
#define INA226_REG_SHUNT_V 0x01
|
||||
#define INA226_REG_BUS_V 0x02
|
||||
#define INA226_REG_POWER 0x03
|
||||
#define INA226_REG_CURRENT 0x04
|
||||
#define INA226_REG_CALIB 0x05
|
||||
#define INA226_REG_MASK_EN 0x06
|
||||
#define INA226_REG_ALERT_L 0x07
|
||||
#define INA226_REG_MFG_ID 0xFE
|
||||
#define INA226_REG_DIE_ID 0xFF
|
||||
|
||||
#define INA226_MFG_ID 0x5449 /* 'TI' */
|
||||
#define INA226_DIE_ID 0x2260
|
||||
|
||||
/* ---- 量程与标定(五路与输入侧同一颗分流电阻,所以是常量不是参数) ------ */
|
||||
|
||||
#define INA226_SHUNT_UOHM 5000 /* 5 mΩ,设计文档 §8.2 */
|
||||
#define INA226_CURRENT_LSB_UA 500 /* 0.5 mA —— 分流 LSB 2.5 µV ÷ 5 mΩ */
|
||||
|
||||
/* CAL = 0.00512 / (Current_LSB × R_shunt) = 0.00512 / (500 µA × 5 mΩ) = 2048 */
|
||||
#define INA226_CALIB 2048
|
||||
|
||||
/* Config:保留位 100 + AVG=4 + VBUSCT=1.1 ms + VSHCT=1.1 ms + 连续测量分流与母线。
|
||||
转换周期 4 × (1.1 + 1.1) = 8.8 ms < t_safety 的 20 ms,所以每次巡检都拿到新的一帧。 */
|
||||
#define INA226_CONFIG 0x4327
|
||||
|
||||
/* ---- 地址表(设计文档 §8.2) ------------------------------------------- */
|
||||
|
||||
#define INA226_RAIL_COUNT 5
|
||||
#define INA226_COUNT 6
|
||||
#define INA226_ADDR_INPUT 0x43 /* U13,输入侧 V+I,整板功率预算用 */
|
||||
|
||||
/* CH1~CH5 → U8~U12 的地址。rail 是 1~5,越界直接 abort。
|
||||
⚠️ **`0x40 + ch` 是错的**,CH3/CH4/CH5 三路全错(0x44 / 0x45 / 0x42)。
|
||||
地址按布线难度重排过(§8.2 那张 A1/A0 表),永远查表,不要算。 */
|
||||
uint8_t ina226_rail_addr(int rail);
|
||||
|
||||
extern const uint8_t ina226_all_addr[INA226_COUNT]; /* CH1..CH5 + 输入侧,扫描用 */
|
||||
|
||||
/* 六个地址互不重复、且不与 DAC63204(0x4A/0x4B) / PCA9555(0x20) / LM75(0x48) 撞车。
|
||||
启动自检调一次;返回 false 说明表被改坏了。 */
|
||||
bool ina226_addr_table_ok(void);
|
||||
|
||||
/* ---- 采样 --------------------------------------------------------------- */
|
||||
|
||||
typedef struct {
|
||||
int32_t bus_mv; /* 母线电压,1.25 mV/LSB */
|
||||
int32_t current_ua; /* **有符号** —— 反灌时为负(SW-8 靠这个判据) */
|
||||
} ina226_vi_t;
|
||||
|
||||
typedef struct {
|
||||
int32_t bus_mv;
|
||||
int32_t shunt_uv; /* 2.5 µV/LSB,四舍五入到 µV(±1.25 µV ≈ ±0.25 mA) */
|
||||
int32_t current_ua;
|
||||
int32_t power_mw; /* 12.5 mW/LSB,向零截断 */
|
||||
} ina226_sample_t;
|
||||
|
||||
/* 读 Manufacturer ID / Die ID 认件。
|
||||
器件不应答 → HAL_ERR_NAK;**应答了但 ID 不对 → HAL_ERR_BUS**
|
||||
(这个地址上有东西,但不是 INA226:多半地址跳线焊错或贴错件)。 */
|
||||
hal_err_t ina226_probe(uint8_t addr);
|
||||
|
||||
/* 写 Config 与 Calibration 并**逐个回读校验**。回读不一致 → HAL_ERR_BUS。 */
|
||||
hal_err_t ina226_init(uint8_t addr);
|
||||
|
||||
/* 只校验 Calibration 还在。掉了的现象是 **Current/Power 恒读 0**(不是报错),
|
||||
看上去就像「这台手机不耗电」,所以巡检里要定期查一次。 */
|
||||
hal_err_t ina226_check(uint8_t addr);
|
||||
|
||||
/* 巡检主路径:母线电压 + 电流,两次事务。 */
|
||||
hal_err_t ina226_read_vi(uint8_t addr, ina226_vi_t *out);
|
||||
|
||||
/* 诊断/事件记录用:四个量一次读齐,四次事务。 */
|
||||
hal_err_t ina226_read_all(uint8_t addr, ina226_sample_t *out);
|
||||
|
||||
hal_err_t ina226_read_bus_mv(uint8_t addr, int32_t *mv);
|
||||
hal_err_t ina226_read_shunt_uv(uint8_t addr, int32_t *uv);
|
||||
hal_err_t ina226_read_current_ua(uint8_t addr, int32_t *ua);
|
||||
hal_err_t ina226_read_power_mw(uint8_t addr, int32_t *mw);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* INA226_H */
|
||||
在新工单中引用
屏蔽一个用户