基于INA226与STM32F103的板级功率测量模块设计
简介:面向嵌入式硬件与固件开发者的一份 INA226 功率测量模块实践资料,围绕 STM32F103 与 INA226 的软硬件协同展开,适合需要实时监测电流、电压与功率的电源管理、功耗分析场景,也可作为课程设计、毕业设计及小型项目的参考。压缩包约 59.3MB,上游暂未提供文件数量与类型明细,从描述可知内容覆盖 I2C 驱动与寄存器配置、AD 采样电路设计以及 PCB 布局要点,可按模块分步查阅。已有 3392 人学习下载,具备一定参考基数。内容重点讲清 INA226 可编程增益与量程选择、分流电阻取值与精度权衡、STM32F103 的 I2C 时序初始化,以及测量结果换算为实际电流、电压、功率的方法,并强调去耦电容、走线宽度和器件靠近负载布置等抗噪细节;Seasky_INA226 部分给出可对照的集成示例,便于读者搭建并调试自己的电力监控方案。
1. 一个把万用表换掉的念头,撑起了整块板子
做电机驱动板、电池供电设备或者小功率电源的时候,想看一眼实时功率,最顺手的办法是串一块万用表。可万用表进不了产品,也没法连续记录几个小时的数据,更没法把电流电压送到上位机画曲线。于是就需要一块能焊在板子上的 INA226 功率测量模块:前端用分流电阻取压降,INA226 内部 16 位 ADC 同时采分流电压和母线电压,硬件直接算功率,主控 STM32F103 通过 I2C 把数据读走。
这套方案把需求、硬件设计、软件驱动和 PCB 布局串成一条线,交付的是一块能直接对标参数验收的模块。适合正在做电源监测毕设、功率计课设,或者要在自研板子上加一路电流监测的读者——照着结构改参数就能落地,不需要从零推公式。
2. 需求拆解与总体方案:三个采样通道怎么分工
动手画原理图之前,先把“测功率”这个模糊目标拆成能写进验收表的指标。拆不清楚,后面分流电阻选多大、校准寄存器填什么、滤波窗口开多少,全是拍脑袋。
2.1 把测功率拆成可验收的指标
一个典型的板级功率测量需求,通常包含这几条:
| 指标项 | 取值示例 | 决定因素 |
|---|---|---|
| 母线电压量程 | 0~36 V | INA226 的 VBUS 引脚耐压上限 |
| 电流量程 | 0~5 A(双向) | 分流电阻阻值 + 分流电压满量程 |
| 电流分辨率 | ≤ 1 mA | Current_LSB 与 ADC 位数 |
| 电压分辨率 | 1.25 mV | INA226 总线电压 LSB 固定 |
| 刷新率 | 10~50 Hz | 转换时间 × 平均次数 |
| 通信接口 | I2C(400 kHz)+ 串口上报 | STM32F103 外设资源 |
| 供电 | 3.3 V 单电源 | 与主控同轨,省一路 LDO |
这里有个容易被忽略的点:电流分辨率不是靠“选个高精度电阻”就能达成的。INA226 的分流电压满量程固定为 ±81.92 mV,内部 16 位有符号 ADC 对应 LSB = 2.5 µV。也就是说,无论分流电阻取多大,分流电压这一路的原始分辨率都是 2.5 µV,落到电流上就是 2.5 µV / R。想做到 1 mA 分辨率,R 至少要 2.5 mΩ;想做到 0.1 mA,R 要到 25 mΩ。分流电阻越大分辨率越高,但压降和功耗也越大,这就是后面选型要平衡的地方。
电压这一路简单得多,VBUS 寄存器 LSB 固定 1.25 mV,0~36 V 全程覆盖,不需要额外分压。
我一般的做法是把指标写成一张表贴在原理图旁边,每改一次电阻值就回头对一遍表,避免出现“分辨率够了但功耗超标”这种返工。特别是做毕设的同学,验收时老师最爱问的一句就是“你这个 1 mA 是怎么算出来的”,表在手上,答起来不慌。
2.2 INA226 与 STM32 内部 ADC 的分工
STM32F103 自带 12 位 ADC,为什么还要外挂一颗 INA226?这是方案评审时必被问的问题,答案要落在具体差异上,不能只说“精度高”。
STM32F103 的 ADC 是 12 位、单端输入,参考电压通常取 3.3 V,最小分辨约 0.8 mV。用它直接测分流电阻上的压降,5 A 量程配 10 mΩ 电阻,满量程压降只有 50 mV,落在 12 位 ADC 上大约只有 62 个码值,量化误差就有几十毫安。而且单端测量要求分流电阻一端接地,一旦被测电路不是共地的,方案直接失效。
INA226 的优势在这三点:一是 16 位 ΔΣ ADC,分流电压满量程 ±81.92 mV 对应 65536 个码,等效 2.5 µV;二是差分输入,IN+ / IN- 直接跨接在分流电阻两端,共模电压允许到 36 V,被测电路不需要共地;三是内部硬件乘法器直接输出功率寄存器,主控不用自己算乘法,也少了一次软件误差。
但这不代表 STM32 的 ADC 就闲置了。实际方案里它还有活干:当被测母线电压超过 36 V,INA226 的 VBUS 引脚就顶不住了,这时候用电阻分压把高压降到 3.3 V 以内,送进 STM32 的 ADC 通道;另外板子上如果放了 NTC 做温度补偿,也是走内部 ADC。所以合理的分工是——INA226 管低边/高边的电流和 36 V 以内的母线电压,STM32 内部 ADC 管高压扩展通道和环境温度,两条链路在软件里合成一份完整的功率数据。
这个分工还有个附带好处:两路数据可以互相校验。比如 INA226 读到 24 V,分压通道换算出来也是 24 V,说明两边的分压比和参考电压都没问题;如果差得离谱,多半是某一侧的电阻焊错了或者参考电压跑偏。调试阶段这个交叉验证能省很多时间。
2.3 系统框图与任务划分
整块板子的信号流是这样的:被测电路的电流从 VIN+ 流入,经过分流电阻 Rshunt 后从 VIN- 流出,分流电阻两端的压降接到 INA226 的 IN+ / IN-;母线电压从分流电阻的高端引一路到 VBUS 引脚;INA226 通过 SCL / SDA 两根线挂到 STM32F103 的 I2C1 上,地址由 A0 / A1 两个引脚决定;STM32 通过 USART1 把换算后的电压、电流、功率打成帧发给上位机,同时可以留一路 SWD 用于在线调试。
软件上按三个任务切:
采样任务跑在定时器中断或者 FreeRTOS 的周期任务里,10 Hz 触发一次,依次读总线电压、分流电压、电流、功率四个寄存器,做滑动平均后存进全局结构体。通信任务负责把结构体打包成固定帧格式,从串口发出去,帧头帧尾加校验,波特率 115200 足够。标定任务不是常态运行的,只在出厂或者换分流电阻之后跑一次,用标准源给两个电流点,反算校准系数存进 Flash。
任务之间用一个双缓冲或者环形队列隔开,采样任务只写、通信任务只读,避免读的时候数据被改了一半。这个结构不复杂,但一开始就摆清楚,后面加 SD 卡记录或者 CAN 上报都不会把代码搅乱。
3. 硬件设计:从分流电阻到开尔文连接的完整链路
硬件这一章是整块板子的地基。分流电阻差一个数量级,后面软件怎么调都救不回来。
3.1 分流电阻的阻值与功率怎么定
选型从两个约束同时下手:分流电压不能超过 ±81.92 mV,功耗要在封装承受范围内。
先按满量程电流算上限:R ≤ 81.92 mV / Imax。5 A 量程对应 16.38 mΩ,这是个理论上限,实际要留 20%~30% 裕量,取 10 mΩ 比较稳,此时满量程压降 50 mV,占用 ADC 满量程的 61%,既留了过流余量,分辨率也不至于太差。
再算功耗:P = I² × R。5 A 通过 10 mΩ 是 0.25 W,听起来不大,但这是持续功耗,2512 封装的普通厚膜电阻额定 1 W,长期工作在 25% 负载下温漂会很明显。所以分流电阻要选合金材料、低温漂(±50 ppm/℃ 以内)、精度 ±0.5% 或更好的型号,封装优先 2512 甚至 2725,把自身发热压下去。温度每升高 1℃,10 mΩ 电阻如果温漂 100 ppm,阻值就变 1 µΩ,5 A 下对应 5 µV 的误差,换算成电流是 0.5 mA——看着小,但这是系统性偏差,不校准会一直存在。
不同量程对应的推荐值可以列成一张速查表:
| 电流量程 | 分流电阻 | 满量程压降 | 5 A 时功耗(参考) | 电流分辨率 |
|---|---|---|---|---|
| 1 A | 50 mΩ | 50 mV | — | 50 µA |
| 2 A | 25 mΩ | 50 mV | — | 100 µA |
| 5 A | 10 mΩ | 50 mV | 0.25 W | 250 µA |
| 10 A | 5 mΩ | 50 mV | 0.5 W(10 A) | 500 µA |
注意分辨率这一列是按 2.5 µV LSB 算的原始分辨率,实际有效分辨率还要看噪声和平均次数,后面软件章会讲怎么用 16 次平均把它压下去。
还有一个细节:分流电阻的接法必须是四线开尔文。电阻本体上通电流的两个焊盘和取电压的两个焊盘要分开,取压的走线从电阻焊盘的内侧边缘引出,不能从电流路径上随便挑一点。否则焊锡电阻和 PCB 走线电阻会串进采样回路,几十微欧的走线电阻在 5 A 下就是几百微伏,直接吃掉分辨率。
3.2 INA226 前端电路与地址配置
INA226 的供电 VS 范围是 2.7~5.5 V,这块板子和 STM32 共用 3.3 V,所以在 VS 和 GND 之间放一个 0.1 µF 的去耦电容,位置尽量贴近芯片引脚。I2C 的 SCL / SDA 各接一个 4.7 kΩ 上拉到 3.3 V,如果总线上挂的器件多或者走线长,可以降到 2.2 kΩ,但要注意 STM32 的灌电流能力。
IN+ / IN- 是差分输入,中间可以加一级 RC 滤波抑制高频干扰:两个 10 Ω 电阻分别串在 IN+ 和 IN- 上,电阻靠近 INA226 一侧再并一个 0.1 µF 电容到地。差分截止频率按 f = 1 / (2π × 2R × C) 估算,10 Ω 和 0.1 µF 对应大约 80 kHz,既能滤掉开关电源的尖峰,又不会影响 1 kHz 以内的真实电流变化。这两个电阻要选 1% 精度、低温漂的,因为它们的失配对差分信号来说是直接的增益误差。
地址由 A0 和 A1 两个引脚决定,每个引脚可以接 GND、VCC、SDA、SCL 四种状态,组合出 16 个地址,基址 0x40。板上如果只挂一颗,两个脚都接地就是 0x40;挂多颗做多路监测时,按表配置:
| A1 | A0 | 7 位地址 |
|---|---|---|
| GND | GND | 0x40 |
| GND | VS | 0x41 |
| GND | SDA | 0x42 |
| GND | SCL | 0x43 |
| VS | GND | 0x44 |
上电后先读制造商 ID 寄存器(0xFE)应该是 0x5449,再读芯片 ID(0xFF)应该是 0x2260,两个都对上才说明 I2C 通了、芯片是正品。这一步建议写进初始化函数里,读不到就直接报错停机,别让后面的读数在错误数据上继续跑。
3.3 高压扩展通道:分压加 STM32 内部 ADC
母线超过 36 V 的场合,VBUS 引脚不能直接接,需要电阻分压后送进 STM32 的 ADC。假设要测到 100 V,分压比取 24:1,上电阻 100 kΩ、下电阻 4.35 kΩ,100 V 输入时分压点约 4.16 V,超过 3.3 V 了,所以要留裕量:上电阻 100 kΩ、下电阻 4.7 kΩ,分压比约 22.3,100 V 对应 4.49 V 仍然超。正确做法是按最大输入电压反算:下电阻固定 4.7 kΩ,要求分压点不超过 3.0 V(留 0.3 V 钳位余量),则上电阻 ≥ (100 - 3) / 3 × 4.7 k ≈ 152 kΩ,取 150 kΩ,分压比 32.9,100 V 对应 3.04 V。
这个分压点的源阻抗是 150 kΩ 并联 4.7 kΩ,约 4.56 kΩ。STM32F103 的 ADC 要求源阻抗在采样时间足够长时不超过几十 kΩ,所以采样周期要设到 239.5 个 ADC 时钟,或者在前端加一级电压跟随器。板子上如果还有余量,加个运放跟随是最稳的,不加的话就把采样时间拉满,并且在分压点并一个 100 nF 电容做电荷缓冲。
分压点还要加钳位保护:两个肖特基二极管分别到 3.3 V 和 GND,防止输入过压把 ADC 引脚打穿。这一步在实际调试里救过不少板子——接线时不小心把 100 V 接到分压输出端,没有钳位的话 STM32 当场报废。
3.4 PCB 布局:开尔文连接和小信号地
原理图对了,PCB 画错照样翻车。INA226 这块板子的布局有几个硬性要求。
分流电阻放最前面,靠近输入端子,两个电流焊盘做大一些,保证 5 A 电流下的载流能力和散热。取压的两根走线从电阻焊盘内侧引出,走成一对紧耦合的差分线,线宽不用大(10 mil 足够,因为不走电流),但要等长、平行、尽量短,中间不要穿任何过孔或者被其他信号切割。
INA226 尽量靠近分流电阻,让 IN+ / IN- 的走线长度控制在 10 mm 以内。芯片的 GND 引脚通过一个过孔直接连到地平面,去耦电容的地端用独立过孔下地,不要和信号地串在一起。
地的处理上,整板用完整地平面,不做分割。模拟小信号和数字信号在平面上的投影区域尽量分开:分流电阻和 INA226 的下面不铺其他信号线,STM32 的晶振和 I2C 走线远离 IN+ / IN- 区域。如果板子空间紧张必须分割,那也是“单点连接”的分割,两个地平面只在一个位置用 0 Ω 电阻或者铜皮桥连起来,连接点选在 INA226 的 GND 附近。
高压部分要留爬电间距。100 V 等级下,正负输入端之间、高压走线与低压走线之间至少留 2 mm,条件允许就开槽。这条在实验室环境下常常被忽略,但它是安全底线,做毕设答辩时老师很可能会问。
4. STM32F103 软件实现:I2C 驱动到功率换算的完整链路
软件这部分的难点不在算法,而在时序和单位。INA226 是 I2C 从机,所有寄存器都是 16 位大端,读回来的原始值要乘上对应的 LSB 才是物理量,中间任何一步搞错,结果就差一个数量级。
4.1 I2C 底层与寄存器读写封装
STM32F103 的硬件 I2C 在圈内名声不太好,早期批次在特定时序下会卡在 BUSY 位不释放,血泪经验是——如果项目时间紧,直接用 GPIO 模拟 I2C,时序自己控,稳定得多;如果坚持用硬件 I2C,务必加上超时和总线恢复逻辑。
这里按 HAL 库的硬件 I2C 写,寄存器地址是 8 位、数据是 16 位大端:
#include "ina226.h" /* INA226 寄存器地址定义 */ #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_MASK 0x06 #define INA226_REG_MANUF_ID 0xFE #define INA226_REG_DIE_ID 0xFF /* HAL 库的从机地址参数是 8 位形式,需要左移一位 */ #define INA226_ADDR (0x40 << 1) static I2C_HandleTypeDef *ina_i2c = &hi2c1; /* 写 16 位寄存器,注意 INA226 是高字节在前 */ HAL_StatusTypeDef ina_write16(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(ina_i2c, INA226_ADDR, reg, I2C_MEMADD_SIZE_8BIT, buf, 2, 10); } /* 读 16 位寄存器,电流和分流电压是有符号的,用 int16_t 接 */ HAL_StatusTypeDef ina_read16(uint8_t reg, int16_t *out) { uint8_t buf[2]; HAL_StatusTypeDef st = HAL_I2C_Mem_Read(ina_i2c, INA226_ADDR, reg, I2C_MEMADD_SIZE_8BIT, buf, 2, 10); if (st != HAL_OK) { return st; } *out = (int16_t)(((uint16_t)buf[0] << 8) | buf[1]); return HAL_OK; }HAL_I2C_Mem_Write的第二个参数是 8 位格式的从机地址,所以0x40要左移一位变成0x80。这个坑很常见,写错的表现是HAL_ERROR一直返回,用逻辑分析仪抓波形能看到地址字节发出去没人应答。超时参数给了 10 ms,400 kHz 下传两个字节不到 100 µs,10 ms 足够宽松,卡住就说明总线有问题,及时返回比死等好。
读函数里把两个字节拼成int16_t,是因为分流电压和电流寄存器都是有符号的——电流反向时读数为负,如果用uint16_t接,反向电流会变成一个接近 65535 的巨值,功率计算全乱。
4.2 初始化序列:配置寄存器与校准寄存器
INA226 上电后配置寄存器的默认值是 0x4127,对应平均次数 1、转换时间 1.1 ms、连续转换模式。想提高信噪比就得改平均次数,想省电就改单次转换。配置寄存器的位定义是这样的:
| 位 | 名称 | 作用 |
|---|---|---|
| 15 | RST | 写 1 复位,自动清零 |
| 14~12 | 保留 | 固定写 100 |
| 11~9 | AVG | 平均次数,000=1,100=128 |
| 8~6 | VBUSCT | 总线电压转换时间 |
| 5~3 | VSHCT | 分流电压转换时间 |
| 2~0 | MODE | 111 = 分流与总线连续转换 |
取平均 16 次、两路转换时间都是 1.1 ms,那么一次完整更新周期是 16 × (1.1 + 1.1) ≈ 35 ms,对应刷新率约 28 Hz,做 10 Hz 采样绰绰有余。
校准寄存器的公式是:
CAL = 0.00512 / (Current_LSB × Rshunt)
其中 Current_LSB 是人为选定的“每 bit 代表多少安培”,通常取最大电流除以 32768。5 A 量程下 Current_LSB = 5 / 32768 ≈ 152.6 µA,取整到 156.25 µA(也就是 0.00015625 A/bit)方便计算。代入 10 mΩ 分流电阻:
CAL = 0.00512 / (0.00015625 × 0.01) = 3276.8
四舍五入写 3277。这个值必须写进 0x05 寄存器,否则电流寄存器和功率寄存器永远是 0——这是新手最常踩的坑,后面避坑章还会细说。
#define RSHUNT_OHM 0.01f /* 10 mΩ 分流电阻 */ #define CURRENT_LSB 0.00015625f /* 156.25 µA/bit,5 A 量程 */ void ina226_init(void) { int16_t id = 0; /* 先验身份,读不到就停,避免在坏总线上继续跑 */ if (ina_read16(INA226_REG_MANUF_ID, &id) != HAL_OK || (uint16_t)id != 0x5449) { Error_Handler(); } if (ina_read16(INA226_REG_DIE_ID, &id) != HAL_OK || (uint16_t)id != 0x2260) { Error_Handler(); } /* AVG=16 次,VBUSCT=1.1ms,VSHCT=1.1ms,MODE=连续转换 */ uint16_t cfg = (0x4 << 9) | (0x4 << 6) | (0x4 << 3) | 0x7; ina_write16(INA226_REG_CONFIG, cfg); /* 校准值决定电流与功率的量纲,必须先写 */ uint16_t cal = (uint16_t)(0.00512f / (CURRENT_LSB * RSHUNT_OHM) + 0.5f); ina_write16(INA226_REG_CAL, cal); }身份校验用两次读,是因为 0xFE 和 0xFF 两个寄存器内容不同,只对一个可能是地址撞上了别的芯片。写完配置和校准之后建议延时 1 ms 再开始读数据,给内部 ADC 一个建立时间。
4.3 数据读取与单位换算
四个数据寄存器的换算关系要记牢,单位搞混是这类项目最常见的“数据看着对但差十倍”的原因:
| 寄存器 | 地址 | 原始类型 | 换算 |
|---|---|---|---|
| 分流电压 | 0x01 | int16 | × 2.5 µV |
| 总线电压 | 0x02 | int16 | × 1.25 mV |
| 功率 | 0x03 | uint16 | × 25 × Current_LSB |
| 电流 | 0x04 | int16 | × Current_LSB |
功率寄存器的 LSB 是电流 LSB 的 25 倍,这是芯片内部乘法器的固定比例,不是随便定的。按上面 156.25 µA 的电流 LSB,功率 LSB = 25 × 0.00015625 = 3.90625 mW。
typedef struct { float bus_voltage; /* V */ float current; /* A */ float power; /* W */ } ina_data_t; void ina226_read(ina_data_t *d) { int16_t raw_bus = 0, raw_cur = 0, raw_pwr = 0; ina_read16(INA226_REG_BUS, &raw_bus); ina_read16(INA226_REG_CURRENT, &raw_cur); ina_read16(INA226_REG_POWER, &raw_pwr); d->bus_voltage = raw_bus * 0.00125f; d->current = raw_cur * CURRENT_LSB; d->power = (uint16_t)raw_pwr * 25.0f * CURRENT_LSB; }功率寄存器是无符号的,读的时候虽然用int16_t接了,但转换前要转回uint16_t,否则功率超过 32.767 W 时会被当成负数。5 A × 24 V = 120 W,这个值早就超过 32767 对应的码值了,不做类型转换会看到功率突然变成负的——这个现象很吓人,但原因就是一行类型。
4.4 滤波与上报
INA226 内部已经做了 16 次平均,但电机类负载的电流是脉动的,单次读数照样会跳。软件端再加一级滑动平均,窗口长度取 8,配合 10 Hz 采样,等效响应时间约 0.8 s,对监视类应用完全够用。
#define FILTER_LEN 8 static float buf[FILTER_LEN]; static uint8_t idx = 0; static float sum = 0.0f; float moving_avg(float in) { sum -= buf[idx]; /* 先减去最老的一个 */ buf[idx] = in; sum += in; idx = (idx + 1) % FILTER_LEN; return sum / FILTER_LEN; }这个写法比每次循环求和快,代价是要维护一个累加和,初始化时记得把buf清零。窗口长度别开太大,16 以上在阶跃负载下会明显滞后,答辩演示时切换负载,曲线跟不上就很难看。
串口上报建议用固定帧格式:帧头 0xAA 0x55、长度、三路 float(小端)、校验和、帧尾。上位机按字节流解析,不要用printf打 ASCII——ASCII 帧在 115200 下每帧三十多个字节,10 Hz 就占了三分之一带宽,而且解析容易错位。float 直接按字节发,上位机用结构体映射,简单可靠。
5. 常见问题排查:五个最容易翻车的现场
5.1 读出来全是 0xFFFF 或者 0x0000
现象是初始化时读制造商 ID 就失败,或者所有寄存器读回来都是固定值。
原因通常有两个:一是 I2C 地址错,0x40没左移,或者 A0 / A1 引脚实际电平与代码里的地址不一致;二是 SDA / SCL 上拉电阻没焊或者焊成了 10 kΩ 以上,400 kHz 下上升沿太慢,从机采样不到。
解决顺序是先量地址引脚电平,再量上拉电阻两端电压,最后用逻辑分析仪抓一次波形看有没有 ACK。代码里加一句超时返回,别让while死等把整个系统卡住。
5.2 电流读数偏小十倍或偏大十倍
现象是电压正常,功率和电流的数值像是被缩放了一个数量级。
原因几乎都是校准寄存器算错。最常见的两种:一是分流电阻的单位用了毫欧而没有换成欧,公式里代入 10 而不是 0.01;二是 Current_LSB 取了 0.001(1 mA/bit)但实际按 5 A / 32768 应该是 0.00015625,两者差 6.4 倍,读数自然对不上。
解决方法是把 CAL 的中间结果打印出来对照手算,同时用标准源给一个准确的 1 A,看读数是不是 1.00 A。这一步是标定的基础,跳过去后面全白搭。
5.3 小电流时读数跳动厉害
现象是 100 mA 以下电流时,读数在 0.05~0.15 A 之间来回跳。
原因是原始分辨率不够加上噪声。10 mΩ 电阻下 1 个 LSB 是 250 µA,100 mA 只对应 400 个码,噪声占几十个码就显出明显跳动了。解决方法是提高平均次数到 128 次,或者在软件端把滑动窗口拉长,同时检查分流电阻的取压走线是不是走成了开尔文——如果取压点落在电流路径上,焊锡电阻的温漂会直接变成低频噪声。
还有一个容易忽略的点:被测电路的负载本身就在小电流下不稳定,先断开负载用纯电阻测,确认是测量问题还是负载问题。
5.4 一接电机功率就乱跳甚至死机
现象是空载时数据正常,接上电机或者开关电源之后,读数剧烈波动,严重时 I2C 通信中断。
原因是共模干扰和地弹。电机换向时母线电流的 di/dt 很大,分流电阻上的共模电压瞬间抬升,超过 INA226 的输入范围或者把 I2C 电平带偏。解决方法是三点:IN+ / IN- 前的 RC 滤波电容要靠近芯片;I2C 走线远离功率回路,必要时加磁珠;软件上给 I2C 通信加重试,连续三次失败就重新初始化总线。
如果干扰特别严重,可以考虑把 INA226 的供电单独用一路 LDO,和 MCU 的数字电源分开,代价是多一颗元件,但对测量稳定性提升明显。
5.5 硬件 I2C 卡在 BUSY 位不释放
现象是程序跑一段时间后HAL_I2C_IsDeviceReady一直返回 BUSY,复位 MCU 才能恢复。
原因是 I2C 总线在传输过程中被干扰打断,从机把 SDA 拉低不放,主机侧状态机就卡住了。这是 STM32F103 硬件 I2C 的老问题。
解决方法是加总线恢复函数:把 SCL 配成 GPIO 输出,手动打 9 个时钟脉冲,直到 SDA 释放,再重新初始化 I2C 外设。恢复逻辑放在初始化函数开头,上电先执行一次,能解决大部分“第一次通信就卡死”的情况。如果项目对可靠性要求高,直接用软件 I2C 更省心。
6. 验收与答辩:把数据摆出来比讲原理管用
最后这一步很多人吃亏——板子做出来了,数据也测了,但站上去讲不清楚,被追问两句就露怯。验收和答辩的核心不是复述原理,是把“我怎么证明它准”这件事摆到台面上。
先做静态精度验收。用可调直流源给三个电压点(比如 5 V、12 V、24 V),负载用大功率电阻分别取三个电流点(比如 0.5 A、2 A、4 A),每个点记录 INA226 读数和高精度台式表读数,算相对误差。判定标准按自己方案里写的指标来,一般工业级模块要求电压误差 ≤ 0.5%、电流误差 ≤ 1%。把这张表打印出来,答辩时直接放:
| 测试点 | 标准表读数 | 模块读数 | 相对误差 | 是否合格 |
|---|---|---|---|---|
| 5 V / 0.5 A | 5.002 V / 0.498 A | 5.01 V / 0.502 A | 0.16% / 0.80% | 合格 |
| 12 V / 2 A | 12.01 V / 1.997 A | 12.03 V / 2.012 A | 0.17% / 0.75% | 合格 |
| 24 V / 4 A | 24.02 V / 3.995 A | 24.05 V / 4.021 A | 0.12% / 0.65% | 合格 |
再做动态响应。用 MOS 管或者继电器切换负载,从 0.5 A 阶跃到 4 A,用串口把数据全部记录下来,画一条时间-电流曲线,标出稳定时间。这个图能直接回答“你的滤波窗口会不会太慢”这类问题。
第三步是长时间稳定性。连续跑 2 小时,每 10 秒记录一次,看零点漂移和增益漂移。板子发热之后分流电阻的温漂会体现出来,如果 2 小时后的读数和初始差超过 0.5%,就得在软件里加温度补偿,或者换更低温度系数的电阻。这一项很多人不做,但它是区分“能演示”和“能交付”的分水岭。
答辩时被问得最多的三个问题,提前准备好答案:分辨率怎么算出来的(答 2.5 µV / Rshunt,再叠加平均次数带来的有效位数提升);校准寄存器为什么是这个值(现场能推导 CAL = 0.00512 / (Current_LSB × Rshunt));如果换成 20 A 量程要改哪里(分流电阻换 2 mΩ、Current_LSB 重算、校准寄存器重写,硬件软件各一处)。能当场推出来,比背十页 PPT 都有说服力。
我自己的习惯是,每块板子第一次上电前,先把这三个问题的答案写在纸上压在键盘底下,测完对照一遍。有一次偷懒没写,答辩时被问到校准值的推导,卡了十几秒,虽然最后答上来了,但那个停顿很掉分。从那以后我每次做测量类项目,验收表和推导过程都强制走一遍,宁可多花半小时,也不在台上现想。希望帮到你。
本文还有配套的精品资源,点击获取