路漫漫其修远兮,吾将上下而求索,,,啊!
博客停写了有两三天了,这几天一直在弄第八届省赛的电子钟的题目,emmm,有点儿心累叭,不过最后还是弄完了,嘻嘻,开熏,学到不少东西,今天来个总结叭~~~
代码参考:<我的GitHub>第八届省赛之电子钟
先上题目:
一. 初始化
1)关闭蜂鸣器、继电器等无关外设;
2)设备初始化时钟为 23 时 59 分 50 秒,闹钟提醒时间 0 时 0 分 0 秒。
二. 显示功能
三. 按键功能
1)按键 S7 定义为“时钟设置”按键,通过该按键可切换选择待调整的时、分、秒,当前选择的显示单元以 1 秒为间隔亮灭,时、分、秒的调整需注意数据边界属性。
2)按键 S6 定义为“闹钟设置”按键,通过该按键可进入闹钟时间设置功能,数码管显示当前设定的闹钟时间。
3)按键 S5 定义为“加”按键,在“时钟设置”或“闹钟设置”状态下,每次按下该按键当前选择的单元(时、分或秒)增加 1 个单位。
4)按键 S4 定义为“减”按键,在“时钟设置”或“闹钟设置”状态下,每次按下该按键当前选择的单元(时、分或秒)减少 1 个单位。
5)按键功能说明:
按键 S4、S5 的“加”、“减”功能只在“时钟设置”或“闹钟设置”状态下有效;在 “时钟显示”状态下,按下 S4 按键,显示温度数据,松开按键,返回“时钟显示”界面。
四. 闹钟提示功能
1)指示灯 L1 以 0.2 秒为间隔闪烁,持续 5 秒钟;
2)闹钟提示状态下,按下任意按键,关闭闪烁提示功能。
几点体会:
void AddTime(struct sTime *time)
{
if(index == 7)//小时
{
(time->hour) += 0x01;
if(((time->hour) & 0x0F) == 0x0A)//如果低四位是A的情况
{
(time->hour) = ((time->hour) & 0xF0) + 0x10; //显示正常值
}
if((time->hour) > 0x23)//小时部分不能超过24,超过24就置0
{
(time->hour) = 0x00;
}
}
else if(index == 4)//分钟
{
time->min += 0x01;
if(((time->min) & 0x0F) == 0x0A)
{
time->min = ((time->min) & 0xF0) + 0x10;
}
if((time->min) > 0x59)//分钟和秒不能超过59
{
time->min = 0x00;
}
}
else//秒
{
(time->sec) += 0x01;
if(((time->sec) & 0x0F) == 0x0A)
{
time->sec = ((time->sec) & 0xF0) + 0x10;
}
if((time->sec) > 0x59)
{
time->sec = 0x00;
}
}
}
if(((time->hour) & 0x0F) == 0x0A)//如果低四位是A的情况
至于或(|)符号,一般在赋值的时候使用,例如:
P2 = (P2 & 0x1F) | 0xA0;
if(tmr200ms >= 100)
{
tmr200ms = 0;
flag200ms = 1;
sta ^= 1;
}
题目分析:
用到哪些底层?
1. 时钟 ——> DS1302
2. 温度 ——>DS18B20
3. 按键 ——>KEY(独立按键)
4. 数码管显示 ——>LED(数码管)
有几个模式?
时钟正常运行模式,时钟设置模式,闹钟设置模式
特殊功能?
闹钟提示(L1闪烁,五秒关闭,且闹钟提示情况下按下任意按键可关闭L1),设置时间时,数码管要以1s为间隔闪烁(注意:在设置模式下,数码管是不实时刷新时间的),按键功能
针对题目分析,我设置了以下几个标志量:
u8 flag18b20 = 0;//0-时钟显示,1-温度显示
u8 flagstop = 1;//0-led开始闪烁,1-led不闪烁
u8 sta = 0;//1-L1亮,0-L1灭
u8 flagmode = 0;//0-运行模式,1-时钟设置模式,2-闹钟设置模式
u8 flagenter = 0;//0-得到实时时间,1-不刷新时间
u8 numBlinkSta = 0;//0-数码管亮,1-数码管灭
下面来说明每个功能的具体实现:
一. 时钟显示:之前写DS1302时就已经写过,照搬过来即可,这里只注意一点!!!!由于我们是支持改变时钟初始时间功能的,那么我们在刷新时间时,从DS1302内部得到的实时时间,就必须是在我们设置后的时钟时间的基础上再自动加一的,也就是说我们改变时钟时间后,要将改变后的值写入DS1302,刷新显示时,得到的实时时间也必须是改变后的时钟时间。
void RefreshTime()
{
GetRealTime(&setClockTime);//改变后写入DS1302的时钟时间
ShowLedNumber(7, setClockTime.hour>>4);
ShowLedNumber(6, setClockTime.hour&0x0F);
ShowLedNumber(5, 0xBF);
ShowLedNumber(4, setClockTime.min>>4);
ShowLedNumber(3, setClockTime.min&0x0F);
ShowLedNumber(2, 0xBF);
ShowLedNumber(1, setClockTime.sec>>4);
ShowLedNumber(0, setClockTime.sec&0x0F);
}
那么我们是什么时候把时钟时间写入 DS1302呢?肯定是要在改变之后再写入鸭,那什么时候改变呢?题目中有加减时间的功能要求,按下S5加一,按下S4减一,那么我们就在按下按键之后写入,于是就有了下面这段代码:
else if(keycode == '3')//按下S5
{
if(flagmode == 0)//如果是正常运行模式
{
if(flagstop == 0)//L1处于闪烁状态
{
flagstop = 1;//那么就让L1停止闪烁
}
}
if(flagmode == 1)//如果是时钟设置模式
{
AddTime(&setClockTime);
SetRealTime(&setClockTime);//将改变后的时钟时间写入DS1302
RefreshSetTime(&setClockTime);//为了在我们设置时间时可以清楚地看到设置后的数字
}
if(flagmode == 2)//如果是闹钟设置模式
{
AddTime(&setAlarmTime);
RefreshSetTime(&setAlarmTime);
}
}
二. 温度显示:这个也很简单,主要就是用到DS18B20的底层,我会再写一篇关于DS18B20的博客,然后再细讲。
三. 数码管闪烁: 这个地方就用到了我们上面说的异或啦,主要思想就是,先定义一个标志位,也就是下面这个,因为是间隔一秒闪烁的,那么我们就隔一秒让我们的标志量与1异或(或者对标志量逻辑取反 (!)),至于这个1秒的间隔当然是要在我们的定时器中断里判断啦。
u8 numBlinkSta = 0;//0-数码管亮,1-数码管灭
主要代码(以时钟设置为例,闹钟设置类似):
void SetClockTimer()//时钟设置
{
if(flagenter == 0)//允许得到实时时间
{
GetRealTime(&setClockTime);//得到实时时间
flagenter = 1;//马上置1,表示不再获取实时时间了,确保设置模式下,数码管不再刷新时间
}
if(numBlinkSta == 1)
{
LedBuff[index] = 0xFF;
LedBuff[index - 1] = 0xFF;
}
else
{
RefreshSetTime(&setClockTime);//显示时间
}
}
判断部分:
static u16 tmr1s = 0;
if(tmr1s >= 500)//每2ms进入一次中断,故500次表示定时1s
{
tmr1s = 0;
numBlinkSta ^= 1;//这里也可以写成numBlinkSta = !numBlinkSta
}
四. LED闪烁,这里思想与数码管一样,但是要特别注意一点: 在主函数中要写一个当flagstop = 1时,直接关闭L1的函数,否则5s后L1会是常亮状态,只要闪烁时间对应奇数个200ms,那么在关闭闪烁函数的时候,L1就是亮的状态,读者可以试一下,代码如下:
void LedBlink()
{
if(sta)
{
P2 = (P2 & 0x1F) | 0x80;
P0 = 0xFE;//点亮L1
P2 &= 0x1F;
}
else
{
P2 = (P2 & 0x1F) | 0x80;
P0 = 0xFF;//关闭L1
P2 &= 0x1F;
}
}
/*主函数中需要增加的部分*/
if(flagstop == 0)
{
LedBlink();
}
else
{
P2 = (P2 & 0x1F) | 0x80;
P0 = 0xFF;
P2 &= 0x1F;
}
五. 时钟(闹钟)设置:程序上面已经放过了,这里主要说明几点:1. 关于index这个变量的作用,是用来判断我们正在设置的是小时还是分,秒,main.c文件中,我定义了index这个全局变量(注意是char类型的),初始化为7,当我们按下按键时,index会执行 -3操作 ,切换到分,再按就切换到秒,此时再判断一下index是否小于0(当然这时候肯定是小于0的),进入运行模式,代码如下:
if(keycode == '1')//按下S7按键
{
if(flagmode == 0)//如果当前为运行模式
{
if(flagstop == 0)
{
flagstop = 1;
}
else
{
flagmode = 1;//那么进入设置模式
flagenter = 0;
index = 7;
}
}
else if(flagmode == 1)//如果按下按键当前为设置时钟模式
{
index -= 3;
if(index < 0)
{
index = 7;
flagmode = 0;//那么进入运行模式
}
RefreshSetTime(&setClockTime);//消除按下按键,时,分,秒,任意两个一起闪烁的情况
}
}
闹钟设置与时钟类似,但是要注意的是,闹钟是不需要从DS1302内部获取时间的,也就是说不需要判断flaginter这个标志量,只需要在定义闹钟时间的结构体里写好初始时间就好了;
struct sTime setAlarmTime={0,0,0,0,0,0,0};//闹钟时间结构体,初始化为0
六. 加减时间功能,话不多说,先上代码,一看便知
void AddTime(struct sTime *time)//结构体指针
{
if(index == 7)//时
{
(time->hour) += 0x01;
if(((time->hour) & 0x0F) == 0x0A)//如果低四位是A的情况
{
(time->hour) = ((time->hour) & 0xF0) + 0x10; //显示正常值
}
if((time->hour) > 0x23)//小时不能超过23
{
(time->hour) = 0x00;
}
}
else if(index == 4)//分
{
time->min += 0x01;
if(((time->min) & 0x0F) == 0x0A)
{
time->min = ((time->min) & 0xF0) + 0x10;
}
if((time->min) > 0x59)//分秒不能超过59
{
time->min = 0x00;
}
}
else//秒
{
(time->sec) += 0x01;
if(((time->sec) & 0x0F) == 0x0A)
{
time->sec = ((time->sec) & 0xF0) + 0x10;
}
if((time->sec) > 0x59)
{
time->sec = 0x00;
}
}
}
低四位为A要特殊判断!!!这里涉及到的就是16进制与10进制加法的不同之处了,16进制是满16进1,10进制满10 进1 ,举个例子:当我们加到9(也就是0x09)时,再加0x01,我们想让数码管显示10,但是在16进制下,10是用A来表示的,所以我们得到的是(0x0A),也就是说这时候如果我们不做处理,数码管显示的就是a。
那么我们是如何处理的呢?特殊情况就特殊处理呗,读者如果把所有的特殊情况都列出来会发现,所有的特殊情况都是在出现9+1,也就是某一位满10的情况下产生的,满10没有进1,而是变成了A,那么我们就判断一下这种情况,然后人为进1就好了鸭,这样再去看我们的程序是不是就好理解了呢?减法也是同样的思路哦:
void SubTime(struct sTime *time)
{
if(index == 7)
{
(time->hour) -= 0x01;
if(((time->hour) & 0x0F) == 0x0F)//如果低四位是A的情况
{
(time->hour) = ((time->hour) & 0xF0) + 0x09; //显示正常值
}
if((time->hour) == 0xF9)
{
(time->hour) = 0x23;
}
}
else if(index == 4)
{
time->min -= 0x01;
if(((time->min) & 0x0F) == 0x0F)
{
time->min = ((time->min) & 0xF0) + 0x09;
}
if((time->min) == 0xF9)
{
time->min = 0x59;
}
}
else
{
(time->sec) -= 0x01;
if(((time->sec) & 0x0F) == 0x0F)
{
time->sec = ((time->sec) & 0xF0) + 0x09;
}
if((time->sec) == 0xF9)
{
time->sec = 0x59;
}
}
}
好了,到这里所有的功能我们就都实现了,把这些功能总和一下,合理地放在我们的程序里就ok啦~
重写代码后发现的问题!!!
为什么要是else if呢?这里涉及到if语句和else if语句在程序执行时的先后问题,如果是两个if语句的话,这两个if语句会一起执行,但是如果是一个if一个else if的话,一定是先执行if,再执行else if,你想啊,如果你按下S7,此时已经是设置模式了,那么index就直接减三了;
还有一个要注意的地方就是:在主函数中定义的变量最好都赋一个初值,不然会默认为是0。我在重写电子钟的代码之后出现了一个bug,就是单片机一上电L1就开始闪烁,5s之后关闭,之后都很正常,后来发现是我在主函数中定义setClockTime这个结构体变量之后,并没有给它赋初值,单片机默认是0,而恰好我的闹钟时间初始化也是0,于是直接就开始闪烁了,但是我之前第一遍写的程序为什么没有问题呢?其实也是有问题的,只是因为我之前是单独写了一个time.c的文件,然后在time.c这个文件里定义的setClockTime这个结构体变量,那么它里面的初始值就是一个垃圾值,相当于乱码了,但是还是不为0,所以L1并没有闪烁。