【Proteus】数码管简易计时器

前言

本章介绍使用定时器与数码管来做一个简易的计数器,使用3个2位一体数码管来分别代表时,分,秒。使用锁存器来控制数码管的段选与位选。

先放一张完整的仿真图:

锁存器:

锁存器在数码管驱动中是非常常用的一种器件。简单来说,锁存器最主要的作用就是缓存。可以按照下面的简单描述来理解:

当LE给到1的时候,锁存器就会使能打开,然后由单片机给输入数据到D0-D7,然后关闭LE端口,也就是LE端口给0,这时候锁存器的输出端,也就是Q0-Q7就会维持之前单片机输入给D0-D7的数据。

段选与位选:

结合上面的锁存器的东西,经常会看到一些文章中说到段选与位选。简单理解的话,如下图:

段选就是控制数码管显示什么内容,也就是实际写给数码管8个灯的数据,如下:

位选就是控制哪个数码管显示,就是写给数码管控制位的数据,如下:

显示数据:

关于单片机如何控制数码管显示数据,这一点可以参照数码管静态显示的内容,网上的描述很多,我就只简单的说一下。

单个数码管的编号图如下:

这些编号都是通用的,那个小数点,有些是编为H,有些编为DP,你明白是指小数点就可以了。除了编号的确定,之后就是共阴共阳的确定了。其实数码管也就是8个LED灯而已,共阴极就是说,这8个LED是负极接在一起的,那么在控制上就是,位选端口给0,A-H数据端口哪个给1,就哪个亮。

以我们的电路图来说,我们这个是两位一体的,假设我们的是共阴极(实际就是共阴极,略略略):

如果我们需要让左边的数码管显示0,右边的不显示,那么我们就需要把1端口置0,2端口置1,这时候就相当于位选选择了左边的数码管,而A-H需要A-F给1,G与H给0,那么就会显示出0的图案,换算成2进制就是,A-H分别是11111100,这时候就需要注意到一个为题,假如我们用单片机的P1口来直接控制8位数据,那么我的P1口是赋值0x3f(00111111)呢,还是赋值0xfc(11111100)呢,我最开始学习单片机的时候肯定是写0xfc,后面发现是不对的,我以为是十六进制的第一位就对应的单片机端口的第一位,实际刚好是相反的,这个应该是跟芯片设计时的大小端模式有关。有兴趣的话,可以百度大小端模式,了解一下相关内容。

那么由于我们电路中加入了锁存器,那么就要把锁存器的内容给加上:

结合上面锁存器的图,我们可以知道,要想让数码管显示0,我们需要先给段选锁存器写入0需要的数据,然后再让位选锁存器给H1为0。那么测试程序如下:

 	uint dat;  //存储位选数据
	uint number;  //存储段选数据

	WE = 1;
	P2 = 0xfe;  //赋值位选
	WE = 0;  //关闭位选

	DATA = 1;  //打开段选
	P2 = 0x3f; //段选信号
	DATA = 0;  //关闭段选

效果如图:

之后我把共阴极的数码管各数据,带小数点,不带小数点的各写入了一个数组,为了以后备用,如下:

//没有小数点的数字数组0~9
uchar code table_nonPos[]=		 
{
  0x3f,0x06,0x5b,0x4f,0x66,
  0x6d,0x7d,0x07,0x7f,0x6f
};

//有小数点的数字数组0~9
uchar code table_Pos[]=		  
{
  0xbf,0x86,0xdb,0xcf,0xe6,
  0xed,0xfd,0x87,0xff,0xef
};

之后就是定时器计时程序的编写了,关于定时器的配置,我一般就设置定时器0的工作方式为工作方式1,这部分的手册资料也是直接就能查得到,然后使用12M晶振定时50ms:

/*************************************************
功能:定时器0初始化函数
*************************************************/
void time_init()
{
    //晶振采用12M晶振
	TMOD=0x01;		  //设置定时器0的工作方式为工作方式1
	TH0=(65525-50000)/256;	//赋初值,定时50ms
	TL0=(65525-50000)%256;
	EA=1;	   //开总中断
	ET0=1;	   //开定时器0中断
	TR0=1;	   //启动定时器0
}

之后的定时器中断程序中,就使用三个变量分别代表时,分,秒,然后记录时间即可:

/*********************************************
功能:定时器中断函数
***********************************************/
void T0_time() interrupt 1
{
   	TH0=(65525-50000)/256;	//赋初值,定时50ms
	TL0=(65525-50000)%256;
	num++; //50ms
	if(num==20)  //1s时间到
	{
	   num=0;   //清零
	   s++;
	   if(s==60)  //60s时间到
	   {
	   	   s=0;
		   m++;
		   if(m==60)  //60分钟时间到
		   {
		      m=0;
			  h++;
			  if(h==60)	 //60小时到,全部清零,重新计时
			  {
			     h=0;
				 m=0;
				 s=0;
			  }
		   }
	   }
	}
}

