51单片机初学3-从零开始制作一款电子时钟

   日期:2021-03-15     浏览:115    评论:0    
核心提示:电子时钟最重要的要求一是计时准确,二是省电。首先需要以下材料:STC89C52芯片(1块),40P底座(1只),面包板(2片)3461BS数码管(2只),电动按钮(9只),LED灯(1只),74HC373锁存芯片(1片),10K 9P排阻(4只),470欧电阻(15只),12M晶振(1只),30pF瓷片电容(两个),排针(15针),led灯,有源蜂鸣器一只,三极管程序如下:/*LED数码管.

今天我们来用制作一款简单的单片机作品:电子时钟。除了基本的走时功能,还能手动调节时间,设置闹钟,待机唤醒。

本文包括硬件与软件设计。

电子时钟需要考虑的两点:一是计时准确,二是省电。

硬件设计:

首先我们需要构思好系统框架:

基本的时钟电路与复位电路不用多说,我们用八位数码管来作为时间显示方式(12-00-00),其中P0口控制其段,P2口控制其位;以八个点动按钮作为键盘输入。

接下来就可以设计原理图:

可以看到数码管的接线较复杂,其原理暂不多说(可参考我上篇文章),可以看到两个数码管的1、2、3、4、5、7、10、11是分别连起来的,然后引出来连接到P0口;两个数码管的6、8、9、12共8个脚与P2口连接。

需要注意,数码管位控制与P2口之间加入了一个锁存器,其作用是在待机时方便关闭数码管。其11脚是地址锁存端口,将其接高电平时,锁存器为透明模式,输入与输出完全相同,这里我直接接入VCC;1脚为输出锁存,高电平时无输出,低电平才有输出,这里我们用P3.6来控制其输出。

为了简化电路,蜂鸣器与LED共用一个I/O口;

单片机的数据串口引出来接到排针上,方便程序烧录。

需要注意,为了防止数码管烧坏,在P0口应串联470欧姆的限流电阻(原理图中未画出)。

 

所以得到所需材料:

STC89C52芯片(1块),40P底座(1只),面包板(2片),3461BS数码管(2只),点动按钮(9只),LED灯(1只),74HC373锁存芯片(1片),10K 9P排阻(4只),470欧电阻(15只),12M晶振(1只),30pF瓷片电容(两个),排针(15针),led灯,有源蜂鸣器一只(关于有源与无源蜂鸣器的区别可在网上查阅),PNP型三极管一只。

最后我们按照原理图焊接元件,测试焊接无误后就可以写入程序测试。

 

为了使作品看起来简洁,我们采用双主板设计。由于定做PCB时间较长,所以我使用洞洞板来制作电路板,可以看到飞线很多,两块主板之间有较多的连接线(为了防止焊点受力而脱落,可以将线绕在洞洞之间)。注意焊接单片机底座时,不要把单片机装在底座上,以免焊接时烧坏单片机芯片;同样,焊接晶振时,要尽可能快,避免长时间给晶振加热而损坏晶振;安插单片机芯片时要注意对齐引脚,以免折断或者接触不良,插好后可以用万用表测量一遍所有引脚是否与底座导通;排阻公共端判断方法:在排阻最左边或者最右边会有个白色小点,有白点的一端为公共端。

单片机程序开发常用 keil软件(这里我们以Keil uVision3为例):

首先新建工程(点击project→new→选择一个文件地址后保存),然后选择CPU型号。

STC89C52是完全兼容AT89C52的(因为STC是国产芯片,keil中没有STC芯片,只能用其他芯片代替),所以我们选择AT89C52即可(首先点Atmel,下拉之后,可以找到AT89C52)。

之后会弹出询问窗口:Copy standard 8051 Startup code to Project Folder and Add File to project?(是否复制8051启动编码到工程文件夹?),点击确认即可。若点击取消,在创建文件时也会自动添加。

可以看到创建了一个Target1的工程文件,下拉时候还有一个Source Group1的文件夹。这个文件夹里有个STARTUP.A51的文件,这就是刚才复制的8051启动编码,里面包含51单片机的寄存器、I/O口等地址的分配,这些都是软件自动生成的,一般不需要去更改。

之后添加C程序文件:File→new。然后会创建一个text1的空白文件。然后我们点击保存(或者Ctrl+S),选择保存地址(保存在一个容易找到的地方,后面需要用到),输入文件名,注意文件名要加后缀.c保存为C文件。如果是用汇编语言写程序,则加后缀.ASM。

接着右击Source Group1,在菜单中找到Add Files To Group ‘Source Group1’点击(这个选项在菜单中有加粗显示)。然后将刚才的c程序文件添加至工程,关闭对话框。可以看到Source Group1下多了之前的C文件。

然后就可以写程序了。

程序编写:

定义单片机C程序的头文件#include<reg51.h>

为了方便后面写程序时,搞混I/O口,我们可以先定义一些功能引脚。例如蜂鸣器,我们查看原理图可以看到,蜂鸣器是由P3.1控制的,所以我们定义P3.1为蜂鸣器:sbit fm=P3^1;(‘sbit’是单片机用于定义引脚的关键字,在C语言中是没有这个关键字的;P3.1之间的点在程序中要用‘^’表示),这样,在之后的程序中,如果我们要用到蜂鸣器,只要让fm等于0或者等于1,就可以控制蜂鸣器的工作了,而不再需要使用P3^1了。

然后我们还要对数码管进行编码,数码管需要显示的字符较多,我们可以使用一个数组来定义:

char codeduan[]={0xc0,0xcf,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x7f,0xbf,0xff,0x89};

(char数据类型:在单片机中,char数据类型所占空间最少,只有1个字节,但他的范围为 -128~127 (signed有符号型),unsigned为0~255。所以除非数据范围太大,一般都是用char类型,这样做可以节省单片机空间)

接着定义全局变量 sec,min,hour.之所以定义为全局变量,是为了让这三个量所有函数中都是能使用的。

在本作品中,延时函数必不可少,比如数码管扫描,走时都需要延时函数。关于延时函数的计算问题可自行百度,为了方便,我们可以直接使用STC-IPS软件自动生成,只要输入需要延时的时间,软件可以自动生成一个延时函数,直接复制粘贴就可以(最小时间为1us)。   由于我们需要多种时间的延时,所以我们可以先把需要的延时函数先写在前面,方便之后的调用。

定义好需要的变量,我们就可以开始写主函数了。这里我们把数码管扫描与计时作为主程序,数码管扫描与计时同时进行(也可以使用定时器中断)。

接着编写调时子函数,闹钟子函数。在主程序插入判定条件,以此调用子函数。

为了添加更多花样,还添加了一个开机‘动画’  motos();(详情看后面的程序)

需要注意的是,子函数应置于主函数前面,否则编译时会提示 未定义子函数 。

再说说键盘的处理。键盘排列与键位设置如下。

K1、K2控制光标的左右移动,K3、K4控制数字加减,K5为确定键,K6为调时(长按4秒进入),K7设置闹钟,K8待机模式。

其他细节暂不多说,看程序即可。

完整程序如下:



#include<reg51.h>
#include<intrins.h>    //定义单片机的头文件
sbit fm=P3^1;          //定义单片机蜂鸣器
sbit plays=P3^6;	     //定义73HC373输出控制位
		 //    0    1    2    3    4    5    6    7    8    9    10  11   12   13   //
char codeduan[]={0xc0,0xcf,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x7f,0xbf,0xff,0x89};	   //数码管段编码
            //    0    1    2    3    4    5    6    7    8    9    dp   -    空	H   //
char codebite[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x00};				   //数码管位编码
char sec=0,min=0,hour=0;
void Delay1ms()		//@12.000MHz,1ms延时函数,用于数码管动态输出
{
	unsigned char i, j;
	i = 2;j = 239;
	do
	{
		while (--j);
	} while (--i);
}
void Delay50ms()		//@12.000MHz,用于蜂鸣器提示音,30ms
{
	unsigned char i, j, k;
	i = 2;j = 95;k = 43;
	do
	{	do
		{   while (--k);
		} while (--j);
	} while (--i);
}
void adjust()			     //时间调整模式子程序
{
   int H=0,cursor=3;
   char ks,twi,temps[8],K[8];
   temps[2]=11;
   temps[5]=11;
   fm=0;Delay50ms();fm=1;  //蜂鸣器响一声提示进入时间调整模式
   while(P1!=0xef)		    //如果没有按下K8,则执行循环
      {
	 if(H<180)	    {twi=0;}  	    //进入调整模式后,光标闪烁
	 if(H>180)	    {twi=1;}  
    	 if(H==360)     {H=0;}  
       for(ks=0;ks<8;ks++)
           {
	      if(cursor==1&&twi==0)
		  {
		  temps[0]=12;temps[1]=12;
		   }
		else
		   {temps[0]=sec%10;         //求余计算秒个位
                temps[1]=sec/10;}         //求商计算秒十位
	     
	      if(cursor==2&&twi==0)
		  {
		  temps[3]=12;temps[4]=12;
		   }
		else
		   {temps[3]=min%10;         //求余计算分个位
                temps[4]=min/10;}         //求商计算分十位
		    
		if(cursor==3&&twi==0)
		  {
		  temps[6]=12;temps[7]=12;
		   }
		else				   
		   {temps[6]=hour%10;	       //求余计算时个位
		    temps[7]=hour/10;}      	 //求余计算时十位		        
	      P2=codebite[ks];	      //数码管输出选位,从第0位开始//
	      P0=codeduan[temps[ks]]; //输出段,输出要显示的数字//
	      Delay1ms();			//延时1ms,防止数码管串码
		H++;
	      P0=codeduan[12];
		}
       if(P1==0xfe)	   			  
	         { K[1]=1;}
       if(K[1]==1&&P1!=0xfe)
		   {K[1]=0;   cursor++;}
		
	 if(P1==0xfd)			   	   
		   { K[2]=1;}
       if(K[2]==1&&P1!=0xfd)
		   {K[2]=0;   cursor--;}
	 if(cursor<1) { cursor=3;}		  
	 if(cursor>3) { cursor=1;}
		   		
	 if(P1==0xfb)			 	  
               { K[3]=1;}
       if(K[3]==1&&P1!=0xfb)
		   { K[3]=0;   
		     switch(cursor)
		       {
			  case 1:sec++;break;
			  case 2:min++;break;
			  case 3:hour++;break;
			  default:break;
		        }
		   }		    
	 if(P1==0xf7)			 	  
               { K[4]=1;}
       if(K[4]==1&&P1!=0xf7)
		   { K[4]=0;   
		     switch(cursor)
		       {
			  case 1:sec--;break;
			  case 2:min--;break;
			  case 3:hour--;break;
			  default:break;
		        }
		   }
	if(sec>59) {sec=0; }			     
	if(sec<0)  {sec=59;}
	if(min>59) {min=0; }
	if(min<0)  {min=59;}	   		   		
   	if(hour>23){hour=0; }
	if(hour<0) {hour=23;}	   		
	 }
       return;					    //如果检测到K8按下,则跳出循环,返回主函数
} 
 
void motos()
{
 int mot=0;
 char m;
 char motobit[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
 char motoduan[8]={0xcf,0xa4,0xc0,0xa4,0x8e,0xc7,0xbf,0xbf}; 
 while(mot<1800)
 {
  for(m=0;m<8;m++)
      { 
	 P2=motobit[m];	      //数码管输出选位,从第0位开始//
	 P0=motoduan[m];   //输出段,输出要显示的数字//
       Delay1ms();			//延时1ms,防止数码管串码
	 P0=codeduan[12];
	 mot++;
	 }
  }
  fm=0;Delay50ms();fm=1;Delay50ms();fm=0;Delay50ms();fm=1;
  return;
} 

 
  
void main()
 {  int num=0,ks=0;
    char k,temp[8],moto=1;
    plays=0;	     
    motos();	     
    temp[2]=11;
    temp[5]=11;
    while(1)
      {
       for(k=0;k<8;k++)
           {	  		     
	      temp[0]=sec%10;         //求余计算秒个位
            temp[1]=sec/10;         //求商计算秒十位
            temp[3]=min%10;         //求余计算分个位
            temp[4]=min/10;         //求商计算分十位
            temp[6]=hour%10;        //求余计算时个位
            temp[7]=hour/10;        //求商计算时十位
	      P2=codebite[k];	      //数码管输出选位,从第0位开始//
	      P0=codeduan[temp[k]];   //输出段,输出要显示的数字//
	      num++;
		Delay1ms();			//延时1ms,防止数码管串码
	      P0=codeduan[12];
		if(P1==0xdf)		//每次循环判断是否按下K1键
		   { 
		    if(num%10==0&&P1==0xdf)		//每10次循环,10ms,判断K1是否仍然按下
		       { 
		            ks++;		//如果每10次循环K1均按下,ks则自加一次
			      if(ks==300)	//如果KS记到300,表明k1已经连续按下4s,则进入时间调整模式,并将Ks清零
			          {
			           ks=0;
				     adjust();
			           } 
		        }		      //如果K1仍然按下,则将KS+1
		    }
            else{ks=0;}			//如果K1不再按下,则清零ks   
		if(num==865)		//经过与电脑时钟对比,找到最合适的值,以下为计时程序
		     {
		      sec++;
                  num=0;	      
                  if (sec==60)
                     {
                      sec=0;
                      min++;
                      if (min==60)
                           {
                            min=0;
                            hour++;
                            if (hour==24)
                               {hour=0;}
                            }
                      }			 	
	           }
	     }
      }   	
}

程序烧录:

写好程序之后,我们需要进行编译。若是首次编译,通常不会自动生成hex文件,需要进行如下设置:点击图中1处按钮“Option for Target”,在弹出的窗口中点击“Output”,然后勾选“Create HEX file”。点击确定后,点击序号4处的编译按钮,即可编译程序。

如果编译无误,则会显示0错误,0警告。并提示‘creating hex file from“#工程名#”’,说明HEX文件已经创建成功。

之后我们需要用到软件STC—IPS,这是专门用于STC系列单片机的程序烧录软件。

 

烧录之前,我们需要使用USB-TTL将电脑与单片机连接。连接方式如下图所示。

连接单片机之后,若提示“串口打开失败”,则点击“扫描”,电脑会自动找到对应的串口。

接着,我们点击“打开程序文件”,选择刚才生成的hex文件,然后点击“下载/编程”即可将程序下载到单片机。若点击下载之后无反应,则关闭单片机电源重新打开,程序便可写入单片机。

这样,整个作品就算完成了。

总结:

功耗计算(暂时找不到标准的5V、3V电源):

充电宝供电:电压5.15V,电流30~40mA,功耗5.15X(30~40)=154.5mA~206mW;

三节镍氢电池:电压3.91V,电流20mA左右,功耗3.91X20=78.2mW。

总的来说,功耗还是偏高,经过测试,主要的功率都消耗在数码管。单片机的功耗不超过10mW,所以待机时将数码管关闭能有效减小功耗。

 

误差问题:本时钟经过实测,还是有可见的误差。

可调的误差:运行程序需要占用很多机器时间,总时间=延时函数的时间+其他程序执行时间。而其他程序执行时间是很难计算的,只能经过对比调试来压缩延时函数的时间。

欲尽可能减小误差,需要与标准时钟(电脑或者手机的网络时间)进行对比,计算出误差,然后调节延时函数的时间。

比如:我们延时函数刚开始设置为1000ms,经过与标准时间对比1小时发现,我的时钟慢了1S,说明我时钟的误差为1/3600=0.0002778s=0.2778ms=277.8us(为了更精确计算出误差,我们可以提高对比时间,时间越长,误差越好计算)。这样,我们就可以把延时函数的时间减小278us,那延时函数就要设置为1000000-278us=999722us.为了调节的方便,我们可以使用两级级延时,一级延时函数以ms为单位,二级延时函数以us为单位,这样就很方便调试。

不可调的误差是晶振的温漂问题,晶振的震荡频率是按照25℃环境制作的,如果温度偏大或者偏小,其震荡频率都会有略微变化,进而影响CPU执行速度,造成走时不准。

更为先进的办法是使用wifi模块esp8266从网络获取时间,再将时间送给单片机,这样,走时不准的问题就能得到彻底的解决。还能使用LCD1602或者LCD12864作为显示器,这样可以显示更多的内容,就可以加入更多的花样(以后会专门介绍这两款显示器)。

关于esp8266的用法,稍微复杂些,以后再做介绍。

 

 

本文仅供参考,如有不足,还请指出。

 

 

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
更多>相关资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服