ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

51单片机+ACS712+ADC0832数字电流表电压表设计与源程序

51单片机+ACS712+ADC0832数字电流表电压表设计与源程序 简介这是一套基于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±5A185 mV/A0.037 V/A 折算到0~5V满量程时约占47%ACS712-20B±20A100 mV/A0.02 V/AACS712-30B±30A66 mV/A0.0132 V/A那么电流换算公式就是I (Vout - 2.5) / sensitivity这里的sensitivity是模块本身参数与单片机端接几伏基准无关。很多新手直接用ADC原始值去除以255再乘5跳过了减2.5这一项结果在0A时显示0.5A以上这就是第一个坑。另一个坑是模块供电。ACS712的VCC直接决定内部偏置电压如果VCC不是稳定的5V2.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采集芯片而不是单片机内置ADCAT89C52、STC89C52RC这类51单片机没有片上ADC只能外接。STC12系列虽然有内置ADC但标题明确写了“AD采集芯片”所以这里按外接方案处理。外接AD芯片种类很多几个常见选型如下芯片接口位数通道数采样速度适合场景ADC0832类SPI3线8位2几十kHz双通道模拟量接线少PCF8591I2C8位411kHz同时需要DA输出ADC0809并行8位810kHz多通道并行采集占IO多ADC128S022SPI12位850kHz高分辨率多通道这个项目只采集一路电流和一路电压ADC0832是最稳妥的选择。两个通道正好对应CH0和CH1三线接口只占用P1.0、P1.1、P1.2三个引脚换算公式简单时序也相对容易写。PCF8591因为是I2C51单片机还需要软件模拟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单片机说明CSP1.0低电平有效片选CLKP1.1时钟由单片机输出DIOP1.2双向数据VCC5V同时作为VREFGNDGND共地第一路电压信号接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/DIF1表示单端输入一个时钟 ADC_DIO 1; ADC_CLK 1; delay_us(2); ADC_CLK 0; // ODD/SIGNCH0为0CH1为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 / 255ACS712电流通道还要再减去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.hADC0832时序、滤波、换算lcd1602.c / lcd1602.hLCD1602驱动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 int10个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.1002A标准电流显示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和单片机必须共地否则引流路径会经过其他电路导致电压读数跟着负载电流变化。本文还有配套的精品资源点击获取
返回列表