轻松学51单片机--基于普中科技开发板练习蓝桥杯及机器人大赛等(7-用定时器实现电子钟)

1、实验目的和要求

(1)基本功能

开机显示设定的时间12:59:55
矩阵键盘key1被按下为时钟设置界面,小时数字闪(数码管0和数码管1闪烁)
矩阵键盘key2被按下为闹钟设置界面,闹钟显示设定值11:11:11,小时数字闪(数码管0和数码管1闪烁)
矩阵键盘key3按下为切换时钟闹钟设置的时分秒,按一下则切换下一个循环。
矩阵键盘key5按下是闪烁的时分秒数码管数字加一。
矩阵键盘key6按下是闪烁的时分秒数码管数字减一。
矩阵键盘key7按下是确认键,设定的值生效,此时显示时钟。

(2)扩展功能

矩阵键盘key4按下可以开启和关闭闹钟,用LED0来指示,开机后默认开启闹钟功能。

闹钟时间到则蜂鸣器鸣叫,矩阵键盘key8按下则关闭蜂鸣器。

2、基本功能

(1)驱动模块添加与引用

该实验需要矩阵键盘和数码管驱动,将c驱动文件和h头文件复制到Driver目录,并到keil中添加到项目目录里。mian.c文件开头。

#include <REGX52.H>
#include "Nixie.h"
#include "MatrixKey.h"

这些固定格式,需要哪个模块的驱动就添加哪个模块的头文件,头文件里面有函数声明,然后再mian函数里就可以直接调用了。我们这个实验介绍详细一点,后面的实验就粗略了。

(2)main函数固定写法

void main()
{
	Timer0Init();
	while (1)
	{
		Key_Proc();
		Seg_Proc();
		Led_Proc();
	}
}

Timer0Init():定时器初始化函数

Key_Proc():键盘扫描函数,这里实现按键按下对应的变量值的变化。

Seg_Proc():数据获取与数码管显示。这里主要是数码管显示,根据显示模式的不同,显示不同的内容

Led_Proc():这里是控制功能的实现,不仅是控制LED灯的显示,还有其他蜂鸣器灯外围设备。

(3)定时器T0初始化Timer0Init()

要实现电子钟,那就用定时器T0先生成1ms的定时函数,这个定时函数比较重要,后面我们会定时器响应中断里面实现数码管、LED的扫描和1S时间的产生。

 该STC89C52RC没有AUXR寄存器删除即可,然后把T0中断和总中断打开,整个T0初始化函数为

/* 定时器0中断初始化函数 */
void Timer0Init(void)		//1毫秒@12.000MHz
{
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	ET0=1;
	EA=1;
}

(4)变量定义

unsigned char Key_Val,Key_Down,Key_Old,Key_Up;//按键专用变量
unsigned char Seg_Buf[8] = {10,10,10,10,10,10,10,10};//数码管显示数据存放数组
unsigned char Seg_Pos;//数码管扫描专用变量
unsigned char Seg_Mode=0;//数码管显示模式
unsigned int Timer_1000ms;
unsigned char Clock_Display[3]={23,59,55};
unsigned char Alarm[3]={11,11,11};

(5)定时器T0中断服务函数

void Timer0Server() interrupt 1
{  
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	if(++Seg_Pos == 8) Seg_Pos = 0;//数码管显示专用
	Nixie(Seg_Pos,Seg_Buf[Seg_Pos]);

	 if(++Timer_1000ms==1000)
		{
			Timer_1000ms=0;
			if(++Clock_Display[2]==60)
			{
				Clock_Display[2]=0;
				if(++Clock_Display[1]==60)
				{
					Clock_Display[1]=0;
					if(++Clock_Display[0]==24) Clock_Display[0]=0;
				}
			}
		}
}

a-定时器中断服务函数每1ms进一次,由于定时器工作模式为1,16位不能重载初值的,所以必须把定时器内保存初值的寄存器TH和TL的初值在函数内重新赋值。

b-数码管的扫描放到定时器里,这样就不用delay函数了,每1ms就扫一次,每次扫一位数码管,一共8个数码管,循环扫。这里暂时没有用到带小数点的显示。

c-Seg_Buf[8]默认给10就是让数码管每个位的LED都关上,不显示,unsigned char code NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x00,0x40};段码表的第10位0x00,对应的段码就是不显示。这样就可以对每个数码管显示的数字进行控制了。

d-每毫秒进中断函数,Timer_1000ms变量加一,加到1000则生成1S,将Timer_1000ms清0,好生成下一秒。然后就是秒加加,生成分,分加加,生成小时,小时加到24,让它回到0。时分秒的变量我们用一个数组Clock_Display来存储。闹钟的显示我们也用一个数组Alarm来存储。

