写在前面:
上篇文末提到可以优化的地方包括加入闹钟,了解无源蜂鸣器的工作原理后,就着手扩展闹钟设置功能了,同时按键按下加入蜂鸣器的提示音。由于上篇的篇幅过长了,所以决定单独写一篇这个闹钟模块。
一、实现思路
- 保持原有功能情况下加入第三个模式
- 当前模式下执行功能,设置时分秒,同样是选中位闪烁,增大减小,越界判断等可以直接照搬前面的
- 设置的时分秒存在一个数组,当当前时间的时分秒都与之对应相等调用蜂鸣器
- 按下任意独立按键可以终止闹钟
二、蜂鸣器
1、工作原理
普中的板子上的是无源蜂鸣器,BZ连接在了步进电机OUT5,ULN2003D逻辑框图如下
对于蜂鸣器驱动需要输入振荡脉冲,ULN2003D作用为继电器驱动,在这里我们只需要考虑输入取反输出即可,因此蜂鸣器连接对应51寄存器地址为P2_5(图和板子不一样)
sbit Buzzer = P2^5; //类型声明蜂鸣器地址
2、蜂鸣器响任意时间函数
循环体内使Buzzer每隔250us取反,一个周期为0.5ms,即2000HZ,循环执行次数决定持续时间,期间接收按键键码,只要键码不为0,即有任意独立按键按下,跳出循环,声音停止
#include <REGX52.H>
#include"Delay.h"
#include"Key.h"
sbit Buzzer = P2^5; //类型声明蜂鸣器地址
unsigned int i; //计数
/**
* @brief 蜂鸣器以2000HZ的时间响x250us
* @param xms 蜂鸣器响的时间
* @retval 无
*/
void Buzzer_Time(unsigned int x250us)
{
unsigned KeyNumber;
for(i=0;i<x250us;i++)
{
KeyNumber = Key();
Buzzer = !Buzzer; //振荡输入
Delay10us(25); //高低电平维持时间为250us,一个周期就是0.5ms
if(KeyNumber)break;
}
}
封装好之后可以用于按键提示音和闹钟
三、代码设计和修改
1、预备工作
定义Clock_Sel为闹钟设置模式下的时间位索引,定义数组存放设置闹钟的时分秒
//存放设置的脑钟时间
char Clock[] = {0,0,0};
//定义键码、设置时间位索引、设置时间闪烁标志、模式(显示时间/设置时间),闹钟设置时间位索引
unsigned char KeyNum, TimeSet_Sel, TimeSetFlag, Mode, Clock_Sel;
2、增加模式
按下1键让Mode在0、1、2之间循环,为了保持原有设置时间功能(从设置时间模式切换到模式0时写入修改后的时间),将模式2作为设置时间模式,模式1为新增的设置闹钟时间。每次切换到闹钟模式时,闹钟时间位索引清0,固定从时开始设置,并且将LCD清空第一行显示“Clock:”
if(KeyNum == 1)
{
Buzzer_Time(100); //蜂鸣器按键提示音
if(Mode == 0)
{
Mode = 1; //切换至设置闹钟模式
LCD_Init();
Delay(100);
Clock_Sel = 0; //每次切换到设置闹钟索引清0
}
else if(Mode == 1 )
{
Mode = 2; //切换至设置时间模式
TimeSet_Sel = 0; //每次切换到设置时间模式索引清0,固定从“年”开始闪烁
}
else if(Mode == 2)
{
Mode = 0; //切换回显示时间模式
DS1302_SetTime(); //将已修改的时间数据写入芯片
}
}
switch(Mode) //显示当前工作模式
{
case 0: LCD_ShowString(2,12,"Show ");break;
case 1: LCD_ShowString(2,12,"Clock ");break;
case 2: LCD_ShowString(2,12,"Set ");break;
}
当Mode == 1时,执行设置闹钟模式代码
3、设置闹钟函数SetClock()
思路可以说是完全照搬了上一篇的设置时间函数,都是按2键索引++,3键当前时间位++,4键当前时间位--,越界判断和校正从之前的函数里截取了时分秒的部分,闪烁设置也是一样的,按键提示音在整个程序所有地方都加上了,没什么好说的直接贴代码
void SetClock(void)
{
LCD_ShowString(1,1,"Clock:");
LCD_ShowNum(1,13,Clock_Sel,2);
if(KeyNum == 2) //2号按键选择设置的时间位
{
Buzzer_Time(100); //蜂鸣器按键提示音
Clock_Sel++;
Clock_Sel %= 3; //索引在0~1
}
if(KeyNum == 3) //3号按键使当前时间位+1
{
Buzzer_Time(100); //蜂鸣器按键提示音
Clock[Clock_Sel]++;
if(Clock[0]>23)Clock[0] = 0; //小时越界判断
if(Clock[1]>59)Clock[1] = 0; //分越界判断
if(Clock[2]>59)Clock[2] = 0; //秒越界判断
}
if(KeyNum == 4) //4号键使当前时间位-1
{
Buzzer_Time(100); //蜂鸣器按键提示音
Clock[Clock_Sel]--;
if(Clock[0]<0)Clock[0] = 23; //小时越界判断
if(Clock[1]<0)Clock[1] = 59; //分越界判断
if(Clock[2]<0)Clock[2] = 59; //秒越界判断
}
LCD_ShowChar(2,3,':');
LCD_ShowChar(2,6,':');
if(Clock_Sel == 0 && TimeSetFlag ==1)
LCD_ShowString(2,1," ");
else
LCD_ShowNum(2,1,Clock[0],2); //显示时
if(Clock_Sel == 1 && TimeSetFlag ==1)
LCD_ShowString(2,4," ");
else
LCD_ShowNum(2,4,Clock[1],2); //显示分
if(Clock_Sel == 2 && TimeSetFlag ==1)
LCD_ShowString(2,7," ");
else
LCD_ShowNum(2,7,Clock[2],2); //显示秒
}
4、闹钟响的条件
为了防止闹钟响个不停,所以设置闹钟的时间位要设计到秒才行,或者默认为0,并且只在模式0,也就是正常显示实时时间的模式下才会响,就不会出现,在设置闹钟的时候数字1位1位的加,“路过”闹钟时间结果响个几秒的尴尬
if(Mode == 0) //模式0,显示时间
{
DS1302_ReadTime();
TranserShowStr_Day(DS1302_Time[6]); //将数字转化为对应字符串显示
ShowTime(); //显示年月日时分秒
if(DS1302_Time[3] == Clock[0]&&DS1302_Time[4] == Clock[1]&&DS1302_Time[5] == Clock[2]) //到时间蜂鸣器响5s
Buzzer_Time(24000);
}
5、遇到的问题
其实遇到的问题挺多的,但很多已经没什么印象了说明不怎么重要
1、关于进入闹钟模式清空屏幕,因为残留的显示数据不会消失,所以必须手动先清空,一开始我把LCD初始化函数写在了Clock_Set函数里,结果闪个不停,并且,时间位闪烁完全看不出来,后来意识到在while循环里只要Mode还等于1,就会不停的清空屏幕,因此闪个不停。但是其实一开始我并没有把模式切换写的那么详细,更喜欢之前的风格,Mode++,然后对3取余反复循环,这样写还要在写一步由模式0切换到1的判断,很麻烦,于是换了
2、由于很多代码都是用的前面的,,中间出了不少差错,还是不够细心..
四、效果演示
基于51单片机的可调时钟的闹钟设置和效果演示