单片机:数字式时钟—日历
功能要求:
(1)显示时分秒,年月日
(2)具有:启动(按下此键后单片机才开始工作)、暂停、继续、清零、设置时间/年月日/闹钟的功能
(3)具有整点报时和闹钟到点报时的功能
(4)具有倒计时的功能
(5)年月日、正常的时钟显示、闹钟、倒计时这四个功能都可通过按键手动修改
该文参照了一篇csdn上的单片机数字式文章,并在此基础上添加功能,使该时钟更加完善。
另外:仿真图中的所有功能均已经测试过,完全能够正常运行!!!!!
引用的原文链接如下:
被引用的文章:
其中:年月日的修改、整点报时和倒计时、时钟的启动、暂停、继续、清零以及闹钟的设置和修是我添加的功能,也是对该时钟的完善。由于时间的原因,我没有对代码进行优化,用的是最简单地语句,虽然时用,但是过于冗长。
下面是代码中的几个重要变量:
1、key1(显示切换键):
最开始是显示时钟,因此flag=0为其初值,后面每按一次,即flag加一,当flag>3时,令flag=0。
2、flag:(1)flag=0时,正常显示时钟
(2)flag=1时,显示年月日
(2)flag=2时,显示所设置的闹钟时间(10:10:00)
(3)flag=3时,开始倒计时(初值为:00:10:00)
3、开关:按键设置时间/年月日/闹钟/倒计时:未按下之前,qh=0,都正常显示,每按一次,qh都加一,当qh>3时,令qh=0
4、qh:(1)qh=0:所有功能正常显示(不论flag为何值)
(2)qh=1:若flag=0,开始修改秒
若flag=1,开始修改日
若flag=2,开始修改闹钟时间中的秒
若flag=3,开始修改倒计时的秒
(3)qh=2:若flag=0,开始修改分
若flag=1,开始修改月
若flag=2,开始修改闹钟时间中的分
若flag=3,开始修改倒计时的分
(4)qh=3:若flag=0,开始修改时
若flag=1,开始修改年
若flag=2,开始修改闹钟时间中的时
若flag=3,开始修改倒计时的时
仿真图如下:
代码中里面的注释也写的比较详细,所以就不多介绍了,比较需要注意的一点就是括号,前面也说了,由于时间原因,所以没有对代码进行优化,因此过于冗长,括号也尤为重要,因此非常需要注意!!!
(当然,要是有小伙伴优化了记得艾特我下)
直接上源程序:
#include<reg52.h> //52单片机头文件
#include<intrins.h>
#define uint unsigned int // 宏定义
#define uchar unsigned char
#define NOT_Break 25 //任意赋值避开条件,只是单独的用来跳出循环用的,没有其他意义
uchar code table[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0XBF,0xff}; //共阳极数码管编码0-9和“—”
uchar val[8]={2,3,10,5,9,10,5,5};sbit key0 = P0^3; //清零
sbit key1 = P0^4; //时间---年月日转换
sbit key2 = P0^0; //启动
sbit key3 = P0^1; //暂停
sbit key4 = P0^2; //继续
sbit key5 = P0^5; //闹钟设置sbit BEEP = P3^7; //蜂鸣器
sbit LED_Port2=P3^5; //倒计时报时
sbit LED_Port1=P3^6; //闹钟报警,暂时设置为10点10uchar pos=0X01,index=0; //pos用来记录显示的数码管位置,index用来记录数据端口下标
void scan()//数码管扫描送显示函数
{P1=pos; // 选择要点亮的数码管是哪一个P2=table[val[index]]; // 给改数码管送显示数据pos=_crol_(pos,1); // 移位,为点亮下一个数码管做准备index++; if(index>7) index=0;
} void delay(uint z) //1毫秒
{uint x,y;for(x=z;x>0;x--)for(y=110;y>0;y--);
}uint num=0,keytime=0,time; //时间更新 1s,20ms扫描一次按键, 控制修改项闪烁
//num用来更新时间,keytime用来记每次扫描按键的相隔时间
void time0() interrupt 1
{TH0=(65536-2000)/256; //装初值12M晶振定时2ms数为2000TL0=(65536-2000)%256;keytime++; //20ms扫描一次按键time++; //控制修改项闪烁num++; //时间更新 1sscan();
} //-----判断P3口的按键-----///uchar key()
{ static uchar kzt=0; //推动循环,每次执行一次caseuchar keyPress=NOT_Break; //任意赋值避开条件,NOT_Break初值为25,keyPress用来记录哪个按键被按下,待switch语句运行完之后,返回一个keyPress,//待函数运行完后,返回一个NOT_Break,此时NOT_Break就是keyPress的值(如果有按键按下)/*思路:如果P3=0xff,P3上的开关都没有按下,当有开关摁下时,P3!=0xff,此时可以将kzt赋值为1,进入下一个case,同时返回一个无关值来跳出循环(1)case 0:首先令P3=oxff当有按键按下时,kzt=1,进入case 1(2)case 1:先令P3=0xff,消除前面按下的键的影响,当有键按下时,根据摁下不同的键,给keyPress赋不同的值,同时结束后,将赋值kzt=2,进入case 2;若没有键按下,则令kzt=0,回到第一步重新开始(3)case 2:同理,先令P3=0xff,消除上次按键的影响,若无按键按下,则令kzt=0,回到第一步重新开始,若有按键按下,直接跳出循环*/switch(kzt) //按顺序依次执行{
//------------case 0:------------//case 0: //第一步P3=0xff; //P3做读入状态时,应该要先写1的,才能正确读入数据 //按照课本原文 作输入时:必须先将1写入口锁存器,使场效应管截止,该口线同时被内部上拉电阻拉成高电平,//同时也能被外部输入源拉成低电平,即外部输入1时,该口线为高电平,输入0时,该口线为低电平if(P3!=0xff) //如果有按键按下{kzt=1; //向下执行第二步return NOT_Break; //返回一个无关的值,用来跳出此次循环 }break; //如果没有按键按下,直接跳出
//------------case 1:------------//case 1: //第二步P3=0xff; //P3做读入状态时,应该要先写1的,才能正确读入数据 //按照课本原文 作输入时:必须先将1写入口锁存器,使场效应管截止,该口线同时被内部上拉电阻拉成高电平,//同时也能被外部输入源拉成低电平,即外部输入1时,该口线为高电平,输入0时,该口线为低电平if(P3!=0xff) //有按键按下{switch(P3){case 0xfe: //第一个按键按下:设置时间1keyPress=0;break; //直接跳出case 0xfd: //第二个按键按下:调加keyPress=1;break; //直接跳出case 0xfb: //第三个按键按下:调减keyPress=2;break; //直接跳出 case 0xf7: //第四个按键按下:确定keyPress=3;break; //直接跳出 }kzt=2; //如果无按键按下 向下执行第三步return keyPress; //返回keyPress的值}else //如果无按键按下 回到第一步{kzt=0;return NOT_Break; //返回一个无关的值,用来跳出此次循环 }break;
//------------case 2:------------//case 2: //第三步P3=0xff; //P3做读入状态时,应该要先写1的,才能正确读入数据 //按照课本原文 作输入时:必须先将1写入口锁存器,使场效应管截止,该口线同时被内部上拉电阻拉成高电平,同时也能被外部输入源拉成低电平,即外部输入1时,该口线为高电平,输入0时,该口线为低电平if(P3==0xff) //如果没有按键按下{kzt=0; //如果无按键按下 回到第一步return NOT_Break;} break; //如果有按键按下 直接跳出} return NOT_Break; }//主函数
void main()
{ char hour=23,minute=59,ms=55,qh=0; //初始化时分秒 状态标志位qh(0:正常,1:改变秒钟,2:改变分钟,3:改变时钟)char year=21,month=1,day=15; //年月日的初始化char hour1=10,minute1=10,ms1=0; //闹钟的初始值char hour2=0,minute2=10,ms2=0; //倒计时的时分秒的初始值char flag=0; //标志位---年月日&&时分秒,flag==1,显示年月日;flag==0,显示时分秒;//flag==2,闹钟设置;flag==3倒计时开始char keycode=NOT_Break; //键值P0=0xff;P1=0xff;P2=0xff;P3=0xff;delay(10);//保证上面三条指令顺利完成/* sbit key2 = P0^0; //启动sbit key3 = P0^1; //暂停sbit key4 = P0^2; //继续sbit key5 = P0^5; //闹钟设置 */while(1) { while(key2) //当未按下启动键时,开始显示“--------”{while(1){P1=pos; // 选择要点亮的数码管是哪一个P2=table[10]; // 给改数码管送显示数据pos=_crol_(pos,1); // 移位,为点亮下一个数码管做准备index++; if(index>7){ index=0;break; }}}while(!key2){ //当启动键按下时,开始正常显示 TMOD=0x01; //设置定时器0为工作方式1TH0=(65536-2000)/256; //装初值12M晶振定时2ms数为2000TL0=(65536-2000)%256;EA=1; //开总中断ET0=1; //开定时器0中断TR0=1; //启动定时器0 while(1){ //循环显示if(key0==0) //清零按键按下{hour=0;minute=0;ms=0; //时分秒清零hour2=0;minute2=0;ms2=0; //倒计时也清零qh=0; //开始显示}if(key1==0) //年月日&&时分秒按键按下{while(!key1); //等待放开按键if(flag==0) {flag=1;} //时分秒正常显示&&年月日&&闹钟设置&&倒计时(0 && 1 && 2 && 3)else if(flag==1) {flag=2;}else if(flag==2) {flag=3;}else if(flag==3) {flag=0;} qh=0; //开始显示} if(!key3) //暂停键按下{while(1) //当按下了暂停键,但是没有按下继续键,则一直在while语句中循环,直到按下继续键,才跳出循环,正常显示{if(!key4)break;}qh=0;} if(keytime>9) //2ms*10=20ms,每20ms扫描一次按键{keytime=0; //计数清零keycode=key(); //检测是否有按键按下 }//---时间更新---// if((num>=500)&&(qh==0)) //时间更新 2ms*500=1s{ /***********正常显示时分秒*********/if(flag==0){ num=0; //计数清零ms++; //秒数加1if(ms>59) //如果秒等于60{ms=0; //秒赋值为0minute++; //分钟加1if(minute>59) //如果分等于60{minute=0; //分赋值为0hour++; //小时加1if(hour>23) hour=0;//如果时等于24 时赋值为0}}}/*************显示倒计时************/else if(flag==3){num=0; //计数清零ms2--; //秒数加1if(ms2<0) //如果秒等于60{ms2=59; //秒赋值为0minute2--; //分钟加1if(minute2<0) //如果分等于60{minute2=59; //分赋值为0hour2--; //小时加1if(hour2<0) hour2=0;//如果时等于24 时赋值为0}}}}//---按键检测---//////---第一个按键按下:选择---////*******当falg==0时,时间调整******/
/**************根据flag值来调整不同的显示*************/if(flag==0 ){if(keycode==0) {qh++; //状态标志位(0:正常,1:改变秒钟,2:改变分钟,3:改变时钟)if(qh>3) qh=1; //如果qh等于4,qh赋值为1 keycode=NOT_Break; //任意赋值避开条件}
///---第二个按键按下:调加---///if(keycode==1) {if(qh==1){ms++; if(ms>59) ms=0;//如果秒等于60 秒赋值为0}if(qh==2){minute++; if(minute>59) minute=0; //如果分等于60 分赋值为0 }if(qh==3){hour++; if(hour>23)hour=0; //如果时等于24 时赋值为0 }keycode=NOT_Break; //任意赋值避开条件}
///---第三个按键按下:调减---///if(keycode==2) {if(qh==1){ms--; if(ms<0) ms=59;//如果秒等于-1 秒赋值为59 }if(qh==2){minute--; if(minute<0) minute=59; //如果分等于-1 分赋值为59 }if(qh==3){hour--; if(hour<0) hour=23; //如果时等于-1 时赋值为23 }keycode=NOT_Break; //任意赋值避开条件}
///---第四个按键按下---/// if(keycode==3) //第四个按键按下 确定{qh=0; keycode=NOT_Break; //任意赋值避开条件}}//if(flag==0)的大括号/********flag==1时,年月份调整*******************************/else if(flag==1){if(keycode==0) //选择 {qh++; //状态标志位(0:正常,1:改变年,2:改变月,3:改变日)if(qh>3) qh=1; //如果qh等于4,qh赋值为1 keycode=NOT_Break; //任意赋值避开条件}
///---第二个按键按下:调加---///if(keycode==1) {if(qh==1){day++; if(day>30) day=1;//如果日等于31 秒赋值为1 }if(qh==2){month++; if(month>12) month=1; //如果月等于13 分赋值为1 }if(qh==3){year++; if(year>99)year=1; //如果年等于100 时赋值为1 }keycode=NOT_Break; //任意赋值避开条件}
///---第三个按键按下:调减---///if(keycode==2) {if(qh==1){day--; if(day<1) day=30;//如果日等于0 秒赋值为30 }if(qh==2){month--; if(month<1) month=12; //如果月等于0 分赋值为12 }if(qh==3){year--; if(year<1) hour=99; //如果年等于0 时赋值为12 }keycode=NOT_Break; //任意赋值避开条件}
///---第四个按键按下---/// if(keycode==3) //第四个按键按下 确定{qh=0; keycode=NOT_Break; //任意赋值避开条件}}/**************当falg==2时,闹钟调整*******************************/else if(flag==2 ){if(keycode==0) {qh++; //状态标志位(0:正常,1:改变秒钟,2:改变分钟,3:改变时钟)if(qh>3) qh=1; //如果qh等于4,qh赋值为1 keycode=NOT_Break; //任意赋值避开条件}
///---第二个按键按下:调加---///if(keycode==1) {if(qh==1){ms1++; if(ms1>59) ms1=0;//如果秒等于60 秒赋值为0}if(qh==2){minute1++; if(minute1>59) minute1=0; //如果分等于60 分赋值为0 }if(qh==3){hour1++; if(hour1>23)hour1=0; //如果时等于24 时赋值为0 }keycode=NOT_Break; //任意赋值避开条件}
///---第三个按键按下:调减---///if(keycode==2) {if(qh==1){ms1--; if(ms1<0) ms1=59;//如果秒等于-1 秒赋值为59 }if(qh==2){minute1--; if(minute1<0) minute1=59; //如果分等于-1 分赋值为59 }if(qh==3){hour1--; if(hour1<0) hour1=23; //如果时等于-1 时赋值为23 }keycode=NOT_Break; //任意赋值避开条件}
///---第四个按键按下---/// if(keycode==3) //第四个按键按下 确定{qh=0; keycode=NOT_Break; //任意赋值避开条件}}
/************flag==3时,倒计时调整**********************************************/if(flag==3 ){if(keycode==0) {qh++; //状态标志位(0:正常,1:改变秒钟,2:改变分钟,3:改变时钟)if(qh>3) qh=1; //如果qh等于4,qh赋值为1 keycode=NOT_Break; //任意赋值避开条件}
///---第二个按键按下:调加---///if(keycode==1) {if(qh==1){ms2++; if(ms2>59) ms2=0;//如果秒等于60 秒赋值为0}if(qh==2){minute2++; if(minute2>59) minute2=0; //如果分等于60 分赋值为0 }if(qh==3){hour2++; if(hour2>23)hour2=0; //如果时等于24 时赋值为0 }keycode=NOT_Break; //任意赋值避开条件}
///---第三个按键按下:调减---///if(keycode==2) {if(qh==1){ms2--; if(ms2<0) ms2=59;//如果秒等于-1 秒赋值为59 }if(qh==2){minute2--; if(minute2<0) minute2=59; //如果分等于-1 分赋值为59 }if(qh==3){hour2--; if(hour2<0) hour2=23; //如果时等于-1 时赋值为23 }keycode=NOT_Break; //任意赋值避开条件}
///---第四个按键按下---/// if(keycode==3) //第四个按键按下 确定{qh=0; keycode=NOT_Break; //任意赋值避开条件}}//if(flag==3)的括号///---显示函数---/// switch(qh) //按键按下后立即显示{ //正常显示//******显示日期 年月日(初始化设置)****/ case 0: if(flag==1) { val[0]=2; //年份21开头定死val[1]=1;val[2]=year/10; //年份十位显示val[3]=year%10; //年份个位显示val[4]=month/10; //月份十位显示val[5]=month%10; //月份个位显示val[6]=day/10; //天数十位显示val[7]=day%10; //天数个位显示}/**********显示时分秒***********/else if(flag==0){val[0]=hour/10; //显示时间 时分秒val[1]=hour%10;val[2]=10;val[3]=minute/10;val[4]=minute%10;/**********整点报时,蜂鸣器响 **********/if(minute==0) { BEEP=0;delay(200);BEEP=1; } /**********闹钟报时,LED灯亮************/ if(hour==hour1 && minute==minute1){ LED_Port1=0;delay(200);LED_Port1=1;} val[5]=10; val[6]=ms/10;val[7]=ms%10;}
/*********闹钟显示********/else if(flag==2){ val[0]=hour1/10; //显示时间 时分秒val[1]=hour1%10;val[2]=10;val[3]=minute1/10;val[4]=minute1%10; val[5]=10; val[6]=ms1/10;val[7]=ms1%10;}
/*******倒计时显示*********/else if(flag==3){ val[0]=hour2/10; //显示倒计时时间 时分秒val[1]=hour2%10;val[2]=10;val[3]=minute2/10;val[4]=minute2%10; val[5]=10; val[6]=ms2/10;val[7]=ms2%10;/**********倒计时报时,LED灯亮**********/ if(hour2==0 && minute2==0 && ms2==0){LED_Port2=0;delay(200);LED_Port2=1;}}break; ////*秒钟修改后显示 其中秒钟部分在闪烁*/case 1:if(flag==0){ //修改时分秒if(time<250) //2ms*250=0.5s{ val[0]=hour/10; //时的十位部分val[1]=hour%10; //时的个位部分val[3]=minute/10; //分的十位部分val[4]=minute%10; //分的个位部分val[6]=ms/10; //秒的十位部分val[7]=ms%10; //秒的个位部分}else if(time<500) //2ms*500=1s {val[0]=hour/10;val[1]=hour%10;val[3]=minute/10;val[4]=minute%10; val[6]=11;val[7]=11; }else if(time>500) //2ms*500=1s{ time=0;}}//if(flag==0)的括号/**********年月日中的day********/else if(flag==1){ if(time<250) //2ms*250=0.5s{ val[2]=year/10; //年的十位部分val[3]=year%10; //年的个位部分val[4]=month/10; //月的十位部分val[5]=month%10; //月的个位部分val[6]=day/10; //天的十位部分val[7]=day%10; //天的个位部分}else if(time<500) //2ms*500=1s /天数在闪烁 {val[2]=year/10;val[3]=year%10;val[4]=month/10;val[5]=month%10; val[6]=11;val[7]=11; }else if(time>500) //2ms*500=1s{ time=0;}}//if(flag==1)的else的括号 /*******修改闹钟的秒******/ else if(flag==2){ if(time<250) //2ms*250=0.5s{ val[0]=hour1/10; //时的十位部分val[1]=hour1%10; //时的个位部分val[3]=minute1/10; //分的十位部分val[4]=minute1%10; //分的个位部分val[6]=ms1/10; //秒的十位部分val[7]=ms1%10; //秒的个位部分}else if(time<500) //2ms*500=1s {val[0]=hour1/10;val[1]=hour1%10;val[3]=minute1/10;val[4]=minute1%10; val[6]=11;val[7]=11; }else if(time>500) //2ms*500=1s{ time=0;} }//修改闹钟的秒/***********修改倒计时的秒***********/else if(flag==3){ if(time<250) //2ms*250=0.5s{ val[0]=hour2/10; //时的十位部分val[1]=hour2%10; //时的个位部分val[3]=minute2/10; //分的十位部分val[4]=minute2%10; //分的个位部分val[6]=ms2/10; //秒的十位部分val[7]=ms2%10; //秒的个位部分}else if(time<500) //2ms*500=1s {val[0]=hour2/10;val[1]=hour2%10;val[3]=minute2/10;val[4]=minute2%10; val[6]=11;val[7]=11; }else if(time>500) //2ms*500=1s{ time=0;} } break;case 2:/*************flag==0:修改时钟的分***************/if(flag==0) { if(time<250){val[0]=hour/10;val[1]=hour%10;val[3]=minute/10;val[4]=minute%10; val[6]=ms/10;val[7]=ms%10; }else if(time<500){ val[0]=hour/10;val[1]=hour%10;val[3]=11;val[4]=11; val[6]=ms/10;val[7]=ms%10; }else if(time>500){time=0;}}//if(flag==0)的括号/************flag==1:修改年月日中的月********************/else if(flag==1){ if(time<250){val[2]=year/10;val[3]=year%10;val[4]=month/10;val[5]=month%10; val[6]=day/10;val[7]=day%10; }else if(time<500){ val[2]=year/10;val[3]=year%10;val[4]=11;val[5]=11; val[6]=day/10;val[7]=day%10; }else if(time>500){time=0;}}/***********flag=2:修改闹钟的分***********************/else if(flag==2){ if(time<250){val[0]=hour1/10;val[1]=hour1%10;val[3]=minute1/10;val[4]=minute1%10; val[6]=ms1/10;val[7]=ms1%10; }else if(time<500){ val[0]=hour1/10;val[1]=hour1%10;val[3]=11;val[4]=11; val[6]=ms1/10;val[7]=ms1%10; }else if(time>500){time=0;}}//闹钟分的修改/*************flag==3:修改倒计时的分**********************/else if(flag==3){ if(time<250){val[0]=hour2/10;val[1]=hour2%10;val[3]=minute2/10;val[4]=minute2%10; val[6]=ms2/10;val[7]=ms2%10; }else if(time<500){ val[0]=hour2/10;val[1]=hour2%10;val[3]=11;val[4]=11; val[6]=ms2/10;val[7]=ms2%10; }else if(time>500){time=0;}}break;case 3:/***********修改时钟的时*************/if(flag==0){if(time<250) //时钟修改后显示 其中时钟部分在闪烁{val[0]=hour/10;val[1]=hour%10;val[3]=minute/10;val[4]=minute%10; val[6]=ms/10;val[7]=ms%10; }else if(time<500) {val[0]=11;val[1]=11;val[3]=minute/10;val[4]=minute%10; val[6]=ms/10;val[7]=ms%10; }else if(time>500){time=0;}}//if(flag==0)的括号/*************修改年月日中的年*************/else if(flag==1){ if(time<250){val[2]=year/10;val[3]=year%10;val[4]=month/10;val[5]=month%10; val[6]=day/10;val[7]=day%10; }else if(time<500) {val[2]=11;val[3]=11;val[4]=month/10;val[5]=month%10; val[6]=day/10;val[7]=day%10; }else if(time>500){time=0;}}/*************修改闹钟的时**************/ else if(flag==2){ if(time<250) //时钟修改后显示 其中时钟部分在闪烁{val[0]=hour1/10;val[1]=hour1%10;val[3]=minute1/10;val[4]=minute1%10; val[6]=ms1/10;val[7]=ms1%10; }else if(time<500) {val[0]=11;val[1]=11;val[3]=minute1/10;val[4]=minute1%10; val[6]=ms1/10;val[7]=ms1%10; }else if(time>500){time=0;}}//闹钟时的修改/**************修改倒计时的时*****************/else if(flag==3){ if(time<250) //时钟修改后显示 其中时钟部分在闪烁{val[0]=hour2/10;val[1]=hour2%10;val[3]=minute2/10;val[4]=minute2%10; val[6]=ms2/10;val[7]=ms2%10; }else if(time<500) {val[0]=11;val[1]=11;val[3]=minute2/10;val[4]=minute2%10; val[6]=ms2/10;val[7]=ms2%10; }else if(time>500){time=0;}}break;}//switch(qh)的括号 }//小while(1)的括号:while(!key2)}//while(!key2)的括号}//大while(1)的括号
}//main的括号
最后,很感谢前面那位作者的免费博客,给了制作我一些方向。
另外也希望我的这篇博客可以给到你们一些帮助,要是觉得可以的话帮忙个点个一键三连下。十分感谢!
我后面还会在接下来的时间里在自己的博客里陆陆续续上传一些单片机和编程方面的文章。希望可以给大家带来一些帮助。