简介面向使用STM32的嵌入式开发者提供一套完整的INA226电流检测芯片驱动源码利用IIC总线实现电流与电压的高精度采集。INA226为常用的高精度电流电压监测器适用于电池管理、电源监控以及负载开关控制等典型场景。压缩包内共有两个文件分别是头文件和源文件解压后代码仅4KB左右十分轻量可直接集成到现有工程。驱动基于HAL库编写涵盖IIC初始化、寄存器读写、采样电阻配置以及实时读取等核心功能并加入通信异常处理逻辑方便开发者快速完成移植。该资源已有六千六百四十一人学习下载适合正在调试电源模块或需要做电流采集的工程师参考。阅读整套驱动不但可以熟悉STM32的IIC外设时序也能理解INA226的内部寄存器映射机制帮助在硬件调试时更快定位问题。1. 用STM32的IIC读INA226五分钟点亮一晚上调不出电流用STM32的IIC去读INA226看起来是五分钟就能点亮的活实际上很多人卡在“电压能读出来电流永远是0”这一关。这颗10脚芯片把测量结果全部放在寄存器里给的是原始值不是工程值分流电压要乘2.5μV总线电压要乘1.25mV而电流和功率必须先把校准寄存器算对否则读回来的就是一串0。这份驱动代码按“设备结构体→寄存器读写→工程值换算”拆开讲把IIC地址、时序和配置参数一次说清。适合刚接触INA226、想做电源监测的嵌入式开发者也适合被电流数据折磨过、想确认自己没配错的硬件工程师。2. 硬件IIC还是软件IIC先把时序和寄存器地图摊开2.1 硬件IIC还是软件IIC先回答三个问题两块画得一样的板子一块用硬件IIC读得顺另一块一上电就卡死在HAL_I2C_IsDeviceReady里这不是芯片体质差异是总线上电瞬间的毛刺把硬件IIC的状态机带偏了。这类问题在INA226这种纯从机、地址固定、读取频率又低的场景里特别常见所以选型前先回答三个问题。第一这条IIC总线上还挂不挂别的器件如果只挂INA226一颗我认为软件IIC更合适读取频率在毫秒级模拟时序完全够用出问题还能直接把GPIO拉低复位总线。第二对读取速率有没有硬要求INA226连续转换模式下140μs左右就能刷新一组分流电压和总线电压串口打印都来不及软件IIC的100kHz~400kHz根本构不成瓶颈。第三调试手段够不够直观软件IIC每一个时钟沿都可以用逻辑分析仪抓出来能直接看到地址应答这对第一次调这颗芯片的人帮助非常大。我一般做法是INA226独占一条总线时用软件IIC十几行代码的事如果总线上还要挂存储、传感器这类多器件再用硬件IIC同时保留一组软件复位兜底。无论选哪种上拉电阻都建议取4.7kΩ到10kΩIIC标准模式上拉电流约3mA400kHz快速模式下1kΩ~4.7kΩ更稳具体看板子上其他器件的负载。2.2 INA226寄存器地图六个必须知道的寄存器INA226一共有10个寄存器但实际驱动里经常打交道的就六个先把地址和用途背下来比对着手册翻快得多。寄存器地址名称读写属性说明00hConfiguration读写转换时间、平均次数、工作模式01hShunt Voltage只读分流电压原始值16位有符号LSB2.5μV02hBus Voltage只读总线电压原始值16位无符号LSB1.25mV03hPower只读功率原始值LSB25×电流LSB04hCurrent只读电流原始值有符号LSB由校准寄存器决定05hCalibration读写校准寄存器不写它电流和功率永远是008h和09h是制造商ID和设备ID可以用来验证IIC通信链路通不通。芯片地址由A0引脚决定A0接地时7位地址是0x40A0接电源是0x41。这个0x40在HAL库里会埋一个坑后面专门讲。所有16位寄存器都是大端序高字节在前、低字节在后。读取时如果分两次读高低字节中间芯片可能已经刷新了数据最好一次性连续读2字节这也是后面封装Mem_Read的原因。2.3 用逻辑分析仪验证时序地址左移的坑从波形里一眼看出来拿到芯片先别急着写完整驱动先用一段最简代码把设备ID读回来配合逻辑分析仪确认总线上到底发生了什么。随便一个几十块的8通道逻辑分析仪就够用采样率不用高IIC 400kHz下20MHz采样绰绰有余。uint8_t id[2] {0}; HAL_StatusTypeDef st; /* 读INA226制造商ID寄存器(08h) */ st HAL_I2C_Mem_Read(hi2c1, (uint16_t)(0x40 1), /* 7位地址左移1位,得到8位地址 */ 0x08, /* 寄存器地址 */ I2C_MEMADD_SIZE_8BIT, /* 寄存器地址宽度 */ id, 2, 100); /* 读2字节,超时100ms */ if (st HAL_OK) { printf(manufacturer0x%04x\r\n, (id[0] 8) | id[1]); }这段代码的关键在参数20x40 1。INA226的从机地址是7位的0x40IIC寻址时最高位放7位地址最低位放读写标志所以HAL接口里必须传8位地址0x80写方向才会变成0x80、读方向变成0x81。直接传0x40波形里地址字节就是0x40/0x41跟从机地址对不上。逻辑分析仪上抓到的读序列大概是START、地址字节0x81、ACK、寄存器地址0x08、ACK、重复START、地址字节0x81、ACK、数据高字节、ACK、数据低字节、NACK、STOP。如果地址字节那一段显示的是0x40而不是0x81不用怀疑就是左移那一下漏了。正常芯片从这里应该读回0x5449设备ID寄存器读回0x2260有了这两个值IIC链路基本就是通的。3. INA226驱动代码实战从原始寄存器到工程值3.1 抽象一个INA226设备结构体参数都放到一块驱动不要写成一堆散函数建议先定义一个设备结构体把IIC句柄、从机地址、分流电阻阻值、电流LSB、功率LSB全部装进去。这样后续多路测量、多块板子复用只要new一个结构体实例就行。/* ina226.h */ typedef struct { I2C_HandleTypeDef *hi2c; /* 硬件IIC时用;软件IIC可换成GPIO句柄 */ uint8_t addr; /* 7位IIC地址,如0x40或0x41 */ float rshunt; /* 分流电阻,单位Ω,常见0.001Ω/0.01Ω */ float cur_lsb; /* 电流LSB,单位A,由最大预期电流算出 */ float pow_lsb; /* 功率LSB,单位W, 25 * cur_lsb */ } ina226_t; typedef struct { int16_t shunt_vol; /* 分流电压原始值,有符号 */ uint16_t bus_vol; /* 总线电压原始值 */ uint16_t power; /* 功率原始值 */ int16_t current; /* 电流原始值,有符号 */ } ina226_meas_t;逻辑上ina226_t保存的是“这颗芯片长什么样、量程怎么设”ina226_meas_t保存的是“某一次读回来的原始值”。分流电压虽然数值上可能为负但在数学上它是双向电流必须用有符号类型总线电压永远为正无符号就够了。参数说明rshunt直接决定校准计算1mΩ还是10mΩ算出来差异很大一定要填实际物料值不要把焊在板子上的标称值抄错。cur_lsb和pow_lsb在初始化时算好后续每次读取直接乘避免在读取函数里重复做除法。3.2 寄存器读写封装HAL接口的三个易错参数基于HAL库封两个底层函数一个写16位寄存器一个读16位寄存器。读写都要先传寄存器地址再传数据所以用HAL_I2C_Mem_Write和HAL_I2C_Mem_Read而不是直接调Master_Transmit。/* ina226.c */ static uint8_t ina226_write_reg(ina226_t *dev, uint8_t reg, uint16_t val) { uint8_t buf[2]; buf[0] (uint8_t)(val 8); /* 高字节在前 */ buf[1] (uint8_t)(val 0xFF); return HAL_I2C_Mem_Write(dev-hi2c, (uint16_t)(dev-addr 1), reg, I2C_MEMADD_SIZE_8BIT, buf, 2, 100); } static uint8_t ina226_read_reg(ina226_t *dev, uint8_t reg, uint16_t *val) { uint8_t buf[2]; if (HAL_I2C_Mem_Read(dev-hi2c, (uint16_t)(dev-addr 1), reg, I2C_MEMADD_SIZE_8BIT, buf, 2, 100) ! HAL_OK) { return 1; } *val (uint16_t)((buf[0] 8) | buf[1]); return 0; }逻辑说明写寄存器时IIC主机先发从机地址8位地址写方向再发寄存器地址然后连续发两个数据字节。buf[0]放高字节是因为INA226寄存器映射是大端序顺序反了配置写进去就是乱的。参数说明I2C_MEMADD_SIZE_8BIT告诉HAL库寄存器地址只有8位INA226所有寄存器地址确实都在00h~09h范围。Size参数是数据字节数这里固定2。超时100ms对毫秒级IIC操作足够宽裕如果总线被拉死这个参数会变成排查超时的线索。3.3 读取与换算电压、电流、功率各乘什么系数读取函数把四个测量寄存器依次读回换算函数再分别乘以各自的LSB。注意读取顺序和大小端保持一致不要把Power和Current搞混。uint8_t ina226_read(ina226_t *dev, ina226_meas_t *m) { uint16_t reg 0; if (ina226_read_reg(dev, 0x01, reg)) return 1; m-shunt_vol (int16_t)reg; /* 01h:分流电压 */ if (ina226_read_reg(dev, 0x02, reg)) return 1; m-bus_vol reg; /* 02h:总线电压 */ if (ina226_read_reg(dev, 0x03, reg)) return 1; m-power reg; /* 03h:功率 */ if (ina226_read_reg(dev, 0x04, reg)) return 1; m-current (int16_t)reg; /* 04h:电流 */ return 0; } float ina226_get_bus_voltage(const ina226_meas_t *m) { return m-bus_vol * 0.00125f; /* 总线电压LSB:1.25mV */ } float ina226_get_shunt_voltage(const ina226_meas_t *m) { return m-shunt_vol * 0.0000025f; /* 分流电压LSB:2.5uV */ } float ina226_get_current(const ina226_t *dev, const ina226_meas_t *m) { return m-current * dev-cur_lsb; } float ina226_get_power(const ina226_t *dev, const ina226_meas_t *m) { return m-power * dev-pow_lsb; }逻辑说明分流电压寄存器是带符号的强制转成int16_t再乘LSB负电流方向才不会算错。总线电压寄存器不带符号直接用uint16_t。功率寄存器在INA226里是16位无符号但如果功率计算出现异常配置这个值也可能读出接近65535的数要结合电流数值判断合理性。换算系数这里最容易出现“差不多先生”bus_vol乘0.00125得到伏特shunt_vol乘0.0000025得到伏特电流寄存器已经是芯片内部用校准系数算好的16位值直接乘cur_lsb就是安培。提示不要用“总线电压÷分流电阻”去算电流。INA226的电流寄存器用的是校准寄存器里的系数加上分流电压计算出来的你手动算会丢掉芯片内部的滤波和定标逻辑数值永远对不上。4. 配置与校准让电流读数不再是玄学的四个关键点4.1 校准寄存器电流不写它永远是0INA226的电流寄存器不是简单的分流电压除以电阻芯片内部需要先知道一个基准——校准寄存器。这个寄存器的值如果保持复位默认值004h电流寄存器和03h功率寄存器读回来的就全是0。这是这颗芯片被骂“玄学”的最主要原因。校准值计算公式是固定的Current_LSB MaxExpectedCurrent / 32768 Calibration 0.00512 / (Current_LSB * Rshunt)为什么除32768因为电流寄存器是16位有符号数最大有效正数为32767量程设计成让满量程电流对应最接近32767的值分辨率最高。举个例子分流电阻1mΩ最大预期电流5ACurrent_LSB 5 / 32768 ≈ 0.0001526A 152.6μA/LSB Calibration 0.00512 / (0.0001526 * 0.001) ≈ 33554 0x8312这个0x8312写进05h寄存器04h电流寄存器读到的原始值乘152.6μA就是实际电流。功率LSB永远是电流LSB的25倍即3.815mW/LSB。实际项目中最大预期电流不是随便填的要结合分流电阻的功率承受能力5A电流流过1mΩ电阻功率损耗是25mW0805封装勉强能扛如果电流更大就得换大封装或者更小阻值否则热漂移会让电流读数越跑越偏。4.2 转换时间与平均次数噪声和响应速度怎么平衡配置寄存器00h里有两个转换时间字段VBUSCT管总线电压、VSHCT管分流电压取值一样都是3位编码。默认值000对应140μs最快的转换001是204μs往后每档大约翻倍最慢的111对应8244μs。编码转换时间000140μs001204μs010332μs011588μs1001100μs1012116μs1104156μs1118244μs如果只测静态电压电流比如电池电压监测转换时间建议拉长到1000μs以上顺便开平均模式读数会稳定很多。如果是动态负载、要看瞬间电流波形用140μs 1次平均最合理代价是数字波动明显。平均模式AVG字段控制芯片内部对连续多次转换结果取平均常见取值从1次到1024次。平均次数并不是越高越好1024次平均意味着每次读到的都是前几百毫秒的累计结果看瞬态响应会被拉成一条平线。我的习惯是电源轨定性测量用64次平均做负载阶跃分析用1次平均。AVG字段的具体编码跟着手册表格走不同批次手册画的位序有差异抄例程时先对着表看一遍。另外MODE字段决定测量模式最常用的两个0b111表示连续测量分流电压总线电压0b011表示单次触发模式触发一次转换一次。低功耗场景用单次触发测完立刻进待机常规场景用连续模式省得每次读之前还要发触发命令。4.3 初始化函数封装把参数固化进代码初始化的顺序有讲究先写配置寄存器再算校准值并写入最后可以回读验证一遍。下面这段代码把量程、采样速度和校准一次搞定。#define INA226_CONFIG_VALUE (0x4127u) /* 例程常用组合配置值 */ uint8_t ina226_init(ina226_t *dev, float imax) { uint16_t cal; /* 1. 配置寄存器:写坏了一天白干,先读后写做保护 */ uint16_t cfg INA226_CONFIG_VALUE; if (ina226_write_reg(dev, 0x00, cfg)) return 1; /* 2. 计算电流LSB和校准值 */ dev-cur_lsb imax / 32768.0f; dev-pow_lsb dev-cur_lsb * 25.0f; cal (uint16_t)(0.00512f / (dev-cur_lsb * dev-rshunt)); /* 3. 写入校准寄存器 */ if (ina226_write_reg(dev, 0x05, cal)) return 1; /* 4. 回读确认,写失败后面全是玄学 */ uint16_t check 0; if (ina226_read_reg(dev, 0x05, check)) return 1; if (check ! cal) return 2; return 0; }逻辑说明0x4127是参考代码里非常常见的一个配置值对应连续测量分流和总线、带一定平均和转换时间组合。我不是让你无脑抄这个魔法数而是要你在手册上把每一位拆开验证一遍确认和当前板子的采样需求匹配。第4步回读是很多人忽略的寄存器写进去不代表芯片就按这个跑了总线上毛刺可能导致写入被从机NACK不检查就往下走后面数据不对根本不知道源头在哪。参数说明imax是最大预期电流而不是额定电流取值偏大会降低分辨率偏小会让电流寄存器溢出。对电源监测这类场景留30%裕量比较合适比如预期峰值4A就按5A算既能覆盖瞬态又不会被溢出的符号位坑到。5. IIC控制INA226的常见坑与排查五条实测翻车记录5.1 五条高频踩坑记录现象、原因、解决坑1地址字节是0x40从机就是不应答。现象是逻辑分析仪上能看到START、地址字节、然后一直等不到ACK。原因是HAL接口的DevAddress参数要传8位地址0x80/0x81直接传7位地址0x40总线上发出去的就是0x40和从机0x40对不上。解决所有读写调用统一用(uint16_t)(dev-addr 1)把7位左移1位。坑2电流和功率寄存器一直读回0。现象是总线电压正常、分流电压正常只有04h和03h是0。原因是校准寄存器没写或者写入了0芯片没有基准去计算电流。解决按0.00512 / (cur_lsb * rshunt)算好值写入05h并在初始化里加回读验证。注意rshunt填的是实际焊上去的电阻值有些板子写的是原理图标识值实际物料差5%就会导致电流读数系统性偏差。坑3分流电压偶尔变成65535附近的大数。现象是电流方向反向或者出现负压差时01h寄存器读回来是一个很大的无符号数。原因是分流电压是16位有符号补码用uint16_t接收再乘LSB就成了65535×2.5μV≈0.16V。解决寄存器值先强转int16_t再乘LSB。总线电压永远无符号不要两种寄存器用同一个类型。坑4硬件IIC跑着跑着卡死在HAL_I2C_IsDeviceReady。现象是上电后第一次读就超时或者运行一段时间后错误码变成BUSY。原因是上电瞬间SDA线毛刺被误判为START从机状态机错乱主机IIC外设的BUSY位不再释放。解决把SCL/SDA临时配置成普通GPIO用软件翻转9个时钟脉冲模拟一次“假时钟”把从机复位再切回复用功能重新初始化。坑5同一块板子换一颗芯片后读数整体偏移。现象是电压电流都线性偏差几个百分点。原因可能不在程序在硬件——新芯片A0引脚没固定地址变了程序读的还是旧地址对应的老从机或者校准寄存器在写入时被总线干扰芯片跑在默认校准状态。解决上电初始化后读一下09h设备ID确认是0x2260再检查05h回读值和预期是否一致两个都对再开始读测量数据。5.2 排查顺序从波形到寄存器一步步缩小范围遇到INA226数据不对我习惯按固定顺序排查不凭感觉乱猜。先看波形逻辑分析仪抓一次读寄存器序列确认地址字节、寄存器地址、ACK位都正常。波形都错就没必要谈数据换算。再看芯片应答读08h和09h确认制造商ID和设备ID这一步直接排除“芯片没焊上”和“地址搞错”。然后看电压读01h和02h两个电压正常说明芯片供电、采样链路都好的。接着看校准读05h确认写入的校准值是否正确、是否被总线干扰改写。最后看电流功率读04h和03h结合LSB换算验证数据合理性。这个顺序从物理层到寄存器层一层层排除基本不会出现拆东墙补西墙的乱试。我见过某开发者跳过波形直接换芯片换了三颗故障依旧最后才发现地址左移那一位错了——这类问题用逻辑分析仪一分钟就能定位。6. 进阶用法用ALERT引脚做硬件过流保护6.1 ALERT引脚怎样接INA226的ALERT引脚是开漏输出手册要求接上拉电阻到电源典型值4.7kΩ。把它接到MCU的一个外部中断引脚上当芯片检测到功率超过阈值时ALERT会拉低MCU在中断里读取寄存器就能拿到报警源。这个功能比主循环轮询可靠得多MCU死机或者任务卡住时ALERT依然能靠硬件拉低去触发一个不可屏蔽的中断在电机控制、充电器这类场景里是实打实的保护。6.2 阈值计算与中断处理阈值寄存器是07hINA226比较的是功率寄存器的值。先算功率LSB再把期望报警功率除以功率LSB得到16位原始值写入07h。假设前面例子中cur_lsb152.6μA那么pow_lsb3.815mW想在12W报警阈值就是12 / 0.003815 ≈ 3146float trip_power 12.0f; /* 报警功率阈值,单位W */ uint16_t trip_raw (uint16_t)(trip_power / dev.pow_lsb); ina226_write_reg(dev, 0x07, trip_raw); /* 07h:报警阈值 */06h是Mask/Enable寄存器把对应“功率超限”的报警使能位写1ALERT脚就接管了报警输出。不同参考代码对这个使能位的位置标注不完全一样动手前务必翻开芯片手册的位域表确认不要直接抄网上例程。中断处理函数里读06h确认报警标志然后按手册流程清掉状态位否则ALERT会一直保持拉低状态中断会不断触发。6.3 验证与自检写完不要直接调高阈值就了事先故意把阈值调低到当前工作功率以下比如正常2W的场景把阈值设成1W此时ALERT应该马上拉低MCU能进入中断。确认触发后再把阈值调回正常工作值以上ALERT恢复高电平才说明整个链路是通的。从那以后我每次做带负载开关或者电机驱动的板子都会先把INA226的ALERT接到一个外部中断上哪怕不启用实际保护逻辑也要用它打个时间戳确认报警链路没断。这个习惯帮我拦住过一次电源短路也帮我少熬过好几个夜。希望帮到你。本文还有配套的精品资源点击获取