在μPD78F0485单片机实验盒上编写程序实现在LCD上显示日历功能
1. 功能概述
此程序实现了一个普通电子表除计时外的所有功能。首先是可以实时显示时间以及当前日期,同时能对他们进行校正;其次是整点报时功能,当用户打开整点报时功能后可以在整点进行报时;还有就是闹铃功能,当用户设置了闹铃并打开闹铃,到达指定时间闹铃就会响一分钟,当然为了方便用户,用户可以在一分钟内关闭闹铃,而影响下一次闹铃响;最后还有一个初始化模块,长按指定按键三秒可以初始化电子钟。
2. 实验环境
操作系统:Windows7(64位)
软件环境:CubeSuite+
硬件环境:以µPD78F0485为芯片的单片机实验盒
3. 总体设计
此程序采用了自动机的思想,使用状态转换图实现电子钟的各个状态转换。因此在时间中断函数中判断当前状态进行显示,同时继续转向下一个状态。按键的主要功能就是改变状态值,或者是直接调用其它函数实现一些功能。而在主函数中在做完所有的初始化工作后就进入死循环状态,当然这里可以用停机之类的指令使单片机尽可能的省电,同时又要求中断能够及时响应。
4. 各模块功能
整个程序中有一些全局变量,Time time表示实时时间,时间与日期就是通过它的值显示的;alarm中有闹铃的时和分,另附加了两个变量表示整点报时和闹铃的开与关;num_code[]数组共11个元素,前十个表示0-9十个数字,最后一个表示“-”;show_mode这个变量当初是表示显示状态,写完这个程序后已远不止这个含义了,这个变量是整个程序最重要的变量,控制着当前状态,很多函数都对其读取和修改。最后有一个sfun变量,是我另外加上去的,在写程序之初没有考虑充分,这个变量仅用于长按第四个变量初始化时间以及用户在闹铃的一分钟内关闭闹铃。
4.1 主模块
在主函数中,在调用初始化硬件的函数后,打开中断后,就陷入无限的死循环,所有的事情都是通过中断函数调用其他函数进行的,因为我只希望它每半秒才进行有效的工作,其他时间都是休息。第一次时间中断最长可能在半秒后发生,所以手动调用一次show函数可以使其通电后马上就显示时间。
4.2 初始化模块
在此模块中共分为五个函数,其中inittime()用于初始化时间中断,设置好每半秒一次中断;init_time()使时间初始化为2015/01/01 8:00,这个函数还会在长按第四个按键三秒后调用;initlcd()是初始化屏幕显示的;InitKey()用于初始化按键中断,设置好端口;InitSound()函数中用于初始化扬声器。后面这些函数与上一次实验中的基本相同。
4.3 时间检查模块
该部分共分为两个函数。
1) int mon_day()。此函数通过全局变量time获得当前月份和年,返回当月最大天数,此函数与书上的基本相同。
2) void check_time()。由于秒、分钟加到了60或者小时加到24等情况,使时间出现了不该有的形式,这个函数就是调整时间,使时间再次变为合法函数,此函数会调用上一个函数检查“日数”的合法性。
4.4 显示模块
我想这可能是最复杂的模块,该模块通过show_mode这个变量来显示日期或者时间,很多功能都是在这里实现的。比如调整时间中,为了提示用户当前的调整项,需要通过闪烁当前项来提示用户,为此就需要加入缺秒的时间,缺分的时间等来实现这些功能。再如长按三秒初始化,也是在此模块中实现的,通过读取端口值来检查按键是否释放,长按一秒后警示就会亮起,三秒后铃声响起,灯熄灭,同时时间恢复默认值。
1) void write_LCD(int i,int pos)。这个函数中i取0-12,pos取0-7,pos表示要显示位置,从左至右依次为0-7表示8个位置;i取0-9时,显示数字0-9,i=10时显示“-”,i=11时显示空白,i=12时在原有显示基础上加一个小数点。其实i本不该取大于9的数,这里是为了避免再写一个函数,增加复杂性。
2) void write_LCDother(int type)。这个函数是辅助上一个函数工作的,用于实现显示整点报时和闹铃功能是否打开,由于显示器有限,我是随意选取了两个像素点分别表示两个功能的。Type取值为0-3,为偶数时显示像素点,为奇数时清除像素点。
3) void show()这个函数有很多功能,主要用了show_mode这个变量。先介绍show_mode为不同值得含义。
show_mode | 含义 | Show_mode | 含义 |
---|---|---|---|
0 | 显示时分秒 | 16 | 16-21是一样的功能,6个状态正好是3秒,判断与执行初始化 |
1 | 显示年月日,show_mode并不会等于此值,只用goto语句跳置此 | 17 | 同上 |
2 | 显示时分,用于校秒 | 18 | 同上 |
3 | 同0,2-3相互切换 | 19 | 同上 |
4 | 显示时秒,校分 | 20 | 同上 |
5 | 同0,4-5相互切换 | 21 | 同上 |
6 | 显示分秒,校时 | 22 | 同24,实现闹钟显示一秒 |
7 | 同0,6-7相互切换 | 23 | 同上 |
8 | 显示年月,校日 | 24 | 显示闹钟时分,同1 |
9 | 同0,8-9相互切换 | 25 | 显示闹铃时,校分 |
10 | 显示年日,校月 | 26 | 25-26转换 |
11 | 同0,10-11相互切换 | 27 | 显示闹铃分,校时 |
12 | 显示月日,校年 | 28 | 27-28转换 |
13 | 同0,12-13相互切换 | ||
14 | 显示日期 | ||
15 | 同14,实现日期显示一秒 |
a) 时间的显示。这里需要取出小时、分钟、秒的十位和个位,通过对10除和取余得到,显示他们时,用“-”隔开。
write_LCD(time.hour/10,0);
write_LCD(time.hour%10,1);
write_LCD(10,2);
write_LCD(time.min/10,3);
write_LCD(time.min%10,4);
write_LCD(10,5);
write_LCD(time.sec/10,6);
write_LCD(time.sec%10,7);
b) 日期显示。与时间显示类似,显示年份时需要显示4位,通过time.year/1000,(time.year/100)%10,(time.year/10)%10,time.year%10
取出各位,中间用小数点隔开。
write_LCD(time.year/1000,0);
write_LCD((time.year/100)%10,1);
write_LCD((time.year/10)%10,2);
write_LCD(time.year%10,3);
write_LCD(12,3);
write_LCD(time.mon/10,4);
write_LCD(time.mon%10,5);
write_LCD(12,5);
write_LCD(time.day/10,6);
write_LCD(time.day%10,7);
c) 秒的闪烁。在调整时间、日期或者闹铃,都要闪烁不同的项,原理都是一样的,这里只介绍秒的闪烁。当用户按下第一个按键1次后,show_mode由0变为2,此后每隔半秒2和3就会显示转换,2状态的时间显示中缺秒,3状态是通过goto到case_0显示全部,这种写法完全是为了节省代码空间,因此就实现了每1秒闪一次。
d) 长按3秒初始化。其状态转换图就是一条链,16-21的顺次转换通过时间中断实现,16-20转为0发生在按键释放,21转为0也为时间中断。show_mode转为18时灯开始亮,21之后扬声器工作,sfun置为1便于后续对铃声关闭,开始的sfun=0为关闭此次闹铃。
sfun=0;//我加一个功能,按这个键可以关闭这次的闹铃
if(show_mode>17)
P3.4=1;//灯亮了,我确定他是长按
if(!P4.3)
show_mode++;
else
show_mode=0;
if(show_mode>21){
init_time();
show_mode=0;
P3.4=1;CKS=0xa0;
sfun=1;//这个标志很重要,当秒数大于1会去除这个标记
}
goto case_0;
e) 闹铃显示。按Key2会转换到22显示闹铃,22转到23后会继续判断按键是否释放,决定是否转到0。
case 22:
case 23:
if(P4.1){
show_mode=0;
}
goto case_24;
4.5 时间中断模块
void half_sec_inter()此函数为中断入口函数,每半秒调用一次,一秒调用一次add_time(),半秒调用一次show(),之后会检查整点报时功能是否打开以及是否快到整点,然后再检查闹铃功能是否打开以及是否到达指定的时间。同时这里还会检查sfun是否为1,当秒数大于1时,就会清零,这样下一次时间中断就会关闭闹铃。
static uchar hal_sec=0;
EI();
hal_sec++;
if(hal_sec>1)
hal_sec=0;
if(hal_sec)
add_time();
show();//对每次都调用它,这样才能实现闪烁
if(alarm.isbao&&(!hal_sec&&time.min==59&&
time.sec==59)){ P3.4=1;CKS=0xa0;}
else if(sfun==1){if(time.sec) sfun=0;}//我不能让初始化铃声一直响下去
else{
if(alarm.ison&&alarm.hour==time.hour&&alarm.min==time.min&&
(sfun==2||time.sec<=2)) {sfun=2;noise();}
/* 显然即使是1分钟,我也让用户可以主动关闭闹铃
但愿他不会2秒内关了,time.sec<=2,这是因为我当闹铃和
整点报时同时出现时,优先考虑了整点报时,但闹铃绝不能忘*/
else endnoise();
}
4.6 按键中断模块
该部分主要是控制实现按键功能,为了实现只需要四个按键实现所有功能,有些按键在不同状态下功能也不一样。为了实现打开关闭闹铃和整点报时功能,这里我用了组合按键实现其功能,主要思路是在原有按键没有被释放的情况下,判断是否有新的按键按下,这可能需要很长时间,所以此函数是允许时间中断嵌套进入的,但不允许自身嵌套。
打开或关闭闹铃功能见以下代码:
while(!P4.1){
if(!P4.0){
if(alarm.ison) {alarm.ison=0;write_LCDother(1);}
else{alarm.ison=1;write_LCDother(0);}
break;
}
}
在设置时间中为了加快速度,允许用户长按按键持续调用时间设置函数:
/*我希望长按这个键会一直调用set_time(),
这样就不用连续多次按键了
*/
if(!P4.2){
set_time();
//这个延时会稍长,之后就不会了
for(i=0;i<200;i++)for(j=0;j<200;j++);
}
while(!P4.2){
set_time();
for(i=0;i<200;i++)for(j=0;j<50;j++);
}
4.7 设置时间模块
该部分主要实现了对时间以及闹钟的设定,考虑到秒数的设定采用加1不方便,于是只是清零,同时有进位;而其它项的设定都是无进位的加1操作;在年与月的设定中要检查日期是否合法。在闹铃设置中,只要用户发生设置,就把闹铃功能打开。这里的代码比较好实现,不过需要考虑的是如果在用户设定时间时恰好闪烁的没有了,所以这里无论状态是多少,我都立即显示了相应的选项。例如下面的代码展示了设置秒的过程:
if(time.sec>30) time.min++;
time.sec=0;
check_time();
/*这里写的不多余,总不能当用户按键校时时看不到那时间变化吧,
不能让用户等半秒*/
write_LCD(time.sec/10,6);
write_LCD(time.sec%10,7);
5. 操作说明
Key1:选择设定时间,设定的内容包括秒、分、时、日、月、年、闹铃时和闹铃分。每按一次,选定设定的内容改变一次,一个周期后又回到默认界面。
Key2:在默认界面下,按这个键显示当前设定闹铃的时间,一秒后自动恢复到主界面;长按下不放时,将一直显示闹铃的时间,并同时等待Key1按下,如果被按下就切换闹铃的打开关闭功能。
Key3:在默认界面下,与Key2功能相似,长按显示的是日期,同时Key1按下时,切换整点报时功能的开关状态。在Key1被按下进入到时间设置页面时,这个按键对选定项设定,长按时会起到快速设置时间的目的。
Key4:当闹铃开始响时,按下这个按键可以立马关闭闹铃而不影响第二天同一时间的闹铃。无论何时,按下这个键都会无条件回到主界面,相当于手机home键。长按这个按键3秒后会自动初始时间为默认状态。
6. 总结
通过这个实验我主要学到两点,一是如何使用状态转换图来实现软件每个状态的转换,二是在单片机中按键往往很少,如何用最少的按键实现最多的功能,同时让一个按键可以有不同的功能。如果不用状态转换图实现同等功能的软件,相对比较复杂,而且由于定义的变量多程序容易出错,出现错误后不容易检查。在心中有了蓝图之后,写这个程序就比较容易了,只是必须一口气写完,否则之后写就需要再次整理思路。在按键功能上,我主要是模仿当前电子表的三个按键功能,与市场上电子表不同的是,没有星期的显示,不能记时。
这里我两个中断函数都是允许其它中断函数嵌套的,这主要是为了计时的准确性以及响应的及时性。在我设计两个按键同时按下这个功能是,尝试了多种办法,其中有一个办法是在按键中断进入后延时一小会,保证两个按键都按下,这时在使用switch语句判断。这种办法主要问题是必须保证两次按键间隔小于那个延时,又必须保证按键能够马上响应,导致这种组合按键不一定能马上生效。还有一种办法是,判断新的按键的语句写在show()里,这种办法也可以,不过show()半秒调用一次,就的要求用户半秒内不能放开按键了。写在按键中断函数中,由于允许中断嵌套,所以即使很长时间按键中断函数没有返回,也不会影响界面的刷新,闹铃的响动等功能。
附件:程序源代码
#pragma sfr
#pragma access
#pragma EI
#pragma DI
#pragma interrupt INTKR key_inter RB1
#pragma interrupt INTRTC half_sec_inter
typedef struct{
int sec,min,hour,day,mon,year;
}Time;
typedef unsigned char uchar;
typedef unsigned int uint;
struct {
int min,hour,ison,isbao;
}alarm={0,6,0};
Time time={0,0,0,1,1,2015};
uint num_code[11]={0x0070d,0x0600,0x030e,0x070a,
0x0603,0x050b,0x050f,0x0700,0x070f,0x070b,0x02};
uint show_mode=0;//控制显示器显示,包括闪烁等
uint sfun=0;//特殊功能变量,在reset和关闭当次闹铃中用到
void init_time();/*各种初始化函数,这是恢复刚开机状态的时间*/
void inittime();/*这才是初始化时间中断*/
void initlcd();
void InitKey();
void InitSound();
/*在第pos个位置使屏幕显示十进制数i,pos:0-7
当i==10时显示"-" 当i==11时清除当前位置数字
当i==12时在原来显示基础上加小数点
*/
void write_LCD(int i,int pos);
void write_LCDother(int type);//显示整点报时、闹铃功能是否打开
void show();
int mon_day();//返回当月天数
void check_time();//检查时间合法性,同时调整成合法时间
void add_time();//加一秒
void set_time();//设置时间
void noise();//在5个状态中转换形成"- - - -"的声音
void endnoise();
void key_inter();//按键中断,这个中断允许嵌套,但不允许自身嵌套
void half_sec_inter();//半秒定时中断,允许嵌套
int mon_day(){
int day;
switch(time.mon){
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
day=31;
break;
case 2:
if((time.year%4==0&&time.year%100!=0)||(time.year%400==0))
day=29;
else
day=28;
break;
default:
day=30;
break;
}
return day;
}
void check_time(){
if(time.sec>=60){
time.sec=0;
time.min++;
}
if(time.min>=60){
time.min=0;
time.hour++;
}
if(time.hour>=24){
time.hour=0;
time.day++;
}
if(time.day>mon_day()){
time.day=1;
time.mon++;
}
if(time.mon>12){
time.mon=1;
time.year++;
}
if(time.year>2050)//间隔不能再大了,不然不方便校时
time.year=1990;
}
void add_time(){
time.sec++;
check_time();
}
void show(){
int tmp;
switch(show_mode){
case 0://正常的时间
case_0:
write_LCD(time.hour/10,0);
write_LCD(time.hour%10,1);
write_LCD(10,2);
write_LCD(time.min/10,3);
write_LCD(time.min%10,4);
write_LCD(10,5);
write_LCD(time.sec/10,6);
write_LCD(time.sec%10,7);
break;
case 1://正常的日历
case_1:
write_LCD(time.year/1000,0);
write_LCD((time.year/100)%10,1);
write_LCD((time.year/10)%10,2);
write_LCD(time.year%10,3);
write_LCD(12,3);
write_LCD(time.mon/10,4);
write_LCD(time.mon%10,5);
write_LCD(12,5);
write_LCD(time.day/10,6);
write_LCD(time.day%10,7);
break;
case 2://缺秒的时间
write_LCD(time.hour/10,0);
write_LCD(time.hour%10,1);
write_LCD(10,2);
write_LCD(time.min/10,3);
write_LCD(time.min%10,4);
write_LCD(10,5);
write_LCD(11,6);
write_LCD(11,7);
show_mode=3;
break;
//这就是闪烁的原因
case 3:
show_mode=2;
goto case_0;
case 4://缺分的时间
write_LCD(time.hour/10,0);
write_LCD(time.hour%10,1);
write_LCD(10,2);
write_LCD(11,3);
write_LCD(11,4);
write_LCD(10,5);
write_LCD(time.sec/10,6);
write_LCD(time.sec%10,7);
show_mode=5;
break;
case 5:
show_mode=4;
goto case_0;
case 6://缺时的时间
write_LCD(11,0);
write_LCD(11,1);
write_LCD(10,2);
write_LCD(time.min/10,3);
write_LCD(time.min%10,4);
write_LCD(10,5);
write_LCD(time.sec/10,6);
write_LCD(time.sec%10,7);
show_mode=7;
break;
case 7:
show_mode=6;
goto case_0;
case 8://缺日的日历
write_LCD(time.year/1000,0);
write_LCD((time.year/100)%10,1);
write_LCD((time.year/10)%10,2);
write_LCD(time.year%10,3);
write_LCD(12,3);
write_LCD(time.mon/10,4);
write_LCD(time.mon%10,5);
write_LCD(12,5);
write_LCD(11,6);
write_LCD(11,7);
show_mode=9;
break;
case 9:
show_mode=8;
goto case_1;
case 10://缺月的日历
write_LCD(time.year/1000,0);
write_LCD((time.year/100)%10,1);
write_LCD((time.year/10)%10,2);
write_LCD(time.year%10,3);
write_LCD(12,3);
write_LCD(11,4);
write_LCD(11,5);
write_LCD(12,5);
write_LCD(time.day/10,6);
write_LCD(time.day%10,7);
show_mode=11;
break;
case 11:
show_mode=10;
goto case_1;
case 12://缺年的日历
write_LCD(11,0);
write_LCD(11,1);
write_LCD(11,2);
write_LCD(11,3);
write_LCD(12,3);
write_LCD(time.mon/10,4);
write_LCD(time.mon%10,5);
write_LCD(12,5);
write_LCD(time.day/10,6);
write_LCD(time.day%10,7);
show_mode=13;
break;
case 13:
show_mode=12;
goto case_1;
/*最少延时一秒显示日历,按键不放就一直显示*/
case 14:
case 15:
if(P4.2){
show_mode=0;
}
goto case_1;
/*这就是长按3秒初始化时间了*/
case 16:
case 17:
case 18:
case 19:
case 20:
case 21:
sfun=0;//我加一个功能,按这个键可以关闭这次的闹铃
if(show_mode>17)
P3.4=1;//灯亮了,我确定他是长按
if(!P4.3)
show_mode++;
else
show_mode=0;
if(show_mode>21){
init_time();
show_mode=0;
P3.4=1;CKS=0xa0;
sfun=1;//这个标志很重要,当秒数大于1会去除这个标记
}
goto case_0;
case 22:
case 23:
if(P4.1){
show_mode=0;
}
goto case_24;
case 24:
case_24:
write_LCD(alarm.hour/10,0);
write_LCD(alarm.hour%10,1);
write_LCD(10,2);
write_LCD(alarm.min/10,3);
write_LCD(alarm.min%10,4);
write_LCD(11,5);
write_LCD(11,6);
write_LCD(11,7);
break;
case 25:
write_LCD(alarm.hour/10,0);
write_LCD(alarm.hour%10,1);
write_LCD(10,2);
write_LCD(11,3);
write_LCD(11,4);
write_LCD(11,5);
write_LCD(11,6);
write_LCD(11,7);
show_mode=26;
break;
case 26:
show_mode=25;
goto case_24;
case 27:
write_LCD(11,0);
write_LCD(11,1);
write_LCD(10,2);
write_LCD(alarm.min/10,3);
write_LCD(alarm.min%10,4);
write_LCD(11,5);
write_LCD(11,6);
write_LCD(11,7);
show_mode=28;
break;
case 28:
show_mode=27;
goto case_24;
default:
break;
}
}
/*i at 0-9 is ok,10:-,11:空白,12:加点*/
void write_LCD(int i,int pos){
int LCD_addr,tmp;
LCD_addr=0xfa40+pos*2;
if(i<11)
pokew(LCD_addr,num_code[i]);
else if(i==11)
pokew(LCD_addr,0);
else if(i==12){
tmp=peekw(LCD_addr);
tmp|=0x0800;
pokew(LCD_addr,tmp);
}
}
void write_LCDother(int type){
//我随意选了两个像素点做闹铃和整点报时的指示
int LCD_addr,tmp;
LCD_addr=0xfa40+8*2;
tmp=peekw(LCD_addr);
switch(type){
case 0:
tmp|=0x01;
break;
case 1:
tmp=tmp&(~0x01);
break;
case 2:
tmp|=0x02;
break;
case 3:
tmp=tmp&(~0x02);
break;
default:
break;
}
pokew(LCD_addr,tmp);
}
__interrupt void key_inter(){
static int iscall=0;
int i,j;
/*不希望按键中断嵌套,因为我用了软件延时
就有可能发生嵌套*/
if(iscall) return;
iscall=1;
EI();
switch(P4&0x3f){
case 0x3e:
//这里我能用数组实现
if(show_mode==0)
show_mode=2;
else if(show_mode==2||show_mode==3)
show_mode=4;
else if(show_mode==4||show_mode==5)
show_mode=6;
else if(show_mode==6||show_mode==7)
show_mode=8;
else if(show_mode==8||show_mode==9)
show_mode=10;
else if(show_mode==10||show_mode==11)
show_mode=12;
else if(show_mode==12||show_mode==13)
show_mode=25;
else if(show_mode==25||show_mode==26)
show_mode=27;
else if(show_mode==27||show_mode==28)
show_mode=0;
break;
case 0x3d:
show_mode=22;
/*当第二个键按下不放,第一个按键按下就改变闹铃状态
当然注意break*/
while(!P4.1){
if(!P4.0){
if(alarm.ison) {alarm.ison=0;write_LCDother(1);}
else{alarm.ison=1;write_LCDother(0);}
break;
}
}
break;
case 0x3b:
if(show_mode==0){
show_mode=14;
while(!P4.2){
if(!P4.0){
if(alarm.isbao){alarm.isbao=0;write_LCDother(3);}
else{alarm.isbao=1;write_LCDother(2);}
break;
}
}
}else{
/*我希望长按这个键会一直调用set_time(),
这样就不用连续多次按键了
*/
if(!P4.2){
set_time();
//这个延时会稍长,之后就不会了
for(i=0;i<200;i++)for(j=0;j<200;j++);
}
while(!P4.2){
set_time();
for(i=0;i<200;i++)for(j=0;j<50;j++);
}
}
break;
case 0x37://长按这个键3秒,初始化时间,当然这里看不出来
if(show_mode>=16&&show_mode<=21) ;
else show_mode=16;
break;
default:
break;
}
iscall=0;//记得把这个变量改回
}
void set_time(){
if(show_mode==2||show_mode==3){
if(time.sec>30) time.min++;
time.sec=0;
check_time();
/*这里写的不多余,总不能当用户按键校时时看不到那时间变化吧,
不能让用户等半秒*/
write_LCD(time.sec/10,6);
write_LCD(time.sec%10,7);
}else if(show_mode==4||show_mode==5){
time.min++;
if(time.min>=60) time.min=0;//我们不希望产生进位
write_LCD(time.min/10,3);
write_LCD(time.min%10,4);
}else if(show_mode==6||show_mode==7){
time.hour++;
if(time.hour>=24) time.hour=0;
write_LCD(time.hour/10,0);
write_LCD(time.hour%10,1);
}else if(show_mode==8||show_mode==9){
time.day++;
if(time.day>mon_day()) time.day=1;
write_LCD(time.day/10,6);
write_LCD(time.day%10,7);
}else if(show_mode==10||show_mode==11){
time.mon++;
if(time.mon>12) time.mon=1;
if(time.day>mon_day()) time.day=mon_day();
/*这里要注意1月31日改成2月31日,绝不能出现非法日期*/
write_LCD(time.mon/10,4);
write_LCD(time.mon%10,5);
}else if(show_mode==12||show_mode==13){
time.year++;
check_time();//我不想再判断年了,让它去检查吧
write_LCD(time.year/1000,0);
write_LCD((time.year/100)%10,1);
write_LCD((time.year/10)%10,2);
write_LCD(time.year%10,3);
}else if(show_mode==25||show_mode==26){
alarm.ison=1;
write_LCDother(0);
alarm.min++;
if(alarm.min>60) alarm.min=0;
write_LCD(alarm.min/10,3);
write_LCD(alarm.min%10,4);
}else if(show_mode==27||show_mode==28){
alarm.ison=1;
write_LCDother(0);
alarm.hour++;
if(alarm.hour>23) alarm.hour=0;
write_LCD(alarm.hour/10,0);
write_LCD(alarm.hour%10,1);
}
}
void noise(){
static uchar state=0;//这就有5个状态的图
if(show_mode>=17&&show_mode<=21) return;
state++;
if(state>=6) state=0;
if(state==0||state==2){ P3.4=1;CKS=0xa0;}
else{ P3.4=0;BZOE=0;}
}
void endnoise(){
if(show_mode>=17&&show_mode<=21) return;//初始化时不能把指示灯关了
P3.4=0;BZOE=0;
}
__interrupt void half_sec_inter(){
static uchar hal_sec=0;
EI();
hal_sec++;
if(hal_sec>1)
hal_sec=0;
if(hal_sec)
add_time();
show();//对每次都调用它,这样才能实现闪烁
if(alarm.isbao&&(!hal_sec&&time.min==59&&
time.sec==59)){ P3.4=1;CKS=0xa0;}
else if(sfun==1){if(time.sec) sfun=0;}//我不能让初始化铃声一直响下去
else{
if(alarm.ison&&alarm.hour==time.hour&&alarm.min==time.min&&
(sfun==2||time.sec<=2)) {sfun=2;noise();}
/* 显然即使是1分钟,我也让用户可以主动关闭闹铃
但愿他不会2秒内关了,time.sec<=2,这是因为我当闹铃和
整点报时同时出现时,优先考虑了整点报时,但闹铃绝不能忘*/
else endnoise();
}
}
void init_time(){
time.year=2015;
time.mon=1;
time.day=1;
time.hour=8;
time.min=0;
time.sec=0;
alarm.hour=6;
alarm.min=0;
alarm.ison=0;
alarm.isbao=0;
write_LCDother(1);
write_LCDother(3);
}
void inittime(){
RTCMK=0;
OSCSELS=1;
RTCCL=0;
RTCC0=0X09;
RTCE=1;
}
void initlcd(){
PFALL=0x0f;
LCDC0=0x34;
LCDMD=0x10;
LCDM=0xc0;
}
void InitKey(){
PM4=0x3f;
PU4=0x3f;
KRM=0x3f;
KRMK=0;
}
void InitSound(){
PM3.3=0;
P3.3=0;
BZOE=0;
}
void main(){
PM3.4=0;
P3.4=0;
inittime();
init_time();
initlcd();
InitKey();
InitSound();
show();//这个很有必要,及时显示,我半秒也不想等
EI();
while(1);//我只它希望在十分必要的时候才刷新屏幕
}