基于STM32的ACS712霍尔电流传感器检测与信号处理实战
2026/8/31 15:39:44 网站建设 项目流程

简介:本资源是一套面向嵌入式初学者与STM32项目开发者的ACS712电流传感器实战开发包,聚焦电流采集与ADC数据处理核心能力训练,适用于智能硬件、物联网终端及电机监控等典型应用场景。压缩包共192个文件,含39个头文件(.h)定义外设接口与宏配置、37个C源文件(.c)实现ADC采样、滤波算法、串口打印及系统时钟初始化等完整功能模块;另有KEIL工程文件(.uvprojx/.uvoptx)、编译中间产物(.o/.d/.crf)、调试配置(.dbgconf/.hex/.axf)及4份PDF手册,结构清晰,便于逐层理解与调试。资源包大小为5.97MB,当前已有59人学习下载。所有例程均基于STM32F103标准库开发,经实机验证稳定运行,配套bat脚本支持一键清理编译环境,代码中明确标注单片机与ACS712模块的引脚连接关系,并提供J-Link/ST-Link烧录提示,显著降低入门门槛与排错成本。 做电流检测这个事,我一开始其实是想直接上一个采样电阻加运放的方案,算了一下成本和调试时间,发现还是ACS712这种现成的霍尔电流传感器模块来得快。不管你是想做电机堵转保护、电池充放电监测,还是简单的用电功率统计,ACS712都是上手门槛最低的方案之一。这篇文章我就以STM32F103最小系统板为核心,配合KEIL MDK开发环境,从ACS712模块的原理、接线、采集代码到最后的电流换算和滤波,完整走一遍,同时把我在实际测试中踩过的坑和解决办法一起写出来,方便后来的人少走弯路。

先交代一下适合谁来读:如果你有单片机基础,已经会建STM32工程、会点灯、会用串口打印,那这篇文章可以让你用半天时间把电流采集功能跑通;如果你是个纯新手,也问题不大,我会把需要用到的关键节点都展开讲清楚,但ADC的基础概念和一些简单的C语言知识还是需要先了解一点。

1. ACS712模块的核心原理与选型思路

1.1 霍尔传感器的工作机制

ACS712是Allegro公司出品的线性霍尔效应电流传感器IC。它的内部结构说白了一句话:让被测电流从芯片内部的一根铜排流过,铜排周围会产生一个与电流大小成正比的磁场,芯片内部的霍尔元件感知到这个磁场后,会输出一个与之对应的模拟电压。

这里有个很重要的细节——ACS712的输出电压并不是从0V开始。它的静态输出(即被测电流为0A时)是电源电压的一半。比如用5V供电,0A对应的输出电压理论上是2.5V。这意味着如果你用3.3V供电的单片机ADC直接去测ACS712模块的输出,2.5V的零点还在ADC量程范围内,但如果电流稍微大一点,输出电压很容易就超过3.3V,直接烧坏ADC引脚,即使不烧坏,超出量程的部分也根本测不准。

所以很多新手上来就踩的一个坑就是:STM32F103的ADC参考电压最大只有3.3V,而ACS712模块推荐供电是5V,输出电压范围可以达到0.5V到4.5V,直接把模块的OUT脚接到单片机PA1上,不仅读数不对,芯片也有风险。正确的做法后面我会细讲。

1.2 不同量程版本的选型

ACS712芯片按照量程分为三个常见型号:ACS712-05B、ACS712-20B、ACS712-30B,对应的量程分别是±5A、±20A、±30A,灵敏度分别是185mV/A、100mV/A、66mV/A。

选型的时候不要只看“够用就行”,要留出合理的裕量。比如你预估电路最大电流是4.5A,选5A量程版本就非常勉强,因为ACS712的输出电压在接近满量程时会逐渐进入非线性区,而且模块本身存在噪声和温漂,接近极限时测量误差会明显变大。更好的做法是选20A量程的型号,虽然灵敏度低一些,但线性度和稳定性都更好。

