简介:面向STM32嵌入式开发者的INA226高精度电流电压监测芯片IIC驱动源码包。该驱动基于HAL库编写,涵盖IIC外设初始化、SDA与SCL引脚复用配置、通信速率设置、寄存器读写及数据转换等完整流程,并提供了层次清晰的函数接口,适用于电池管理、电源监控、负载开关控制等典型场景。压缩包仅含一个源文件和一个头文件,共两个文件,整体大小仅4KB,代码结构紧凑、无冗余依赖,便于直接移植到现有工程。驱动程序内置对配置寄存器(如采样电阻、电压量程、测量频率)的写入逻辑,以及通过HAL_I2C回调处理通信超时、数据冲突等异常,同时包含初始化、参数配置、数据读取等模块化函数,帮助开发者快速理解并实现电流、电压实时测量。目前已有6641人学习下载,适合具备基础IIC协议知识、希望缩短外设驱动开发周期的STM32用户参考复用。
1. 拆开 STM32 IIC 控制 INA226:先让电流数据从 I2C 总线上落下来
拿到一块带 STM32 和 INA226 的板子,第一次用 IIC 把电流读数调出来,我用了大概一晚上。INA226 是一颗 16 位 ADC 的电流/功率监测芯片,外部只需一个采样电阻,就能同时读回分流电压、总线电压、电流和功率四个量。相比用 ADC 自己搭采集电路,它的优势是内部有放大器、有校准寄存器、有报警输出,STM32 这边只需要两根线的 I2C 就能把数据搬回来。这篇文章写给两类人:一类是新手,想照着代码把 INA226 跑通;另一类是已经在用这颗芯片但被寄存器、校准值和 I2C 时序折腾过的人,我把踩过的坑按“现象→原因→解决”整理在后面,可以直接对号入座。
2. 先把两端底摸清:STM32 的 I2C 时序与 INA226 寄存器地图
2.1 I2C 外设的四个关键点
STM32 的 I2C 外设,无论是标准外设库还是 HAL 库,底层都要处理四件事:起始条件、停止条件、字节传输和应答位。很多人调不通 INA226,不是芯片问题,而是 I2C 地址和寄存器地址的封装方式没对齐。
STM32 的 I2C 地址分两种:7 位地址和 8 位地址。INA226 数据手册里写的是 7 位地址,比如 A0、A1 都接地时,7 位地址是 0x40,但在 HAL 库函数里,HAL_I2C_Mem_Read和HAL_I2C_Mem_Write的DevAddress参数要求传入的是 8 位地址,也就是 7 位地址左移一位变成 0x80。这一步一旦搞错,总线上根本找不到设备。
另外,I2C 的速率也值得注意。INA226 支持最高 2.8MHz 的 I2C 时钟,但 STM32 的 I2C 外设在 400kHz 以上时对板级走线和上拉电阻的要求明显变高。我的习惯是调试阶段用 100kHz,功能跑通后再视情况拉到 400kHz,不要在第一步就同时引入速率和逻辑两个变量。
2.2 INA226 的寄存器地图
INA226 内部寄存器不多,实际要用的就八个,我把常用寄存器列成一张表:
| 寄存器地址 | 名称 | 功能 |
|---|---|---|
| 0x00 | Configuration | 配置转换模式、平均次数、转换时间 |
| 0x01 | Shunt Voltage | 分流电压,LSB 固定 2.5µV |
| 0x02 | Bus Voltage | 总线电压,LSB 固定 1.25mV |
| 0x03 | Power | 功率,LSB = 25 × 电流 LSB |
| 0x04 | Current | 电流,数值 × 电流 LSB |
| 0x05 | Calibration | 校准寄存器,决定电流和功率的 LSB |
| 0x06 | Mask/Enable | 报警屏蔽与使能 |
| 0x07 | Alert Limit | 报警阈值 |
这里有一个容易忽略的点:0x01、0x02、0x03、0x04 四个数据寄存器返回的都是 16 位有符号数,尤其分流电压和电流,在电流反向或采样电阻接反时会出现负值。读取时如果只按uint16_t拼接,负值会变成 65535 之类的巨大正数,换算出的电流完全不靠谱。
2.3 地址引脚与差分输入的范围
INA226 的 A0、A1 两个引脚组合出四种 I2C 地址:都接地是 0x40,A0 接高是 0x41,A1 接高是 0x42,都接高是 0x43。这个组合在一根 I2C 总线上最多挂四颗 INA226,多通道采集就是这么扩展的。
采样电阻接在 IN+ 和 IN- 之间,差分输入默认量程 ±81.92mV。举个实际例子:采样电阻选 10mΩ,默认量程下最大能测 8.192A,超过这个电流,分流电压寄存器会饱和。所以选采样电阻不是只看阻值精度,还要和预期最大电流、PGA 量程一起算。
提示:INA226 总线电压测量范围是 0 到 40.96V,芯片额定电压 36V。如果要测 48V 系统,必须先用电阻分压把电压降到范围内,不能直接接进去。
3. 从零写驱动:初始化、寄存器读写与校准三步走
3.1 硬件连接和引脚定义
以 STM32F103 的 I2C1 为例,PB6 对应 SCL,PB7 对应 SDA,INA226 的 SCL、SDA 分别接上去,A0、A1 接地,地址固定为 0x40。采样电阻 RS 接在 IN+ 和 IN- 之间,负载电流从 IN+ 流入、经 RS 流到 IN-。VBUS 引脚接负载端电压,用来测总线电压。
CubeMX 里把 I2C1 速率设成 100kHz,生成工程后,第一步先确认这两个引脚上有波形。我习惯用逻辑分析仪看 SCL 和 SDA,确认起始条件、地址字节和 ACK 都正常,再往下写业务代码。
3.2 封装寄存器读写函数
#include "main.h" extern I2C_HandleTypeDef hi2c1; #define INA226_ADDR 0x40 // 7位地址,A0=A1=GND #define INA226_REG_CONFIG 0x00 #define INA226_REG_SHUNT 0x01 #define INA226_REG_BUS 0x02 #define INA226_REG_POWER 0x03 #define INA226_REG_CURRENT 0x04 #define INA226_REG_CAL 0x05 #define INA226_REG_ALERT 0x07 uint16_t INA226_ReadReg(uint8_t reg) { uint8_t buf[2]; uint16_t val; // 注意:HAL的Mem_Read要求8位地址,所以要左移一位 HAL_I2C_Mem_Read(&hi2c1, (INA226_ADDR << 1), reg, I2C_MEMSIZE_8BIT, buf, 2, 100); val = ((uint16_t)buf[0] << 8) | buf[1]; return val; } void INA226_WriteReg(uint8_t reg, uint16_t val) { uint8_t buf[2]; // INA226寄存器是16位,I2C传输时高字节在前 buf[0] = (uint8_t)(val >> 8); buf[1] = (uint8_t)(val & 0xFF); HAL_I2C_Mem_Write(&hi2c1, (INA226_ADDR << 1), reg, I2C_MEMSIZE_8BIT, buf, 2, 100); }这段代码的逻辑很清楚:HAL_I2C_Mem_Read的第一个地址参数传0x80,是因为 HAL 库内部会在传输时把最低位作为读/写位,传入的地址本身就应该带这个位。寄存器地址走MemAddress参数,固定为 8 位寄存器地址,这正好匹配 INA226 的指针寄存器机制。
这里有两个参数值得说明。超时时间100单位是毫秒,如果 I2C 总线上设备没应答,HAL 库会阻塞到超时返回错误码,所以调用后最好检查返回值,不要直接忽略。另外,寄存器数据是 16 位,I2C 先传高字节再传低字节,读取时按buf[0] << 8 | buf[1]拼接,写入时反过来拆分,顺序错了数据就完全对不上。
3.3 校准寄存器计算
校准寄存器是 INA226 和普通 ADC 芯片最大的区别。它决定了电流寄存器和功率寄存器的 LSB,不写这个寄存器,读出来的电流和功率恒为 0。
校准值的计算公式如下:
#define RSENSE 0.01f // 采样电阻,单位欧姆 #define CURRENT_LSB 0.0001f // 电流LSB,单位安培,这里取100uA/LSB void INA226_Init(void) { uint16_t calib; // 计算校准值:0.00512 / (CURRENT_LSB * RSENSE) calib = (uint16_t)(0.00512f / (CURRENT_LSB * RSENSE)); // 写校准寄存器 INA226_WriteReg(INA226_REG_CAL, calib); }按上面的参数算出来 calibr 是 5120,落在 16 位范围内,可以直接写入。CURRENT_LSB 的选择不是随便定的,它决定了电流分辨率,也受采样电阻和最大电流约束。常用做法是先把预期最大电流除以 2 的 15 次方,得到理论最小 LSB,再取一个比它大、且方便换算的“整齐”值。比如最大电流 2A,32768 分之一的原始分辨率约 61µA,取 100µA 完全够用,换算也直观。
校准值计算有两条边界:CAL 寄存器上限是 0x7FFF,算出来超过 32767 说明电流 LSB 选得太大或者采样电阻太小;反过来,LSB 选得太小,电流寄存器很快溢出。实际项目中,这两者往往要迭代几次才能定下来。我一般会把 LSB、采样电阻、最大电流三个量列一张表,算完再写代码。
3.4 读回电压、电流和功率
void INA226_ReadAll(float *bus_v, float *current_a, float *power_w) { uint16_t bus_raw; uint16_t cur_raw; uint16_t pwr_raw; bus_raw = INA226_ReadReg(INA226_REG_BUS); cur_raw = INA226_ReadReg(INA226_REG_CURRENT); pwr_raw = INA226_ReadReg(INA226_REG_POWER); // 总线电压:LSB固定1.25mV,寄存器有符号,但总线电压不会为负,直接乘即可 *bus_v = bus_raw * 0.00125f; // 电流:寄存器是有符号数,必须按int16_t解析 *current_a = (int16_t)cur_raw * CURRENT_LSB; // 功率:LSB = 25 * CURRENT_LSB,寄存器同样按有符号解析 *power_w = (int16_t)pwr_raw * 25.0f * CURRENT_LSB; }这里最容易翻车的是类型转换。INA226_ReadReg返回uint16_t,如果采样电阻两端接反,电流寄存器会返回负数,比如0xFFCE代表 -50,不转int16_t直接乘 0.0001f,得到的不是 -0.005A 而是 65.5A。总线电压寄存器虽然本质也有符号,但 INA226 的 VBUS 引脚只接受正电压,所以读不到负值,直接无符号处理问题不大。
功率 LSB 是 25 倍电流 LSB,这个 25 来自芯片内部 2.5µV 分流电压 LSB 与 1.25mV 总线电压 LSB 的乘积关系,不同型号的监测芯片这个系数不一样,有的芯片功率 LSB 直接等于电流 LSB 乘以总线 LSB,不能照搬。手头这颗 INA226,就是 25 倍。
4. 避坑记:地址、校准和时序五个高频翻车点
4.1 I2C 总线上找不到设备
现象:调用HAL_I2C_Mem_Read返回HAL_ERROR,或者逻辑分析仪上看到地址字节后面跟的是 NACK。
原因:最常见的有三种。第一种是 7 位地址和 8 位地址混用,HAL 库要求传 8 位地址,直接把 0x40 传进去等于在总线上发了 0x20 这个地址,设备当然不应答。第二种是 A0、A1 引脚悬空,INA226 内部没有默认下拉,悬空电平不确定,导致实际地址和代码里的不一致。第三种是 SDA、SCL 接反。
解决:用万用表量 A0、A1 对地电压,确认电平状态;代码里检查地址是否左移。最稳妥的办法是写一个 I2C 扫描函数,把 0x00 到 0x7F 全部探测一遍,看设备到底在哪个地址上。我每次换板子都会先扫一遍再写固件。
4.2 电流和功率始终为零
现象:总线电压、分流电压都有读数,电流和功率却一直输出 0.000。
原因:CAL 寄存器没写进去,或者写入后被后续代码覆盖。INA226 的电流和功率是内部用分流电压除以校准系数算出来的,CAL 寄存器保持默认值 0x0000 时,这两个寄存器永远是 0。还有一种情况是写入函数本身有问题,比如高字节低字节顺序反了,写进去的校准值变成了别的数。
解决:在初始化函数里先写 CAL,初始化完成后马上读回来,确认写进去的值一致。读回来的值如果是 0x0000,大概率是写寄存器函数的数据顺序错误。校准寄存器一旦确定,运行过程中不需要反复写,不要把它放在采集循环里。
4.3 负电流显示成六万多大数
现象:电流寄存器读回来 0xFFCE 之类的值,按无符号数换算,电流变成了 65A 这种离谱数据。
原因:电流寄存器是有符号补码格式,采样电阻两端接反、负载放电或者电流方向反向时,寄存器值为负数。我在 3.4 节里已经写了(int16_t)转换,但实际工程中很多人图省事,直接用uint16_t乘 LSB。
解决:所有经过 INA226 电流、功率寄存器的值,一律先转int16_t再做乘法。另外,分流电压寄存器同样是有符号的,需要判断电流方向时直接读 0x01 寄存器,符号位一眼就能看出来。
4.4 一次 I2C 事务读多个寄存器失败
现象:有人图省事,想一次 I2C 连续读四个数据寄存器,结果只有第一个寄存器值正确,后面全返回 0xFF 或者上上次的数据。
原因:INA226 的寄存器指针不支持自动递增,每次 I2C 读事务只能读当前指针指向的那一个寄存器。用HAL_I2C_Mem_Read时,每次调用都会先写寄存器地址再读数据,所以多次调用没问题;但如果自己拼 I2C 帧、想一次 burst 读多字节,就和芯片行为不匹配。
解决:每次读寄存器都单独发一次HAL_I2C_Mem_Read,不要试图一次事务搬多个寄存器的数据。如果嫌调用次数多,就在连续读之前先写寄存器指针,再在同一个事务里重复读取,但应用层代码会更绕。
4.5 校准值写不进去,或读出来数据乱跳
现象:校准寄存器写入后,电流数值偏差很大,或者数据在相邻两次读取之间跳变明显。
原因:校准值计算公式里的 0.00512 是固定常数,单位是伏特,和内部基准相关。有次我把 0.00512 错记成 0.512,校准值大了 100 倍,电流读数整体偏小两个量级。另外,电流 LSB 选得太接近理论极限时,CAL 寄存器截断误差会被放大。数据跳变则多半是 I2C 线路上干扰,或者读取时芯片还在转换中。
解决:校准值算完后,用最大预期电流反推验证 LSB 是否合理,再实测一个已知电流对比。比如恒流源给 1.000A,读出 0.998A,误差 0.2%,说明校准没问题。转换中的数据跳变,是连续模式下正常现象,读到的永远是上一次转换完成的结果,不是错误。
5. 让读数稳定可用:模式、平均与读回策略
5.1 连续模式和触发模式的选择
INA226 上电默认就是连续分流加总线测量,这也是多数应用直接能用的状态。触发模式适合低功耗场景:每次需要数据时,往配置寄存器写触发位,芯片转换完进入待机,适合电池供电、间歇上报的设备。
我的建议是前期调通用默认模式,把电压电流读出来、校准验证通过,再回头优化功耗。直接在上电就切触发模式,会引入“触发后要等多久才能读”的问题,调试时多一个变量。
5.2 平均次数与转换时间的取舍
配置寄存器里有一组平均位,可以把多次转换结果取平均,换噪声降低。平均 4 次、16 次、64 次在噪声明显的板子上差别很大,尤其是采样电阻压降只有几毫伏时,单次转换的抖动肉眼可见。
代价是转换时间线性累加。比如单次转换 1.1ms,平均 16 次后接近 18ms。对电池监测、电源老化测试这类慢变化量,这个延迟无所谓;但如果要做电机瞬态电流监测,就尽量用短转换时间、少平均,靠外部滤波。
实际调节时,不要凭感觉填平均次数,先在目标电流下看原始分流电压,读取寄存器 0x01,把值换算成毫伏,噪声幅度清楚以后再决定平均位数。我看到过有人 256 次平均用在动态电流测量上,波形完全被抹平,那不是滤波,是低通到失真。
5.3 读回策略:不要在转换中途拆数据
连续模式下,四个数据寄存器是独立更新的,读总线电压和电流之间,可能跨了一次转换。对于一般监测场景,这点时间差无所谓。但如果要做功率积分、或者四个量严格同步采集,就要在业务层做对齐。
常见做法是:先写配置寄存器切到触发模式,触发一次转换,等转换完成标志或者延时足够时间后,连续读四个寄存器。触发模式下四个寄存器是同一轮转换的结果,数据一致性好。
延时时间怎么定?按转换时间表查单次转换最长时间,再留 20% 余量。没有标志位可查时,延时是最土但最有效的办法。
5.4 报警输出:给 I2C 轮询减负
如果系统不要求实时读电流,可以让 INA226 自己盯着阈值,超限后拉低 Alert 引脚,MCU 这边用一个 GPIO 外部中断响应。配置寄存器里使能电流报警,往 0x07 寄存器写报警阈值,阈值的单位就是电流 LSB。比如阈值 2A、LSB 100µA,0x07 就写 20000。
这个功能把“频繁读 I2C”改成“等中断”,对 STM32 的负载压力小很多。不过要注意,报警清除需要读 0x06 寄存器来复位,不然报警引脚一直锁死,中断会不停触发。
6. 往深一层:多通道轮询与差分布板验证
6.1 四个地址轮询所有通道
一根 I2C 总线上最多四颗 INA226,靠 A0、A1 区分。轮询代码只需要把地址做成参数:
const uint8_t ina_addr[4] = {0x40, 0x41, 0x42, 0x43}; for (int i = 0; i < 4; i++) { uint16_t bus_raw = INA226_ReadRegEx(ina_addr[i], INA226_REG_BUS); float bus_v = bus_raw * 0.00125f; printf("ch%d bus=%.3fV\n", i, bus_v); }要点是读写函数里的设备地址改成参数传入,寄存器操作的流程完全复用。四颗芯片挂同一根总线时,上拉电阻要相应调小一点,我一般从 4.7k 换到 2.2k,防止总线负载过大导致边沿变缓。
6.2 采样电阻的差分布板验证
分流电压只有几十毫伏,PCB 走线压降和地线噪声都会直接进入测量。正确接法是采样电阻两端各引两条线:一条走主电流,一条走信号到 IN+ 和 IN-,也就是开尔文法。信号线要从采样电阻的内侧引出,不要引出点在负载端,否则负载电流在走线上的压降也会被算进分流电压里。
我验证 INA226 是否准,不只是看软件读数。板子回来后,先用恒流源给固定 1A 电流,对比 INA226 电流读数和恒流源显示值;再用万用表量采样电阻两端电压,除以阻值核对分流电压寄存器。两个独立验证都通过,才敢说校准没问题。
从那以后,我每次拿到新板子都强制先走一遍 I2C 地址扫描,再读一次 0xFE 设备 ID 寄存器确认通信稳定,最后才调业务参数。这套流程帮我少拆了好几次板子,也把很多玄学问题挡在门外。希望帮到你。
本文还有配套的精品资源,点击获取