简介:这是一套基于51单片机、ACS712电流检测模块和AD采集芯片实现数字电流表电压表的完整设计资料,面向电子工程初学者、嵌入式开发人员及高校课程设计参考,可用于教学演示、小型仪器自制与毕业设计。压缩包共31个文件、整体仅374KB,包含Keil工程源程序(C/汇编)、可烧录hex文件、SCHDOC格式原理图、PCB预览图、PDF格式说明文档及详细元器件清单等,从硬件连接、代码逻辑到烧录验证均有覆盖;源程序涵盖ADC初始化、电流电压换算和显示驱动等关键环节,适合边读边调。已有4439人学习浏览,参考价值较高。资源还提供系统硬件框图和程序流程图,可帮助快速理解ACS712电流信号感知、AD模数转换、单片机数据处理与数字显示输出的完整链路,对正在设计电流表电压表或完成课程设计的人而言,是一份可以直接使用的方案模板。
1. 用51单片机、ACS712和AD采集芯片做数字电流表电压表,卡点不在代码而在链路
把ACS712电流检测模块接到51单片机上做数字电流表电压表,最容易出现的情况是ADC已经能读到数字,但显示的电流和万用表对不上。原因是ACS712的输出并不是从0V开始,而是以2.5V为基线叠加一个与电流成正比的电压,电压通道还要先稳压再分压。这个标题里的“源程序原理图设计资料”本质上包含三层:模拟链路计算、AD采集芯片选型与接线、标度变换与显示。我按这三点把整个方案拆开讲,读者可以用STC89C52RC加上一片ADC0832把它跑起来,适合课程设计、工控监控这类需要0.1A分辨率的场合。
2. ACS712电流模块输出特性和电压采样分压网络:先算灵敏度再定分压比
2.1 ACS712输出特性:2.5V偏置之后的电流怎么换算
ACS712内部是一个霍尔传感器加一个放大调理链路,外部只暴露一个模拟输出引脚。它的输出电压公式是:
Vout = VCC/2 + sensitivity × I
当VCC取5.0V时,零电流输出是2.5V。电流越大,输出电压越高;电流反向时输出低于2.5V。ACS712模块常见的量程和灵敏度如下:
| 型号丝印 | 量程 | 灵敏度 | 输出斜率 |
|---|---|---|---|
| ACS712-05B | ±5A | 185 mV/A | 0.037 V/A 折算到0~5V满量程时约占47% |
| ACS712-20B | ±20A | 100 mV/A | 0.02 V/A |
| ACS712-30B | ±30A | 66 mV/A | 0.0132 V/A |
那么电流换算公式就是:
I = (Vout - 2.5) / sensitivity
这里的sensitivity是模块本身参数,与单片机端接几伏基准无关。很多新手直接用ADC原始值去除以255再乘5,跳过了减2.5这一项,结果在0A时显示0.5A以上,这就是第一个坑。
另一个坑是模块供电。ACS712的VCC直接决定内部偏置电压,如果VCC不是稳定的5V,2.5V偏置会跟着飘。常见做法是在模块电源引脚旁边放一个10uF电解电容加一个0.1uF陶瓷电容,并且不要让电机这类大负载和模块共用同一路电源。如果单片机板上只有一颗AMS1117-5.0,建议先给模块供电,再接其他负载。
2.2 电压采集通道:电阻分压把0~30V降到ADC输入范围
电压表通道与电流通道不同:ACS712输出已经是0~5V范围,而外部被测电压往往超过5V,必须用电阻分压网络先降到ADC可接收的范围。一般测量0~30V,我常用100kΩ和10kΩ两个电阻:
分压比 = (R1 + R2) / R2 = (100k + 10k) / 10k = 11
当外部电压是30V时,分压点电压为:
V_adc = 30 / 11 ≈ 2.73V
留了接近1倍的余量给过压保护,同时ADC0832的参考电压是5V,分辨率是5/255 ≈ 19.6mV,折算到电压表端是19.6mV × 11 ≈ 0.216V,显示一位小数是足够的。
分压电阻阻值不能随便放大。100kΩ和10kΩ这个组合,输入电流大约30V/110k ≈ 0.27mA,不会对被测量电路造成明显负载。如果直接用1MΩ加100kΩ,分压点阻抗会升高,ADC0832的采样开关会引入误差。正规做法是在分压点后面接一个电压跟随器,比如LM358或TL072,跟随器输出是低阻,再进ADC,读数会更稳。即使不加跟随器,也必须在分压点到地之间放一个0.1uF电容,作为采样保持电容。
2.3 为什么选ADC0832作为AD采集芯片而不是单片机内置ADC
AT89C52、STC89C52RC这类51单片机没有片上ADC,只能外接。STC12系列虽然有内置ADC,但标题明确写了“AD采集芯片”,所以这里按外接方案处理。外接AD芯片种类很多,几个常见选型如下:
| 芯片 | 接口 | 位数 | 通道数 | 采样速度 | 适合场景 |
|---|---|---|---|---|---|
| ADC0832 | 类SPI,3线 | 8位 | 2 | 几十kHz | 双通道模拟量,接线少 |
| PCF8591 | I2C | 8位 | 4 | 11kHz | 同时需要DA输出 |
| ADC0809 | 并行 | 8位 | 8 | 10kHz | 多通道并行采集,占IO多 |
| ADC128S022 | SPI | 12位 | 8 | 50kHz | 高分辨率多通道 |
这个项目只采集一路电流和一路电压,ADC0832是最稳妥的选择。两个通道正好对应CH0和CH1,三线接口只占用P1.0、P1.1、P1.2三个引脚,换算公式简单,时序也相对容易写。PCF8591因为是I2C,51单片机还需要软件模拟I2C协议,调试时多做一步;ADC0809要占用至少11个IO口,对一个显示表工程不够划算。
2.4 原理图连接要点:参考电压和去耦电容决定精度
绘制原理图时,电流通道这样接:ACS712的VIOUT引脚先经过一个100Ω电阻和一个0.1uF电容组成RC低通,再送到ADC0832的CHO引脚。电压通道这样接:分压点经过电压跟随器LM358后送到ADC0832的CH1。两个通道到ADC0832之间都不要直接长走线,源头端加RC滤波比在软件里做平均值滤波更有效。
ADC0832的VREF引脚直接接VCC,所以VCC必须干净。C51里读回来的数字量是相对VCC的比例,如果VCC是5.2V,而程序里按5.0V计算,电流和电压都会偏大约4%。建议原理图上在ADC0832的VCC和GND之间加一个10uF电解电容和一个0.1uF陶瓷电容,并且尽量靠近芯片引脚。
ACS712模块输出端有一个小电容,反应速度有限。如果被测电流是几十kHz的PWM波,输出会是一条波动的曲线,直接采样会跳字。这时需要在软件里做均值滤波,后面章节会给出代码。
3. ADC0832与51单片机接线和时序:用C语言读CH0/CH1两个通道
3.1 接线方式与IO分配
ADC0832的核心引脚是CS、CLK、DI、DO。实际上这个芯片的DI和DO在芯片内部是同一个数据口,只是在不同时序阶段分别承担输入和输出角色。为了方便写程序,我一般把DI和DO合并成一个DIO引脚,接到单片机的同一个IO上。STC89C52RC的P1口是准双向口,既能在写入阶段输出高低电平,又能在读取阶段读引脚状态,所以这种接法完全可行。
典型接线如下:
| ADC0832引脚 | 接51单片机 | 说明 |
|---|---|---|
| CS | P1.0 | 低电平有效,片选 |
| CLK | P1.1 | 时钟,由单片机输出 |
| DIO | P1.2 | 双向数据 |
| VCC | +5V | 同时作为VREF |
| GND | GND | 共地 |
第一路电压信号接ADC0832的CH0,用来测ACS712输出;第二路接CH1,用来测分压后的电压。两个输入通道的模拟地都与单片机共地,否则ADC数据会在低几位跳动。
3.2 C51读ADC0832的源程序
下面是读ADC0832两个通道的标准函数。这段代码在Keil C51环境下可以直接编译,运行时钟为12MHz或11.0592MHz都行,延时函数可以自备。
#include <reg52.h> sbit ADC_CS = P1^0; sbit ADC_CLK = P1^1; sbit ADC_DIO = P1^2; void delay_us(unsigned int n) { while (n--); } unsigned char ADC0832_Read(unsigned char ch) { unsigned char i, dat1 = 0, dat2 = 0; ADC_CS = 0; ADC_CLK = 0; // 起始位:DIO置1,一个时钟 ADC_DIO = 1; ADC_CLK = 1; delay_us(2); ADC_CLK = 0; // SGL/DIF=1,表示单端输入,一个时钟 ADC_DIO = 1; ADC_CLK = 1; delay_us(2); ADC_CLK = 0; // ODD/SIGN:CH0为0,CH1为1 if (ch == 0) ADC_DIO = 0; else ADC_DIO = 1; ADC_CLK = 1; delay_us(2); ADC_CLK = 0; // 释放数据线,等待转换结果 ADC_DIO = 1; // 先读8位MSB,最高位在前 for (i = 0; i < 8; i++) { ADC_CLK = 1; delay_us(2); dat1 = (dat1 << 1) | ADC_DIO; ADC_CLK = 0; delay_us(2); } // 再读8位LSB,最低位在前,可用来校验 for (i = 0; i < 8; i++) { dat2 |= (ADC_DIO << i); ADC_CLK = 1; delay_us(2); ADC_CLK = 0; delay_us(2); } ADC_CS = 1; return dat1; // 返回8位AD值 }这段代码的逻辑是:先把CS拉低,让ADC0832进入工作状态;然后连续产生3个时钟,对应控制字的3个bit,完成开始转换和通道选择;接着读取两次8位数据,第一次是MSB优先,第二次是LSB优先。正常情况下两次结果按位反转后应该一致,如果偏差较大,说明时钟太快或引脚接触不良。返回的dat1就是0~255之间的数字量。
3.3 AD值换算成实际电压和电流
读到的AD值是0到255,对应的电压范围是0到VREF,也就是0到5V。计算公式是:
V_adc = dat1 × 5.0 / 255
ACS712电流通道还要再减去2.5V偏置,然后除以灵敏度。以20A量程模块为例,完整换算代码:
float adc_ch0_v, adc_ch1_v; float current_a, voltage_v; adc_ch0_v = ADC0832_Read(0) * 5.0f / 255.0f; current_a = (adc_ch0_v - 2.50f) / 0.100f; // 20A模块灵敏度100mV/A adc_ch1_v = ADC0832_Read(1) * 5.0f / 255.0f; voltage_v = adc_ch1_v * 11.0f; // 分压比11:1注意这里current_a的结果是有符号的,直流反向连接时会显示负值,这正好可以用来判断电流方向。如果不需要负电流显示,可以加一个if (current_a < 0) current_a = 0;,但建议保留,因为在做电机正反转检测时负电流很有用。
3.4 用示波器验证ADC0832时序的三个检查点
如果接上硬件后读数一直为0或255,不要先怀疑代码,先看时序。用示波器或逻辑分析仪抓CLK和DIO两个引脚的波形,重点检查三点:第一,CS拉低后的前三个时钟,DIO是否依次为1、1、0或1、1、1;第二,读数据阶段CLK高电平时DIO是否有稳定的变化;第三,整个读周期结束后CS是否恢复到高电平。
这三个点没问题,ADC0832的硬件链路就是通的。如果第三个时钟后DIO一直拉不高,问题大概率出在DIO引脚没有释放,导致单片机输出低电平把芯片的数据输出拉住了。解决办法是在读循环前加一次ADC_DIO = 1;,准双向IO输出1后才会释放总线。
4. 源程序框架:主循环、LCD1602显示和按键切换
4.1 工程文件划分与Keil设置
一个完整的数字电流表电压表源程序,不应该只有一个main.c堆到底。我一般这样拆分文件:
| 文件 | 职责 |
|---|---|
| main.c | 主循环、按键状态机、显示调度 |
| adc0832.c / adc0832.h | ADC0832时序、滤波、换算 |
| lcd1602.c / lcd1602.h | LCD1602驱动 |
| delay.c / delay.h | 延时函数 |
Keil工程里单片机型号选AT89C52或STC89C52RC,晶振频率按实际板子设置。STC89C52RC可以用内部晶振,但ADC0832的CLK频率与晶振无关,只要软件延时不乱改,12MHz和11.0592MHz都能稳定工作。烧录时用STC-ISP软件选择对应型号,不需要外部下载器。
4.2 采样平均和中值滤波代码
ADC0832本身是8位分辨率,单次采样值会受开关电源纹波影响。最稳的滤波方式是先连续采10次,去掉最大值和最小值,再对剩下的8个值求平均。
unsigned char adc_filter(unsigned char channel) { unsigned char i, v; unsigned char minv = 255, maxv = 0; unsigned int sum = 0; for (i = 0; i < 10; i++) { v = ADC0832_Read(channel); sum += v; if (v < minv) minv = v; if (v > maxv) maxv = v; } sum -= minv + maxv; return (unsigned char)(sum / 8); }这个函数比直接求平均值更抗脉冲干扰。注意sum是unsigned int,10个255的累加和是2550,不会溢出。去掉最大最小值后除以8,得到的结果是0~255之间的整数。调用时可以直接用adc_filter(0)和adc_filter(1),不需要每个循环都加延时。
4.3 LCD1602显示电流电压的关键代码
显示部分使用LCD1602,第一行显示电流,第二行显示电压。由于C51使用sprintf会占用较多code空间,我直接把浮点数拆成整数部分和小数部分显示。
void lcd_show_float(unsigned char row, unsigned char col, float value) { int ip = (int)value; int fp = (int)((value - ip) * 10); unsigned char buf[6]; buf[0] = '0' + ip / 10; buf[1] = '0' + ip % 10; buf[2] = '.'; buf[3] = '0' + fp; buf[4] = ' '; buf[5] = '\0'; lcd_write_string(row, col, buf); }这个函数把浮点值限制在0到99.9之间,刚好够电流电压表使用。如果电压超过99.9V,需要改百位显示逻辑。LCD1602的写指令和写数据函数属于常规驱动,初始化时序为:延时40ms,写入0x38,延时100us,写入0x0C,延时100us,写入0x01清屏,最后设置光标移动方式。
按键切换显示模式用P3.1口,按下一次在电流优先和电压优先之间切换,代码:
void key_scan(void) { if (KEY_SW == 0) { delay_ms(20); if (KEY_SW == 0) { display_mode = !display_mode; } while (KEY_SW == 0); } }20ms消抖可以滤掉机械按键的抖动,while等待释放可以避免一次按下触发多次切换。display_mode为0时,第一行显示电流,第二行显示电压;为1时反过来。
4.4 量程标定宏:换模块只改一个头文件
ACS712不同量程的灵敏度不同,电压分压比也会因为实际电阻误差而改变。我在adc0832.h里定义两个宏:
#ifndef __ADC0832_H__ #define __ADC0832_H__ #define ADC_VREF 5.0f /* ADC参考电压 */ #define ACS712_SENSITIVITY 0.100f /* 20A模块100mV/A */ #define ACS712_ZERO 2.50f /* 零电流时的输出电压 */ #define VOLTAGE_DIVIDER 11.0f /* 实际分压比 */ #endif换5A模块时只需要把ACS712_SENSITIVITY改成0.185f,其他代码全部不动。电压分压比最好用万用表量一下两个电阻的实际阻值再算,因为普通电阻误差1%就会导致电压显示偏差0.1V以上。
5. 校准与进阶:用零点偏移和分段线性补偿把精度提升到±1%
5.1 空载零点校准
ACS712的零点不是绝对2.50V,模块个体差异、温漂和供电波动都会让零点偏移。最可靠的办法是上电后按一下校准按键,单片机采样一次空载电流通道的电压,把这个值记作zero_offset,之后所有电流计算都减去它:
float zero_offset = 2.50f; void calibrate_zero(void) { unsigned int sum = 0; unsigned char i; for (i = 0; i < 20; i++) { sum += ADC0832_Read(0); } zero_offset = (sum / 20.0f) * ADC_VREF / 255.0f; }20次采样平均得到的零点比较稳定。这个值可以烧录时写在EEPROM里,下次上电自动调用,但EEPROM地址要避开程序后面要用的区域。如果只是临时演示,放在全局变量里也够用。
5.2 实际灵敏度校准
ACS712的灵敏度参数在手册里是一个典型值,实际器件会有2%左右的偏差。校准方法是:串联一个标准电流表,先通一个已知电流比如2.00A,读程序算出来的电流值I_display,再把灵敏度宏改成:
S_real = S_typ × I_real / I_display
比如20A模块默认0.100,2A标准电流显示1.96A,则实际灵敏度为0.100 × 2.00 / 1.96 ≈ 0.102。把这个值填到宏里,再重新烧录。这个过程做一次,基本能把读数误差压到1%以内。
5.3 分段线性补偿
如果要求更高,可以用分段线性表。ACS712在1A以下小电流段非线性比大电流明显,可以把0~5A分成几个区间,每段用一次线性修正:
float current_compensate(float raw) { if (raw < 0.5f) return raw * 1.02f; else if (raw < 2.0f) return raw * 0.99f + 0.015f; else if (raw < 5.0f) return raw * 1.00f; else return raw; }这个表需要实际测量后填,不同模块的规律不一样。分段点越多,效果越好,但也会因为参考点噪声累积误差,一般3到5段足够。
5.4 交流电流测量扩展
ACS712可以测交流,但51单片机按直流方式采样得到的是平均值,不是有效值。如果要做交流数字电流表,常见做法是把输出接到ADC后连续采样一个工频周期,计算平方平均再开根号。51单片机主频12MHz时,每周期采20个点,配合快速开方算法也可以做,但精度有限。更省事的方案是先用真有效值芯片RMS转DC,再进ADC,这套代码里的标度变换部分完全复用。
5.5 移植到STC内置ADC时注意时钟线
如果手上是STC12C5A60S2这类自带ADC的单片机,可以去掉ADC0832,把ACS712输出直接接到P1.0模拟输入口。STC内置ADC的参考电压是VCC,代码里换算公式不变。需要注意的是STC12的ADC时钟不要选太高,ADC_CONTR里的SPEED位建议设为慢速挡,否则采样值会在低四位波动。移植时保留原程序里的滤波函数,只把ADC0832读函数替换成STC的读ADC函数即可。外部AD方案和内置AD方案切换时,最容易被忽略的是分压网络的地线,ADC0832和单片机必须共地,否则引流路径会经过其他电路,导致电压读数跟着负载电流变化。
本文还有配套的精品资源,点击获取