我自己的经验倾向是:能选大量程就不要选小量程。很多人觉得5A量程灵敏度高、分辨率好,但如果被测电流峰值超过量程的80%,读数的跳动会让你怀疑人生。而且ACS712模块本身的带宽是80kHz(实际上还有很多噪声),ADC采集到的原始数据如果不做滤波,跳动幅度会很明显,这个后面会专门讲。

2. 硬件接线与信号调理

2.1 模块引脚定义和基础接线

市面上卖的ACS712模块板子一般引出三个或四个引脚。三个脚的通常是VCC、GND、OUT,四个脚的会多一个VCCT(用于设置内部参考电压的滤波,通常悬空或通过电容接地即可)。

接线的基本思路是:

  • VCC接5V电源(推荐纯直流,不要用开关电源的纹波直接供,如果只有开关电源,最好在模块附近加一个100uF电解电容和0.1uF瓷片电容并联滤波)
  • GND与STM32的GND共地,这是必须的
  • OUT接STM32的ADC输入引脚,但中间要加分压电路

这里需要特别强调共地。很多朋友第一次做电流检测,模块供电用了一个5V适配器,单片机用USB供电,两个电源如果不共地,模块输出电压和单片机的地之间存在电位差,测出来的数值会非常诡异,而且总是不稳定。一定要把两个电源的负极连在一起。

2.2 5V输出信号接入3.3V ADC的降压处理

这是整个项目里最关键的一个地方。ACS712模块用5V供电,静态输出2.5V,最大输出能到4.5V左右,而STM32F103的ADC输入范围是0到3.3V,超过3.3V会损坏引脚。

最稳妥的方案是使用电阻分压网络,把ACS712的输出电压按比例缩小到ADC安全范围内。我实测使用且推荐的做法是:两个10kΩ电阻串联在OUT和GND之间,OUT接到分压点,分压点(即两个电阻中间)接到单片机ADC引脚。这样ADC引脚得到的电压是ACS712输出电压的1/2,最大只有2.25V左右,完全在安全范围内。

用10kΩ的原因是:ACS712模块输出端有一定的驱动能力,10kΩ的负载对它来说几乎没有压力,同时ADC输入端的采样电容在采样瞬间会抽取一定的电荷,10kΩ电阻网络不至于让采样电压有太大的跌落。当然,你如果非要更精确,可以加一个运放跟随器做阻抗匹配,但在一般场景下,10kΩ分压已经足够了,没必要增加成本。

分压之后,代码里的换算逻辑就要对应调整:ADC读到的电压值实际上是ACS712实际输出电压的一半,所以计算电流之前要先把ADC电压加倍还原成模块输出端的实际电压。

2.3 电容滤波的放置位置

在ADC引脚和GND之间加一个0.1uF的瓷片电容,能够有效滤除高频噪声,这个电容要尽量靠近单片机引脚放置。另外在ACS712模块的OUT引脚和GND之间加一个10uF以上的电解电容,可以把霍尔元件本身的高频噪声过滤一部分,实测对后续ADC采样的稳定性提升非常明显。

理论上ACS712有一个内部滤波器引脚,可以通过外接电容调节带宽,但市面上大多数模块板已经把电容焊死在板上了,我们就不折腾那个引脚了,直接用外部滤波电容加软件滤波解决噪声问题。

3. STM32F103 ADC驱动代码实现

3.1 KEIL工程与标准外设库的ADC初始化

我使用的是KEIL MDK 5环境,配合STM32F103标准外设库(Standard Peripheral Library,很多人也叫SPL库)。ADC这部分代码其实不长,但配置的时候有几个细节经常出问题。

首先是RCC时钟配置。ADC1挂在APB2总线上,使用前必须先使能GPIO时钟和ADC1时钟:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);

然后是GPIO配置。PA1作为ADC输入引脚,要配置为模拟输入模式:

GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOA, &GPIO_InitStructure);

注意这里不是复用推挽输出,也不是浮空输入,必须是模拟输入模式,因为ADC要采样的是外部模拟电压,不是数字电平。有些人不小心配成了GPIO_Mode_IN_FLOATING,也能采到数据,但实际上内部的上拉或下拉电路会影响采样精度,尤其是高阻源的分压网络,所以正确的模式应该固定是GPIO_Mode_AIN。

接下来是ADC工作模式配置。我用的是单次转换模式加软件触发,然后用一个循环读取多次取平均。这种方式的优势是逻辑简单,容易理解,适合初学者;缺点是转换过程中CPU一直在忙,效率比较低。如果你后面要做系统级开发,建议改用DMA加扫描模式,效率高很多,但代码复杂度也上去了:

ADC_InitTypeDef ADC_InitStructure; ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = DISABLE; ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1; ADC_Init(ADC1, &ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_55Cycles5); ADC_Cmd(ADC1, ENABLE);

ADC_SampleTime_55Cycles5表示采样时间是55.5个ADC时钟周期。采样时间越长,采样电容充电越充分,测量结果越准确。对于分压网络这种中等阻抗的源,55.5个周期是够用的,如果你用的是高阻源(比如100kΩ级别的分压网络),建议拉到239.5个周期,宁可慢一点也要保证精度。

ADC校准也是一定要做的。STM32F103的ADC有一个内置校准功能,上电后执行一下校准,能消除内部电容的初始误差,每次上电做一次就行:

ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1));

3.2 ADC采样与电压换算函数

读取ADC值的标准流程是:启动转换 -> 等待转换完成 -> 读取数据寄存器。我封装了一个函数,一次性读取64次并求平均,用来有效滤除随机噪声:

uint32_t ADC_Read_Average(uint8_t channel, uint8_t times) { uint32_t sum = 0; uint16_t i; for(i = 0; i < times; i++) { ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_55Cycles5); ADC_SoftwareStartConvCmd(ADC1, ENABLE); while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); sum += ADC_GetConversionValue(ADC1); } return sum / times; }

这个函数看起来简单,但有一个很重要的细节:每次采样前重新配置了通道。在单通道场景下其实这一行可以省略,但如果你以后要扩展到多通道轮流采样,保留这一行能避免通道切换时配置不对的bug,所以我还是建议写上。

拿到ADC值以后,换算成实际电压:

float adc_voltage = (float)adc_value * 3.3f / 4095.0f;

这里用到3.3V作为参考电压。但这个3.3V是STM32F103芯片内部ADC参考电压的标称值,实际上不同板子的3.3V稳压芯片输出会有偏差,有的板子实测只有3.28V,有的到3.32V。如果你对电流精度要求比较高,建议用万用表实测一下板子上的3.3V电压,然后用实测值替换代码里的3.3,这个操作叫做ADC参考电压校正。

3.3 把ADC电压还原成ACS712实际输出电压

由于我们在硬件上做了1/2分压,所以ACS712模块的实际输出电压是ADC电压的两倍:

float acs712_vout = adc_voltage * 2.0f;

请注意,这一步不能直接省略成“乘2”,而是要清楚地知道为什么要乘2。如果以后你改了分压比,比如用3:1分压,那这个系数就要变成3。我在实际开发中就吃过这个亏:一开始用2:1分压,后来为了给其他功能腾引脚,改成了4:1分压,但代码里的系数忘了改,导致测电流偏小了一倍,排查了整整一个下午才发现。

4. 电流计算、零点校准与软件滤波

4.1 零点偏移和灵敏度换算

ACS712的输出电压和电流的关系是一条斜线:Vout = VCC/2 + Sensitivity × I。其中VCC/2是零点偏移,Sensitivity是灵敏度,单位是mV/A。

以ACS712-20A为例,灵敏度是100mV/A,5V供电时零点理论上是2.5V。那么电流的计算公式就是:

float current = (acs712_vout - 2.5f) / 0.100f;

如果算出来是负数,说明电流方向与模块上标注的IP+到IP-方向相反,这是正常的。

