简介:这是一套面向嵌入式开发者的INA226电流/功率传感器驱动完整工程,基于STM32微控制器,采用C语言编写,配套Keil MDK开发环境,适合电源管理、电池管理、系统监控及工业自动化等场景下的电流电压采集需求。压缩包共151个文件,约2.97MB,以36个h头文件与35个c源文件为核心,涵盖STM32标准外设库、INA226寄存器配置与数据读取逻辑,另含o、d、crf等编译中间文件及uvprojx、uvoptx工程文件,hex与axf可直接烧录调试,map与lst便于分析内存占用。资源已获4709人学习下载,读者可据此理解INA226数据手册的寄存器设定方法,掌握初始化配置、启动测量、中断处理与数据解析的完整流程,并借助现成工程快速搭建验证环境,缩短从硬件对接到功能调试的周期,对需要监控电流与功率的嵌入式系统设计者具有较高参考价值。1. INA226 电流监测模块:从寄存器配置到串口输出的完整工程如果你手头有一块 INA226 模块,想用 STM32 通过 I2C 读出电压、电流和功率值,却卡在寄存器配置或者数据换算上,这个工程压缩包就是为你准备的。它用 C 语言在 Keil MDK 环境下搭建了一个完整的采集框架,把 INA226 的初始化、校准寄存器计算、数据读取和串口打印串成了一条线。适合正在做电源管理、电池监测或者低功耗设备功耗分析的嵌入式开发者,尤其是那些不想从零啃数据手册、希望直接拿一份能跑的代码来改的人。整个工程不依赖 HAL 库之外的复杂中间件,移植到 F103、F407 这类常见芯片上只需要改几个宏定义。2. 工程结构与 INA226 驱动分层:文件怎么放、依赖怎么理2.1 目录树与模块划分拿到压缩包解压后,根目录下是标准的 Keil MDK 工程结构。核心文件集中在Hardware和User两个文件夹里,这种分法在 STM32 裸机项目里很常见,好处是硬件驱动和业务逻辑不混在一起,换芯片或者换传感器的时候只动Hardware就行。INA226_Project/ ├── CMSIS/ # 内核头文件,Keil 自带 ├── FWlib/ # 标准外设库(或 HAL 库) ├── Hardware/ │ ├── i2c.c / i2c.h # 软件模拟 I2C 或硬件 I2C 封装 │ ├── ina226.c / ina226.h # INA226 寄存器定义与读写函数 │ └── usart.c / usart.h # 串口初始化与 printf 重定向 ├── User/ │ ├── main.c # 主循环,周期读取并打印 │ └── stm32f10x_it.c # 中断服务函数 └── Project.uvprojx # Keil MDK 工程文件ina226.h里用宏定义列出了所有寄存器地址,比如INA226_REG_CONFIG、INA226_REG_SHUNT_VOLTAGE、INA226_REG_BUS_VOLTAGE这些,后面写代码的时候直接引用宏名,不用记十六进制地址。i2c.c里封装了I2C_WriteByte、I2C_ReadByte这类底层函数,INA226 的读写函数再调用它们,这样分层之后,如果从软件 I2C 换成硬件 I2C,只需要改i2c.c的实现,ina226.c一行都不用动。2.2 INA226 的 I2C 地址与通信速率INA226 的 7 位地址由 A1、A0 两个引脚决定,默认都接地时地址是0x40。工程里ina226.h中定义了INA226_ADDR宏,写的是0x40 << 1,因为 STM32 的 I2C 外设要求传入的是 8 位地址(包含读写位)。如果你板子上 A0 接了 VCC,地址就变成0x41,这时候改宏定义就行。通信速率方面,INA226 支持标准模式(100kHz)和快速模式(400kHz)。工程里 I2C 初始化配置的是 400kHz,实际用示波器抓波形,SCL 周期在 2.5us 左右。如果排线比较长或者上拉电阻偏大导致波形上升沿变缓,可以把速率降到 100kHz 试试,这是排查通信失败时最先检查的地方。注意:INA226 的 SDA 和 SCL 都需要接上拉电阻,典型值是 4.7kΩ。有些模块自带上拉,有些没有,买回来先用万用表量一下,不然 I2C 总线拉不高,读出来全是 0xFF。2.3 关键宏定义与可移植性设计工程里把和硬件相关的参数都抽成了宏,放在ina226.h顶部。移植的时候主要改这几个:// ina226.h 中的可配置宏 #define INA226_ADDR 0x40 << 1 // 7位地址左移一位 #define INA226_RSHUNT 0.002f // 采样电阻阻值,单位欧姆,默认2mΩ #define INA226_MAX_CURRENT 10.0f // 预期最大电流,单位安培INA226_RSHUNT和INA226_MAX_CURRENT这两个值直接决定校准寄存器的计算结果。采样电阻是板子上实际焊的那个,常见的有 2mΩ、5mΩ、10mΩ,你得根据自己模块上的丝印或者原理图确认。最大电流是你预期流过的电流上限,用来计算电流分辨率。这两个值填错,读出来的电流值会成比例地偏大或偏小,但电压值不受影响,这是判断校准是否正确的依据。3. 核心驱动实现:寄存器配置、校准与数据换算3.1 初始化流程与配置寄存器写入INA226 上电后需要先写配置寄存器,设定平均次数、电压转换时间、电流转换时间和工作模式。工程里的初始化函数长这样:// ina226.c 中的初始化函数 void INA226_Init(void) { I2C_Init(); // 底层 I2C 初始化,配置 GPIO 和时钟 // 配置寄存器:平均次数16,总线电压转换时间1.1ms, // 分流电压转换时间1.1ms,连续测量模式 // 位域:[15:12]平均次数 [11:9]总线转换时间 // [8:6]分流转换时间 [5:3]工作模式 uint16_t config = 0x4527; INA226_WriteReg(INA226_REG_CONFIG, config); // 根据采样电阻和最大电流计算校准值 INA226_Calibrate(INA226_RSHUNT, INA226_MAX_CURRENT); }0x4527这个值拆开看:最高 4 位0100表示平均 16 次,接下来 3 位101表示总线转换时间 1.1ms,再 3 位001表示分流转换时间 1.1ms,最低 3 位111表示连续测量分流和总线。平均次数越多,读数越稳,但响应越慢。16 次平均下,每次更新周期大约是 16 × (1.1ms + 1.1ms) ≈ 35ms,对于电池监测这种慢变量足够了。如果你要抓快速变化的电流波形,把平均次数降到 1 次,转换时间也调短,更新周期能压到 2ms 左右。3.2 校准寄存器计算:一个容易算错的公式校准寄存器的值决定了电流读数的比例系数,公式在数据手册里写得很清楚,但实际写代码的时候容易把单位搞混。工程里的实现是这样的:// ina226.c 中的校准函数 void INA226_Calibrate(float r_shunt, float max_current) { // 电流LSB = 最大电流 / 32768 float current_lsb = max_current / 32768.0f; // 校准值 = 0.00512 / (电流LSB × 采样电阻) // 0.00512 是数据手册给出的固定常数 uint16_t cal_value = (uint16_t)(0.00512f / (current_lsb * r_shunt)); INA226_WriteReg(INA226_REG_CALIBRATION, cal_value); }这里current_lsb是电流的最低有效位对应的电流值,单位是安培。0.00512这个常数来自数据手册,不用深究怎么来的,记住就行。算出来的cal_value写入校准寄存器后,后面读到的电流寄存器原始值乘以current_lsb就是实际电流。举个例子:采样电阻 2mΩ,最大电流 10A,那么current_lsb = 10/32768 ≈ 0.000305A,cal_value = 0.00512 / (0.000305 × 0.002) ≈ 8390。这个值在 0 到 65535 之间,如果算出来超了,说明采样电阻或者最大电流设得不合理。提示:校准寄存器写入后,电流读数才会正确。如果忘了写这一步,电流寄存器读出来是 0 或者乱跳,但总线电压和分流电压是正常的。这是新手最容易踩的坑之一。3.3 读取电压、电流与功率的完整链路数据读取部分封装了三个函数,分别读总线电压、分流电压和电流,功率寄存器可以直接读,也可以用电压乘电流自己算。工程里用的是直接读功率寄存器的方式:// ina226.c 中读取数据的函数 float INA226_ReadBusVoltage(void) { uint16_t raw = INA226_ReadReg(INA226_REG_BUS_VOLTAGE); // 总线电压LSB固定为1.25mV return raw * 0.00125f; } float INA226_ReadCurrent(void) { uint16_t raw = INA226_ReadReg(INA226_REG_CURRENT); // 电流LSB在初始化时已经确定,这里用全局变量存一下 return raw * g_current_lsb; } float INA226_ReadPower(void) { uint16_t raw = INA226_ReadReg(INA226_REG_POWER); // 功率LSB = 25 × 电流LSB return raw * 25.0f * g_current_lsb; }总线电压的 LSB 是固定的 1.25mV,这个不用校准。电流的 LSB 就是初始化时算出来的current_lsb,工程里用了一个全局变量g_current_lsb存着,读电流的时候直接乘。功率的 LSB 是电流 LSB 的 25 倍,这也是数据手册里的固定关系。分流电压的 LSB 是 2.5uV,一般用得少,但调试的时候可以读出来看看,用来判断采样电阻上是不是真的有压降。3.4 主循环与串口打印main.c里的逻辑很直接:初始化系统时钟、串口、INA226,然后进 while(1) 每隔 500ms 读一次数据,通过串口打印出去。// main.c 主循环 int main(void) { USART1_Init(115200); // 串口初始化,波特率115200 INA226_Init(); printf("INA226 Demo Start\r\n"); while(1) { float bus_v = INA226_ReadBusVoltage(); float current = INA226_ReadCurrent(); float power = INA226_ReadPower(); printf("Bus: %.3f V, Current: %.3f A, Power: %.3f W\r\n", bus_v, current, power); Delay_ms(500); } }printf能输出到串口,是因为工程里重定向了fputc函数,把字符一个个写到 USART1 的数据寄存器。这个重定向代码在usart.c里,用的是标准库的写法。如果你用的是 HAL 库,重定向方式稍微不同,但思路一样。串口波特率 115200,用 USB 转 TTL 模块接到 PA9 和 PA10 就能在电脑上看数据。4. 避坑与排查:I2C 通信失败、读数异常、校准偏差4.1 读出来全是 0xFF 或者 0x00现象是串口打印的电压电流值要么是 65535 对应的满量程,要么直接是 0。原因通常是 I2C 通信根本没建立起来。先检查硬件:SDA 和 SCL 有没有接反,上拉电阻有没有焊,模块供电是不是 3.3V(INA226 不支持 5V 供电)。然后看软件:INA226_ADDR宏定义对不对,用逻辑分析仪抓一下 I2C 波形,看有没有 ACK 回应。如果地址不对,从机不会拉低 SDA,主机收到 NACK 后读出来就是 0xFF。4.2 电压读数正常但电流始终为 0总线电压能读对,说明 I2C 通信没问题,问题出在校准寄存器或者采样电阻上。先确认INA226_Calibrate函数有没有被调用,再看INA226_RSHUNT宏定义的值和板子上实际焊的电阻是否一致。还有一种情况是负载电流太小,低于电流 LSB 对应的分辨率,读出来就是 0。比如最大电流设了 10A,电流 LSB 约 0.3mA,如果实际电流只有 0.1mA,那确实读不出来。这时候把最大电流改小,比如改成 1A,分辨率就提高到 0.03mA。4.3 电流值成比例偏大或偏小读出来的电流和万用表测的差一个固定倍数,比如总是大 2 倍或者小一半。这是采样电阻值填错了。模块上如果焊的是 5mΩ,你宏定义里写的 2mΩ,算出来的校准值就偏大,电流读数就偏小。反过来也一样。用万用表量一下采样电阻两端的电压,除以电阻值算出实际电流,和 INA226 读出来的对比,就能判断是哪个环节的问题。4.4 读数跳动厉害,不稳定电流值在小范围内频繁跳动,比如 ±10mA 的波动。这是正常的噪声,INA226 的 ADC 本身有噪声,加上电源纹波和采样电阻的热噪声。解决办法是增加平均次数,把配置寄存器里的平均次数从 16 次调到 128 次甚至 256 次,更新周期会变长,但读数会稳很多。另外,采样电阻的走线尽量短,用地线包围,减少干扰耦合。4.5 移植到其他 STM32 芯片后编译报错从 F103 换到 F407 或者 G0 系列,标准外设库的寄存器定义不一样,i2c.c和usart.c里的底层操作需要重写。如果用的是 HAL 库,HAL_I2C_Mem_Write和HAL_I2C_Mem_Read可以直接替换工程里的读写函数,但要注意 HAL 库的地址参数是 7 位地址左移一位,和工程里的宏定义一致。移植的时候先把 I2C 和串口调通,再跑 INA226 的初始化,一步一步来,别一次性全改完再编译。5. 二次开发与扩展:从单模块到多通道监测5.1 多片 INA226 挂同一条 I2C 总线INA226 的地址由 A1、A0 引脚决定,最多可以在一条总线上挂 4 片,地址分别是 0x40、0x41、0x44、0x45(取决于 A1、A0 的接法)。工程里的INA226_WriteReg和INA226_ReadReg函数目前用的是固定的INA226_ADDR宏,要支持多片的话,把地址作为参数传进去就行:// 改造后的读写函数,地址作为参数 void INA226_WriteReg(uint8_t dev_addr, uint8_t reg, uint16_t data) { I2C_WriteByte(dev_addr, reg, (uint8_t)(data >> 8)); I2C_WriteByte(dev_addr, reg + 1, (uint8_t)(data & 0xFF)); } uint16_t INA226_ReadReg(uint8_t dev_addr, uint8_t reg) { uint8_t buf[2]; I2C_ReadBytes(dev_addr, reg, buf, 2); return (buf[0] << 8) | buf[1]; }然后在主循环里对每个地址分别调用读取函数,打印的时候加上通道号。注意每片 INA226 的采样电阻和最大电流可能不同,校准的时候要分别算,不能共用一个g_current_lsb。我一般会定义一个结构体数组,把地址、采样电阻、电流 LSB 都存进去,遍历数组来读。5.2 数据上报与阈值告警串口打印适合调试,但实际产品里通常需要把数据上报到上位机或者触发告警。一个简单的做法是在主循环里加阈值判断:// 电流超过阈值时点亮 LED 或发送告警帧 if(current > 5.0f) { GPIO_ResetBits(GPIOA, GPIO_Pin_0); // 点亮告警 LED printf("ALERT: Over current! %.3f A\r\n", current); }如果要上报到上位机,可以把数据打包成固定格式的帧,比如[0xAA][电压高][电压低][电流高][电流低][校验][0x55],上位机按协议解析。这样比直接打印字符串更可靠,也方便做数据记录和曲线绘制。5.3 用定时器中断替代主循环延时主循环里用Delay_ms(500)会阻塞 CPU,如果你还想同时做别的事情,比如按键扫描或者 PWM 输出,就会互相干扰。更好的做法是用定时器中断来触发采集:// 定时器中断服务函数,每 100ms 触发一次 void TIM2_IRQHandler(void) { if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); g_bus_voltage = INA226_ReadBusVoltage(); g_current = INA226_ReadCurrent(); g_power = INA226_ReadPower(); g_data_ready = 1; // 标志位,主循环里检测到就打印 } }主循环里只负责检测g_data_ready标志然后打印,采集在中断里完成,互不阻塞。注意 I2C 读写函数在中断里调用时,要确保没有其他中断打断 I2C 时序,必要的时候在 I2C 操作前后关中断。5.4 校准值的验证方法写完校准代码后,怎么确认算出来的cal_value是对的?我的习惯是拿一个已知阻值的功率电阻做负载,比如 10Ω 5W 的水泥电阻,接在电源和 INA226 的采样电阻之间,用万用表测出实际电流,然后和 INA226 读出来的对比。如果误差在 1% 以内,说明校准没问题。如果误差大,先检查采样电阻的实际阻值,有些便宜模块上标的 2mΩ 实际可能是 1.8mΩ 或者 2.2mΩ,用四线法测一下最准。从那以后我每次拿到新的 INA226 模块,都先用万用表确认采样电阻阻值,再改宏定义,然后跑一遍校准验证,最后才集成到项目里。这个习惯帮我省了不少返工的时间。希望帮到你。本文还有配套的精品资源,点击获取
简介:这是一套面向嵌入式开发者的INA226电流/功率传感器驱动完整工程,基于STM32微控制器,采用C语言编写,配套Keil MDK开发环境,适合电源管理、电池管理、系统监控及工业自动化等场景下的电流电压采集需求。压缩包共151个文件,约2.97MB,以36个h头文件与35个c源文件为核心,涵盖STM32标准外设库、INA226寄存器配置与数据读取逻辑,另含o、d、crf等编译中间文件及uvprojx、uvoptx工程文件,hex与axf可直接烧录调试,map与lst便于分析内存占用。资源已获4709人学习下载,读者可据此理解INA226数据手册的寄存器设定方法,掌握初始化配置、启动测量、中断处理与数据解析的完整流程,并借助现成工程快速搭建验证环境,缩短从硬件对接到功能调试的周期,对需要监控电流与功率的嵌入式系统设计者具有较高参考价值。1. INA226 电流监测模块:从寄存器配置到串口输出的完整工程如果你手头有一块 INA226 模块,想用 STM32 通过 I2C 读出电压、电流和功率值,却卡在寄存器配置或者数据换算上,这个工程压缩包就是为你准备的。它用 C 语言在 Keil MDK 环境下搭建了一个完整的采集框架,把 INA226 的初始化、校准寄存器计算、数据读取和串口打印串成了一条线。适合正在做电源管理、电池监测或者低功耗设备功耗分析的嵌入式开发者,尤其是那些不想从零啃数据手册、希望直接拿一份能跑的代码来改的人。整个工程不依赖 HAL 库之外的复杂中间件,移植到 F103、F407 这类常见芯片上只需要改几个宏定义。2. 工程结构与 INA226 驱动分层:文件怎么放、依赖怎么理2.1 目录树与模块划分拿到压缩包解压后,根目录下是标准的 Keil MDK 工程结构。核心文件集中在Hardware和User两个文件夹里,这种分法在 STM32 裸机项目里很常见,好处是硬件驱动和业务逻辑不混在一起,换芯片或者换传感器的时候只动Hardware就行。INA226_Project/ ├── CMSIS/ # 内核头文件,Keil 自带 ├── FWlib/ # 标准外设库(或 HAL 库) ├── Hardware/ │ ├── i2c.c / i2c.h # 软件模拟 I2C 或硬件 I2C 封装 │ ├── ina226.c / ina226.h # INA226 寄存器定义与读写函数 │ └── usart.c / usart.h # 串口初始化与 printf 重定向 ├── User/ │ ├── main.c # 主循环,周期读取并打印 │ └── stm32f10x_it.c # 中断服务函数 └── Project.uvprojx # Keil MDK 工程文件ina226.h里用宏定义列出了所有寄存器地址,比如INA226_REG_CONFIG、INA226_REG_SHUNT_VOLTAGE、INA226_REG_BUS_VOLTAGE这些,后面写代码的时候直接引用宏名,不用记十六进制地址。i2c.c里封装了I2C_WriteByte、I2C_ReadByte这类底层函数,INA226 的读写函数再调用它们,这样分层之后,如果从软件 I2C 换成硬件 I2C,只需要改i2c.c的实现,ina226.c一行都不用动。2.2 INA226 的 I2C 地址与通信速率INA226 的 7 位地址由 A1、A0 两个引脚决定,默认都接地时地址是0x40。工程里ina226.h中定义了INA226_ADDR宏,写的是0x40 << 1,因为 STM32 的 I2C 外设要求传入的是 8 位地址(包含读写位)。如果你板子上 A0 接了 VCC,地址就变成0x41,这时候改宏定义就行。通信速率方面,INA226 支持标准模式(100kHz)和快速模式(400kHz)。工程里 I2C 初始化配置的是 400kHz,实际用示波器抓波形,SCL 周期在 2.5us 左右。如果排线比较长或者上拉电阻偏大导致波形上升沿变缓,可以把速率降到 100kHz 试试,这是排查通信失败时最先检查的地方。注意:INA226 的 SDA 和 SCL 都需要接上拉电阻,典型值是 4.7kΩ。有些模块自带上拉,有些没有,买回来先用万用表量一下,不然 I2C 总线拉不高,读出来全是 0xFF。2.3 关键宏定义与可移植性设计工程里把和硬件相关的参数都抽成了宏,放在ina226.h顶部。移植的时候主要改这几个:// ina226.h 中的可配置宏 #define INA226_ADDR 0x40 << 1 // 7位地址左移一位 #define INA226_RSHUNT 0.002f // 采样电阻阻值,单位欧姆,默认2mΩ #define INA226_MAX_CURRENT 10.0f // 预期最大电流,单位安培INA226_RSHUNT和INA226_MAX_CURRENT这两个值直接决定校准寄存器的计算结果。采样电阻是板子上实际焊的那个,常见的有 2mΩ、5mΩ、10mΩ,你得根据自己模块上的丝印或者原理图确认。最大电流是你预期流过的电流上限,用来计算电流分辨率。这两个值填错,读出来的电流值会成比例地偏大或偏小,但电压值不受影响,这是判断校准是否正确的依据。3. 核心驱动实现:寄存器配置、校准与数据换算3.1 初始化流程与配置寄存器写入INA226 上电后需要先写配置寄存器,设定平均次数、电压转换时间、电流转换时间和工作模式。工程里的初始化函数长这样:// ina226.c 中的初始化函数 void INA226_Init(void) { I2C_Init(); // 底层 I2C 初始化,配置 GPIO 和时钟 // 配置寄存器:平均次数16,总线电压转换时间1.1ms, // 分流电压转换时间1.1ms,连续测量模式 // 位域:[15:12]平均次数 [11:9]总线转换时间 // [8:6]分流转换时间 [5:3]工作模式 uint16_t config = 0x4527; INA226_WriteReg(INA226_REG_CONFIG, config); // 根据采样电阻和最大电流计算校准值 INA226_Calibrate(INA226_RSHUNT, INA226_MAX_CURRENT); }0x4527这个值拆开看:最高 4 位0100表示平均 16 次,接下来 3 位101表示总线转换时间 1.1ms,再 3 位001表示分流转换时间 1.1ms,最低 3 位111表示连续测量分流和总线。平均次数越多,读数越稳,但响应越慢。16 次平均下,每次更新周期大约是 16 × (1.1ms + 1.1ms) ≈ 35ms,对于电池监测这种慢变量足够了。如果你要抓快速变化的电流波形,把平均次数降到 1 次,转换时间也调短,更新周期能压到 2ms 左右。3.2 校准寄存器计算:一个容易算错的公式校准寄存器的值决定了电流读数的比例系数,公式在数据手册里写得很清楚,但实际写代码的时候容易把单位搞混。工程里的实现是这样的:// ina226.c 中的校准函数 void INA226_Calibrate(float r_shunt, float max_current) { // 电流LSB = 最大电流 / 32768 float current_lsb = max_current / 32768.0f; // 校准值 = 0.00512 / (电流LSB × 采样电阻) // 0.00512 是数据手册给出的固定常数 uint16_t cal_value = (uint16_t)(0.00512f / (current_lsb * r_shunt)); INA226_WriteReg(INA226_REG_CALIBRATION, cal_value); }这里current_lsb是电流的最低有效位对应的电流值,单位是安培。0.00512这个常数来自数据手册,不用深究怎么来的,记住就行。算出来的cal_value写入校准寄存器后,后面读到的电流寄存器原始值乘以current_lsb就是实际电流。举个例子:采样电阻 2mΩ,最大电流 10A,那么current_lsb = 10/32768 ≈ 0.000305A,cal_value = 0.00512 / (0.000305 × 0.002) ≈ 8390。这个值在 0 到 65535 之间,如果算出来超了,说明采样电阻或者最大电流设得不合理。提示:校准寄存器写入后,电流读数才会正确。如果忘了写这一步,电流寄存器读出来是 0 或者乱跳,但总线电压和分流电压是正常的。这是新手最容易踩的坑之一。3.3 读取电压、电流与功率的完整链路数据读取部分封装了三个函数,分别读总线电压、分流电压和电流,功率寄存器可以直接读,也可以用电压乘电流自己算。工程里用的是直接读功率寄存器的方式:// ina226.c 中读取数据的函数 float INA226_ReadBusVoltage(void) { uint16_t raw = INA226_ReadReg(INA226_REG_BUS_VOLTAGE); // 总线电压LSB固定为1.25mV return raw * 0.00125f; } float INA226_ReadCurrent(void) { uint16_t raw = INA226_ReadReg(INA226_REG_CURRENT); // 电流LSB在初始化时已经确定,这里用全局变量存一下 return raw * g_current_lsb; } float INA226_ReadPower(void) { uint16_t raw = INA226_ReadReg(INA226_REG_POWER); // 功率LSB = 25 × 电流LSB return raw * 25.0f * g_current_lsb; }总线电压的 LSB 是固定的 1.25mV,这个不用校准。电流的 LSB 就是初始化时算出来的current_lsb,工程里用了一个全局变量g_current_lsb存着,读电流的时候直接乘。功率的 LSB 是电流 LSB 的 25 倍,这也是数据手册里的固定关系。分流电压的 LSB 是 2.5uV,一般用得少,但调试的时候可以读出来看看,用来判断采样电阻上是不是真的有压降。3.4 主循环与串口打印main.c里的逻辑很直接:初始化系统时钟、串口、INA226,然后进 while(1) 每隔 500ms 读一次数据,通过串口打印出去。// main.c 主循环 int main(void) { USART1_Init(115200); // 串口初始化,波特率115200 INA226_Init(); printf("INA226 Demo Start\r\n"); while(1) { float bus_v = INA226_ReadBusVoltage(); float current = INA226_ReadCurrent(); float power = INA226_ReadPower(); printf("Bus: %.3f V, Current: %.3f A, Power: %.3f W\r\n", bus_v, current, power); Delay_ms(500); } }printf能输出到串口,是因为工程里重定向了fputc函数,把字符一个个写到 USART1 的数据寄存器。这个重定向代码在usart.c里,用的是标准库的写法。如果你用的是 HAL 库,重定向方式稍微不同,但思路一样。串口波特率 115200,用 USB 转 TTL 模块接到 PA9 和 PA10 就能在电脑上看数据。4. 避坑与排查:I2C 通信失败、读数异常、校准偏差4.1 读出来全是 0xFF 或者 0x00现象是串口打印的电压电流值要么是 65535 对应的满量程,要么直接是 0。原因通常是 I2C 通信根本没建立起来。先检查硬件:SDA 和 SCL 有没有接反,上拉电阻有没有焊,模块供电是不是 3.3V(INA226 不支持 5V 供电)。然后看软件:INA226_ADDR宏定义对不对,用逻辑分析仪抓一下 I2C 波形,看有没有 ACK 回应。如果地址不对,从机不会拉低 SDA,主机收到 NACK 后读出来就是 0xFF。4.2 电压读数正常但电流始终为 0总线电压能读对,说明 I2C 通信没问题,问题出在校准寄存器或者采样电阻上。先确认INA226_Calibrate函数有没有被调用,再看INA226_RSHUNT宏定义的值和板子上实际焊的电阻是否一致。还有一种情况是负载电流太小,低于电流 LSB 对应的分辨率,读出来就是 0。比如最大电流设了 10A,电流 LSB 约 0.3mA,如果实际电流只有 0.1mA,那确实读不出来。这时候把最大电流改小,比如改成 1A,分辨率就提高到 0.03mA。4.3 电流值成比例偏大或偏小读出来的电流和万用表测的差一个固定倍数,比如总是大 2 倍或者小一半。这是采样电阻值填错了。模块上如果焊的是 5mΩ,你宏定义里写的 2mΩ,算出来的校准值就偏大,电流读数就偏小。反过来也一样。用万用表量一下采样电阻两端的电压,除以电阻值算出实际电流,和 INA226 读出来的对比,就能判断是哪个环节的问题。4.4 读数跳动厉害,不稳定电流值在小范围内频繁跳动,比如 ±10mA 的波动。这是正常的噪声,INA226 的 ADC 本身有噪声,加上电源纹波和采样电阻的热噪声。解决办法是增加平均次数,把配置寄存器里的平均次数从 16 次调到 128 次甚至 256 次,更新周期会变长,但读数会稳很多。另外,采样电阻的走线尽量短,用地线包围,减少干扰耦合。4.5 移植到其他 STM32 芯片后编译报错从 F103 换到 F407 或者 G0 系列,标准外设库的寄存器定义不一样,i2c.c和usart.c里的底层操作需要重写。如果用的是 HAL 库,HAL_I2C_Mem_Write和HAL_I2C_Mem_Read可以直接替换工程里的读写函数,但要注意 HAL 库的地址参数是 7 位地址左移一位,和工程里的宏定义一致。移植的时候先把 I2C 和串口调通,再跑 INA226 的初始化,一步一步来,别一次性全改完再编译。5. 二次开发与扩展:从单模块到多通道监测5.1 多片 INA226 挂同一条 I2C 总线INA226 的地址由 A1、A0 引脚决定,最多可以在一条总线上挂 4 片,地址分别是 0x40、0x41、0x44、0x45(取决于 A1、A0 的接法)。工程里的INA226_WriteReg和INA226_ReadReg函数目前用的是固定的INA226_ADDR宏,要支持多片的话,把地址作为参数传进去就行:// 改造后的读写函数,地址作为参数 void INA226_WriteReg(uint8_t dev_addr, uint8_t reg, uint16_t data) { I2C_WriteByte(dev_addr, reg, (uint8_t)(data >> 8)); I2C_WriteByte(dev_addr, reg + 1, (uint8_t)(data & 0xFF)); } uint16_t INA226_ReadReg(uint8_t dev_addr, uint8_t reg) { uint8_t buf[2]; I2C_ReadBytes(dev_addr, reg, buf, 2); return (buf[0] << 8) | buf[1]; }然后在主循环里对每个地址分别调用读取函数,打印的时候加上通道号。注意每片 INA226 的采样电阻和最大电流可能不同,校准的时候要分别算,不能共用一个g_current_lsb。我一般会定义一个结构体数组,把地址、采样电阻、电流 LSB 都存进去,遍历数组来读。5.2 数据上报与阈值告警串口打印适合调试,但实际产品里通常需要把数据上报到上位机或者触发告警。一个简单的做法是在主循环里加阈值判断:// 电流超过阈值时点亮 LED 或发送告警帧 if(current > 5.0f) { GPIO_ResetBits(GPIOA, GPIO_Pin_0); // 点亮告警 LED printf("ALERT: Over current! %.3f A\r\n", current); }如果要上报到上位机,可以把数据打包成固定格式的帧,比如[0xAA][电压高][电压低][电流高][电流低][校验][0x55],上位机按协议解析。这样比直接打印字符串更可靠,也方便做数据记录和曲线绘制。5.3 用定时器中断替代主循环延时主循环里用Delay_ms(500)会阻塞 CPU,如果你还想同时做别的事情,比如按键扫描或者 PWM 输出,就会互相干扰。更好的做法是用定时器中断来触发采集:// 定时器中断服务函数,每 100ms 触发一次 void TIM2_IRQHandler(void) { if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); g_bus_voltage = INA226_ReadBusVoltage(); g_current = INA226_ReadCurrent(); g_power = INA226_ReadPower(); g_data_ready = 1; // 标志位,主循环里检测到就打印 } }主循环里只负责检测g_data_ready标志然后打印,采集在中断里完成,互不阻塞。注意 I2C 读写函数在中断里调用时,要确保没有其他中断打断 I2C 时序,必要的时候在 I2C 操作前后关中断。5.4 校准值的验证方法写完校准代码后,怎么确认算出来的cal_value是对的?我的习惯是拿一个已知阻值的功率电阻做负载,比如 10Ω 5W 的水泥电阻,接在电源和 INA226 的采样电阻之间,用万用表测出实际电流,然后和 INA226 读出来的对比。如果误差在 1% 以内,说明校准没问题。如果误差大,先检查采样电阻的实际阻值,有些便宜模块上标的 2mΩ 实际可能是 1.8mΩ 或者 2.2mΩ,用四线法测一下最准。从那以后我每次拿到新的 INA226 模块,都先用万用表确认采样电阻阻值,再改宏定义,然后跑一遍校准验证,最后才集成到项目里。这个习惯帮我省了不少返工的时间。希望帮到你。本文还有配套的精品资源,点击获取