2. 测量工具链搭建:I2C/SMBus智能电池监控、INA226/INA219电流检测芯片驱动、ADC数据采集与校准

做功耗测量,第一步不是调代码,而是把工具链搭稳。我见过太多工程师上来就写驱动,结果读回来的数据全是错的——不是寄存器地址配反了,就是校准系数没算对。这一章,咱们就把测量工具链的每个环节掰开揉碎。

2.1 测量工具链整体架构

先看整体结构。一个典型的嵌入式功耗测量系统,从上到下分三层:

嵌入式功耗测量工具链架构 应用层 功耗监控守护进程 / 日志采集 / 远程上报 驱动层 INA226/INA219 驱动 | 智能电池 SMBus 驱动 | ADC 驱动 校准模块 | 数据滤波 | 单位转换 硬件层 I2C/SMBus 总线 | 电流检测电阻 | 智能电池 ADC 采样通道 | 分压电阻网络 核心原则:硬件层保证精度 → 驱动层保证可靠性 → 应用层保证可用性

说白了,硬件层决定了你能测多准,驱动层决定了你能多稳地拿到数据,应用层决定了数据好不好用。这三层缺一不可。

2.2 I2C/SMBus 智能电池监控

智能电池(Smart Battery)用的是 SMBus 协议,它和 I2C 很像,但有区别。我刚开始搞的时候,直接用 I2C 驱动去读 SMBus 设备,结果校验和一直报错——后来才发现 SMBus 多了个 PEC(包错误校验)机制。

2.2.1 SMBus 与 I2C 的关键差异

特性 I2C SMBus
时钟频率 最高 3.4MHz 10kHz ~ 100kHz
超时机制 无硬件超时 35ms 超时检测
PEC 校验 可选 强制支持
地址类型 7位/10位 7位
典型应用 传感器、存储器 智能电池、电源管理

2.2.2 读取智能电池数据的典型流程

以读取电池剩余容量为例,SMBus 的读写流程是这样的:

// 伪代码:SMBus 读取电池容量(Word 数据)
uint16_t read_battery_capacity(uint8_t slave_addr) {
    uint8_t cmd = 0x0F;  // 剩余容量命令码
    uint8_t tx_buf[2] = {cmd, 0x00};
    uint8_t rx_buf[2] = {0};
    
    // 发送命令码
    i2c_master_write(slave_addr, tx_buf, 1);
    // 等待转换完成(SMBus 要求至少 4ms)
    usleep(5000);
    // 读取 2 字节数据
    i2c_master_read(slave_addr, rx_buf, 2);
    
    // 小端格式:低字节在前
    return (rx_buf[1] << 8) | rx_buf[0];
}
注意:SMBus 的读写之间必须插入延时。我遇到过一块电池,读出来的容量忽大忽小,查了半天才发现是连续读写太快,电池芯片没准备好数据。加个 5ms 延时就稳了。

2.3 INA226/INA219 电流检测芯片驱动

INA226 和 INA219 是 TI 家的经典电流检测芯片。两者原理一样,但 INA226 精度更高、功能更全。我个人习惯在量产项目上用 INA226,原型验证阶段用 INA219——便宜,够用。

2.3.1 芯片参数对比

参数 INA219 INA226
总线电压范围 0~26V 0~36V
电流分辨率 1mA(典型) 0.1mA(典型)
转换时间 可配置(140μs~8.244ms) 可配置(140μs~8.244ms)
连续模式 支持 支持
警报输出 无 有(可配置阈值)
价格(参考) ~$0.5 ~$1.2

2.3.2 INA226 驱动核心代码

驱动 INA226 的关键是配置校准寄存器。校准值算不对,读出来的电流就是错的。公式很简单:

// 校准值 = 0.00512 / (电流分辨率 × 采样电阻)
// 假设采样电阻 0.01Ω,目标分辨率 0.1mA
// 校准值 = 0.00512 / (0.0001 × 0.01) = 5120 = 0x1400

