一个能测直流和交流并自动换挡的仿真模型(基于51单片机)

1.制作要求:

1)直流电压量程: 200mV2V20V , 精度±(5%+1)

2 交流电压量程:200mV2V20V,精度±(5%+1)内,频率响应40~400Hz

3LCDOLED数码显示。

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;}		
	 }
}

 一些结果:

直流:

 

 

交流:

 

 

 

 

  • 3
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值