但问题来了:零点真的是2.5V吗?不是。因为分压电阻本身的精度、模块的供电电压误差、芯片的个体差异都会导致实际零点偏移。ACS712的数据手册明确写了,静态输出电压典型值是VCC/2,但个体偏差可以达到±50mV甚至更大。换算成电流误差,20A量程下就是±0.5A,这个误差如果不校准,对低电流测量的影响是致命的。

所以,零点校准必须在代码里做,而且最好是每次上电后做一次,或者通过按键触发校准。做法很简单:系统上电后,保证被测电路没有电流流过(断开负载或让电流为0),采集一次输出电压,把这个值存起来作为基准offset:

float zero_offset = 0.0f; void calibrate_zero(void) { // 采集64次平均作为零点 uint32_t adc_value = ADC_Read_Average(ADC_Channel_1, 64); float adc_voltage = (float)adc_value * 3.3f / 4095.0f; zero_offset = adc_voltage * 2.0f; printf("Zero offset: %.3f V\r\n", zero_offset); }

然后在计算电流时用:

float current = (acs712_vout - zero_offset) / 0.100f;

这样做之后,就算供电电压有点偏差,零点也能被校准掉。我实测过,校准前后零飘电流从0.15A降到0.01A以下,效果立竿见影。

4.2 一阶低通滤波和滑动平均滤波对比

ACS712的输出噪声和霍尔元件的特性有关,即使电流恒定,ADC采样到的原始值也会有比较明显的跳动。我实验过,不滤波直接显示,电流值上下跳动范围在±50mA到±150mA之间,具体幅度和电源质量有关。

滤波方案我推荐两种,根据你的场景选择。

第一种是滑动平均滤波。思路是维护一个长度为N的采样缓冲区,每个新数据进来,就把N个数据求平均值作为输出。N越大越平滑,但响应越慢。我在做直流电流监测时取N=32,实测跳动降低到±10mA以内,响应时间约32个采样周期,完全够用。

#define FILTER_SIZE 32 float buffer[FILTER_SIZE] = {0}; uint8_t buffer_index = 0; float sum = 0; float moving_average_filter(float new_value) { sum -= buffer[buffer_index]; buffer[buffer_index] = new_value; sum += new_value; buffer_index = (buffer_index + 1) % FILTER_SIZE; return sum / FILTER_SIZE; }

第二种是一阶低通滤波,也就是所谓的惯性滤波。公式是:

float alpha = 0.3f; float filtered_value = 0.0f; float low_pass_filter(float new_value) { filtered_value = filtered_value * (1.0f - alpha) + new_value * alpha; return filtered_value; }

alpha的值决定滤波强度,alpha越小输出越平滑,但跟随性越差。我实际调试时发现alpha=0.2到0.4之间的手感比较好,如果你的系统对电流变化响应要求高,比如做电机堵转保护,alpha可以调到0.5以上,缺点是显示会比较跳。

我个人的习惯是:用于显示的场景用滑动平均,用于控制保护用一阶低通。两种滤波器在代码实现上都很简单,但在性能表现上的差异值得你花点时间实测体会。

4.3 实际量程和分辨率分析

STM32F103的ADC是12位的,采样值范围0到4095。经过1/2分压后,ADC满量程对应ACS712的6.6V输出,也就是说ADC分辨率对应的电压分辨率是3.3/4095 ≈ 0.8mV,还原到ACS712输出端是1.6mV。

在20A量程、100mV/A的灵敏度下,理论上电流分辨率为16mA。但这是理想情况,考虑到噪声和温漂,实际稳定分辨率大概在30mA左右。这个精度对绝大多数电流监测场景来说已经足够。如果你需要更高的分辨率,有两个方向:一是换更高精度的外部ADC,比如16位的ADS1115;二是使用ACS712 5A量程版本,灵敏度185mV/A,理论分辨率能到8.6mA。但要注意量程限制,取舍要看实际需求。

5. KEIL MDK工程实战要点

5.1 工程创建与核心文件配置

关于KEIL MDK版本,我用的是MDK 5.27,配合STM32F103的标准外设库。如果你手头是更高的5.38或5.39版本,建议先在Pack Installer里装好STM32F1系列的Device Family Pack,否则新建工程时根本找不到STM32F103C8T6这个芯片型号。

MDK的正版授权问题简单说一句:MDK提供免费的评估版,但有代码量限制(大概是32KB),对很多小项目来说够用,如果项目超过这个规模,还是建议购买正版授权。选芯片时在Device选项卡里搜索STM32F103C8,选中后点击OK,工程自动配置好启动文件和链接脚本,不用手动加。

标准外设库的源文件需要把以下文件添加到工程里:

  • stm32f10x_gpio.c
  • stm32f10x_adc.c
  • stm32f10x_rcc.c
  • stm32f10x_usart.c(如果用串口打印调试)
  • misc.c(如果需要配置NVIC中断)

同时在工程选项的C/C++选项卡里,Define一栏要写:USE_STDPERIPH_DRIVER, STM32F10X_MD。USE_STDPERIPH_DRIVER是告诉库函数要使用外设驱动层,STM32F10X_MD是告诉库文件芯片是中等容量产品(STM32F103C8T6是64KB Flash,属于中等容量)。这两个宏忘了任何一个,编译都会报一堆找不到定义之类的错误。

5.2 浮点打印的调通技巧

STM32F103没有硬件浮点单元,所有float运算都是软件模拟的,速度慢但功能没问题。真正容易卡住你的是printf打印float型数据时输出为0的问题。

这是因为KEIL MDK下printf默认用的是微库(MicroLIB)模式。如果没开MicroLIB,printf的浮点支持虽然能工作,但会占用大量代码空间,而且运行时内存开销也大。有两个解决办法:

第一种,在工程选项的Target选项卡里勾选Use MicroLIB,然后重新编译下载,printf就能正常打印浮点数了,这个方法最省事,我推荐它。

第二种,自己重写fputc函数,把printf输出重定向到串口:

int fputc(int ch, FILE *f) { USART_SendData(USART1, (uint8_t)ch); while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); return ch; }

