1.制作要求:
(1)直流电压量程: 200mV、2V、20V , 精度±(5%+1)内
(2) 交流电压量程:200mV、2V、20V,精度±(5%+1)内,频率响应40~400Hz。
(3)LCD或OLED数码显示。
(4)量程自动转换功能。
(5)要求用一节9V电池供电。
2.简单设计思路
1.先是最简单的,要求用9v供电,而51系列单片机要求5v供电。因此我们可以选择使用7805芯片,将9v电压降为5v,然后再用5v电压驱动单片机。
2.对于LCD或OLCD数码显示,可以选择LCD1602或者LCD12864,这里选择了LCD1602.
3.对于量程的自动转换,可以采用ADC0808,它能够在8路中选择输出。
4.直流电压测量,小电压需要用运放放大,大电压需要用运放减小。
5.对于交流电的测量,思路与直流电基本一致,采用的是交流转直流的路数,在交流转直流的过程中对电压进行控制。
3.相关资料与设计过程
1.7805芯片
7805是三端稳压集成电路。7805几乎是我们最常用到的稳压芯片了,它的使用方便,用很简单的电路即可以输入一个直流稳压电源,他的输出电压恰好为5v,刚好是51系列单片机运行所需的电压。
如图所示,它可以将+9V的电压降到+5V,这样我们就可以使用5v电压给AT89C51芯片和ADC0808供电。
2.AT89C51芯片
2.1 最小系统
先依据如图所示搭好最小系统。
2.2 1602显示电路的搭建
1602的VDD引脚是电源脚,1602液晶屏最佳工作电压是5V;
VEE引脚用于调整1602显示的对比度(亮度),一般会外接电位器(当引脚电压为0时对比度最高);
RS引脚为数据/命令选择端,该引脚高电平时1602操作的是数据,而低电平时1602操作的是命令。数据就是我们要让1602液晶屏显示的具体内容,而命令就是对1602的设置(比如光标是否闪烁等);
R/W引脚为读写选择端,该此脚高电平可对1602进行读数据操作,而低电平时进行写数据操作。由于实际应用1602时我们基本上都是对1602进行写操作,所以很多板子都是直接将该引脚拉低。若需要对1602进行读操作,在实际设计PCB时可以接一个排针,通过跳帽来选择高低电平;
E引脚是使能信号,跟1602的读写操作时序有关,产生一个高脉冲有效;
D0~D7引脚是8位并行数据口,使得对1602的数据读写大为方便。
部分相关代码:
#define LEDDATA P0
sbit lcdrs=P2^0;
sbit lcden=P2^1;
unsigned char code mytable0[]=" Welcome to use ";
unsigned char code mytable1[]="Auto Voltmeter!";
unsigned char code line0[]=" Voltmeter ";
unsigned char code line1[]=" Value: V ";
void delay(unsigned int z) //延时1ms
{
unsigned int x,y;
for(x=z;x>0;x--)
for(y=110;y>0;y--);
}
void write_com(unsigned char c) //向1602写入命令的函数
{
lcdrs=0; //低电平写入命令
lcden=0;
LEDDATA=c; //把指令写入P0口
delay(5); //延时
lcden=1; //开使能
delay(5); //给时间读取指令
lcden=0; //关使能
}
void write_data(unsigned char d) //写数据函数
{
lcdrs=1; //高电平写入数据
LEDDATA=d; //把数据写入P0口
delay(5);
lcden=1;
delay(5);
lcden=0;
}
void initialize() //1602初始化函数
{
unsigned char num;
lcden=0;
write_com(0x38);
write_com(0x0c);
write_com(0x06);
write_com(0x01);
write_com(0x80+0x10); //第一行,选择顶格显示
for(num=0;num<17;num++)
{
write_data(mytable0[num]);
delay(10);
}
write_com(0x80+0x50); // 第二行,选择从第一格显示
for(num=0;num<15;num++)
{
write_data(mytable1[num]);
delay(10);
}
for(num=0;num<16;num++)
{
write_com(0x1c);
delay(300);
}
delay(1000);
write_com(0x01);
write_com(0x80);
for(num=0;num<14;num++)
{
write_data(line0[num]);
delay(10);
}
write_com(0x80+0x40);
for(num=0;num<15;num++)
{
write_data(line1[num]);
delay(10);
}
}
void value(unsigned char add,unsigned char dat)
{
write_com(0x80+0x47+add);
if(l==3&&add==2||l!=3&&add==1)
{
write_data(0x2e);
}
else
{
write_data(0x30+dat);
}
}
2.3 ADC0808的相关参数与数模转换电路的搭建
2.3.1 相关参数
对于ADC0808,它是CMOS单片型逐次逼近式A/D转换器,它有8路模拟开关 、地址锁存与译码器、比较器、8位开关树型A/D转换器。
我们可以控制12管脚和16管脚的电压来控制它的电压范围,这个依自己的需求而定。如现在我现在画的,12脚接5V,16脚接地。这么做的话,那么我们在IN0-IN7的电压输入就最好不要大于5V。
这时,那么如果ADC0808的输入端得到了一个电压值为3.45v的电压,那么它的输出将为00001101,最前面为out1的输出,最后一位为out8的输出。其中out8的输出代表最高位。
ADDA、ADDB、ABBC三个端口可以受到AT89C51控制,依此实现自动换路输出。
ALE端地址锁存允许信号,输入高电平时有效。
START 端为A/D转换启动脉冲输入端,当有一个正脉冲输入时,它才启动(脉冲上升沿使0808复位,下降沿启动A/D转换)。
EOC端为 A/D转换结束信号,输出,当A/D转换结束时,此端输出一个高电平(转换期间一直为低电平)。
OE端为数据输出允许信号,输入高电平有效。当A/D转换结束时,此端输入一个高电平,才能打开输出三态门,输出数字量。
CLK端为时钟脉冲输入端,一般要求时钟频率不高于640KHZ。当频率过低时,会导致仿真速率极慢,而且在测直流时会发现出现不能仿真的错误(这时建议将脉冲调高)。所以建议设在50KHz左右.
2.3.2 相关电路和程序
2.3.2.1 直流电压测量电路
因为直流电压的量程为200m,2v和20v。所以我们选择将小信号放大二十倍,这样可以占满0-4v的输出;而对于中等电压信号,我们选择放大两倍,这样可以占据0.4-4v的输出;对于大的电压信号,我们选择缩小5倍输出,这样可以占据0.4-4v的输出。
相关电路搭建:
电路的最上方的输出OUTPUT0为缩小5倍的大电压,接ADC0808的IN1脚(这个脚可以依据自己的想法接,IN0-1N7是完全相同的,之后在程序里确定自己的选择即可。)
同理可以做到OUTPUT1接IN2,OUTPUT3接IN3。
对于如何自动切换量程,我们的思路是:
先测量大电压信号的输出电压,当我们检测到输出电压小于0.4V时,我们通过控制单片机来控制ADDA、ADDB、ADDC端口的电平,来做到自动换路输出的效果。即当检测到电压小于0.4v时,我们将信号的输入移到IN2通道。若是此时仍发现检测电压小于0.4V时,我们再通过控制单片机来控制ADDA、ADDB、ADDC端口的电平,使得ADC0808检测IN4端口的输入电压。
对于真实电压的恢复,也可以通过相关程序来实现。详见代码如下:
#define v20_on {s3=0;s2=0;s1=1;} //定义ADDA,ADDB,ADDC端口
#define v2_on {s3=0;s2=1;s1=0;}
#define v02_on {s3=1;s2=0;s1=0;}
sbit s3=P3^7;
sbit s2=P3^6;
sbit s1=P3^5;
sbit OE=P3^0;
sbit EOC=P3^1;
sbit ST=P3^2; //控制start、ALE端
unsigned char code dispcode[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x00};
unsigned char dispbuf[8]={0,0,0,0,0,0,0,0};
unsigned char getdata;
unsigned long temp1,temp2;
_20v: //20v电压段
v20_on;
ST=0;
ST=1;
ST=0;
while(EOC==0);
OE=1; //0808芯片送数据此端口要为高电平
getdata=P1; //将P1端收到的数据存入getdata中
OE=0; //送数据结束,将其置为0,以防51单片机数据处理不过来
if(getdata<21) //判断输入电压是否小于0.4v
{
goto _2v;
}
l=3;
temp1=getdata;
temp1=(temp1*1000/51)/2; //恢复出真实电压值
goto disp1; //送入显示程序
_2v:
v2_on;
ST=0;
ST=1;
ST=0;
while(EOC==0);
OE=1;
getdata=P1;
OE=0;
if(getdata<21)
{
goto _02v;
}
else if(getdata>204)
{
goto _20v;
}
l=2;
temp1=getdata;
temp1=(temp1*1000/51)/2;
goto disp1;
_02v:
v02_on;
ST=0;
ST=1;
ST=0;
while(EOC==0);
OE=1;
getdata=P1;
OE=0;
if(getdata>204)
{
goto _2v;
}
l=1;
temp1=getdata;
temp1=(temp1*1000/51)/2;
m=temp1%10;
if(m>5){temp1=temp1/10+1;}
else{temp1=temp1/10;}
goto disp1;
disp1: for(i=0;i<=3;i++)
{
dispbuf[i]=temp1%10;
temp1=temp1/10;
}
if(l==3)
{
for(i=4;i>=3;i--)
dispbuf[i]=dispbuf[i-1];
}
else
{
dispbuf[4]=dispbuf[3];
}
for(k=0;k<5;k++)
{
value(k,dispbuf[4-k]);
}
if(l==2){goto _2v;}
else if(l==1){goto _02v;}
}
2.3.2.1 交流电压测量电路
对于交流电压的测量,我们的思路是将交流先转化为直流,在进行测量。
总图如下:
而为了将交流转化为直流,我们需要交流采集电路和峰值采集电路。
交流采集电路:
对于交流采集电路,我们可以用两个二极管来做到半波输出,同时控制波形的输出大小。(图中的电路实际上是小了十倍。)
峰值检测电路主要以C5为核心,依赖大电容的充电速度快而放电速度满的特性,使得最后的输出为电容上电压的输出,即交流电压的峰值电压。
当交流的频率越大,电压越低时,则测的越准。对于大电压时,电容的充电电压会略小于最初的电压,这时需要我们对数值进行相关的调整。最终这里测得当电压信号的有效值为17.8时,当频率为40Hz时,误差约在0.8%左右,误差已经很小了。而在400Hz时,仿真误差在0.4%左右。
对于交流如何换挡和量程的问题,我们的处理与直流一致,详见交流代码。
交流代码段:
#define fv20_on {s3=1;s2=0;s1=1;}
#define fv2_on {s3=1;s2=1;s1=0;}
#define fv02_on {s3=1;s2=1;s1=1;}
_f20v:
fv20_on;
ST=0;
ST=1;
ST=0;
while(EOC==0);
OE=1;
getdata=P1;
OE=0;
if(getdata<11)
{
goto _f2v;
}
l=3;
temp2=getdata;
temp2=0.67*(temp2*1000/48); //需要对大电压进行调整和修正
goto disp2;
_f2v:
fv2_on;
ST=0;
ST=1;
ST=0;
while(EOC==0);
OE=1;
getdata=P1;
OE=0;
if(getdata<21)
{
goto _f02v;
}
else if(getdata>204)
{
goto _f20v;
}
l=2;
temp2=getdata;
temp2=0.71*(temp2*1000/50)/2;
goto disp2;
_f02v:
fv02_on;
ST=0;
ST=1;
ST=0;
while(EOC==0);
OE=1;
getdata=P1;
OE=0;
if(getdata>204)
{
goto _f2v;
}
l=1;
temp2=getdata;
temp2=0.71*(temp2*1000/50)/2;
m=temp2%10;
if(m>5){temp2=temp2/10+1;}
else{temp2=temp2/10;}
goto disp2;
disp2: for(i=0;i<=3;i++)
{
dispbuf[i]=temp2%10;
temp2=temp2/10;
}
if(l==3)
{
for(i=4;i>=3;i--)
dispbuf[i]=dispbuf[i-1];
}
else
{
dispbuf[4]=dispbuf[3];
}
for(k=0;k<5;k++)
{
value(k,dispbuf[4-k]);
}
if(l==2){goto _f2v;}
else if(l==1){goto _f02v;}
当我们从测直流转为测交流时,我们需要告知单片机,这时,我们需要在单片机里的02V段加上一段:
else if(getdata<15)
{
goto _f;
}
因为0808的最小量程为0.019v,最终给到GETDATA的值约为19,即理论上当getdata上有值时,它将会>=20,所以,当getdata小于15时,我们认为这时没有任何电压加在直流端,这时我们让程序走向交流端。
同时我们将交流端同直流端都封装在一个while里,这样可以相互不受影响。(注:仿真时发现,当直流端不接电源时,交流端会有比较大的跳变。而当给直流端接地后再测,交流端的跳变消失了。)
附:
总代码:
#include <reg52.H>
#define LEDDATA P0
#define v20_on {s3=0;s2=0;s1=1;}
#define v2_on {s3=0;s2=1;s1=0;}
#define v02_on {s3=1;s2=0;s1=0;}
#define fv20_on {s3=1;s2=0;s1=1;}
#define fv2_on {s3=1;s2=1;s1=0;}
#define fv02_on {s3=1;s2=1;s1=1;}
#define R_on {s3=1;s2=0;s1=1;}
#define R1_on {s4=1;s5=0;s6=0;}
#define R2_on {s4=0;s5=1;s6=0;}
#define R3_on {s4=1;s5=1;s6=0;}
#define R4_on {s4=0;s5=0;s6=1;}
#define R5_on {s4=1;s5=0;s6=1;}
#define R6_on {s4=0;s5=1;s6=1;}
unsigned char code dispcode[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x00};
unsigned char dispbuf[8]={0,0,0,0,0,0,0,0};
unsigned char getdata;
unsigned long temp1,temp2;
unsigned char i,k,l,m,n;
unsigned char code mytable0[]=" Welcome to use ";
unsigned char code mytable1[]="Auto Voltmeter!";
unsigned char code line0[]=" Voltmeter ";
unsigned char code line1[]=" Value: V ";
//Òý½Å¶¨Òå
sbit lcdrs=P2^0;
sbit lcden=P2^1;
sbit s3=P3^7;
sbit s2=P3^6;
sbit s1=P3^5;
sbit OE=P3^0;
sbit EOC=P3^1;
sbit ST=P3^2;
void delay(unsigned int z)
{
unsigned int x,y;
for(x=z;x>0;x--)
for(y=110;y>0;y--);
}
void write_com(unsigned char c)
{
lcdrs=0;
lcden=0;
LEDDATA=c;
delay(5);
lcden=1;
delay(5);
lcden=0;
}
void write_data(unsigned char d)
{
lcdrs=1;
LEDDATA=d;
delay(5);
lcden=1;
delay(5);
lcden=0;
}
void initialize()
{
unsigned char num;
lcden=0;
write_com(0x38);
write_com(0x0c);
write_com(0x06);
write_com(0x01);
write_com(0x80+0x10);
for(num=0;num<17;num++)
{
write_data(mytable0[num]);
delay(10);
}
write_com(0x80+0x50);
for(num=0;num<15;num++)
{
write_data(mytable1[num]);
delay(10);
}
for(num=0;num<16;num++)
{
write_com(0x1c);
delay(300);
}
delay(1000);
write_com(0x01);
write_com(0x80);
for(num=0;num<14;num++)
{
write_data(line0[num]);
delay(10);
}
write_com(0x80+0x40);
for(num=0;num<15;num++)
{
write_data(line1[num]);
delay(10);
}
}
void value(unsigned char add,unsigned char dat)
{
write_com(0x80+0x47+add);
if(l==3&&add==2||l!=3&&add==1)
{
write_data(0x2e);
}
else
{
write_data(0x30+dat);
}
}
main()
{
initialize();
while(1)
{
_20v:
v20_on;
ST=0;
ST=1;
ST=0;
while(EOC==0);
OE=1;
getdata=P1;
OE=0;
if(getdata<21)
{
goto _2v;
}
else if(getdata<2)
{
goto _f;
}
l=3;
temp1=getdata;
temp1=(temp1*1000/51)/2;
goto disp1;
_2v:
v2_on;
ST=0;
ST=1;
ST=0;
while(EOC==0);
OE=1;
getdata=P1;
OE=0;
if(getdata<21)
{
goto _02v;
}
else if(getdata>204)
{
goto _20v;
}
l=2;
temp1=getdata;
temp1=(temp1*1000/51)/2;
goto disp1;
_02v:
v02_on;
ST=0;
ST=1;
ST=0;
while(EOC==0);
OE=1;
getdata=P1;
OE=0;
if(getdata>204)
{
goto _2v;
}
else if(getdata<15)
{
goto _f;
}
l=1;
temp1=getdata;
temp1=(temp1*1000/51)/2;
m=temp1%10;
if(m>5){temp1=temp1/10+1;}
else{temp1=temp1/10;}
goto disp1;
disp1: for(i=0;i<=3;i++)
{
dispbuf[i]=temp1%10;
temp1=temp1/10;
}
if(l==3)
{
for(i=4;i>=3;i--)
dispbuf[i]=dispbuf[i-1];
}
else
{
dispbuf[4]=dispbuf[3];
}
for(k=0;k<5;k++)
{
value(k,dispbuf[4-k]);
}
if(l==2){goto _2v;}
else if(l==1){goto _02v;}
}
_f: while(1)
{
_f20v:
fv20_on;
ST=0;
ST=1;
ST=0;
while(EOC==0);
OE=1;
getdata=P1;
OE=0;
if(getdata<11)
{
goto _f2v;
}
l=3;
temp2=getdata;
temp2=0.67*(temp2*1000/48);
goto disp2;
_f2v:
fv2_on;
ST=0;
ST=1;
ST=0;
while(EOC==0);
OE=1;
getdata=P1;
OE=0;
if(getdata<21)
{
goto _f02v;
}
else if(getdata>204)
{
goto _f20v;
}
l=2;
temp2=getdata;
temp2=0.71*(temp2*1000/50)/2;
goto disp2;
_f02v:
fv02_on;
ST=0;
ST=1;
ST=0;
while(EOC==0);
OE=1;
getdata=P1;
OE=0;
if(getdata>204)
{
goto _f2v;
}
else if(getdata<15)
{
goto _20v;
}
l=1;
temp2=getdata;
temp2=0.71*(temp2*1000/50)/2;
m=temp2%10;
if(m>5){temp2=temp2/10+1;}
else{temp2=temp2/10;}
goto disp2;
disp2: for(i=0;i<=3;i++)
{
dispbuf[i]=temp2%10;
temp2=temp2/10;
}
if(l==3)
{
for(i=4;i>=3;i--)
dispbuf[i]=dispbuf[i-1];
}
else
{
dispbuf[4]=dispbuf[3];
}
for(k=0;k<5;k++)
{
value(k,dispbuf[4-k]);
}
if(l==2){goto _f2v;}
else if(l==1){goto _f02v;}
}
}
一些结果:
直流:
交流: