简介:本资源是面向全国大学生电子设计竞赛(电赛)参赛者与嵌入式初学者的STM32电流检测实战项目,聚焦高精度电流/电压监测这一典型赛题需求。项目以STM32F103为主控,通过I²C总线驱动INA219传感器实现0–36V电压与0–3.2A电流的实时采集,并创新引入数字FIR滤波算法抑制电磁干扰与采样噪声,显著提升测量稳定性与精度,适用于电源监控、电池管理系统等实际场景。压缩包含221个文件,总计6.34MB,涵盖核心源码(38个.c、40个.h)、编译中间文件(39个.o、39个.d)、工程配置(uvprojx、uvoptx、dbgconf)、调试输出(axf、hex、map、lst)及关键外设驱动(IIC_SCCB.c、stm32f10x_i2c.c、stm32f10x_tim.c等),目录结构完整,便于理解工程构建逻辑与模块分工。已有365人学习下载,提供可直接编译运行的Keil MDK工程,含滤波算法实现、I²C通信封装及数据处理全流程代码,是掌握传感器融合与嵌入式信号处理的优质实践范例。 电赛前夜在实验室调板子,万用表电流档量程不够,还得把表笔串进电路里,一个不小心就烧了保险丝。后来换成INA219之后,情况一下子舒服了:它串在电源正端,I2C直接把电流、电压、功率读回来,单片机屏幕上实时刷新,再也不用对着万用表抠数值。这篇博文主要围绕STM32F103 + INA219的组合,把我从硬件选型、寄存器配置、软件驱动到校准误区的完整过程写清楚,适合正在备赛电赛、或者想给嵌入式项目加一路电流监测的朋友参考。文章不会只贴代码,更多的是告诉你每一步为什么这么做、以及哪些坑我已经替你踩过了。
1. 为什么在电赛里要专门用一颗INA219芯片测电流
1.1 高边检测与低边检测的本质区别
很多人在采样电流时,第一反应是“在负载和GND之间串一个小电阻,然后测电阻两端的电压”。这就是典型的低边电流检测。低边方案在原理上完全没问题,但它在实际系统中会引入很糟糕的副作用:采样电阻放在地端,会让负载的“地”不再真正是0V,而是一个被抬高了几十毫伏甚至上百毫伏的参考点。在纯模拟电路里可能无所谓,但STM32这类单片机周围往往还有传感器、通信芯片、OLED显示模块,它们之间是共地的,地电位一旦有偏移,I2C通信可能直接乱掉,ADC采集也会出现莫名其妙的跳字。
INA219走的是高边检测路线,采样电阻放在电源正端和负载之间,负载的GND仍然和系统地直接相连,不会抬高任何模块的地电位。更重要的是,高边检测能实时监控到负载到底从电源抽走了多大电流,如果电流超出阈值,可以直接在软件里关断输出,做短路保护或者过流保护。这类保护动作在低边方案里也能做,但反应速度和安全性往往不如高边方案。
还有一个细节值得说:INA219内部不是简单地把采到的差分电压送进一个普通ADC,而是经过了可编程增益放大器(PGA),范围可以从±40mV一路放到±320mV。这意味着小电流测量也能有比较可观的精度,不像有些方案小电流时直接被噪声淹没。
用一句话总结:低边采样是“简单但容易惹事”,高边采样是“稍微多花一点心思,但系统的地更干净,安全性也更高”。电赛作品通常是一个多模块协作的系统,地线不干净后面会有一连串连锁问题,所以我更推荐高边方案。
1.2 采样电阻选型与量程设定
市面上常见的GY-INA219模块,板载采样电阻通常是一个0.1Ω的合金电阻。0.1Ω这个值不高不低,当电流是2A时,它上面的压降是0.2V,功耗是0.4W。对于大多数电赛题目用的1A~2A电流范围,这个阻值是比较合理的。
INA219内部电流寄存器是16位有符号数,最高位为符号位,实际有效位是15位。如果想让满量程电流对应2A附近,电流LSB(每位代表的安培数)通常是:
- 2A / 32768 ≈ 61.035uA/LSB
但手册和官方库都倾向于选一个“看起来整齐”的数值,比如100uA/LSB或者1mA/LSB,然后倒推校准寄存器。这里有一点一定要注意:如果你把电流量程设置成了2A,但实际负载电流瞬时超过了2A,读数并不会像普通ADC那样自然饱和,而是有可能变成一个看起来完全没规律的负值或者乱跳的值。因为内部寄存器是带符号的,超量程之后数据直接溢出。
选采样电阻时,不要只看阻值,还要看功率。0.1Ω的电阻,在2A电流下就有0.4W功耗,如果你的负载峰值到3A,那就要按P=I²R算一下,功率需求大概是0.9W。电赛现场经常用的是0805贴片电阻,额定功率大概在0.125W,用在2A场景下完全不够,模块上原装的2512封装合金电阻才是比较稳妥的方案。如果你是自己画板子,建议直接用2512封装的0.1Ω/1%电阻,温漂尽量选低一点。
1.3 与“运放+ADC”方案的对比:为什么要选专用IC
用运放搭差分放大电路再去测电流,很多教材里都有讲,核心是使用仪表放大器或者差分放大器,先把采样电阻两侧的小电压放大,再送到STM32的内部ADC。这个方案的最大的问题在于,STM32F103内部ADC是12位的,参考电压是3.3V,折算下来1个LSB大约是0.8mV。如果你用的是0.1Ω采样电阻,1A电流产生的压降是100mV,换算成ADC的LSB,大约126个码值,看起来够用,但当电流只有50mA时,对应的压降只有5mV,ADC读数只有6个LSB左右,稍微有一点噪声,读出来的电流就是跳来跳去的。
而且运放方案还要面对另一堆麻烦:用什么型号的运放、失调电压怎么调、共模电压范围够不够、PCB走线要不要包地、要不要加滤波电容。这些在电赛现场都是时间黑洞。
INA219把可编程放大器、12位ADC、I2C数字接口、报警比较器全部集成到一个芯片里,外部只需要一个采样电阻和两个去耦电容。它测量的是差分电压,不是绝对电压,所以即使采样电阻两端相对GND的电位都在某个体外范围内,也能准确测出差值。对于电赛这种时间紧、变量多的场景,拿一块专用IC替代运放手工电路,绝对是省时间、降风险的做法。
2. 硬件搭建:从最小系统到INA219模块的连线细节
2.1 STM32F103最小系统与I2C引脚分配
先把STM32F103最小系统这块说清楚。市面上的“STM32F103最小系统板”通常已经集成了8MHz晶振、复位电路、BOOT选择、AMS1117-3.3稳压芯片、Type-C或MicroUSB接口,以及若干个排针引出所有IO。如果你是自己从零画一块最小系统板,核心的几个要素:
- 8MHz晶振配合两个20pF电容,单片机内部PLL倍频到72MHz;
- VDD每个引脚加上100nF去耦电容,VDDA引脚单独接一个1uF电容,保证ADC等的模拟电源干净;
- BOOT0下拉到GND,让系统从Flash启动;
- NRST引脚的复位电路,通常是一个10k上拉电阻加一个按键,再加一个100nF电容。
对于INA219的I2C通信,最简单、最常用的引脚组合是PB6(I2C1_SCL)和PB7(I2C1_SDA)。这两个引脚在STM32F103上默认复用为I2C1,硬件上还需要外接两个上拉电阻。STM32内部没有上拉使能,或者说内部上拉的阻值不足以满足I2C通信的要求,所以必须在外部接两个上拉电阻,常见阻值:100kHz标准模式下用4.7k,400kHz快速模式下用2.2k~4.7k。
提示:电赛现场如果发现I2C通信偶发失败,优先检查SDA和SCL两条线上的上拉电阻是否接触良好。尤其是使用杜邦线连接时,插拔几次后线阻会变化,这是我实际遇到过的。
2.2 INA219模块上的A0/A1地址配置与电源接入
INA219有两个地址引脚A0和A1,通过它们的电平组合决定芯片的I2C地址。默认模块上A0和A1都接地,此时地址是0x40。如果你需要同时接两个INA219模块(比如一路测电机电流、一路测电池电压),就必须把其中一片的A0或A1接到VCC,让它的地址变成0x41或者0x42,不能两片都用默认地址。
关于模块供电,市面上大多数INA219模块上带了一个稳压芯片(通常是小型的LDO),所以VCC可以直接接5V,模块会对内部逻辑电路做稳压,同时I2C引脚的电平兼容性一般也能和3.3V的单片机互通。不过稳妥起见,我还是建议VCC直接接3.3V,逻辑电平完全一致,少一层电平转换,I2C的波形也更干净。
接线是最容易出错的地方,把每一个物理引脚对应关系写清楚:
| INA219模块引脚 | 接哪里 | 说明 |
|---|---|---|
| VCC | STM32F103的3.3V | 模块电源,也是I2C上拉电源 |
| GND | 系统地GND | 必须与STM32共地 |
| SDA | PB7 | 接I2C1的数据线 |
| SCL | PB6 | 接I2C1的时钟线 |
| VIN+ | 电源正极 | 连接电源,电流从这进 |
| VIN- | 负载正端 | 连接负载,电流从这出 |
单独强调一下VIN+和VIN-的接法。它俩不是供电引脚,而是测量引脚。必须让负载电流真正流过VIN+到VIN-之间的采样电阻,INA219才能测到电流。如果把VIN+和VIN-直接连到电源正极而不经过负载,读到的永远是接近0的电流。
2.3 电源去耦与采样走线布局
如果是在面包板上做实验,倒不用太纠结布局,但如果是自己画PCB,采样电阻的走线值得认真处理。采样电阻两端应该用“开尔文接法”:从电阻本体两端各引出一根细走线,直接送到INA219的VIN+和VIN-引脚附近,然后再用细线把这两个点引到芯片。这样做的目的是避免大电流走线上的压降被当作采样电压读到ADC里。PCB上大电流走线的铜箔虽然电阻很低,但1A电流流过10cm长、0.5mm宽的走线,压降可能就有几十毫伏,这直接搞坏了测量精度。
芯片的电源引脚旁边要放一个100nF电容,优先靠近VCC和GND引脚放置。如果电路板上还有其他大功率器件(电机驱动芯片、继电器等),建议在INA219的电源输入端再加一个10uF的电解电容,能吸收一部分母线电压的跌落和振铃。
还有一点,SDA和SCL线尽量远离电机的驱动线。电机的PWM驱动线上有大电流快速切换,会产生很强的电磁干扰,如果I2C线和它平行走线几厘米以上,可能会导致通信错误。我自己做电机驱动板的时候,I2C线都是和PWM线分两层走,交叉时也尽量垂直,这个细节能省去很多现场调试的麻烦。
3. 读懂INA219的寄存器:不踩时序的坑
3.1 Configuration寄存器0x00:每一个bit都要算清楚
INA219操作的核心是先写配置寄存器,再写校准寄存器,然后不断读取电压、电流和功率寄存器。但很多人一上来就照抄例程的0x019F,出了问题又不知道从哪改,所以这里把Configuration寄存器的关键bit逐个拆开看。
Configuration寄存器是一个16位寄存器,各位的含义如下:
| bit位 | 名称 | 作用 |
|---|---|---|
| bit15 | RST | 写1复位所有寄存器 |
| bit14 | 保留 | 必须写0 |
| bit13 | BNG | 总线电压量程:0 → 16V,1 → 32V |
| bit12 | PG1/PG0 | PGA增益:00 → ±40mV,01 → ±80mV,10 → ±160mV,11 → ±320mV |
| bit11 | BADC | 总线ADC的分辨率和采样模式 |
| bit10 | SADC | 分流ADC的分辨率和采样模式 |
| bit9 | 模式 | 连续/触发、分流/总线/两者 |
以常见的配置值0x019F为例,二进制展开:
- 0000 0001 1001 1111
其中bit13为0,表示总线电压量程16V;bit12:11为“11”,PGA选择±320mV,也就是在0.1Ω采样电阻下的±3.2A电流量程;bit10:7为“1001”,表示总线ADC是12位分辨率加128次平均;bit6:3为“1111”,表示分流ADC同样12位加128次平均;bit2:0为“111”,表示连续测量分流电压和总线电压。
这里的“128次平均”很关键,相当于芯片内部做了数字滤波,对电赛现场的工业噪声有很好的抑制效果。代价是数据更新速度变慢,但INA219的采样本来就不是为了做高速电流环,平均模式反而更实用。
如果想改PGA增益,需要注意:PGA选±40mV时小电流分辨力更高,但量程变小;同时Calibration寄存器的值要重新算。不要以为配置值随便改一下就行,改了PGA不改校准值,读出来的电流会差好几倍。
3.2 Calibration寄存器与电流LSB的关系
校准寄存器(0x05)的值决定了电流寄存器每变化一个数字,到底代表多大的电流。手册给出的公式是:
cal = trunc(0.04096 / (current_LSB × R_shunt))
其中current_LSB是你期望的每位电流分辨率,单位是A/LSB。举个例子:如果我期望每个LSB代表100uA,采样电阻是0.1Ω,那么:
- 0.04096 / (0.0001 × 0.1) = 0.04096 / 0.00001 = 4096
所以cal = 4096。写入0x05寄存器后,电流寄存器的原始值乘以0.0001A,就是实际电流。这样算出来的好处是,电流值可以直接通过移位或者整数运算再转到显示层,不需要频繁使用浮点数。
功率寄存器的换算稍微特殊一点:功率寄存器(0x03)的原始值乘以20,再乘以current_LSB,才等于实际功率(单位瓦特)。也就是说,在0.100mA/LSB的设置下,功率寄存器的每个字代表2mW。曾经看到有人把功率寄存器直接乘current_LSB,结果功率只有实际值的一半,这就是没乘20导致的。
注意:如果PGA增益设置改变,或者电流量程预期改变,务必重新计算cal。这个寄存器一旦写错,虽然通信一切正常,但读出来的所有电流值、功率值都会系统性偏大或偏小,属于最隐蔽的“貌似正常,实则全错”问题。
3.3 I2C读写注意事项
INA219使用的I2C时序很简单:先向从机设备地址(如0x40)发送要访问的寄存器指针,然后再读或写数据。但有几个细节很容易被忽略:
读寄存器时,地址字节是7位从机地址左移1位,加上读写位。在HAL库中,hdev.Address要填的是0x40的左移版,也就是0x80,可以直接填0x40然后在HAL_I2C_Mem_Read里传参,不同写法要对照你使用的库定义,千万别地址搞错,否则一直返回超时。
一次读取两个字节时,高位在前、低位在后,需要自行合并:value = (uint16_t)((buf[0] << 8) | buf[1])。
写配置寄存器时,如果先写寄存器指针再写两个字节数据,两段之间需要一点延时,或者在HAL库中直接用HAL_I2C_Mem_Write接口,这样一个函数就能完成,不用手动管Start/Stop信号。
关于STM32F103的硬件I2C,这里专门提一句:很多老工程师对F103的硬件I2C有心理阴影。在我使用HAL库的过程中,只要初始化正确,加上超时重试机制,硬件I2C基本能稳定工作。但如果你的工程是标准库写的,使用硬件I2C时偶尔会遇到总线忙死锁的情况,如果你不想在这种问题上浪费电赛备赛时间,最简单粗暴的方案是:用两个普通GPIO软件模拟I2C时序。INA219是I2C从设备,对时序要求不算苛刻,软件模拟稳定可靠,代码量也不大。这里用HAL库硬件I2C的方式继续讲,但软件模拟代码作为备选也是好选择。
4. 软件实现:HAL库下用I2C驱动INA219的完整过程
4.1 初始化流程:复位、配置、校准三件事
在开始写代码之前,先看一下整体初始化顺序:
- 开启I2C1外设时钟,配置PB6、PB7为复用开漏输出模式,设置上拉电阻;
- 设置I2C时钟频率为100kHz,使能I2C;
- 向寄存器0x00写入0x019F,完成配置;
- 向寄存器0x05写入校准值4096;
- 可以读取一遍所有寄存器,验证是否读到正常数据。
这里提供一个完整的HAL库初始化函数框架:
#include "stm32f1xx_hal.h" I2C_HandleTypeDef hi2c1; void MX_I2C1_Init(void) { hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(&hi2c1) != HAL_OK) { Error_Handler(); } } #define INA219_ADDR 0x40 #define INA219_REG_CFG 0x00 #define INA219_REG_CAL 0x05 void INA219_Init(void) { uint8_t cfg_data[2]; uint8_t cal_data[2]; // 修正寄存器值 0x019F:16V量程、PGA±320mV、12bit+128平均、连续测量 cfg_data[0] = 0x01; cfg_data[1] = 0x9F; HAL_I2C_Mem_Write(&hi2c1, INA219_ADDR << 1, INA219_REG_CFG, I2C_MEMADD_SIZE_8BIT, cfg_data, 2, 100); // 校准值 4096,对应100uA/LSB cal_data[0] = 0x10; cal_data[1] = 0x00; HAL_I2C_Mem_Write(&hi2c1, INA219_ADDR << 1, INA219_REG_CAL, I2C_MEMADD_SIZE_8BIT, cal_data, 2, 100); }4.2 封装读取函数:电压、电流、功率一次读清
初始化完成后,读取数据就很简单了。唯一容易出错的地方是有符号数的处理。INA219的分流电压寄存器和电流寄存器都是有符号16位,当电流反向流动时,寄存器值会是负数,对应的二进制是0xFFFF开头。如果直接按无符号十六进制转十进制,会得到65535这种大数。
正确的做法是判断最高位,如果最高位为1,则用原值减65536。我这里以电流为例:
int16_t INA219_ReadRaw(uint8_t reg) { uint8_t buf[2]; HAL_I2C_Mem_Read(&hi2c1, INA219_ADDR << 1, reg, I2C_MEMADD_SIZE_8BIT, buf, 2, 100); int16_t val = (int16_t)((buf[0] << 8) | buf[1]); return val; } float INA219_ReadBusVoltage(void) { // 总线电压寄存器 bit15:3 是有效位,最低位代表4mV int16_t raw = INA219_ReadRaw(0x02); return (float)(raw >> 3) * 0.004f; } float INA219_ReadCurrent(void) { // 电流寄存器直接用,乘以LSB 0.0001 int16_t raw = INA219_ReadRaw(0x04); return (float)raw * 0.0001f; } float INA219_ReadPower(void) { // 功率寄存器:原始值 * 20 * 0.0001 int16_t raw = INA219_ReadRaw(0x03); return (float)raw * 0.002f; }有一个细节:读总线电压时,手册说寄存器低3位为0,实际数据是bit15:3。所以先右移3位再乘4mV,得到的才是正确的母线电压。如果你不右移直接乘,数据会偏大到没有意义。
4.3 连续采集与去抖动处理
电赛现场常见的负载是直流电机、继电器、发热丝这类设备。电机启动时电流尖峰很大,稳定时电流又比较平稳,如果直接拿原始电流值去控制逻辑,可能会有误判。INA219本身支持128次平均,这是第一个滤波手段;软件层面建议再加一个简单的滑动平均,比如每50ms读取一次,缓存最近20个值,取平均值作为当前电流。
关于读取速度,INA219在平均模式下,一次完整的电流和功率读取大概需要几毫秒到几十毫秒不等,对于电赛的监测需求完全足够。不建议为了提高刷新率把ADC分辨率降到9位,除非你要做的是高频电流保护,那样的话更推荐直接使用INA219的中断比较功能(Alert引脚),而不是靠单片机轮询。
下面这段代码是滑动平均的简单实现,适用于裸机环境:
#define SAMPLE_NUM 20 float current_buf[SAMPLE_NUM]; uint8_t current_index = 0; float get_current_average(void) { float current = INA219_ReadCurrent(); current_buf[current_index] = current; current_index = (current_index + 1) % SAMPLE_NUM; float sum = 0; for (uint8_t i = 0; i < SAMPLE_NUM; i++) { sum += current_buf[i]; } return sum / SAMPLE_NUM; }5. 校准精度:为什么手册公式算出来的电流不准
5.1 误差来源拆解
很多人烧录完程序,读到的电流值和万用表实测值对不上,第一反应是程序写错了。实际情况是,INA219本身和采样电阻都存在误差,这些误差叠加起来,电流读数出现百分之几的偏差非常正常。
误差来源可以分成三类:
采样电阻的阻值误差:模块上原装电阻一般是1%精度,但如果是自己焊的普通贴片电阻,误差可能到5%。而Calibration寄存器里的校准值是基于“标称阻值”算的,电阻实际值如果不是0.100Ω,电流读数就会等比例偏大或偏小。
INA219的增益误差和偏移误差:数据手册中给出的典型增益误差大约在0.5%~1%之间,零点偏移误差通常在几微伏级别。对于小电流测量,偏移误差的影响会比较明显。
温度漂移:电流流过采样电阻会发热,发热后阻值变化。金属膜电阻的温漂可能是几十ppm/℃,而合金电阻通常好得多。如果长时间过大电流,采样电阻阻值变化,读数也会慢慢漂。
理解这些来源之后,就能意识到:手册算出来的校准值只是一个“初始化值”,真正想要电赛得分更高、数据更可信,还是要做一次实测校准。
5.2 两步标定法:零电流偏移和增益校正
校准分两步。第一步是零电流校准:系统上电,负载保持断开状态,此时INA219读到的电流理论上应该是0,但实际可能读到几mA甚至十几mA,这个值就是零点偏移。把它记录下来,后续每次读取实际电流时先减掉它。
第二步是增益校准:找一个稳定负载,比如一个大功率电阻或者电子负载,让电流稳定在一个已知值I_ref,用万用表或者前级电源读数作为参考。同时记录INA219读出并经零点校正后的电流值I_raw,然后计算增益修正系数:
gain = I_ref / I_raw
之后每次读取,就用:
I_real = (I_raw - offset) × gain
如果条件允许,最好选两个标定点,分别在量程的30%和80%附近,这样能同时修正线性度上的一些偏差。电赛现场时间比较紧张,一个点也够用,但要知道这是折中方案。
将修正系数固化到程序里之前,建议多测几组不同电流的数据,确认修正后的曲线是否平直。如果修正曲线的斜率不断变化,说明采样电阻可能已经严重发热,或者选用的电阻本身就不是高精度类型,这时候要考虑换电阻。
5.3 用OLED/串口显示电流的联动调试
电赛现场很少有人只接一路电流检测而不看数据。最常用的调试方式是借助STM32F103的串口,把测量到的电压、电流、功率打印到串口助手,或者接一块0.96寸I2C OLED屏幕直接显示出来。
有一个常见的坑:OLED模块的I2C地址通常是0x3C,INA219的地址是0x40,地址不同,可以挂在同一条I2C总线上。但OLED初始化时如果频繁拉低SDA线,会占用总线一段时间,如果此时INA219也在连续测量,软件轮询可能会有延迟。我的建议是:显示刷新频率不需要太高,OLED每200ms刷新一次即可,电流数据的采集放在定时器中断里,保证采样周期稳定,不因为显示刷新而抖动。
6. 我做这套方案时踩过的四个坑
6.1 多片INA219共用同一个I2C地址
有一年电赛,我需要在电机电源端和主控电源端各测一路电流,于是第一版直接放了两片INA219模块,代码里读的都是0x40地址,结果两路数据完全一样,排查了半天才发现第二片芯片的A0、A1没设置成不同地址。
解决方法是:第二片INA219把A0引脚接VCC(或者接SDA),让它工作在0x41地址。代码里定义两个不同的从机地址,读取函数加一个地址参数,就可以分别读取两路数据了。类似这种问题,如果前期布局时没规划好,后期就只能飞线改地址,麻烦但能解决。
6.2 母线电压过高导致读回全FFFF
电赛电源类题目中,输入电压经常是12V、24V甚至更高。INA219的Configuration寄存器里,bit13控制总线电压量程:0是16V档,1是32V档。如果你用的是16V档,并且母线电压实际是24V,那么读回来的总线电压寄存器就会饱和,表现出来就是0x7FF0之类的大数,或者各种非预期数据。
我建议:在配置寄存器时,如果题目没有明确说明电压一定低于16V,就直接选32V档,也就是把bit13置1。这样虽然低电压时的分辨率从4mV/LSB变成8mV/LSB,但对大多数电赛应用影响不大。电流检测部分不受这个档位影响,PGA的范围还是由bit12:11决定。
6.3 电机负载下的瞬时峰值干扰
带电机负载时,电流波形不是一条直线,而是在平均值附近叠加脉动。电机换向时,瞬间电流可能接近堵转电流。INA219的PGA如果设置在±40mV,在0.1Ω采样电阻下对应最大电流只有0.4A,电机稍微一启动就超量程,电流寄存器就会乱跳甚至变成负值,读者很容易误判成“短路了”或者“芯片烧了”。
所以我强烈建议:在摸清负载电流范围之前,先把PGA设为±320mV档,也就是配置值的“11”。如果发现小电流时分辨率不够(比如待机电流只有10mA,读数经常在0到20mA之间乱跳),再考虑把PGA调低到±160mV或±80mV,同时注意相应更新校准值。
6.4 地线噪声对测量结果的影响
还有一个容易被忽视的问题:ESP32、STM32这类单片机板子上的GND并不一定非常干净,特别是与其他大电流模块直接共地时。INA219是高边检测芯片,它本身测的是VIN+和VIN-之间的差分电压,对GND电平的波动有一定的共模抑制能力。但如果采样电阻两端的走线绕过了一大段地线,或者VIN+、VIN-两根线不对称,共模噪声就会转变成差模误差。
解决方法是把VIN+和VIN-两根线尽量从采样电阻两端“单独引出”,短而粗,不要跟负载大电流线混在一起走(参考前面提到的开尔文接法)。如果实在没法布线,可以使用双绞线或者加一组RC滤波再进芯片引脚,效果会好很多。
7. 电赛现场扩展:把电流检测模块变成子系统
7.1 与PWM输出联动做限流保护
电赛题经常需要控制电机转速,PWM输出是标配。当负载电流超过设定阈值时,把PWM关闭或者占空比降到安全值,是一个简单但非常实用的保护策略。
利用STM32F103的定时器产生PWM,比如TIM3_CH1输出到电机驱动芯片的EN或PWM引脚。在电流检测模块里判断,如果连续N次读取到的电流超过阈值,就调用HAL_TIM_PWM_Stop停止输出,或者把比较寄存器CCR的值改小。这个保护动作完全不需要外部硬件,纯软件可以实现,响应速度通常在几十毫秒级别,适合电赛这种对实时性要求不是特别苛刻的场景。
如果要更快响应,可以借助INA219的Alert引脚。连接方式是:芯片内部有一个比较器,当电流(或总线电压)超过设定阈值时,Alert引脚会拉低,触发STM32的外部中断,然后立即关断PWM。这种方式可以在几百微秒内完成保护动作,但需要额外配置Alert相关的寄存器,电赛现场时间充裕的话值得尝试。
7.2 把监测数据通过CAN/SPI上报
电赛作品如果是“主板 + 驱动板”的结构,那么驱动板上的电流检测数据需要回传给主板做显示和控制。STM32F103自带的CAN外设和SPI外设都能胜任这个任务。
如果走CAN总线,可以参考标准CAN例程,把电流、电压、功率打包成一个8字节数组,周期发送。CAN协议有ACK机制,比串口更可靠,适合距离较长、干扰较大的场景。如果走SPI,一般是一个MCU做主机,另一个做从机,从机把INA219数据写入SPI发送缓冲区,主机周期读取。注意SPI从机发送时,不能像串口一样随时发起,必须等主机发起CLK,所以从机端要维护一个“准备好新数据”的寄存器标志位。
7.3 现场调参建议与故障预案
电赛现场调电流检测模块,我的顺序一般是:
先单独读总线电压,确认能否读到电源电压值。如果这个值不对,说明I2C地址、硬件连接、模块供电至少有一个有问题,先解决掉再说下一步。
再读分流电压寄存器,观察负载断开时是否为0。不为0,先做零点校正。
再读电流寄存器,对比万用表或者电源自带电流显示的数值,判断增益偏差是否在可接受范围。
确认显示输出的曲线稳定,没有明显的跳变和异常尖峰。
现场调试时一定要备两个额外的上拉电阻(2.2k和4.7k各若干)和至少一片备用INA219模块。I2C上拉电阻不匹配在实验室可能不明显,到了赛场换了一套杜邦线、接触电阻变化后,通信失败的概率会明显上升。芯片本身也不贵,万一模块上的合金电阻意外断开或者芯片烧了,换个模块比当场换芯片快得多。
提示:无论做哪类题目,建议在程序里加一个“自检模式”:开机后读一次INA219寄存器,如果两个寄存器的值都是0xFFFF或者读不到ACK,就在OLED/串口上明确提示“INA219无响应”,而不是让系统傻傻地继续运行。这个小小的自检在电赛现场往往能帮你节省半小时的排查时间。
把一路电流检测做成一个稳定可靠的子系统,后续无论接到哪个题目上,都能给主控提供干净的数据。我在实际项目中体会最深的一点是:INA219这类带数字接口的传感器,真正的难点不是通信协议,而是你对量程、校准和误差来源有没有足够的敬畏心。把所有数据源都过一遍校准、加一层滤波、留好自检入口,测量结果才会真的可信,系统也才有底气带更复杂的控制逻辑。
本文还有配套的精品资源,点击获取