简介:涵盖ACS712、GY-712-5A及20A、30A量程的单片机电流传感器模块开发实例,基于STM32F103和Keil标准库实现,适用于嵌入式物联网项目中的电流检测、数据采集、传感器驱动调试等场景,也适合做STM32外设编程的进阶练习。压缩包共159个文件,大小约4.18MB,内部包含c/h源码、Keil工程文件(uvprojx/uvoptx)、编译生成的hex/axf固件、PDF器件手册,以及重置编译和清理中间文件的批处理脚本,文件类型覆盖开发、编译、烧录、查阅等多个环节,结构清楚。截止目前已有616人学习下载。整套代码均来自实际项目验证,若换成STM32F103系列其他型号,只需在Keil中调整芯片型号与Flash容量;模块接线定义在代码中,配合PDF手册可快速对照实物完成电流采样。工程内注释点出了关键寄存器和采样流程,便于二次开发,适合入门者系统理解标准库工程组织方式,并快速完成电流传感器模块的移植与调试。
1. ACS712 与 STM32F103:电流采集最省事的一对组合
做电机堵转检测时,很多新手直接把 ACS712 的 ADC 原始值当电流用,5A 量程下空载读数不是 0,而是一千五百多。问题出在模块输出的 2.5V 零电流偏置没处理。ACS712 是霍尔效应电流传感器,5A 量程灵敏度 185mV/A,配合 STM32F103 内置 12 位 ADC,是嵌入式物联网项目里最常见的电流采样组合,电机控制、电池放电监测、电源负载分析都会用到。
这套资料带 KEIL 标准库工程,代码里已经把 ADC 初始化、采样滤波、串口输出整理好,拿来改量程和通道就能用。适合做课程设计的在校生,也适合急着评估 ACS712 精度的工程师——不用从寄存器查起,直接看关键代码怎么改参数就行。
2. ACS712 工作原理与量程选型
2.1 霍尔效应与模块内部结构
ACS712 内部真正流过电流的是一条低阻铜排,而不是普通采样电阻。电流从 IP+ 流到 IP- 时,铜排周围产生与电流近似成正比的磁场,封装内的差分霍尔片感应这个磁场,经过放大和温度补偿电路后,从 VIOUT 引脚输出电压。因为测的是磁场而非直接测电压降,ACS712 本身具备电气隔离特性,也不引入明显的插入损耗。
模块支持双向电流测量,以 5A 量程为例,-5A 到 +5A 被映射到以 2.5V 为中心、跨度约 ±0.925V 的电压区间。GY-712-5A 这类模块板只是把芯片引脚转接出来,没有额外信号调理,所以最终精度取决于芯片本身和后面的采样电路。这一点决定了我们在软件里必须做两件事:采零电流基准、做数字滤波。
2.2 量程、灵敏度与关键参数
| 参数 | GY-712-5A / ACS712-05B | ACS712-20A | ACS712-30A |
|---|---|---|---|
| 测量范围 | ±5 A | ±20 A | ±30 A |
| 灵敏度 | 185 mV/A | 100 mV/A | 66 mV/A |
| 零电流输出(VCC=5V) | 2.5 V | 2.5 V | 2.5 V |
| 通流电阻 | 1.2 mΩ | 1.2 mΩ | 1.2 mΩ |
| 典型带宽 | 80 kHz | 80 kHz | 80 kHz |
选型逻辑很简单:被测电流峰值不超过量程的 70%~80%,然后选灵敏度最高的型号。5A 量程 1A 电流对应 185mV,STM32F103 的 12 位 ADC 在 3.3V 参考下每个 LSB 约 0.806mV,理论分辨率接近 4.4mA;20A 量程每 LSB 对应约 8mA。但芯片输出本身带十几到几十毫伏的噪声,所以实际分辨率要靠软件滤波去逼近。
2.3 为什么这个例程用内置 ADC 而不是外置 ADC
STM32F103 的 ADC1 是 12 位逐次逼近型,ADC 时钟最高 14MHz,单次转换约 1μs 级别。ACS712 带宽 80kHz,对电机电流、电池放电这类几十到几百赫兹的缓变信号,F103 内置 ADC 完全够用。
需要外置 ADC 的场景主要有三种:要真正 16 位以上分辨率、要求主控与采样端电气隔离、多通道需要同步采样。对课程设计和一般物联网节点,外挂 I2C 型高精度 ADC 反而引入总线时序和功耗问题。这个例程固定走 ADC1 + PA1,标准库里 stm32f10x_adc.c 已经写好,改通道只需要动一个宏。
3. KEIL 标准库工程搭建与 ADC1 通道初始化
3.1 工程文件结构和编译前的处理
资料里的文件看起来杂,其实只有几类:ADC.uvprojx 是 KEIL 工程文件,ADC.uvguix 是界面布局配置,ADC.axf 是编译生成的映像文件,ADC_sct.Bak 是分散加载文件备份。还有两个批处理:keilkilll.bat 和 重置KEIL编译.bat,作用都是清理编译中间文件。我拿到例程的第一件事是先跑一遍清理脚本,再重新 Build,避免旧的 .o 和 .axf 残留导致"改了代码不生效"的假象。
用 Keil uVision5 打开工程后,第一件事是确认 Device 型号。STM32F103C8 是 64KB Flash,CB 是 128KB,ZET 是 512KB。Options for Target -> Device 里选成板子实际型号,Flash Download 页面里编程算法也要对应。标准库代码对 F103 全系通用,但 Flash 容量配错会直接导致下载失败。
3.2 时钟、GPIO 与 ADC1 初始化代码
void ACS712_ADC_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; ADC_InitTypeDef ADC_InitStructure; // 使能 ADC1 与 GPIOA 时钟,PA1 属于 GPIOA RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE); // PCLK2 = 72MHz,6 分频得到 12MHz ADC 时钟 RCC_ADCCLKConfig(RCC_PCLK2_Div6); // PA1 配置为模拟输入,ADC 引脚不能用复用推挽 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOA, &GPIO_InitStructure); // 独立模式,单次转换,软件触发,右对齐,1 个通道 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); // 规则组第 1 个通道为 ADC_Channel_1,采样时间 55.5 周期 ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_55Cycles5); ADC_Cmd(ADC1, ENABLE); }时钟分频选 RCC_PCLK2_Div6 是 F103 的常见做法:APB2 总线 72MHz,ADC 时钟不能超过 14MHz,6 分频得到 12MHz,余量充足。如果用 Div4 得到 18MHz,超出上限后转换结果会不稳定。PA1 必须配成 GPIO_Mode_AIN 模拟输入,否则引脚上的施密特触发器会干扰采样。
3.3 采样时间与触发方式的选择依据
| 采样时间配置 | 总转换周期 | 适用场景 |
|---|---|---|
| ADC_SampleTime_1Cycles5 | 14 | 低阻源、追求最高吞吐 |
| ADC_SampleTime_7Cycles5 | 20 | 普通电压信号 |
| ADC_SampleTime_13Cycles5 | 26 | 中等阻抗源 |
| ADC_SampleTime_28Cycles5 | 41 | 高阻抗传感器 |
| ADC_SampleTime_55Cycles5 | 68 | 带噪声的传感器输出 |
| ADC_SampleTime_239Cycles5 | 252 | 极慢变化的直流信号 |
ACS712 输出阻抗很低,理论上用 1.5 周期采样也读得准。但传感器输出带噪声,较长采样时间等效于在硬件前端做了一次积分低通,能明显压低波形毛刺。例程选 55Cycles5 是合理折中,单通道转换时间约 5.7μs,采样率仍然足够。
触发方式关掉连续转换、只用软件触发,目的是让滤波循环里每一次采样都有明确边界。如果开了连续转换,轮询 EOC 标志时可能拿到的是最新一次结果,循环语义会混乱。
4. 电流换算、滤波与串口验证
4.1 零电流基准为什么不能直接用理论值
理论上 VCC=5V 时零电流输出 2.5V,3.3V 参考下 ADC 读数应该是 2.5V / 3.3V × 4095 ≈ 3102。但芯片本身有零漂,模块供电纹波也会叠加进去。我一般开机后先空载采样 200 到 500 次求平均,把结果存成 zero_adc,后面的换算以它作基准。
#define ACS712_SENSITIVITY 185.0f // mV/A,5A 模块 #define ADC_REF_VOLTAGE 3300.0f // mV,STM32F103 VDDA #define ADC_FULL_SCALE 4095.0f // 空载时调用,返回零电流基准 ADC 值 uint16_t ACS712_CalibrateZero(void) { uint32_t sum = 0; for (uint8_t i = 0; i < 256; i++) { ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); sum += ADC_GetConversionValue(ADC1); } return (uint16_t)(sum / 256); }4.2 采样均值与电流换算
float ACS712_ReadCurrent(uint16_t zero_adc) { uint32_t sum = 0; float voltage_mv; float zero_mv; float current; for (uint8_t i = 0; i < 32; i++) { ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); sum += ADC_GetConversionValue(ADC1); } // 平均 ADC 值换算成毫伏 voltage_mv = (float)sum / 32.0f / ADC_FULL_SCALE * ADC_REF_VOLTAGE; // 零电流基准同样换成毫伏 zero_mv = (float)zero_adc / ADC_FULL_SCALE * ADC_REF_VOLTAGE; // 减偏置后除以灵敏度,得到安培 current = (voltage_mv - zero_mv) / ACS712_SENSITIVITY; return current; }这段代码把 ADC 原始值先归一化到毫伏,再统一做减法,最后除以灵敏度。注意 ACS712 测量的是双向电流,换算结果是带符号的 float,正负代表电流方向。整个过程中间变量都用 float,避免整数除法截断;在 32 位 MCU 上浮点运算几十微秒,对采样率没有影响。
4.3 数字滤波方式怎么选
| 滤波方式 | 窗口 | 抗脉冲能力 | 实时性 | 适用场景 |
|---|---|---|---|---|
| 算术平均 | 16~256 | 差 | 中等 | 稳态电流监测 |
| 滑动平均 | 8~16 | 一般 | 好 | 变化较慢的负载 |
| 中值滤波 | 5~11 | 强 | 差 | 电机启动、继电器通断 |
| 去极值平均 | 先剔除最大最小再平均 | 强 | 中等 | 推荐用于电机回路 |
电机启动瞬间会有尖峰毛刺,中值滤波能干净地剔除;电源功耗监测则适合滑动平均,因为波形相对平缓。例程的 32 次算术平均是通用起步配置,简单且不容易把有效信号滤掉。改滤波方式时注意窗口越大延迟越大,做堵转保护时迟滞必须留够。
4.4 串口输出与实测验证
验证电流读数最直接的办法是串口打印。例程用 USART1 重定向 printf,接一个 USB-TTL 模块,波特率 115200,串口助手每秒刷新一次即可。如果换成串口 3,注意 USART3 挂在 APB1 总线上,时钟使能要用 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE),同时波特率计算的分频基准也从 72MHz 变成 36MHz,这是标准库里串口 1 和串口 3 最容易踩的差异。
int fputc(int ch, FILE *f) { USART_SendData(USART1, (uint8_t)ch); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); return ch; }重定向之后,printf("Current: %.2f A\r\n", current) 就能直接在上位机看到数值。验证方法:拿一个 12V 灯泡串在 ACS712 的 IP+ 与 IP- 之间,万用表电流档对照,读数偏差一般在 2% 以内。
5. 量程切换、下载器与 3.3V/5V 电压匹配
5.1 换 20A、30A 模块只需改灵敏度
#define ACS712_SENSITIVITY 100.0f // 20A 模块 #define ACS712_ZERO_VOLTAGE 2500.0f // mV,VCC=5V换模块后只改这一个宏,换算逻辑不用动。5A 模块全量程输出 2.5V ± 0.925V,20A 模块是 2.5V ± 2V,30A 模块是 2.5V ± 1.98V。
5.2 J-LINK、ST-LINK 与 axf 缺失
KEIL 里 Debug 页面必须与实际下载器一致:J-LINK 选 J-LINK / J-Trace Cortex,ST-LINK 选 ST-Link Debugger。选错会报 No J-Link found 或 Error: Flash Download failed。另外,如果 Build 后发现工程目录下没有 .axf 文件,说明编译并未真正完成,先看 Build Output 里的 Error 数量,再查 Options -> Output 里 Create HEX File 是否勾选。
5.3 5V 输出超过 3.3V ADC 量程的解法
ACS712 必须 5V 供电,它的供电范围下限是 4.5V,直接接 3.3V 不可靠。但 5V 供电时,20A 模块在 10A 以上输出就超过 3.3V 了。F103 的 ADC 输入不能超过 VDDA+0.3V,也就是约 3.6V,超过轻则读数饱和在 4095,重则损伤引脚。我的做法是输出端加电阻分压:R1 取 10kΩ、R2 取 20kΩ,分压比 2/3,满量程 4.5V 降到 3.0V,留出安全余量。软件里把换算电压除以 2/3,同时重新采集一次零电流基准,因为分压后 2.5V 变成了约 1.667V。方向放在分压系数和零基准上,读数才会和实际电流对得上。
本文还有配套的精品资源,点击获取