资讯动态

STM32操作IIC控制INA226:16位电流电压监测驱动实战

发布时间:2026/10/16 7:29:50 来源:尧图企业网站定制
简介STM32通过IIC协议控制INA226电流检测芯片的驱动源码基于两线制IIC总线仅需SDA与SCL两条信号线即可完成主从通信专为物联网、嵌入式控制等领域需要精确电流电压监测的开发者准备。资源包共两个文件分别是头文件和C源文件压缩后仅4KB结构精简可直接加入STM32的HAL库或LL库工程编译使用减少底层驱动开发时间。INA226是一款高精度低功耗的电流电压监测器能实时采样电流和电压通过IIC接口可灵活配置采样电阻、量程与测量频率适用于电池管理、电源监控、负载开关控制和工业仪表等场景。驱动实现了采样电阻、电压量程及测量频率等关键寄存器的配置封装了IIC初始化、寄存器写入、数据读取等核心函数并对通信中的应答位、超时等异常做了处理便于集成后快速排查问题。已有6639人学习下载代码简洁可直接复用能加速STM32产品中电流电压监测功能的开发也便于理解INA226寄存器配置与IIC通信细节。1. STM32 操作 IIC 控制 INA226给电源板装一个 16 位电流电压监视器做电源调试的时候最烦的就是想量某一路负载的实时电流。万用表要串进去得先断电、拆线、焊跳线钳形表又跟不上瞬态变化。这套 STM32 操作 IIC 控制 INA226 的驱动资源就是专门治这个毛病的INA226 负责采样分流电阻两端的微小压差把电流、总线电压、功率全部换算成 16 位数字STM32 只需要通过 IIC 总线把配置写进去再周期性地把结果读回来。它适合电池管理、功耗统计、电源老化监测这类场景也适合想把电流采样做成“数据”而不是“读数”的嵌入式开发者。整条路线拆成三块寄存器怎么配、IIC 时序怎么写、上电后怎么排查。2. INA226 寄存器与 STM32 IIC 时序先拆开测量链路的两个黑匣子INA226 是一颗常见的双向电流/功率监测芯片内部带 16 位 ADC外部只需要一个分流电阻和一个电源轨。它不像普通 ADC 那样等主控发启动信号而是自己持续采样结果放在几个只读寄存器里主控要做的就是把校准寄存器写好然后定时去读。这套思路决定了代码骨架初始化只写两个寄存器运行时不阻塞地轮询。2.1 INA226 寄存器布局与配置流程INA226 的寄存器不算多工程里真正常用的就下面这几个。配置寄存器0x00和校准寄存器0x05是上电后必须写的其余按只读对待。告警相关的 0x06、0x07 在不需要中断输出时可以先不管。寄存器地址名称读写作用0x00ConfigurationR/WADC 模式、转换时间、平均次数0x01Shunt VoltageR分流电压2.5µV/LSB有符号0x02Bus VoltageR总线电压1.25mV/LSB无符号0x03PowerR功率功率 LSB 电流 LSB × 250x04CurrentR电流LSB 由校准寄存器决定0x05CalibrationR/W校准值决定电流结果的倍率0x06 / 0x07Mask/Enable、Alert LimitR/W告警产生与阈值配置寄存器 0x00 是 16 位高位 4 位管平均次数接下来 3 位管分流电压 ADC 转换时间再 3 位管总线电压 ADC 转换时间低 3 位是连续测量/触发模式。复位默认值是 0x4127我一般直接沿用16 次平均、1.1ms 转换时间、连续测量。平均次数拉高以后读回来的抖动明显变小适合功耗统计但代价是更新频率变低做瞬态抓取的人不会这么配。校准寄存器是整套配置里最容易算错的地方。电流 LSB 最大预期电流 / 32768校准值 0.00512 /电流 LSB × 分流电阻阻值。举例最大预期电流 2A分流电阻 100mΩ电流 LSB 2 / 32768 61.035µA/bit校准值 0.00512 /61.035µA × 0.1 839整数部分直接写进 0x05。校准寄存器是高字节在前低字节在后写 16 位数据时顺序不能颠倒否则芯片会当成两个不同寄存器去解析。这套资源里的初始化函数把上述公式封装好了传入分流电阻和最大电流自动算 LSB 和 CAL再写配置寄存器和校准寄存器。要提醒的是 CAL 是整数除法结果有小数时要先四舍五入如果小数部分超过 0.5说明电流 LSB 选得不够整可以调整最大电流期望值让 LSB 更靠近 2 的幂次附近这会影响电流读数末尾几位的稳定性。2.2 STM32 IIC 工作模式选择与初始化参数INA226 的通信接口是标准 IIC地址由 A0、A1 引脚决定。两个引脚都接 GND 时地址是 0x40组合后一共有 0x40 到 0x43 四个地址一块主控最多能挂四条电源轨。我习惯用 0x40挂在 I2C1 上SDA 和 SCL 各接一个 4.7kΩ 上拉到手板电源总线速度 400kHz。INA226 支持 400kHz 快速模式对这个应用来说已经足够不建议继续往上提高速率在稍长一点的排线上很容易出现沿变缓、误采样。STM32 接 IIC 有两种常见做法硬件 I2C 外设或者 GPIO 模拟。硬件外设的优点是 CPU 占用低但不同系列芯片的引脚复用经常打架总线异常时寄存器状态难查。我在这套驱动里直接选 GPIO 模拟原因很实际INA226 读写量一天也就几 KBCPU 花在翻转 GPIO 上的时间可以忽略而模拟时序在逻辑分析仪上每一根沿都看得见排错成本低很多。参数取值说明IIC 速率400kHz快速模式满足电源监控吞吐SDA / SCL开漏输出 外部上拉避免推挽输出互相顶牛设备地址0x40A1A00四选一分流电阻100mΩ按被测电流选功耗约 I²R采样平均16 次读值平稳损耗小初始化时 GPIO 要配成开漏不能在引脚上直接输出强电平。开漏加外部上拉是 IIC 的硬件规范也是多设备共总线的基础配置如果用推挽输出两个设备同时驱动一根线时谁强谁弱就可能烧引脚。配置完 GPIO 后只要再给寄存器写入准备好总线就是一组完全可控的逻辑电平下一步直接照时序去翻转。3. 手写 IIC 驱动把寄存器读写翻译成可复现的 C 代码既然选了 GPIO 模拟驱动就从最小单元开始拉高拉低 SCL/SDA、产生 START/STOP、收发一个字节、处理 ACK。把这些串起来再封装成“先写寄存器指针、再读两字节”的读函数INA226 就被当成一个普通外设来用了。代码使用某系列标准外设库换成别的系列时只要改引脚宏定义就行。3.1 寄存器读写基础函数START / STOP / ACK 时序下面的代码把 SCL 放在某端口 6 脚SDA 放在某端口 7 脚。HAL 图方便直接用寄存器操作写 BSRR 和 BRR读数据用 IDR这样每一条指令都是单个 GPIO 操作时序不会被库函数拖慢。#include stm32f10x.h #define INA226_ADDR 0x40 #define I2C_SCL_H() GPIOB-BSRR GPIO_Pin_6 #define I2C_SCL_L() GPIOB-BRR GPIO_Pin_6 #define I2C_SDA_H() GPIOB-BSRR GPIO_Pin_7 #define I2C_SDA_L() GPIOB-BRR GPIO_Pin_7 #define I2C_SDA_READ() ((GPIOB-IDR GPIO_Pin_7) ! 0) static void i2c_delay(unsigned int n) { while (n--); } static void i2c_start(void) { I2C_SDA_H(); I2C_SCL_H(); i2c_delay(10); I2C_SDA_L(); /* SCL 高电平期间SDA 由高跳低 */ i2c_delay(10); I2C_SCL_L(); i2c_delay(10); } static void i2c_stop(void) { I2C_SDA_L(); I2C_SCL_H(); i2c_delay(10); I2C_SDA_H(); /* SCL 高电平期间SDA 由低跳高 */ i2c_delay(10); } static int i2c_write_byte(uint8_t dat) { for (int i 0; i 8; i) { if (dat 0x80) I2C_SDA_H(); else I2C_SDA_L(); dat 1; I2C_SCL_H(); i2c_delay(10); I2C_SCL_L(); i2c_delay(10); } /* 第 9 个时钟主机释放 SDA读从机 ACK */ I2C_SDA_H(); i2c_delay(5); I2C_SCL_H(); i2c_delay(10); int ack I2C_SDA_READ(); I2C_SCL_L(); i2c_delay(10); return ack; } static uint8_t i2c_read_byte(uint8_t ack) { uint8_t dat 0; I2C_SDA_H(); for (int i 0; i 8; i) { dat 1; I2C_SCL_H(); i2c_delay(10); if (I2C_SDA_READ()) dat | 0x01; I2C_SCL_L(); i2c_delay(10); } /* 主机回 ACK(0) 继续读回 NACK(1) 代表最后一个字节 */ if (ack) I2C_SDA_H(); else I2C_SDA_L(); I2C_SCL_H(); i2c_delay(10); I2C_SCL_L(); i2c_delay(10); I2C_SDA_H(); i2c_delay(10); return dat; }时序的核心判断只有两条SCL 为高时SDA 上的电平变化代表 START 或 STOPSCL 为高时SDA 被读取的一位数据才是有效数据。所以翻转顺序永远是“先摆 SDA再拉高 SCL采样后拉低 SCL”。ACK 那一拍也要等 SCL 拉高再去读 SDA如果拉高的同时立刻读上升沿抖动会给你一个假电平。i2c_delay(10) 在 72MHz 下大约是十几微秒IIC 只要求最短保持时间没有严格上限实际值不需要精确。有了字节收发读寄存器就只是“写指针 重复起始 读两字节”的事。读写之间的那一拍 START是 IIC 协议里的重复起始不是 STOP 后再 START如果先 STOP 再 START总线会被释放一次多设备共存时容易被其他设备插一脚。uint16_t ina226_read_reg(uint8_t dev_addr, uint8_t reg) { uint16_t val 0; i2c_start(); i2c_write_byte((dev_addr 1) | 0); /* 7 位地址左移 1 位末位 0 表示写 */ i2c_write_byte(reg); /* 写入寄存器指针 */ i2c_start(); /* 重复起始切换读方向 */ i2c_write_byte((dev_addr 1) | 1); val (uint16_t)i2c_read_byte(1) 8; /* 高字节回 ACK */ val | i2c_read_byte(0); /* 低字节回 NACK */ i2c_stop(); return val; }读两个字节时第一个字节回 ACK 表示“我还要继续读”第二个字节回 NACK 表示“到此结束”。如果顺序搞反第二个字节回 ACK从机会继续拉总线低位数据可能被下一次 START 冲掉。这个函数对四个地址都通用只要把 dev_addr 换成 0x40 到 0x43 之间的值即可。3.2 配置与读取电流电压校准值初始化函数要写两块寄存器0x00 配置0x05 校准。把 2A 满量程、0.1Ω 分流电阻的场景硬编码成宏放在文件顶部方便后面改参。#define R_SHUNT 0.1f /* 100mΩ 采样电阻 */ #define MAX_CURRENT 2.0f /* 最大预期电流 2A */ #define CONFIG_REG 0x00 #define CALIB_REG 0x05 #define CONFIG_VALUE 0x4127 /* 16 次平均连续测量 */ static float current_lsb; void ina226_init(uint8_t dev_addr) { current_lsb MAX_CURRENT / 32768.0f; uint16_t cal (uint16_t)(0.00512f / (current_lsb * R_SHUNT) 0.5f); /* 写配置寄存器 */ i2c_start(); i2c_write_byte((dev_addr 1) | 0); i2c_write_byte(CONFIG_REG); i2c_write_byte(CONFIG_VALUE 8); i2c_write_byte(CONFIG_VALUE 0xFF); i2c_stop(); /* 写校准寄存器 */ i2c_start(); i2c_write_byte((dev_addr 1) | 0); i2c_write_byte(CALIB_REG); i2c_write_byte(cal 8); i2c_write_byte(cal 0xFF); i2c_stop(); }末尾的 0.5f 是为了让除法结果在强制转整型前先四舍五入。直接截断会损失 1 个 LSB 的校准精度在 2A 满量程下影响不大但先舍入再转型是好习惯。写 16 位寄存器时寄存器地址放最前面然后是数据高字节最后是数据低字节这个顺序在芯片手册里有明确说明不要凭感觉调整。读电流和读功率的封装集中在下面几个函数。电流寄存器 0x04 存的是有符号补码分流方向反着时会读到负值所以最后用 int16_t 转一下再乘 LSB总线电压 0x02 是无符号不需要转负号。float ina226_read_bus_voltage(uint8_t dev_addr) { uint16_t raw ina226_read_reg(dev_addr, 0x02); return (float)raw * 0.00125f; /* 1.25mV / LSB */ } float ina226_read_current(uint8_t dev_addr) { uint16_t raw ina226_read_reg(dev_addr, 0x04); return (float)(int16_t)raw * current_lsb; } float ina226_read_power(uint8_t dev_addr) { uint16_t raw ina226_read_reg(dev_addr, 0x03); return (float)(uint16_t)raw * current_lsb * 25.0f; /* 固定倍率 */ } float ina226_read_shunt_voltage(uint8_t dev_addr) { uint16_t raw ina226_read_reg(dev_addr, 0x01); return (float)(int16_t)raw * 0.0000025f; /* 2.5µV / LSB */ }这些函数都把 dev_addr 作为显式参数而不是写成全局变量。目前只挂一片感觉多此一举等下次加第二路电源轨时直接复用参数就能少改好多代码。功率寄存器里的 LSB 是电流 LSB 乘以 25这是芯片内部写死的倍率不要自己用电压乘电流去反推寄存器已经包含内部校准关系自己算反而多一道舍入误差。到这里驱动主链路已经通了上电初始化一次主循环里每隔 10ms 读一组电压、电流、功率。下一章要讲的是这条链路上最容易翻车的地方。4. 常见问题与避坑IIC 挂在 0x40 的四个真实翻车现场GPIO 模拟 IIC 跑通只花了一个下午真正耗时间的反而是上电后那些“看起来正常但数据不对”的状态。下面四条是这个项目里真实遇到过的坑按现象、原因、解决顺序记下来每一条都对应一个具体的调试动作。4.1 硬件 I2C 死锁总线被从机拉死主控卡在事件等待现象第一次读寄存器很正常第二次再读就卡住逻辑分析仪看到 SDA 一直被拉低SCL 只出来半截时钟。用硬件 I2C 外设时表现为等待事件标志超时程序死等。原因硬件 I2C 外设做连续读写时如果主机在从机 ACK 尚未结束时启动了 STOP从机会把 SDA 拉低不放总线进入死锁。这个状态不会自己恢复因为 SCL 已经没有时钟了。解决我后来统一改用 GPIO 模拟驱动NLOCK 在这条驱动里几乎不会出现如果工程强制用硬件 I2C初始化前加一段总线恢复序列把 SCL 拉出 9 个时钟期间不断释放 SDA清掉从机内部残留状态。所有收发函数都套一个 50ms 超时超时后跳出并重新初始化别让监控程序死等。static int i2c_send_with_timeout(uint8_t val) { int timeout 5000; while (timeout--) { if (i2c_write_byte(val) 0) return 0; i2c_stop(); i2c_delay(100); } return -1; }超时逻辑看着笨但能挡住九成通信异常。要注意 i2c_write_byte 返回值要能表达总线忙否则超时永远不会触发。4.2 电流值错得离谱校准寄存器少除了一次 2048现象1A 负载下读电流寄存器得到 16384乘以电流 LSB 后是 1.000A看起来正常换到另一块板同样负载突然变成 2048算下来 125mA差了八倍。原因分流电压寄存器 0x01 和电流寄存器 0x04 之间存在固定关系电流 分流电压 × CAL / 2048。这个 2048 是内部 ADC 的固定系数不是常规的 1024 或 4096。第二块板上我偷懒去读分流电压 0x01再手动乘 CAL结果漏了除法。解决封装读电流函数时直接把 0x04 当最终结果芯片已经把 2048 系数算进去了。0x04 的数据就是“电流寄存器原始值 × 电流 LSB”的真实电流不要再叠加任何换算。如果手头只有分流电压寄存器就按完整公式做别在脑内约分。4.3 7 位地址和 8 位地址左右颠倒现象逻辑分析仪上设备地址在 0x40 和 0x80 之间反复横跳读写第一次成功第二次就失败有时地址对了但应答异常读回来全是 0xFF。原因INA226 的地址引脚只决定 7 位地址 0x40IIC 协议里发送时还要左移一位末位附带读/写标志所以总线上实际出现的是 0x80写和 0x81读。第一版驱动把 dev_addr 直接写成 0x80再整体左移等于地址翻了一倍从机根本不响应。解决变量里统一存 7 位地址 0x40发送时现场移位读函数里那句(dev_addr 1) | 1就是规则落地。HAL 库的 HAL_I2C_Mem_Read 接收的同样是 7 位地址而自写的 i2c_write_byte 要求 8 位地址两者混用时最容易错封装层进门处加一行注释标明当前函数收的是哪一位。4.4 总线电压一直为零共地与 VBUS 接法现象电流读数正常但总线电压寄存器 0x02 永远为 0功率也是 0。用万用表量 IN 和 IN- 之间明明有 3.3V。原因INA226 的总线电压采样的是 VBUS 引脚对 GND 的电压不是 IN 对 IN- 的差。把 VBUS 接到了负载电源正极但负载负极和 INA226 的 GND 没有共地VBUS 引脚实际悬空。解决把负载负极、INA226 的 GND、STM32 的 GND 全部接到同一个参考地。VBUS 接负载电源正极IN、IN- 串联在电源和负载之间电流从 IN 进 IN- 出。共地问题排查起来比看代码更费时间因为画板时默认所有地都是同一个网络实际插件测试时常见。5. 校准与验证用万用表做交叉校验不用再调电位器5.1 两点校准的误差系数驱动能读出一堆数字不算完数字准不准才是关键。INA226 的电流精度依赖两个外部因素分流电阻实际阻值和电流 LSB 的选取。分流电阻标称 100mΩ实际可能是 99.8mΩ 或 100.3mΩ这 0.5% 偏差会直接进电流结果。我常用的校准方法是两点交叉校验。准备一个电子负载先设 0.5A再设 1.5A分别记下万用表串在负载回路里的读数和 INA226 给出的读数。float k (meter_1_5 - meter_0_5) / (ina_1_5 - ina_0_5);k 是误差系数用它修正电流 LSB比只做单点偏置准得多因为它把分流电阻的温漂和布局电阻都包含进去了。校准完成后把修正后的等效分流电阻替换掉 R_SHUNT 宏重新编译烧录再跑一遍 0.5A 和 1.5A 确认。整个过程不需要电位器也不需要去刮电路板上的电阻。5.2 稳定性验证与读数抖动验证时我习惯连续读 100 次每次间隔 10ms把最大值和最小值之差作为抖动指标。配置成 16 次平均、1.1ms 转换时间时100 次读数的抖动通常能压在 1 到 2 个 LSB 以内如果超过 3 个 LSB先检查电源纹波再检查 IIC 线上的干扰不要先怀疑芯片。负载电流万用表读数INA226 校准前校准后误差0.500A0.502A0.481A0.3%1.500A1.505A1.453A-0.2%这个表只是其中一块板的数据不同板子不同环境结果会不一样校准的意义就在于把这次的差异吃掉。从那以后我每次换新板子都强制先把校准流程跑完再交给测试省下来的返工时间远超那十分钟。希望这套 STM32 操作 IIC 控制 INA226 的驱动和排错思路能帮你少翻几次车。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