(6)数码管显示Seg_Proc()

void Seg_Proc()
{
	unsigned char i;
  Seg_Buf[2]=11;
	Seg_Buf[5]=11;
	
	if(Seg_Mode==0)
	{ 
		for(i=0;i<3;i++)
		{
			Seg_Buf[3*i]=Clock_Display[i]/10%10;
			Seg_Buf[1+3*i]=Clock_Display[i]/1%10;	
		}
	}
}

 数码管约定下,都是从第0为开始数。那么第2和第5位显示-,“-”的段码是0x40,数组索引为11.

开机数码管显示模式Seg_Mode变量为0,显示时钟。0-1位显示时,3-4显示分,6-7显示秒,如果没有找到规律,可以先用逐位显示的方式写,然后再找规律,写成for循环。写到这就可以编译上电下载到单片机看看效果了。

(7)按键功能实现Key_Proc()

void Key_Proc()
{

	Key_Val = MatrixKey();//实时读取键码值
	Key_Down = Key_Val & (Key_Old ^ Key_Val);//捕捉按键下降沿
	Key_Up = ~Key_Val & (Key_Old ^ Key_Val);//捕捉按键上降沿
	Key_Old = Key_Val;//辅助扫描变量

}

这几句写法主要是用来实现比较复杂的按键逻辑功能,比如是上升沿触发还是下降沿触发,按住不松手实现自增等,我们这里主要用下降沿Key_Down捕获。

	switch(Key_Down)
	{
		case 1:
			Seg_Mode=1;
		break;
		case 2:
			Seg_Mode=2;
		break;
		case 8:
			Seg_Mode=0;
		break;
	}
}

这里按Key1,切换时钟设置,按Key2,为闹钟设置。改变Seg_Mode的值,然后数码管根据Seg_Mode的值进行相应的显示,所以还要定义2个时钟、闹钟设置数组,存储设置的变量.

unsigned char Time_Set[3]={0,0,0};
unsigned char Alarm_Set[3];

数码管显示函数增加

	else if(Seg_Mode==1)
	{

			for(i=0;i<3;i++)
			{
				Seg_Buf[3*i]=Time_Set[i]/10%10;
				Seg_Buf[1+3*i]=Time_Set[i]/1%10;
			} 
			
	}
	else
	{
			for(i=0;i<3;i++)
			{
				Seg_Buf[3*i]=Alarm[i]/10%10;
				Seg_Buf[1+3*i]=Alarm[i]/1%10;
			} 
	}

(8)设置界面的时分秒切换

因为要设置时分秒变量,就要切换数组的索引,要定义个变量来指示这个索引,还要定义切换后的显示,让时分秒对应的数码管闪烁

unsigned char Time_Set_Index;
unsigned int Timer_500ms;
bit Seg_Flag;

在定时器中断服务函数里面增加500毫秒让Seg_Flag取反,我们好在显示里面让其亮灭。

    if(++Timer_500ms==500)
    {
        Timer_500ms=0;
        Seg_Flag=~Seg_Flag;
    }

显示控制函数里面增加

	else if(Seg_Mode==1)
	{

			for(i=0;i<3;i++)
			{
				Seg_Buf[3*i]=Time_Set[i]/10%10;
				Seg_Buf[1+3*i]=Time_Set[i]/1%10;
				Seg_Buf[3*Time_Set_Index]=Seg_Flag?Time_Set[Time_Set_Index]/10%10:10;
				Seg_Buf[1+3*Time_Set_Index]=Seg_Flag?Time_Set[Time_Set_Index]%10:10;
			} 
			
	}
	else
	{
			for(i=0;i<3;i++)
			{
				Seg_Buf[3*i]=Alarm[i]/10%10;
				Seg_Buf[1+3*i]=Alarm[i]/1%10;
				Seg_Buf[3*Time_Set_Index]=Seg_Flag?Alarm[Time_Set_Index]/10%10:10;
				Seg_Buf[1+3*Time_Set_Index]=Seg_Flag?Alarm[Time_Set_Index]%10:10;
			} 
	}

由于程序的顺序执行,后面2句会覆盖前面2句。由于Time_Set_Index初值是0,所以开始时时钟位在Seg_Mode为1或2的时候闪烁,我们需要按Key3来切换这个Time_Set_Index变量

        case 3:
            if(Seg_Mode==1||Seg_Mode==2)
            {
                if(++Time_Set_Index==3) Time_Set_Index=0;
            }
        break;

(9)设置时钟闹钟的时分秒

