资讯动态

STM32驱动INA226:I2C接口电流电压功率监测工程详解

发布时间:2026/10/16 3:40:51 来源:尧图企业网站定制
简介INA226.rar是一套基于STM32微控制器的INA226电流与功率监控完整驱动工程面向嵌入式开发者、电源管理系统设计者及传感器应用学习者。芯片支持最高260V电压与40A电流测量可满足电池管理、工业自动化及负载检测等场景的电流电压采集需求。工程共151个文件压缩包约2.97MB其中包含36个h头文件、35个c源码文件以及编译生成的o目标文件、map内存映射与axf调试文件uvprojx与uvoptx为Keil MDK工程配置直接导入即可编译调试。代码基于标准外设库展开涉及stm32f10x定时器、ADC、I2C、USART等外设模块有助于梳理INA226寄存器配置、启动测量、数据读取的完整驱动流程。已有4709人学习下载对于需要在高性能低功耗系统中精确监控功率的开发者是一份可直接参考运行的实战样例。1. 用 STM32 读 INA226把电源监测做成一件十分钟能搞定的事接到一个电池供电的设备最头疼的不是写业务逻辑而是验证功耗到底是不是手册上写的那个数。拿万用表串电流读数跳、要手抄、还容易把表笔怼短路用电子负载更麻烦得把整块板子拆下来单独供电原本的运行环境全变了。INA226 这颗 I2C 接口的电流电压功率传感器就是为了这个场景准备的一颗 16 位 ADC 芯片总线电压最高能测 36V分流电压分辨率 2.5µV配合 STM32 的 C 语言工程在 Keil MDK 里编译烧进去就能从串口打出实时的电压、电流、功率曲线全程不用断开负载回路。这个资源包就是一个完整的 INA226 STM32 传感器驱动工程适合拿来改造成电池放电测试、电源轨掉电监测或老化打点工具也适合刚接触 I2C 传感器驱动的开发者当样板工程读。2. 项目概览与技术选型INA226 的寄存器地图和工程目录2.1 INA226 的工作原理与核心寄存器INA226 采用的是“外部分流电阻加内部 16 位 ADC”的结构在电源回路里串一颗毫欧级电阻常见 2mΩ 到 20mΩ芯片测量这颗电阻两端的压降得到分流电压Shunt Voltage同时测量 IN- 对 GND 的总线电压Bus Voltage。电流和功率并不是 ADC 直接采出来的而是芯片根据你在校准寄存器里写的一个系数实时算出来的。这一点和很多只输出 ADC 原始值的传感器不一样也决定了驱动里第一步不是读数据而是先写校准。驱动代码里反复要用到的寄存器就那么几个我习惯先把它们列成一张对照表钉在头文件注释里写代码时不用反复翻手册地址寄存器方向说明00hConfiguration读写转换时间、均值、工作模式、软复位01hShunt Voltage只读分流电压LSB 2.5µV有符号02hBus Voltage只读总线电压LSB 1.25mV无符号03hPower只读功率LSB 25 × 电流 LSB04hCurrent只读电流LSB 由校准寄存器决定05hCalibration读写校准值不写它电流和功率永远为 006hMask/Enable读写告警使能、锁存、极性配置07hAlert Limit读写告警阈值3Eh / 3FhManufacturer ID / Device ID只读0x5449 / 0x2260移植时拿来验证通信配置寄存器00h是驱动初始化时最重要的一个 16 位寄存器它把复位、均值、转换时间、工作模式全部打包在一个字里。位段拆开看其实很规整位字段作用15RST写 1 软复位自清零14~12AVG[2:0]平均次数1/4/16/64/128/256/512/102411~9VBUSCT[2:0]总线电压转换时间8~6VSHCT[2:0]分流电压转换时间5~3MODE[2:0]关断、触发、连续模式选择2~0保留读出为 0转换时间的编码规则是 000 对应 140µs001 对应 204µs010 对应 332µs011 对应 588µs100 对应 1.1ms往后依次是 2.116ms、4.156ms、8.244ms。驱动里选多少合适取决于你的采样周期和噪声要求老化打点这种慢场景可以选 8.244ms 加高平均次数做动态电流分析就选 140µs 关掉平均后文初始化代码会演示一个折中值。2.2 Keil MDK 工程结构与文件职责解压后是一套标准的 Keil MDK 工程目录分层和大多数 STM32 传感器项目一致BSP 放驱动、User 放业务、MDK-ARM 放编译工程INA226/ ├─ Core/ # 启动代码、中断服务、系统时钟 │ ├─ main.c # 初始化入口与主循环 │ └─ stm32f1xx_it.c # 中断处理 ├─ Drivers/ │ ├─ CMSIS/ # 内核与芯片寄存器定义 │ ├─ HAL_Driver/ # HAL 库源码 │ └─ BSP/ │ ├─ bsp_i2c.c # I2C 引脚初始化与底层读写封装 │ ├─ ina226.c # INA226 驱动初始化、校准、数据读取 │ └─ ina226.h # 寄存器宏、结构体、接口声明 ├─ User/ │ ├─ app_monitor.c # 采样调度与数据打印逻辑 │ └─ retarget.c # printf 串口重定向 └─ MDK-ARM/ # Keil MDK 工程文件 └─ INA226.uvprojxmain.c 负责时钟和 I2C 的初始化app_monitor.c 是业务层决定什么周期读一次、数据打到哪里ina226.c 只做传感器相关的事不掺和业务逻辑。这种分法在只跑一个板子时看不出好处一旦要把同样一颗芯片换到另一个单片机平台或者在同一块板子上把采样周期从 100ms 改成 10ms你就知道边界清晰有多省事——业务层的改动完全不用碰驱动文件。2.3 移植思路驱动只依赖两个底层函数ina226.c 在设计上只依赖 bsp_i2c.c 里暴露的两个函数往寄存器写一个字、从寄存器读一个字没有直接在驱动里调用 HAL 库的 I2C 接口。这么做的好处是换平台时只需要重写 bsp_i2c.cina226.c 一个字节都不用动。还有一个细节要在移植前先确认I2C 地址是 7 位还是 8 位。INA226 的 A0 引脚决定地址A0 接 GND 是 0x40接 VCC 是 0x41这说的是 7 位地址。STM32 的 HAL 库接口里要求传的是带读写位的 8 位地址也就是把 0x40 左移一位变成 0x80。我见过不少人在这一步翻车驱动里写的是 7 位地址结果一直找不到设备排查半天才发现是地址表示方式的问题。3. 核心驱动实现从 I2C 读写到电压电流功率换算3.1 I2C 寄存器读写封装INA226 的寄存器都是 16 位的I2C 传输时高位在前大端所以写一个寄存器要发三个字节寄存器地址、数据高 8 位、数据低 8 位。读一个寄存器则是先发寄存器地址再收两个字节拼成一个 16 位值。底层封装就围绕这两件事写/* bsp_i2c.c底层只保留“写寄存器地址再读/写数据”两个动作 */ static HAL_StatusTypeDef ina226_write_reg(uint8_t dev_addr, uint8_t reg, uint16_t val) { uint8_t buf[3]; buf[0] reg; buf[1] (uint8_t)(val 8); /* INA226 要求大端高位在前 */ buf[2] (uint8_t)(val 0xFF); return HAL_I2C_Master_Transmit(hi2c1, (uint16_t)(dev_addr 1), /* HAL 需要 8 位地址 */ buf, 3, 100); } static HAL_StatusTypeDef ina226_read_reg(uint8_t dev_addr, uint8_t reg, uint16_t *val) { uint8_t tmp[2]; HAL_StatusTypeDef st; /* 先写寄存器地址把读指针指过去 */ st HAL_I2C_Master_Transmit(hi2c1, (uint16_t)(dev_addr 1), reg, 1, 100); if (st ! HAL_OK) return st; /* 再收两个字节拼成 16 位寄存器值 */ st HAL_I2C_Master_Receive(hi2c1, (uint16_t)(dev_addr 1), tmp, 2, 100); *val ((uint16_t)tmp[0] 8) | tmp[1]; return st; }参数说明dev_addr 是 7 位地址函数内部左移一位转成 HAL 能用的形式调用方不用关心 8 位地址的细节超时时间 100ms 按 100kHz 标准模式算绰绰有余如果总线挂了 HAL 会返回超时而不是死等。这两个函数就是整个驱动的地基后面所有寄存器操作都走它们。提示HAL 的 I2C 接口参数类型是 uint16_t但地址位的含义由 I2C_InitStruct 里的 AddressingMode 决定。用 7 位模式时传参就是 dev_addr 1千万别自己再拼一个读写位进去写 1 会被当成广播地址0x00处理。3.2 初始化与校准寄存器计算初始化分两步写配置寄存器确定转换速度和模式再写校准寄存器确定电流和功率的换算系数。很多刚上手的开发者只关注电压不写校准就去读电流结果电流寄存器永远是 0这个坑在避坑章节会细说。/* ina226.c设备结构体与初始化 */ typedef struct { uint8_t addr; /* 7 位 I2C 地址A0GND 时是 0x40 */ float rshunt; /* 分流电阻值单位欧姆例如 0.01 */ float ilsb; /* 电流 LSB单位安培 */ uint16_t calib; /* 校准寄存器值 */ } ina226_t; void ina226_init(ina226_t *dev) { /* 0x0738 展开AVG000(1次), VBUSCT011(588us), VSHCT100(1.1ms), MODE111(分流总线连续测量) */ ina226_write_reg(dev-addr, 0x00, 0x0738); /* 示例参数最大预期电流 5A分流电阻 10mΩ * ILSB 取 200uA/bit量程 200uA * 32767 ≈ 6.55A * CAL 0.00512 / (ILSB * Rshunt) 2560 0x0A00 */ dev-ilsb 0.0002f; dev-calib (uint16_t)(0.00512f / (dev-ilsb * dev-rshunt)); ina226_write_reg(dev-addr, 0x05, dev-calib); }校准公式 CAL 0.00512 / (ILSB × Rshunt) 是手册里给的0.00512 是芯片内部固定的换算常数不要自己改。ILSB 的选择有个经验准则芯片电流寄存器是 15 位有效符号数所以 ILSB 必须满足 ILSB × 32767 大于你预期的最大电流取整到好算的十进制数比如 0.0002、0.0005、0.001。这里有一点容易忽略ILSB 取得越大电流分辨率越粗但量程也越大如果实际负载只有 50mA你却按 10A 量程配 ILSB小电流下的读数会因为分辨率不足而显得跳。3.3 电压、电流、功率换算寄存器原始值不是物理量换算系数由硬件设计决定总线电压 LSB 固定 1.25mV分流电压 LSB 固定 2.5µV电流 LSB 来自你的 ILSB功率 LSB 是 25 倍的 ILSB。C 语言实现时注意符号问题——电流和分流电压寄存器是有符号数要用 int16_t 承接再转 float/* ina226.c数据读取与换算 */ float ina226_read_bus_voltage(ina226_t *dev) { uint16_t raw; ina226_read_reg(dev-addr, 0x02, raw); return (float)raw * 1.25e-3f; /* 1 LSB 1.25mV */ } float ina226_read_shunt_voltage(ina226_t *dev) { int16_t raw; /* 分流电压可正可负 */ ina226_read_reg(dev-addr, 0x01, (uint16_t *)raw); return (float)raw * 2.5e-6f; /* 1 LSB 2.5uV */ } float ina226_read_current(ina226_t *dev) { int16_t raw; /* 电流寄存器是有符号数 */ ina226_read_reg(dev-addr, 0x04, (uint16_t *)raw); return (float)raw * dev-ilsb; /* 换算系数来自校准寄存器 */ } float ina226_read_power(ina226_t *dev) { uint16_t raw; ina226_read_reg(dev-addr, 0x03, raw); return (float)raw * 25.0f * dev-ilsb; /* 功率 LSB 25 * ILSB */ }这套换算的关键理解点在于芯片内部在每次转换结束后会自己把分流电压和校准值乘起来生成电流寄存器的值再把电流乘以 25 生成功率值所以你读到的电流、功率其实是一拍以前算好的中间结果不是从原始 ADC 值现场推导的。我在做动态负载测试时会同时读这三个寄存器并打印偶尔能看到电流寄存器已经变了而总线电压还没跟上这是正常的时序错位不是驱动 bug。3.4 main 里的 10ms 轮询采样驱动函数齐了之后业务层的用法非常简单初始化设备、进循环、按周期读数据、串口输出/* Core/main.c10ms 轮询采样示例 */ int main(void) { ina226_t dev { .addr 0x40, .rshunt 0.01f }; HAL_Init(); SystemClock_Config(); MX_I2C1_Init(); MX_USART1_UART_Init(); ina226_init(dev); while (1) { float u ina226_read_bus_voltage(dev); float i ina226_read_current(dev); float p ina226_read_power(dev); printf(U%.3fV I%.3fA P%.3fW\r\n, u, i, p); HAL_Delay(10); /* 想要更密的数据改成 HAL_Delay(1) */ } }10ms 打一次点是大多数功耗分析的合理起点串口 115200 波特率下每秒 100 行输出完全不会拥堵。如果你要做的是捕捉某个瞬间的浪涌电流这个轮询写法就不够了正确做法是把采样挪进定时器中断或者改用 INA226 的告警引脚触发外部中断后者在二次开发章节会展开。printf 能直接用功劳在 retarget.c 把 fputc 重定向到了 UART1这是 STM32 工程里常见的套路值得保留。提示改采样周期时留意一下配置寄存器里的转换时间。如果 VBUSCT 和 VSHCT 都选了 8.244ms一次完整测量要 16ms 以上10ms 轮询就会读到重复的旧数据这时应该先把配置改成更快的转换时间或者调低平均次数。4. 避坑记录INA226 调试中常见的五个问题4.1 I2C 读回 0xFF 或者一直 NACK现象用逻辑分析仪看总线主机发地址后从机不拉低 ACK 位读任何寄存器都返回 0xFFFF。原因最常见的是地址不对。A0 引脚悬空或焊接不良时芯片实际地址可能既不是 0x40 也不是 0x41另外就是地址表示方式错了把 7 位地址 0x40 直接传给 HAL实际总线上一对比等于在找一个不存在的设备。还有一类原因是 I2C 上拉电阻缺失STM32 的 I2C 引脚是开漏输出没有外部上拉总线根本拉不高。解决先用万用表量 A0 引脚对地电压确认是低还是高然后按“7 位地址左移一位”的规则核对 HAL 传参最后确认 SCL/SDA 到 VCC 有 4.7kΩ 上拉。还有一个绝招读 3Eh 和 3Fh 两个 ID 寄存器能读回 0x5449 和 0x2260 就说明通信链路完全通了。4.2 电压正常电流和功率恒为 0现象总线电压读数准确分流电压也有值但电流和功率寄存器始终是 0或者偶尔蹦出个错得离谱的数。原因校准寄存器 Calibration05h没写或者写成了 0。INA226 的电流和功率是在芯片内部基于校准值算出来的校准寄存器为 0 时乘法结果就是 0写错数值则会导致电流量纲不对比如把 10mΩ 的 rshunt 写成了 0.1读数会整体偏大十倍。解决初始化里先写配置再写校准顺序不能反校准公式 CAL 0.00512 / (ILSB × Rshunt) 里所有单位必须用标准单位安培、欧姆不要拿 mA 和 mΩ 直接代入。我一般会顺手把 dev-ilsb 和 dev-calib 通过串口打印出来确认计算值不是 0 再往下走。4.3 上电复位后数据一动不动现象芯片刚上电时读出来的电压和电流全是 0写配置之后才恢复正常或者每次复位后要等很久才有数据。原因INA226 的复位默认配置里MODE 位段的值是 100对应关断模式shutdown。也就是说芯片上电后默认是不采样的必须写一次配置寄存器把 MODE 改成连续测量模式它才开始干活。寄存器默认值 0x4127 里 AVG 还是 128 次平均转换时间也偏慢看起来就像“卡死”了。解决初始化函数里无条件写一次配置寄存器把 0x0738 这类带连续模式位段的值写进去。注意写配置的时机要在校准之前因为写入新配置会触发芯片重新开始转换流程先校准再写配置会白白浪费一次转换周期。4.4 功率读数是电压乘电流的四倍现象用上位机把总线电压和电流相乘和功率寄存器读回来的值对不上差固定倍数。原因很多驱动是从 INA219 的工程改过来的那边功率换算系数是 20 × ILSB而 INA226 的功率寄存器换算系数是 25 × ILSB。一字之差表现在数据上就是功率整体放大 25/20 1.25 倍不是四倍如果你看到的是四倍那多半是把 25 写成了 100或者把 ILSB 的单位搞错了倍率。解决核对换算代码里功率这一行的系数。INA226 只认 25没有别的选项芯片手册的 Power 寄存器说明里写得很直白换芯片型号时不要想当然沿用老工程的换算系数。4.5 电流值来回跳甚至出现负值现象空载时电流寄存器在正负几百 mA 之间乱跳负载固定时读数也不稳定或者明明接的是正极性读出来却是负值。原因分流电阻压降本身很小10mΩ 电阻过 100mA 电流也只有 1mV接近 ADC 的底噪水平这时候外部干扰、电源纹波、甚至 PCB 上地平面的回流路径都会直接影响读数。负值的另一个常见来源是 IN 和 IN- 的接反了或者分流电阻两端走线不等长引入了寄生电压。解决先在配置寄存器里把 AVG 调到 128 或 256 次平均信噪比立刻上一个台阶然后在 PCB 布局上把分流电阻的两根采样线做成开尔文接法直接从电阻焊盘引出不要共用大电流走线最后确认电流方向——从 IN 流向 IN- 读数为正接反了在软件层面乘以 -1 也不是不行但最好还是硬件改对。5. 二次开发把单路读取改成多通道监测与过流告警5.1 多实例管理与 I2C 地址扩展只要板子上有两颗 INA226分别把 A0 接 GND 和 VCC就能在同一个 I2C 总线上用两个地址管理它们。驱动里我已经把设备信息收进了结构体所以扩展成多实例几乎是白送的/* app_monitor.c双通道监测 */ ina226_t dev[2] { { .addr 0x40, .rshunt 0.010f }, { .addr 0x41, .rshunt 0.010f }, }; for (int i 0; i 2; i) { ina226_init(dev[i]); } while (1) { for (int i 0; i 2; i) { printf(CH%d U%.3fV I%.3fA P%.3fW\r\n, i, ina226_read_bus_voltage(dev[i]), ina226_read_current(dev[i]), ina226_read_power(dev[i])); } HAL_Delay(100); }如果通道数超过两个同一根 I2C 总线就不够用了常见做法是给每路 INA226 配一组 I2C 引脚用两个硬件 I2C 外设或者加一颗 I2C 多路复用器按通道切换总线。多路复用器的用法是在切换前后各加一个 200ns 左右的延时让总线稳定下来再发起传输否则经常会丢第一个字节。两块板子共用一个分流电阻这种错误设计也要避免每颗 INA226 必须有自己独立的分流电阻否则校准值互相干扰读数全是乱的。5.2 过流告警与数据落盘轮询采样在高频场景下会漏事件二次开发的进阶方向是把 INA226 的告警功能用起来。把预期电流上限换算成寄存器值写进 Alert Limit07h在 Mask/Enable06h里使能告警位并设置锁存然后让 ALERT 引脚接到 STM32 的外部中断芯片检测到过流时会主动拉低引脚中断里再去读电流寄存器确认现场数据。这套机制的关键点是锁存模式不锁存的话告警引脚会跟着测量值自动恢复中断可能一闪而过进中断时寄存器已经是正常值了什么都抓不到设成锁存模式后必须读一次 Mask/Enable 清标志告警引脚才会复位这给业务层留足了读取时间。数据落盘方面我一般会在轮询循环里把 printf 的输出同时写进一个环形缓冲再由另一段代码定期把缓冲刷到 SD 卡或者通过 WiFi 模块上报。环形缓冲是这类监测项目的标配它把采样和落盘解耦采样频率就不会被存储介质的写卡时间拖累。做电池放电曲线时这个方案比用上位机直接采数省心得多不用一直挂着电脑设备自己就能跑完整轮测试。从那以后我每次拿到一颗新传感器芯片都会先写一个最小工程验证寄存器映射和读写封装确认能读回 ID 寄存器再谈业务逻辑。这套 INA226 驱动也是这么磨出来的先保证“读数正确”再追求“功能好看”按这个顺序来基本不返工。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