简介:本资源是一套面向嵌入式初学者与STM32开发者的ACS712电流传感器实战开发包,聚焦电流采集与ADC数据处理核心能力训练,适用于物联网终端、智能仪表及电机监控等典型应用场景。压缩包共192个文件,含39个头文件(.h)定义外设接口与参数,37个源文件(.c)实现ADC采样、滤波校准与串口输出逻辑,另有KEIL工程配置文件(.uvprojx/.uvoptx)、编译中间文件(.o/.d/.crf)、调试脚本(.bat)及4份PDF手册,完整覆盖从硬件接线、驱动移植到数据可视化的全流程。资源包大小为5.97MB,结构规范,所有引脚定义与初始化逻辑均在代码中明确注释,支持STM32F103全系列芯片快速适配。已有59人学习下载,配套标准库工程可直接编译运行,附带keilkill.bat等实用工具脚本,显著降低环境配置门槛,特别适合缺乏传感器项目经验的开发者快速上手并理解ADC采样链路设计要点。 最近在做一套直流电机驱动板的母线电流监测,需要实时读取电流值,超过阈值就快速关断保护。手头正好有 ACS712 电流传感器模块,主控用的 STM32F103C8T6 最小系统板,开发环境是 KEIL MDK。整个流程从看手册、接线、写代码到标定,折腾了两天总算稳定跑起来了。这篇文章把这次开发过程完整记录下来,包括 ACS712 的选型细节、输出特性、STM32 的 ADC 采集配置、滤波校准方法,以及调试过程中踩过的坑。如果你正准备用 ACS712 做电流采样,或者想搞明白 STM32 的 ADC 到底怎么配才能采得稳,这篇应该能帮你少走不少弯路。
1. 项目概述与整体设计思路
1.1 为什么选 ACS712 做电流采样
电流检测方案其实不少,常见的有采样电阻加运放、电流互感器、霍尔电流传感器等。我这次选 ACS712,核心原因是它便宜、模块化程度高、而且是隔离测量。模块本身已经把霍尔传感器、运算放大器和线性调理电路集成好了,我们只需要接上电源,从 OUT 引脚读模拟电压即可,不需要额外搭放大电路,对快速验证方案非常友好。
它的工作原理是让被测电流流过芯片内部的一小段铜排,铜排周围产生磁场,霍尔元件检测磁场强度后转换成电压信号输出。因为被测电流通路和信号调理电路是电气隔离的,所以在电机驱动这种高边或低边都可能存在共模电压的场景下,ACS712 更安全。相比之下,采样电阻方案虽然精度可以做得更高,但需要精密运放、需要考虑共模电压和功率损耗,调试周期明显更长。
对于我这种要做过流保护、同时又想快速把功能跑通的需求,ACS712 是性价比很高的选择。
1.2 系统硬件架构与引脚规划
整套系统结构很简单:STM32F103C8T6 最小系统板负责 ADC 采样和逻辑判断,ACS712 模块负责把电流转换成电压,另加一个串口模块把实时数据打印到电脑上观察。如果后续要做过流保护,可以直接用 STM32 的 GPIO 控制MOSFET或继电器的驱动电路,这次文章主要聚焦采样的部分。
引脚规划上,ACS712 的 OUT 接到了 STM32 的 PA1。为什么选 PA1?因为 PA1 对应 ADC1 的通道1(ADC1_IN1),在最小系统板上这个引脚容易引出,没有和其他常用功能冲突。模块的 VCC 接 5V,GND 和 STM32 共地。这里有个容易忽略的细节:ACS712 的 VCC 必须用 5V 供电,因为它的输出是以 VCC/2 为中点电压的,如果供电电压不标准,中点电压也会漂移,后面校准就得按实际值来算。
串口我用 PA9(USART1_TX)和 PA10(USART1_RX),波特率 115200,方便看实时数据。
硬件连接关系如下表:
| ACS712 模块引脚 | 连接目标 | 说明 |
|---|---|---|
| VCC | 5V 电源 | 模块必须 5V 供电,4.5~5.5V 范围 |
| GND | 公共地 | 与 STM32 共地,不能浮空 |
| OUT | PA1(ADC1_IN1) | 模拟电压输出 |
| IP+ | 被测电流输入 | 串入负载回路 |
| IP- | 被测电流输出 | 串入负载回路 |
2. ACS712 芯片原理与关键参数解读
2.1 从手册里读出的核心信息
拿到 ACS712 的数据手册,别急着往后翻,前几页的几个参数才是决定整个采样精度的关键。首先是型号区分,ACS712 按量程分为 05B、20A、30A 三个常见版本,每个版本的灵敏度和最大测量范围不一样。这个选错基本就废了,比如你量 10A 的电流却选了个 05B 版本,输出直接饱和,读数完全不对。
三个版本的参数对比如下:
| 型号 | 量程 | 灵敏度 | 零电流输出 | 25°C 总输出误差 |
|---|---|---|---|---|
| ACS712-05B | ±5A | 185 mV/A | VCC/2 | ±1.5% |
| ACS712-20A | ±20A | 100 mV/A | VCC/2 | ±1.5% |
| ACS712-30A | ±30A | 66 mV/A | VCC/2 | ±1.5% |
这次项目测量的是直流电机母线电流,正常工作范围大概在 0~3A,偶尔会冲到 4A 左右,所以我选的是 05B 版本,灵敏度 185mV/A,分辨率最高。这里有个取舍:量程小的版本灵敏度高,小电流时电压变化更明显,精度更好;量程大的版本虽然能测大电流,但在小电流区间分辨率就差了。选型时一定要先估算好自己应用的最大电流,再反推用哪个版本。
2.2 输出电压与电流的换算关系
手册里给出了输出公式,本质上是一个线性关系:
Vout = VCC/2 + Sensitivity × I
也就是说,零电流时输出等于供电电压的一半,电流为正时输出电压升高,电流为负时输出电压降低。以 5V 供电和 05B 版本为例,电流每增加 1A,输出电压增加 185mV。如果电流是 -5A,输出就是 2.5V - 0.925V = 1.575V;如果电流是 +5A,输出就是 2.5V + 0.925V = 3.425V。
这个看似简单的公式,牵扯出一个重要问题:ACS712 的输出范围可能超过 STM32 的 ADC 输入范围。STM32F103 的 ADC 输入量程是 0~3.3V,而 ACS712-05B 在满量程正电流时输出可以达到 3.425V,超过了 3.3V。如果直接把 OUT 接到 PA1,在电流超过约 4.32A 时((3.3-2.5)V / 0.185V/A ≈ 4.32A),ADC 读数会钳位到 4095,数据失真。
对于我这个应用,正常工作电流在 3A 以内,偶尔 4A 出头,直连勉强能用但余量不大。如果你测量范围可能超过 4.3A,就需要在 OUT 和 PA1 之间加一个分压电阻网络,把电压按比例衰减到 3.3V 以内,然后在代码里反向还原。这个点很多人容易忽略,等发现读数到顶了还以为是模块坏了,其实是 ADC 饱和了。
2.3 误差来源与对策
ACS712 的精度不是无限好的,手册里标注的总输出误差在 25°C 时约为 ±1.5%,但这只是理想情况。实际使用中误差来源主要有三个:
第一个是零点偏置误差。由于流片工艺的离散性,每一片 ACS712 的零电流输出电压并不完全等于 VCC/2,可能会有几十毫伏的偏差。换算成电流,比如 30mV 的偏差在 05B 版本上就相当于约 0.16A 的误差。解决办法很简单:上电后在没有电流的情况下采集一次输出电压,存下来作为零点补偿值。
第二个是灵敏度误差。手册里的 185mV/A、100mV/A 是典型值,实际芯片会有 ±1.5% 左右的偏差。如果对精度要求高,需要用已知电流源做两点校准,反推出实际的灵敏度值。
第三个是温漂和噪声。ACS712 的输出带有一定的高频噪声,电源纹波大时尤其明显。后面我会讲滤波和硬件电容的处理办法。
3. STM32F103 ADC 采集代码实现与参数详解
3.1 开发环境准备与工程搭建
开发环境我用的 KEIL MDK 5.x。新建工程的时候有两个细节容易出问题:一是要装对应芯片的器件支持包,STM32F103 需要安装 Keil.STM32F1xx_DFP,否则新建工程时根本找不到 STM32F103C8 这个型号;二是需要把标准外设库的源文件添加到工程里,至少要用到 stm32f10x_gpio.c、stm32f10x_adc.c、stm32f10x_rcc.c 这几个。
如果你用的是 STM32CubeMX 加 HAL 库的方式,思路也完全一致,只是 API 名称略有不同。我这次用标准外设库是因为工程是之前就搭好的,而且 STM32F103 用标准库的代码量更简洁,适合快速上手。新手如果从零开始,用 STM32CubeMX 生成 HAL 库工程反而更不容易漏配置。
工程建立后,记得在 Keil 的 Options for Target 里把 Flash Download 选项卡下的编程算法选对。STM32F103C8T6 是 64KB Flash,选 STM32F10x Med-density Flash 即可。如果这里没配好,下载时会报 Flash Download failed,程序根本烧不进去。
3.2 ADC 初始化代码详解
下面这段代码是标准外设库版本的 ADC1 初始化,配置了 PA1 作为模拟输入通道,采样时间设置为最长的 239.5 个周期。
void ACS712_ADC_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; ADC_InitTypeDef ADC_InitStruct; // 使能 GPIOA 和 ADC1 时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); // ADC 时钟分频:72MHz / 6 = 12MHz,不超过 14MHz 上限 RCC_ADCCLKConfig(RCC_PCLK2_Div6); // PA1 配置为模拟输入 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOA, &GPIO_InitStruct); // ADC1 独立模式、单通道、连续转换、软件触发 ADC_InitStruct.ADC_Mode = ADC_Mode_Independent; ADC_InitStruct.ADC_ScanConvMode = DISABLE; ADC_InitStruct.ADC_ContinuousConvMode = ENABLE; ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStruct.ADC_NbrOfChannel = 1; ADC_Init(ADC1, &ADC_InitStruct); // 规则组通道1,采样时间 239.5 周期 ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5); // 使能 ADC ADC_Cmd(ADC1, ENABLE); // ADC 校准,务必执行 ADC_ResetCalibration(ADC1); while (ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1)); // 软件触发连续转换 ADC_SoftwareStartConvCmd(ADC1, ENABLE); }这里有几个参数值得解释一下。首先是 ADC 时钟分频,STM32F103 的 ADC 时钟最高不能超过 14MHz,而 APB2 总线是 72MHz,所以至少要分频 6 倍得到 12MHz。如果分频太小,ADC 时钟超了规格,转换结果不可靠;如果分频太大,采样率会下降,响应变慢。分频 6 是比较常用的选择。
其次是采样时间。ADC 采样时间本质上决定了内部采样电容充电的时间,时间太短,采样电容没充满,转换结果就会偏小且不稳定。对于 ACS712 这种输出阻抗相对较高的传感器,以及我们希望获得更平稳数据的需求,把采样时间拉到最大的 239.5 周期是合理的。相应地,一次完整转换时间为 (239.5 + 12.5) 个 ADC 周期,在 12MHz 时钟下约 21us,对应约 47kHz 的采样率,对于直流电流的实时监测完全够用。
最后是校准步骤。STM32F103 的 ADC 上电后需要依次执行复位校准和启动校准,否则转换精度会打折。校准是阻塞式的,在校准完成前不要启动转换,这是很多新手容易忽略但影响精度的细节。
3.3 数据读取与电压电流换算
初始化完成后,ADC1 会在连续转换模式下不断刷新结果寄存器。读取时只需要等待 EOC 标志位置位,然后读取 ADC_GetConversionValue 即可。
下面的函数实现了读取并做 32 次平均,最后换算成电流值:
float Read_ACS712_Current(void) { uint32_t adc_sum = 0; uint8_t i; for (i = 0; i < 32; i++) { while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); adc_sum += ADC_GetConversionValue(ADC1); } float adc_avg = (float)adc_sum / 32.0f; float voltage = adc_avg * 3.3f / 4095.0f; // 引脚实际电压 float sensor_out = voltage; // 直连场景 float current = (sensor_out - 2.5f) / 0.185f; // 05B 版本灵敏度 return current; }注意最关键的换算公式:ADC 值是 12 位,满量程 4095 对应 3.3V,所以 voltage = adc_avg × 3.3 / 4095。然后用 ACS712 的输出公式反推电流:current = (Vout - 2.5V) / 0.185V/A。
如果你的电路加了分压电阻,比如把 OUT 分成 63.7% 给 ADC,那么 sensor_out = voltage / 0.637,然后再代入电流公式。这个换算逻辑一定要想清楚,别直接把 ADC 电压当模块输出电压算。
我建议在初始调试阶段,用 KEIL 的 Debug 模式配合 Watch 窗口直接观察 adc_avg 这个变量,确认它是否在正常范围内。零电流时,05B 版本输出的理论值是 2.5V,对应 ADC 读数约 3100(2.5/3.3×4095≈3100)。如果这个值差得太多,先查供电电压和硬件接线,再查软件。
4. 数据滤波、零点校准与实测结果分析
4.1 原始数据的噪声长什么样
刚把程序烧进去,通过串口把 ADC 原始值打出来,你会发现数据并不稳定。零电流时,ADC 读数在 3090 到 3115 之间波动,这是完全正常的。ACS712 的输出本身带有一定的高频噪声,再加上开关电源的纹波和电磁干扰,ADC 读数跳动十几个 LSB 很正常。
以 05B 版本为例,ADC 读数跳动 10 个 LSB,对应电压变化约 8mV,换算成电流就是约 43mA 的波动。对于电流监测来说,几十毫安的噪声在大部分场景下可以接受,但如果要做高精度测量,必须滤波。
4.2 滑动平均滤波实现
最简单的滤波是移动平均,也就是连续采 N 次取平均值。N 太小滤波效果差,N 太大响应变慢。我测试下来,32 次平均是一个比较均衡的选择,既能明显压制噪声,又不会让电流变化的响应显得太迟钝。
为了更平滑,还可以用带缓冲区的滑动平均,每采集一个新值就丢弃最老的值,始终保持最近 N 个点的平均值。这样做在实时性和平滑性之间平衡得更好。
#define FILTER_SIZE 32 uint16_t filter_buf[FILTER_SIZE]; uint8_t filter_index = 0; float Read_ACS712_Filtered(void) { uint16_t adc_val; uint32_t sum = 0; uint8_t i; while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); adc_val = ADC_GetConversionValue(ADC1); filter_buf[filter_index++] = adc_val; if (filter_index >= FILTER_SIZE) filter_index = 0; for (i = 0; i < FILTER_SIZE; i++) sum += filter_buf[i]; float avg = (float)sum / FILTER_SIZE; float voltage = avg * 3.3f / 4095.0f; float current = (voltage - 2.5f) / 0.185f; return current; }使用滑动平均后,零电流时读数的波动从 ±20LSB 降到 ±3LSB 左右,效果很明显。如果应用对响应速度要求不高,例如做温度监测、电池电量统计,可以进一步增大窗口到 64 或 128,平滑效果更佳。
4.3 零点校准与灵敏度标定
滤波只能解决随机噪声,零点偏置这种系统误差必须通过校准来消除。我之前说过,ACS712 的零电流输出电压不是精确的 VCC/2,而是有一定偏差。实际测量中,我手里这块模块在 5.02V 供电下,零电流输出实测约为 2.508V,而不是理论上的 2.51V(5.02/2)。这个 2mV 的偏差在 185mV/A 灵敏度下相当于约 11mA 的误差,不算大,但在精度要求高的场合不能忽略。
更关键的是,如果你的 5V 电源实际输出只有 4.8V,那么零电流输出只有 2.4V,如果代码里还是按 2.5V 做减法,就会出现约 0.54A 的误差。这个误差对于电流监测来说是致命的。
所以我在正式测量前都会做一次零点校准:系统启动时,确保负载断电,连续采样 64 次求平均值,把这个值保存为 offset_voltage。之后计算电流时用 voltage - offset_voltage 代替 voltage - 2.5V。
float offset_voltage = 2.5f; void Calibrate_Zero(void) { uint32_t adc_sum = 0; uint8_t i; for (i = 0; i < 64; i++) { while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); adc_sum += ADC_GetConversionValue(ADC1); } float avg = (float)adc_sum / 64.0f; offset_voltage = avg * 3.3f / 4095.0f; }灵敏度标定如果有条件也建议做。方法是用一个已知电流(比如电子负载设定为 2.000A),读取电压后反推实际灵敏度 real_sensitivity = (Vout - offset_voltage) / 2.000。把这个值存下来代替 0.185 使用。我实测过几块 ACS712-05B,灵敏度在 0.183~0.187 之间,1.5% 的偏差是真实存在的。
4.4 实测数据与结果对比
用电子负载设定不同的电流值,对比 ACS712 读数和万用表读数:
| 设定电流 (A) | ACS712 读数 (A) | 偏差 (A) | 偏差比例 |
|---|---|---|---|
| 0.000 | 0.012 | 0.012 | — |
| 0.500 | 0.493 | -0.007 | -1.4% |
| 1.000 | 1.018 | 0.018 | 1.8% |
| 2.000 | 2.031 | 0.031 | 1.55% |
| 3.000 | 2.968 | -0.032 | -1.07% |
可以看到,经过零点校准后,实测偏差基本控制在 ±2% 以内,和手册标称的总输出误差水平一致。对于电流保护用途来说,这个精度完全足够。
5. 常见问题排查与调试心法
5.1 KEIL 工程编译与下载问题
这一部分把我实际踩过的 KEIL 相关坑整理出来,都是高频问题。
第一个是编译报 error: #5: cannot open source file "stm32f10x.h"。这个基本是头文件路径没配置好,或者芯片支持包没安装。解决方法是在 Options for Target 的 C/C++ 选项卡里,把标准外设库的 Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x 路径加入 Include Paths。如果用的是 STM32CubeMX 生成的工程,直接把对应文件夹路径复制进来自动包含即可。
第二个是下载时报 Flash Download failed。主要原因是 Flash Download 页面里的编程算法没选择或者选错了。STM32F103C8T6 是中等容量芯片,必须选择 STM32F10x Med-density Flash,并且把 Reset and Run 勾上。检查这个配置,能解决绝大多数下载问题。
第三个是调试时断点无效或变量看不到。这通常是因为编译优化等级太高。KEIL 默认优化等级是 -O0 或默认,但如果你手动设置了 -O2、-O3,程序执行顺序会被优化,断点可能被跳过,Watch 窗口里的变量也可能显示为已优化。排查阶段建议把优化等级设为 -O0,调试完成后需要高优化再改回去。
还有一个很常见的坑是 printf 重定向。在 KEIL 工程里直接调用 printf 输出浮点数,如果没勾选 Use MicroLIB,会编译报错或程序卡死在半主机模式。解决方法是在 Options for Target 的 Target 选项卡里勾选 Use MicroLIB,并实现 fputc 重定向到串口:
int fputc(int ch, FILE *f) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); USART_SendData(USART1, (uint8_t)ch); return ch; }5.2 ADC 读数异常的排查思路
如果串口打印出来的 ADC 值一直固定是 4095,大概率是输入电压超过了 3.3V 量程,或者 PA1 引脚配置成了非模拟模式。先用万用表量 PA1 对地电压,如果电压确实超过了 3.3V,就要检查是否接入了过大的电流,或者分压电路是否失效。如果电压正常但 ADC 仍然满量程,检查 GPIO_Mode 是否设置成了 GPIO_Mode_AIN。
如果 ADC 读数跳变非常剧烈,几十甚至上百 LSB 地跳,先检查 ACS712 模块的 VCC 和 GND,确保供电干净。可以在模块的 VCC 和 GND 之间加一个 10uF 的电解电容和 100nF 的陶瓷电容,这是最简单有效的降噪手段。同时,模块的 OUT 到 PA1 的连线尽量短,避免长线引入干扰。
如果读数稳定但电流数值不对,比如零电流时读数换算出来是 0.3A,那就是零点偏置问题。执行一次零点校准,或者用万用表实测零电流输出电压,替换代码里的 offset_voltage。
5.3 电流测量不准的排查清单
我整理了一张排查清单,按优先级从高到低排列:
| 现象 | 优先检查项 | 解决办法 |
|---|---|---|
| 零电流读数不为 0 | 5V 电压是否准确 | 实测 VCC,更新 offset_voltage |
| 大电流读数明显偏小 | 输出是否超过 3.3V | 加装分压电阻,在代码中还原 |
| 读数始终为 0 | 模块是否有 5V 供电 | 检查接线,万用表量 VCC |
| 读数来回跳 | 电源纹波是否过大 | 模块电源处加滤波电容 |
| 读数随负载变化但比例不对 | 灵敏度参数是否标定 | 用已知电流做灵敏度反向校准 |
5.4 调试心法与整体优化建议
最后分享几个我在实际开发中沉淀下来的调试习惯。第一,先硬件后软件。任何传感器接入后,第一件事不是写代码,而是用万用表量模块输出端对地电压,确认电压是否在预期范围内。ACS712 在 5V 供电下,零电流输出应该在 2.4~2.6V 之间,如果完全不对,先修硬件再调软件。
第二,分阶段验证。先把串口打印原始 ADC 值,确认 ADC 采集本身是正常的;再加入滤波和换算,逐步增加复杂度。不要一上来就写一整套滤波加校准函数,出了问题会很难定位。
第三,把关键技术参数定义成宏或全局常量。比如灵敏度、零点电压、分压系数,集中放在文件头部,方便后续根据实际模块标定值修改。不要把这些魔法数字散落在代码各个角落,否则三个月后你自己都看不懂。
这个项目做完之后,我把这套采集模块用在了电机驱动板的过流保护上,效果很稳定。如果你想做闭环控制,可以在这个基础上把电流值通过串口或 CAN 送出去,或者提升采样率配合 DMA 做波形捕获。底层采集和校准做扎实了,往上扩展都很顺手。
本文还有配套的精品资源,点击获取