在定时器中断函数中记录了时,分,秒,那么在mian函数中就驱动数码管显示出来就可以了。这个驱动函数我已经写好,所有完整程序如下:

#include <reg52.h>
#define uchar unsigned char
#define uint unsigned int

int num=0;  //定时器计数变量
int h=0,m=0,s=0;  //分别代表时,分,秒
int hour_1=0;  //时第1位
int hour_2=0;  //时第2位
int min_1=0;   //分第1位
int min_2=0;   //分第2位
int s_1=0;     //秒第1位
int s_2=0;     //秒第2位

sbit WE   = P1^2;  //锁存器位选
sbit DATA = P1^3;  //锁存器段选

//没有小数点的数字数组0~9
uchar code table_nonPos[]=		 
{
  0x3f,0x06,0x5b,0x4f,0x66,
  0x6d,0x7d,0x07,0x7f,0x6f
};

//有小数点的数字数组0~9
uchar code table_Pos[]=		  
{
  0xbf,0x86,0xdb,0xcf,0xe6,
  0xed,0xfd,0x87,0xff,0xef
};

/**************************************
功能:延时函数

**************************************/
void delay(uint num)
{
   int i,j;
   for(i=0;i<num;i++)
     for(j=0;j<5;j++);
}



/**************************************
功能:二位一体数码管驱动函数,
      number数码管段选
	  x数码管位选	1位H1 2为H2 3为M1 4为M2 5为S1 6为S2
**************************************/
void two_display(uchar number,uint x)
{
	//注意端口赋值时是倒序的
	uint dat;  //存储位选数据

	if(x == 1)    //选中第一位数码管
	  dat = 0xfe;
	else if(x == 2)
	  dat = 0xfd;
	else if(x == 3)
	  dat = 0xfb;
	else if(x == 4)
	  dat = 0xf7;
	else if(x == 5)
	  dat = 0xef;
	else if(x == 6)
	  dat = 0xdf;

	WE = 1;
	P2 = dat;  //赋值位选
	WE = 0;  //关闭位选

	DATA = 1;  //打开段选
	P2 = number; //段选信号
	DATA = 0;  //关闭段选
}
/*************************************************
功能:定时器0初始化函数
*************************************************/
void time_init()
{
    //晶振采用12M晶振
	TMOD=0x01;		  //设置定时器0的工作方式为工作方式1
	TH0=(65525-50000)/256;	//赋初值,定时50ms
	TL0=(65525-50000)%256;
	EA=1;	   //开总中断
	ET0=1;	   //开定时器0中断
	TR0=1;	   //启动定时器0
}

/*************************************************
功能:数码管显示函数
*************************************************/
void display()
{
		   //秒
	   s_1=s/10;   //十位
	   two_display(table_nonPos[s_1],5);
	   delay(1);
	   P2=0xff;   //全部清除,防止数据混乱
	   s_2=s%10;   //个位 下同
	   two_display(table_nonPos[s_2],6);
	   delay(1);
	   P2=0xff;   //全部清除,防止数据混乱
	   		//分
	   min_1=m/10;
	   two_display(table_nonPos[min_1],3);
	   delay(1);
	   P2=0xff;   //全部清除,防止数据混乱
	   min_2=m%10;
	   two_display(table_nonPos[min_2],4);
	   delay(1);
	   P2=0xff;   //全部清除,防止数据混乱
	   		//时
	   hour_1=h/10;
	   two_display(table_nonPos[hour_1],1);
	   delay(1);
	   P2=0xff;   //全部清除,防止数据混乱
	   hour_2=h%10;
	   two_display(table_nonPos[hour_2],2);
	   delay(1);
	   P2=0xff;   //全部清除,防止数据混乱	
}

void main()
{
    time_init();
	while(1)
	{
		display();  //数码管显示函数
	}
}

/*********************************************
功能:定时器中断函数
***********************************************/
void T0_time() interrupt 1
{
   	TH0=(65525-50000)/256;	//赋初值,定时50ms
	TL0=(65525-50000)%256;
	num++; //50ms
	if(num==20)  //1s时间到
	{
	   num=0;   //清零
	   s++;
	   if(s==60)  //60s时间到
	   {
	   	   s=0;
		   m++;
		   if(m==60)  //60分钟时间到
		   {
		      m=0;
			  h++;
			  if(h==60)	 //60小时到,全部清零,重新计时
			  {
			     h=0;
				 m=0;
				 s=0;
			  }
		   }
	   }
	}
}

点个赞喽!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值