矩阵键盘key5按下是闪烁的时分秒数码管数字加一。
矩阵键盘key6按下是闪烁的时分秒数码管数字减一。

        case 5:
			if(Seg_Mode==1)
			{
				Time_Set[Time_Set_Index]++;
				if(Time_Set[Time_Set_Index]==(Time_Set_Index==0?24:60))  
					Time_Set[Time_Set_Index]=0;
			}
			if(Seg_Mode==2)
			{
				Alarm[Time_Set_Index]++;
				if(Alarm[Time_Set_Index]==(Time_Set_Index==0?24:60))  
					Alarm[Time_Set_Index]=0;
			}
		break;
		case 6:
			if(Seg_Mode==1)
			{
				Time_Set[Time_Set_Index]--;
				if(Time_Set[Time_Set_Index]==255)  
					Time_Set[Time_Set_Index]=(Time_Set_Index==0?23:59);
			}
			if(Seg_Mode==2)
			{
				Alarm[Time_Set_Index]--;
				if(Alarm[Time_Set_Index]==255)  
					Alarm[Time_Set_Index]=(Time_Set_Index==0?23:59);
			}
		break;

a-Seg_Mode的不同改变显示的内容是不一样的,所以要用if来判断下。

b-时和分钟不一样,我们这里有一个三目运算符的判断,如果是小时等于24,那么就让其为0,如果不是小时,那么等于60,回0.

c-这里需要特别注意,当Seg_Mode=2我们设置的是闹钟,而不是闹钟显示,但是我们在调整这个闹钟设置数组的时候,数码管并不显示Alarm_Set而是显示Alarm。所以这里必须要改的变量是Alarm而不是Alarm_Set。

(10)设置的结果生效

			if(Seg_Mode==1)
			{
				Clock_Display[0]=Time_Set[0];
				Clock_Display[1]=Time_Set[1];
				Clock_Display[2]=Time_Set[2];
			}
			if(Seg_Mode==2)
			{
				Alarm_Set[0]=Alarm[0];
				Alarm_Set[1]=Alarm[1];
				Alarm_Set[2]=Alarm[2];
			}
			Seg_Mode=0;

在最后确认完,Seg_Mode=0让系统回到时钟显示界面。由于时钟设置的时间默认是0,所以我们希望在设置时钟的时候能够读取实时时间,所以在按key1的时候将Clock_Display的值给Time_Set。而且可以将case 8的返回功能给删除了,因为Key7已经实现了。

        case 1:
            Time_Set[0]=Clock_Display[0];
			Time_Set[1]=Clock_Display[1];
			Time_Set[2]=Clock_Display[2];
			Seg_Mode=1;
		break;

3、扩展功能

(1)矩阵键盘key4按下可以开启和关闭闹钟,用LED0来指示,开机后默认开启闹钟功能。

如果用到LED的显示功能,还需要把LED的驱动引用,请参考之前的文章。

bit Alarm_Enable=1; 开启闹钟指示
unsigned char ucLed[8] = {0,0,0,0,0,0,0,0};//Led数据存放数组Led_Disp(Seg_Pos,ucLed[Seg_Pos]); 将LED扫描函数放到定时器服务里面。

增加一个Led_Proc(),并且在main函数里面循环执行。ucLed[0]=1 灯0亮,为0灭。

void Led_Proc()
{
	if(Alarm_Enable==1)
	{
		ucLed[0]=1;

	}
	else
	{
		ucLed[0]=0;
	}
}

(2)闹钟时间到则蜂鸣器鸣叫,矩阵键盘key8按下则关闭蜂鸣器。

蜂鸣器接P3.0口,P0用来显示数码管的段码,P1是矩阵键盘扫描,P2是LED和数码管位选(数码管位选是用P2.2-2.4接的三八译码器,因此这几位对应的LED是无法控制的),如果将蜂鸣器接这几个口会出现鸣叫断续的问题,因此接P3口最好。

sbit Beeper=P3^0;

bit BEEP_Enable;

闹钟时间到则蜂鸣器鸣叫

if(Clock_Display[0]==Alarm[0]&&Clock_Display[1]==Alarm[1]&&Clock_Display[2]==Alarm[2])
		{
			BEEP_Enable=1;
		}
		if(BEEP_Enable==1)
		{
			Beeper=0;
		}
		else
		{
			Beeper=1;
		}

      矩阵键盘key8按下则关闭蜂鸣器

		case 8:
			BEEP_Enable=0;
		break;

4、实现现象 

数码管用肉眼看不见闪烁,但是录视频的时候显示不是太清晰,蜂鸣器好像没有录进去声音,我就不重新录了,肯定是可以的。

51单片机实现的电子钟

  • 24
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值