串口初始化完毕之后,printf函数就能往串口发送数据,打开串口助手选择对应波特率,就能看到电流值了。我调试时一般用115200 8N1,稳得很。

还有一个坑:如果你DEBUG模式下打断了断点,但实际上电流都流过传感器了,看串口数据半天没更新,大概率是代码停在断点或者芯片跑飞了。这时候先在调试界面看PC寄存器的值是否在正常范围内,或者直接全速运行,多半能解决。

5.3 优化级别与代码行为

KEIL的编译优化等级在工程选项的C/C++选项卡里设置。很多朋友在做浮点运算比较多、代码又不太大的工程时,觉得编译速度无所谓,默认用-O0就可以了。

但我要提醒一下:如果你用了-O2以上的优化,而且代码里有一些未初始化或看似“无用”的变量访问,编译器可能会把这些操作优化掉,导致调试时看变量值不对。尤其是ADC读取函数里的延时循环,如果计时变量被优化掉了,整个时序就会乱。所以调试阶段建议用-O0或者-O1,等到代码稳定跑起来了,再考虑提高优化等级。

另外,在KEIL里查看局部变量时,如果发现有些变量在watch窗口显示“ ”,不用太慌,一是可能你在release模式下,变量被分配到了寄存器中,调试信息不足;二是变量被优化了。把优化等级降到-O0,重新编译,大部分都能看到。

6. 常见问题排查与实操经验记录

6.1 典型故障现象与解决方案

我把自己和身边朋友在这个项目上踩过的坑整理了一下,做成一个速查表,每个问题都是实际遇到过的,不是理论推演:

