上节分享了单片机定时/计数器实现数码管动态刷新的程序代码,本节利用定时/计数器以及外部中断实现一简单的电子时钟功能。对应的视频课程为:单片机应用实践篇之定时/计数器实现按键可调电子时钟,视频链接:https://www.ixigua.com/6840071457402257928。此处把视频课程中的程序代码重新整理了下,按照模块化进行了简单的处理,分为main.c,display.c,display.h,time.c,time.h,exit.c,exit.h。此处的按键可调电子时钟功能比较简单,主要是为了给初学单片机的同学介绍一个综合的应用实例,同时,也考虑下状态机的编程思路,及程序的框架处理。程序当然有许多需要改进和优化的地方,此处只是采用了4位数码管进行程序的功能演示,大家如果做实物的话,可以考虑采用时钟专用数码管,另外程序为了演示效果,当前界面是显示的分钟:秒,小时并没有显示,大家可以根据自己的需求,进行相应的界面设计及修改。如果在此代码的基础上,适当的增加些小算法代码就可以实现一个万年历的功能,如果有网友有想要实现万年历的功能代码的话,可以留言,我后面可以进行分享以前调试的一个代码。话不多说,代码撸起来。
//main.c
/***********************************************************
程序功能:按键可调电子时钟的设计
四位数码管显示 分钟:秒(可根据需要调整显示界面)
KEY1 外部中断0 状态切换
KEY2 外部中断1 参数调整
产生时间基准 定时器0
数码管动态刷新 定时器1
*************************************************************/
#include <reg51.h>
#include "display.h"
#include "time.h"
#include "exit.h"
void showNormal();
void showSec();
void showMin();
void showHour();
//状态变量
unsigned char state = 0;
void main()
{
EX0_Init();
EX1_Init();
Timer1Init();
Timer0Init();
EA = 1;
while(1)
{
switch(state)
{
case 0:TR0 = 1;showNormal();break;//正常时钟状态
case 1:showSec();break;//调秒
case 2:showMin();break;//调分
case 3:showHour();break;//调时
default:break;
}
}
}
//正常时钟显示界面
void showNormal()
{
LEDBuf[0] = min/10;
LEDBuf[1] = min;
LEDBuf[2] = sec/10;
LEDBuf[3] = sec;
}
//调秒显示界面
void showSec()
{
LEDBuf[0] = 23;//关
LEDBuf[1] = 23;//关
LEDBuf[2] = sec/10;
LEDBuf[3] = sec;
}
//调分显示界面
void showMin()
{
LEDBuf[0] = min/10;
LEDBuf[1] = min;
LEDBuf[2] = 23;//关
LEDBuf[3] = 23;//关
}
//调时显示界面
void showHour()
{
LEDBuf[0] = hour/10;
LEDBuf[1] = hour;
LEDBuf[2] = 23;//关
LEDBuf[3] = 23;//关
}
//time.c
#include "time.h"
#include "display.h"
//全局变量定义
unsigned char timerCnt;
unsigned char sec;
unsigned char min;
unsigned char hour;
void Timer0Init(void) //50毫秒@12.000MHz
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0xB0; //设置定时初值
TH0 = 0x3C; //设置定时初值
TF0 = 0; //清除TF0标志
ET0 = 1;
}
void Timer1Init(void) //1毫秒@12.000MHz
{
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x10; //设置定时器模式
TL1 = 0x18; //设置定时初值
TH1 = 0xFC; //设置定时初值
TF1 = 0; //清除TF1标志
ET1 = 1;
TR1 = 1; //定时器1开始计时
}
/*************************************
1、中断服务函数一定是一个没有返回值的函数
2、中断服务函数一定是没有参数的函数
3、中断服务函数函数名后跟关键字 interrupt
4、interrupt n 0 - 4 5个中断源 8*n 0003H
0003H INT0 000BH T0 0013H INT1 001BH T1 0023H ES
5、中断服务函数不能被主程序或其他程序所调用
6、 n 后面 跟 using m (0 - 3 )工作寄存器组
***************************************/
void timer0_ISR(void) interrupt 1
{
timerCnt ;
if(timerCnt >= 20)//20次50ms中断,即1S时间到
{
timerCnt = 0;
sec ;
if(sec >= 60)
{
sec = 0;
min ;
if(min >= 60)
{
min = 0;
hour ;
if(hour >= 24)
{
hour = 0;
}
}
}
}
TL0 = 0xB0; //设置定时初值
TH0 = 0x3C; //设置定时初值
}
void timer1_ISR(void) interrupt 3
{
TR1 = 0;
Display();
TL1 = 0x18; //设置定时初值
TH1 = 0xFC; //设置定时初值
TR1 = 1;
}
//time.h
#ifndef __TIME_H__
#define __TIME_H__
#include <reg51.h>
extern unsigned char sec;
extern unsigned char min;
extern unsigned char hour;
void Timer0Init(void);
void Timer1Init(void);
#endif
//exti.c
#include "exit.h"
#include "time.h"
//外部变量声明
extern unsigned char state;
void EX0_Init()
{
IT0 = 1;
EX0 = 1;
}
void EX1_Init()
{
IT1 = 1;
EX1 = 1;
}
void EX0_ISR() interrupt 0
{
TR0 = 0;
state ;
if(state >3 )
state = 0;
}
void EX1_ISR() interrupt 2
{
switch(state)
{
case 0:break;
case 1:sec ;if(sec >= 60) sec = 0;break;
case 2:min ;if(min >= 60) min = 0;break;
case 3:hour ;if(hour >= 24) hour = 0;break;
default:break;
}
}
//exti.h
#ifndef __EXIT_H__
#define __EXIT_H__
#include <reg51.h>
void EX0_Init();
void EX1_Init();
#endif
//display.c
#include "display.h"
unsigned char code leddata[]={ //数码管的段码表
0x3F, //"0"
0x06, //"1"
0x5B, //"2"
0x4F, //"3"
0x66, //"4"
0x6D, //"5"
0x7D, //"6"
0x07, //"7"
0x7F, //"8"
0x6F, //"9"
0x77, //"A"
0x7C, //"B"
0x39, //"C"
0x5E, //"D"
0x79, //"E"
0x71, //"F"
0x76, //"H"
0x38, //"L"
0x37, //"n"
0x3E, //"u"
0x73, //"P"
0x5C, //"o"
0x40, //"-"
0x00, //熄灭
};
unsigned char LEDBuf[] = {22,22,22,22};//数据显示缓冲区
unsigned char code PLACE_CODE[] = {0xfe,0xfd,0xfb,0xf7};//位选信号
void Display()
{
static unsigned char i = 0;
//1、送段码
//2、送位选
//3、延时
//4、消隐
switch(i)
{
case 0:
GPIO_DIG = 0x00;
GPIO_DIG = leddata[LEDBuf[0]];
GPIO_PLACE = PLACE_CODE[0];
i ;
break;
case 1:
GPIO_DIG = 0x00;
GPIO_DIG = leddata[LEDBuf[1]];
GPIO_PLACE = PLACE_CODE[1];
i ;
break;
case 2:
GPIO_DIG = 0x00;
GPIO_DIG = leddata[LEDBuf[2]];
GPIO_PLACE = PLACE_CODE[2];
i ;
break;
case 3:
GPIO_DIG = 0x00;
GPIO_DIG = leddata[LEDBuf[3]];
GPIO_PLACE = PLACE_CODE[3];
i = 0;
break;
default:break;
}
}
//display.h
#ifndef __DISPLAY_H__
#define __DISPLAY_H__
#include <reg51.h>
//IO的声明
#define GPIO_DIG P0 //段码 IO
#define GPIO_PLACE P2 //位选 IO
#define N 4 //数码管个数
unsigned char code leddata[];
extern unsigned char LEDBuf[];
void Display();
#endif
执行代码后,通过Proteus仿真的显示界面如下:
正常时钟显示界面(分钟:秒)
调秒显示界面
调分显示界面
调时显示界面
程序代码仅供初学的同学参考学习,代码可以根据自己的风格进行相应的优化处理,仅供参考。