C51单片机学习笔记——电子时钟的实现
前言
其实很早就想写点东西,但是自己身为一名学渣实在是没什么自信,但是有了某人的鼓励和支持,我决定开始做出来一点学习习惯上的改变。往后我会磕磕绊绊将学习中的小知识和小经验统统写出来,一方面巩固自己的掌握程度,另一方面希望能给跟同我一样退伍返校学习有困难的大学生一点小小的帮助。往后哪里有说的不对的地方,希望各位大佬能多担待着点儿,帮我指出问题,在此提前谢谢了
任务介绍
利用80C51芯片实现一个电子时钟,要求:
- .动态显示 时 分 秒的数值
- 通过两个按键对当前时间进行调整
(相关文件正在审核上传:C源程序、protues工程文件。稍后去我的“主页—资源”里翻找应该可以找到)
(如果你没有keil和portues的话。。。我也不介意分享给你安装包 >_< )
proteus原理图,方便仿真调试
先用proteus画出原理图,方便后面程序进行仿真调试,(废话不多说上图就行了,由于我太懒了直接用的是学校老师发给我的实例图,但是各位看官请相信,c源代码是学渣阿诏用一指禅一个个敲上去的。)
程序构想
作为一个学渣,流程图什么的全靠脑子想(这不是个好习惯,我自己也在努力改正!!!),首先第一步明确程序的大体结构:
- 定时器0 产生1s的周期信号,进行秒数的累加
- 定时器1 进行数码管的动态扫描和显示,同时完成按键防抖等后续问题
- 外部中断1 进行选择需要调时的位置
- 外部中断0 将当前所选位置的数值进行相应改变
二话不多说,首先上代码
- 首先进行初始化,我先把前期需要定义各种变量的代码摆出来,后面提到相关的时候具体介绍:
#include <reg51.h>
#define u8 unsigned char
#define u16 unsigned int
#define time_scan 4000 //定时器1 每4ms进中断
//共阴数码管 0 1 2 3 4 5 6 7 8 9 - “空”
u8 led_cc[]={0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x40,0x00};
//数码管从左往右,第1 2 3 4 5 6 7 8个
u8 led_scan[8] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
//数码管初始显示内容:23-59-55
u8 led_show[]={2,3,10,5,9,10,5,5};
//(个人喜好)用指针名称方便清楚识别
u8 *hh=&led_show[0];//h时 h高位
u8 *hl=&led_show[1];//h时 l地位
u8 *mh=&led_show[3];//m分 h地位
u8 *ml=&led_show[4];//m时 l地位
u8 *sh=&led_show[6];//s分 h地位
u8 *sl=&led_show[7];//s时 l地位
u8 fd=0;//防抖标识
u8 gg=8;//更改标识
u8 sx=0;//闪烁标识
u8 tk=0;//无操作标识
void main()
{
/* 打开全部中断 */
EA = 1;
/* 打开外部中断 */
EX0 = 1;
IT0 = 0; //外部中断0低电平触发
EX1 = 1;
IT1 = 0; //外部中断1低电平触发
/* 打开定时器 */
TMOD = 0x12; // 定时器1为方式一 定时器0为方式二
// 定时器0 产生250us 的脉冲
TR0 = 1;
TH0 = 6;
TL0 = 6;
ET0 = 1;
// 定时器1 主要用作数码管扫描并显示
TR1 = 1;
TH1 = (65536-time_scan)/256;
TL1 = (65536-time_scan)%256;
ET1 = 1;
while(1);
}
(这里跟我一样的学渣们注意啊,重点程序都在中断函数里,这还没摆出来呢!)
- 解决1ms的周期信号:
定时器0采用方式2工作,既可以省略重新复制也可以提高精准度(如果你不知道什么是方式2的话,建议你去百度下定时的四种工作方式),它的初始化在上面那段儿代码里有,然后它的中断函数是介样式儿的:
/* 产生时钟脉冲 */
void ET_0(void) interrupt 1
{
static u16 i = 0;
i++;
if(i==4000)
{
(*sl)++;//秒数+1
i=0;
}
else{}
}
这里补充说明一下,定时器方式2,最高累计256us,TFx变为1进入中断,但是可以自动重新赋值就是该方式会自带 TLx = THx; 所以这里用了一个静态局部变量i ,每250us,i++,直到i == 4000,中断程序累计了1s,同时将要显示的 秒数+1
- 先实现数码管的动态显示
首先,说下如何实现动态显示,我的大白话就是:循环点亮各个数码管,在相应点亮的位置显示相应的数字,所以需要定义数码管显示数字的相应编码 led_cc[ ](不管共阳还是共阴百度一搜就有,这里用的共阴数码管),同时定义相对应的位显数组 led_scan[ ] (这个是为了方便单独点亮的操作),设置好这些我还定义了一个显示数组 led_show[ ] 这样只要改变该数组的值就可以改变显示的内容了。初始定义的那些指针只是为了我自己方便区别,其实可以不用,同时要注意时钟数值每个位置的进制不一样。我感觉我有点说不清楚了,直接怼代码:
/* 扫描数码管并显示 */
void ET_1(void) interrupt 3
{
static u8 w = 0;
P2 = 0;
TH1 = (65536-time_scan)/256;
TL1 = (65536-time_scan)%256;
/* 将时间赋值给 数组led_show[] */
if(*sl>=10)//秒低位 10进制
{
*sl=0;
if(gg==8)(*sh)++;
}else{}
if(*sh>=6)//秒高位 6进制
{
*sh=0;
if(gg==8)(*ml)++;
}else{}
if(*ml>=10)//分低位 10进制
{
*ml=0;
if(gg==8)(*mh)++;
}else{}
if(*mh>=6)//分高位 6进制
{
*mh=0;
if(gg==8)(*hl)++;
}else{}
if(*hl>=4)//时低位 4进制
{
*hl=0;
if(gg==8)(*hh)++;
}else{}
if(*hh>=3)//时高位 3进制
{
*hh=0;
}else{}
/* 显示led_show[] */
if(gg==w&&sx<=15)
{P1 = 0x00;}
else
{P1 = led_scan[w];}
P2 = led_cc[ led_show[w] ];
w++;
if(w==8)
{
w=0;
sx++; //每32ms累加sx一次
tk++; //每32ms累加tk一次
}else{}
/* 防抖70*4ms = 280ms */
if(fd>0)
{
fd++;
if(fd==71)
{
fd=0;
IE0=0; //清空外部中断0中断源
EX0=1; //打开外部中断0
IE1=0; //清空外部中断1中断源
EX1=1; //打开外部中断1
}else{}
}else{}
/* 闪烁间断时间 */
if(sx>=30) //闪烁周期时间时间30*32ms = 960ms
{sx=0;}
else{}
/* 5秒钟无按键操作自动退出调整 */
if(tk==157) //无操作时间时间157*32ms = 5024ms 后开始计时
{
tk=0;
gg=8;
TR0=1;
}else{}
}
补充:定时器1 采用方式1工作:每4ms改变一次显示位(这个间隔时间不能太长也不能太短)。
,这两个代码写完后,应该就可以开始计时了下面来说上面代码其他部分。
- 实现调时功能
在进行按键操作,最不能忘的就是防抖工作,之前都是delay();搞定,今天用的是定时器,实现方式就是:当有按键按下防抖标志fd=1,同时关闭响应的中断,通过定时器的累加到相应时间再打开中断(这时一定要记得先清除中断源 IEx=0),代码如下:
/* 进入调数 */
void EX_0(void) interrupt 0
{
EX0 = 0;
tk=1; //有按键按下
fd=1; //进入防抖
if(gg!=8)
(*(hh+gg))++;
else{}
}
相对应的打开操作,动态显示的那张代码里有
言归正传,我阐述下我实现调时的思路,首先建立一个标识gg(更改标识),当gg==8;则表示不需要调整时间,反之进行调整时间。为了能够准确找到要更改数值的相应数组下标,利用switch赋值操作,如下:
/* 按下进入调时 */
void EX_1(void) interrupt 2
{
static u8 c=0;
EX1 = 0; //关闭外部中断1
tk=1; //有按键按下
fd=1; //进入防抖
TR0 = 0; //暂停定时器0
if(gg==8)c=0;//确保每次进入调整从最左边开始
c++; //选择当前要调整的数值位置
switch(c)
{
case 1:gg=0;break;//选择*hh进行更改
case 2:gg=1;break;//选择*hl进行更改
case 3:gg=3;break;//选择*mh进行更改
case 4:gg=4;break;//选择*ml进行更改
case 5:gg=6;break;//选择*sh进行更改
case 6:gg=7;c=1;break;//选择*sl进行更改
// case 7:gg=8;c=0;TR0=1;break;//如果想通过调整键退出调整程序,将此行打开
}
}
然后通过gg标识,在另一个中断程序中进行相应的数值加“ (*(hh+gg))++; ”,如果你不喜欢用指针还可以这样写“ led_show[gg]++ ”也是可以的。
这样基本上就已经实现全部要求了,如果追求完美的客户体验的话还有以下小操作,具体思路和选择调时为一样,**通过激活一个标识,然后判断标识的状态再选择性进行相应的操作。**下面我把这些小修饰的代码单独拎出来方便你来查阅。
- 当前调时位闪烁
用到的标识为 sx//闪烁标识
/* 闪烁间断时间 */
if(sx>=30) //闪烁周期时间时间30*32ms = 960ms
{sx=0;}
else{}
配套的要用的还有,这一小段儿:
/* 显示led_show[] */
if(gg==w&&sx<=15)
{P1 = 0x00;}
else
{P1 = led_scan[w];}
P2 = led_cc[ led_show[w] ];
- 5秒钟不出现按键自动开始走时
用到的标识为 tk;//无操作标识
/* 5秒钟无按键操作自动退出调整 */
if(tk==157) //无操作时间时间157*32ms = 5024ms 后开始计时
{
tk=0;
gg=8;
TR0=1;
}else{}
主要的操作是改变gg值和重新打开定时器0产生的周期信号。
At The End
我知道我写的不好,有好多地方还请求各位大佬指教,但毕竟是自己码了好长时间的希望能对我这样的学渣在单片机的入门路上有所帮助。我的文件稍后也将双手奉上,protues是学校老师给的如有觉得眼熟,那你可能是我的学长,还请学长多多海涵!!!