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单片机实现的电子钟