#define INA226_ADDR      0x40
#define REG_CONFIG       0x00
#define REG_CALIBRATION  0x05
#define REG_SHUNT_VOLT   0x01
#define REG_BUS_VOLT     0x02
#define REG_POWER        0x03
#define REG_CURRENT      0x04

void ina226_init(uint8_t addr, uint16_t cal_value) {
    // 配置:连续模式,128次平均,1.1ms转换时间
    uint16_t config = 0x4127;
    i2c_write_word(addr, REG_CONFIG, config);
    usleep(2000);
    
    // 写入校准值
    i2c_write_word(addr, REG_CALIBRATION, cal_value);
}

float ina226_read_current(uint8_t addr) {
    int16_t raw = i2c_read_word(addr, REG_CURRENT);
    // 电流 = 原始值 × 电流分辨率
    return (float)raw * 0.0001f;  // 单位:安培
}
经验之谈:校准值算出来如果不是整数,取整后误差会累积。我一般用浮点算好,再转成整数写入。另外,采样电阻的温度系数很关键——普通电阻温度漂移 100ppm/°C,测大电流时误差能到 5% 以上。建议用 25ppm 以下的精密电阻。

2.4 ADC 数据采集与校准

有些场景下,INA226 不够用——比如你要测 -5V 的负压,或者电流范围特别大。这时候就得自己搭 ADC 采集电路。

2.4.1 ADC 采集链路

一个典型的 ADC 采集链路包括:

  1. 分压电阻网络:把被测电压降到 ADC 输入范围(通常是 0~3.3V)
  2. RC 低通滤波:滤除高频噪声,截止频率一般设在 1kHz~10kHz
  3. 电压跟随器:用运放做缓冲,提高输入阻抗
  4. ADC 采样:内置 ADC 或外置 ADC(如 ADS1115)

2.4.2 校准方法:两点校准 vs 多点校准

校准方式 原理 适用场景 精度
两点校准 零点和满量程点 线性度好的 ADC ±1% ~ ±2%
多点校准 5~10 个参考点,分段线性插值 非线性明显或宽范围测量 ±0.1% ~ ±0.5%
曲线拟合 多项式或样条拟合 高精度实验室测量 ±0.01% ~ ±0.1%

我一般用两点校准做快速验证,量产时用多点校准。为什么?因为 ADC 的线性度在中间段最好,两头容易偏。多点校准能把这个误差吃掉。

2.4.3 两点校准实现示例

// 两点校准结构体
typedef struct {
    float v_ref_low;    // 参考低电压
    float v_ref_high;   // 参考高电压
    uint16_t adc_low;   // 对应 ADC 原始值
    uint16_t adc_high;  // 对应 ADC 原始值
    float gain;         // 增益系数
    float offset;       // 偏移量
} adc_cal_t;

void adc_cal_init(adc_cal_t *cal) {
    // 计算增益和偏移
    cal->gain = (cal->v_ref_high - cal->v_ref_low) / 
                (float)(cal->adc_high - cal->adc_low);
    cal->offset = cal->v_ref_low - cal->gain * cal->adc_low;
}

float adc_cal_convert(adc_cal_t *cal, uint16_t raw) {
    return cal->gain * raw + cal->offset;
}
校准注意事项:
  • 参考电压源必须稳定,建议用 TL431 或 ADR4525 这类精密基准源
  • 校准环境温度要和实际工作温度一致——ADC 的温漂是硬伤
  • 每次上电后先做一次自校准,消除电源波动的影响

2.5 工具链集成与验证

驱动写好了,校准也做了,怎么验证整个工具链对不对?我的做法是三步:

  1. 回环测试:用精密电阻代替负载,测电流值是否和理论值一致
  2. 对比测试:用万用表或示波器电流探头做对照
  3. 长时间跑测:连续采集 24 小时,看数据有没有漂移或跳变

记得有一次,INA226 读出来的电流总是偏大 3%。查了三天,最后发现是 PCB 走线太长,引入了额外的电阻。嗯,硬件问题往往比软件问题更难排查。