故障现象可能原因排查与解决
电流始终显示0未使能ADC时钟或GPIO模式错误检查RCC_APB2PeriphClockCmd是否包含ADC1和GPIOA时钟;检查GPIO是否为GPIO_Mode_AIN
电压换算值明显偏大实测电压高于分压公式预期检查分压电阻阻值,可能是标称值和实际值差异大,用万用表实测并修正系数
电流跳动范围很大电源纹波大或未加滤波电容在模块电源端并联100uF+0.1uF电容,在ADC引脚加0.1uF电容,必要时软件增加滑动平均
0A时读数不是零点ACS712零点偏差或未校准确认供电电压稳定后,执行一次零点校准流程
读数随温度漂移明显芯片自热或环境温度变化减少连续大电流工作时间,或对offset按温度做二次补偿,如果精度要求极高,考虑换闭环霍尔传感器
串口打印不出浮点数MicroLIB未开启在Target选项卡勾选Use MicroLIB,重新编译下载
电流读数比实际小一半分压系数忘记乘2检查ads712_vout换算时乘的系数是否和硬件分压比一致
模块发热严重持续大电流超过量程重新评估量程选型,或降低测试电流

6.2 稳定的供电是测量的前提

我在这个项目中吃过最大的教训就是供电问题。一开始ACS712模块直接用电脑USB的5V供电,按理说USB的5V是稳压输出的,但是当被测电流到1A以上的时候,模块读数会开始慢慢往上飘。后来用示波器一看,USB的5V在负载变化时出现了明显的纹波,导致ACS712的内部参考电压也跟着抖,输出自然就漂了。

正确做法是给ACS712模块单独提供一个低纹波的5V电源。最简单的方案是用AMS1117-5.0从单片机板子的5V输入端取电,或者用锂电池升压到5V再接一个LC滤波。实测下来,电源质量对ACS712测量的影响远大于ADC本身的分辨率影响,这个环节值得多花一点心思。

6.3 用这个方案还能做什么

STM32F103加ACS712这套组合,跑通之后能扩展的方向很多:

一是做电池容量统计。把电流对时间做积分,就算出了消耗的安时数,用来估电池剩余电量非常实用。代码层面就是在主循环里定时累加电流值,然后除以时间得到安时数。

二是做电机堵转保护。电机堵转时电流会突然飙升到正常值的好几倍,用ACS712监测电流,超过阈值立即切断电机驱动信号,能保护电机和驱动器。这个场景对响应速度要求高,建议把ADC做成DMA连续采样模式,在中断里做阈值判断,响应时间可以压到100us以内。

三是做功率统计。电流乘以电压就是功率,再加上时间就是电能。找个电压采集通道同时采样,计算瞬时功率并累加,一个简易的功率计就出来了。

我个人的建议是,第一版先用裸机轮询加软件滤波的方式跑通,等理解了这个传感器的特性和换算逻辑之后,再考虑DMA、中断和更复杂的滤波算法。一上来就上复杂架构,出了问题反而不好排查。

6.4 关于测量精度的进一步优化

如果你的项目对电流精度要求比较高,比如要让测量的误差控制在±1%以内,有几点值得做:

第一,对ADC参考电压做实测校正。用精密万用表实测板子上的3.3V电压,假设实测是3.29V,把代码里的3.3改成3.29,这个操作虽然简单,但对精度的提升是直接的。

第二,对灵敏度和零点做两段式校准。在零电流时记录零点,再通入一个已知大小的标准电流(比如用电子负载拉5A),反推实际的灵敏度,然后用这个实测灵敏度替换手册的标称值。因为芯片个体差异,实测灵敏度和标称值可能差几个百分点。

第三,采样时序要做好防抖。读取ADC的时候可以在两次采样之间加一个小的延时,避免采样电容还没稳定就开始下一次转换。我一般会在循环里加一个几微秒的空转,就是几个NOP指令的事,但对读数的稳定性有好处。

最后还有一个容易被忽略的点:分压电阻的温漂。1/2分压网络里的两个电阻,如果温度特性不一致,分压比会随温度变化,进而影响测量精度。选用温度系数相近的电阻,或者选用一个0.1%精度的精密分压模块,都能在一定程度上缓解这个问题。一般场景不用太在意,但如果设备要在温差很大的环境里工作,这个细节就值得关注了。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询