目录
一、设计任务与要求
设计万年历,实现以下功能:
- 采用实时时钟芯片;
- 在图形液晶上显示日历和时钟;
- 采用三键调整模式,调整日期和时间;
- 可设定定时闹钟。
二、方案设计与论证
以STC89C52单片机为核心器件,配合电阻、电容、晶振等器件,构成单片机的最小系统。
显示设备使用LCD1602液晶,可以同时显示年、月、日、星期、时、分、秒等基本时间信息;时钟模块采用DS1302芯片,初始化之后,就会开始运行计算时间,单片机只需进行时间信息的读取即可;4个按键作为操作输入设备,可以进行时间、闹钟的设置等;同时还有蜂鸣器模块,用来实现闹钟的闹铃;供电采用常用的USB 5V进行供电。
1.主控电路设计
本设计采用STC89C52芯片作为硬件核心,最小系统如图所示。
整个最小系统由三个部分组成,晶振电路部分、复位电路部分、电源电路等三个部分组成。
晶振电路包括2个30pF的电容,以及12M的晶振。电容的作用在这里是起振作用,帮助晶振更容易的起振。在进行电路设计的时候,晶振部分越靠近单片机越好。
复位电路由10uF的极性电容和10K的电阻构成。利用电容电压不能突变的性质,可以知道,当系统一上电,RST脚将会出现高电平,并且这个高电平持续的时间由电路的RC值来决定。在本电路中,电容的的大小是10uF,电阻的大小是10k,根据公式可以算出电容充电到电源电压的0.7倍,需要的时间是10K×10uF=0.1s。也就是说在电脑启动的0.1s内,电容两端的电压时在0-3.5V增加,这个时候RST引脚所接收到的电压是5V-1.5V。在5V正常工作的51单片机中小于1.5V的电压信号为低电平信号,而大于1.5V的电压信号为高电平信号。
引脚图如图所示。
2.显示电路设计
采用LCD液晶显示屏,液晶显示屏的显示功能强大,可显示大量文字,图形,显示多样,清晰可见,对于电子万年历而言,一个1602的液晶屏即可。
液晶模块的电路的连接图如图所示。
引脚图如图所示。
第1脚和第2脚分别接到了电路的GND和VCC。
第3脚VEE为对比调整电压,接VCC时对比度最弱,接GND时对比度最高。
通过一个10K的电位器连接到地端,可通过调节该电位器来调节液晶的对比度。
第4脚RS是液晶的寄存器控制脚,0=输入指令,1=输入数据。接到了单片机的P2.7脚上。
第5脚RW是液晶的读写控制脚,0=写入指令或数据,1=读取信息。接到了单片机的P2.6脚上。
第6脚E是液晶的使能脚,高电平时读取信息,下降沿执行指令。接到了单片机的P2.5脚上。
第7脚到第14脚是液晶的数据/地址8位总线,接到了单片机的P0口上。
第15脚和第16脚是液晶的背光电源脚,分别连接系统VCC和GND。
3.时钟电路设计
采用DS1302时钟芯片实现时钟,它可以对年、月、日、周日、时、分、秒进行计时,工作电压为2V~5.5V。增加了主电源/后备电源双电源引脚,主要特点是采用串行数据传输,可为掉电保护电源提供可编程的充电功能,并且可以关闭充电功能。
DS1302模块的电路图如图所示。
引脚图如图所示。
第1脚和第4脚分别接到了电路的VCC和GND。
第8脚是后备电源脚,接了一个3V的纽扣电池作为时钟芯片的后备电池,可以保证断掉主电源后时钟继续行走。
第2和第3脚接了一个32.768K的晶振给芯片提供时钟脉冲。
第5、6、7依次连接到单片机的IO口,进行数据的传输。
4.按键输入设计
由于采用的按键数量较少,只有4个按键,分别是“时钟设置”、“闹钟设置”、“减”、“加”,故采用了独立按键的方式。
按键的连接图所示:
上拉电阻与按键组成一个简单的开关电路。当按键没有被按下时,上拉电阻将输入信号拉高至高电平;当按键按下时,输入信号由高电平变为低电平。这样可以通过检测输入信号的状态来判断按键是否被按下。上拉电阻在按键电路中起到了保持输入信号稳定的作用。
最常见的独立按键开关如下图的四角轻触开关:
四脚轻触开关实际内部是两两相连,设计为四脚一方面是为了稳定性(四脚固定在按动时会比两脚更稳),另一方面也是为了硬件布线可以更方便。内部1与2, 3与4直接相连,在按下时,1/2才会和3/4闭合,接线时需要注意,不要弄错引脚,导致按键为常闭状态。
5.蜂鸣器电路设计
直流驱动,对驱动口输出驱动电平并通过三极管放大驱动电流就能使蜂鸣器发出声音。
三、总原理图及元器件清单
1.总原理图
2.元件选择耗材及价格清单
序号 | 元件标号 | 器件名称 | 主要参数 | 数量 | 单价/元 | 备注 |
1 | U1 | STC89C52单片机 |
| 1 |
|
|
2 |
| 单片机座子 |
| 1 |
|
|
3 | X1 | 晶振 | 12M | 1 |
|
|
4 | C1、C2 | 陶片电容 | 30pF | 2 |
|
|
5 | C3 | 电解电容 | 10uF | 1 |
|
|
6 | R1-R6 | 电阻 | 10K | 6 |
|
|
7 | LCD1 | 1602液晶 |
| 1 |
|
|
8 |
| 电位器 | 10K | 1 |
|
|
9 | RP1 | 排阻 | 10K | 1 |
|
|
10 | U2 | 时钟芯片DS1302 |
| 1 |
|
|
11 |
| 时钟芯片座子 |
| 1 |
|
|
12 | X2 | 晶振 | 32.768K | 1 |
|
|
13 |
| 轻触开关 |
| 4 |
|
|
14 | Q1 | 2N5401三极管 | PNP型 | 1 |
|
|
15 | BUZ1 | 有源蜂鸣器 |
| 1 |
|
|
16 |
| CR2032纽扣电池 | 3V | 1 |
|
|
17 |
| 电源开关 |
| 1 |
|
|
18 |
| 电源座 |
| 1 |
|
|
19 |
| 电源线 |
| 1 |
|
|
20 |
| 导线 |
| 若干 |
|
|
21 |
| 排插 |
| 若干 |
|
|
四、程序流程图
本系统的软件流程图如上图所示,最开始先进行液晶的初始化,包括液晶功能初始化和液晶显示内容初始化,接着就进行时钟芯片的初始化,主要就是初始化时钟芯片引脚电平。然就进入了一个循环处理的过程,包括先读取时钟芯片的日期时间数据,然后在1602液晶上面显示出来,接着判断读取到的时间是否需要启动闹钟报警,是的话则控制蜂鸣器鸣叫。再往下就是判断设置按键或闹钟按键是否被按下,是的话,则进入时钟设置或闹钟设置状态。
五、PROTEUS仿真与测试
1.时间仿真界面
通过相应按键可以对界面所显示的时间、日期进行调节。
2.闹钟显示仿真界面
到达设定时间后,蜂鸣器高低电平交替变化,即蜂鸣器开始鸣叫。
六、性能测试与分析
1.在图形液晶上显示日历和时钟;
2.按键调整日期和时间;
3.设定定时闹钟。
七、设计作品图片
八、程序代码
#define uchar unsigned char #define uint unsigned int sfr ISP_DATA = 0xe2; // 数据寄存器 sfr ISP_ADDRH = 0xe3; // 地址寄存器高八位 sfr ISP_ADDRL = 0xe4; // 地址寄存器低八位 sfr ISP_CMD = 0xe5; // 命令寄存器 sfr ISP_TRIG = 0xe6; // 命令触发寄存器 sfr ISP_CONTR = 0xe7; // 命令寄存器 sbit LcdRs_P = P2^7; // 1602液晶的RS管脚 sbit LcdRw_P = P2^6; // 1602液晶的RW管脚 sbit LcdEn_P = P2^5; // 1602液晶的EN管脚 sbit RST_P = P1^3; // 时钟芯片DS1302的RST管脚 sbit SDA_P = P1^2; // 时钟芯片DS1302的SDA管脚 sbit SCK_P = P1^1; // 时钟芯片DS1302的SCK管脚 sbit KeySet_P = P3^4; // 设置时间按键 sbit KeyClock_P = P3^5; // 设置闹钟按键 sbit KeyDown_P = P3^6; // 减按键 sbit KeyUp_P = P3^7; // 加按键 sbit Buzzer_P = P2^0; // 蜂鸣器 uchar TimeBuff[7]={23,12,19,3,9,30,50}; // 时间数组,默认2023年12月19日,星期二,9:30:50 uchar Clock_Hour; // 闹钟的小时 uchar Clock_Minute; // 闹钟的分钟 uchar Clock_Swt; // 闹钟的开关 uchar Buzzer_Flag=0; // 蜂鸣器工作标志 void ISP_Disable() // 单片机内部EEPROM不使能 { ISP_CONTR = 0; //关闭EEPROM控制器 ISP_ADDRH = 0; //将EEPROM地址高位设置为0 ISP_ADDRL = 0; //将EEPROM地址低位设置为0 } unsigned char EEPROM_Read(unsigned int add) {// 从单片机内部EEPROM读一个字节,从0x2000地址开始 ISP_DATA = 0x00; // 初始化EEPROM数据为0 ISP_CONTR = 0x83; // 启用EEPROM控制器和读取操作 ISP_CMD = 0x01; // 设定EEPROM操作命令为读取 ISP_ADDRH = (unsigned char)(add>>8); // 设置EEPROM地址高位 ISP_ADDRL = (unsigned char)(add&0xff);// 设置EEPROM地址低位 ISP_TRIG = 0x46; //先写入0x46,再写入0xB9,ISP/IAP才会生效 ISP_TRIG = 0xB9; _nop_(); ISP_Disable(); // 禁用EEPROM return (ISP_DATA); // 返回读取的EEPROM数据 } void EEPROM_Write(unsigned int add,unsigned char ch) {// 往单片机内部EEPROM写一个字节,从0x2000地址开始 ISP_CONTR = 0x83; // 启用EEPROM控制器和写入操作 ISP_CMD = 0x02; // 设定EEPROM操作命令为写入 ISP_ADDRH = (unsigned char)(add>>8); // 设置EEPROM地址高位 ISP_ADDRL = (unsigned char)(add&0xff);// 设置EEPROM地址低位 ISP_DATA = ch; // 设置要写入的EEPROM数据 ISP_TRIG = 0x46; ISP_TRIG = 0xB9; _nop_(); ISP_Disable(); } void Sector_Erase(unsigned int add) { //写8个扇区中随便一个的地址,便擦除该扇区,写入前要先擦除 ISP_CONTR = 0x83; // 启用EEPROM控制器和扇区擦除操作 ISP_CMD = 0x03; // 设定EEPROM操作命令为扇区擦除 ISP_ADDRH = (unsigned char)(add>>8); // 设置要擦除的EEPROM地址高位 ISP_ADDRL = (unsigned char)(add&0xff);// 设置要擦除的EEPROM地址低位 ISP_TRIG = 0x46; ISP_TRIG = 0xB9; _nop_(); ISP_Disable(); } void DelayMs(uint time)// 毫秒级的延时函数 { uint i,j; for(i=time;i>0;i--) // 外层循环,根据传入的 time 参数确定循环次数 for(j=112;j>0;j--);// 内层循环,固定次数的空循环来实现延时 } /*********************************************************/ // 1602液晶相关函数 /*********************************************************/ //1602液晶写命令函数,cmd就是要写入的命令 void LcdWriteCmd(uchar cmd) { LcdRs_P = 0; // RS置0,表示发送命令 LcdRw_P = 0; // RW置0,表示写入模式 LcdEn_P = 0; // 使能端拉低 P0=cmd; // 将命令写入P0口 DelayMs(2); // 延时2ms LcdEn_P = 1; // 使能端拉高 DelayMs(2); LcdEn_P = 0; // 使能端拉低 } //1602液晶写数据函数,dat就是要写入的命令 void LcdWriteData(uchar dat) { LcdRs_P = 1; // RS置1,表示发送数据 LcdRw_P = 0; LcdEn_P = 0; P0=dat; DelayMs(2); LcdEn_P = 1; DelayMs(2); LcdEn_P = 0; } //1602液晶初始化函数 void LcdInit() { LcdWriteCmd(0x38); // 16*2显示,5*7点阵,8位数据口 LcdWriteCmd(0x0C); // 开显示,不显示光标 LcdWriteCmd(0x06); // 地址加1,当写入数据后光标右移 LcdWriteCmd(0x01); // 清屏 } //1602液晶光标定位函数 void LcdGotoXY(uchar line,uchar column) { if(line==0) LcdWriteCmd(0x80+column); // 第一行 if(line==1) LcdWriteCmd(0x80+0x40+column);// 第二行 } //1602液晶输出字符串函数 void LcdPrintStr(uchar *str) { while(*str!='\0') LcdWriteData(*str++);// 逐个字符写入 } //1602液晶显示内容的初始化 void LcdShowInit() { LcdGotoXY(0,0); LcdPrintStr("20 - - ");// 初始化第一行显示内容 LcdGotoXY(1,0); LcdPrintStr(" : : ");// 初始化第二行显示内容 } // 1602液晶输出数字 void LcdPrintNum(uchar num) { LcdWriteData(num/10+48); // 十位 LcdWriteData(num%10+48); // 个位 } //1602液晶显示星期 void LcdPrintWeek(uchar week) { switch(week) { case 1: LcdPrintStr(" Sun"); break; case 2: LcdPrintStr(" Mon"); break; case 3: LcdPrintStr("Tues"); break; case 4: LcdPrintStr(" Wed"); break; case 5: LcdPrintStr("Thur"); break; case 6: LcdPrintStr(" Fri"); break; case 7: LcdPrintStr(" Sat"); break; default: break; } } // 刷新时间显示 void FlashTime() { // 逐个位置输出时间信息 LcdGotoXY(0,2); // 年份 LcdPrintNum(TimeBuff[0]); LcdGotoXY(0,5); // 月份 LcdPrintNum(TimeBuff[1]); LcdGotoXY(0,8); // 日期 LcdPrintNum(TimeBuff[2]); LcdGotoXY(1,4); // 小时 LcdPrintNum(TimeBuff[4]); LcdGotoXY(1,7); // 分 LcdPrintNum(TimeBuff[5]); LcdGotoXY(1,10); // 秒 LcdPrintNum(TimeBuff[6]); LcdGotoXY(0,12); // 星期 LcdPrintWeek(TimeBuff[3]); } /*********************************************************/ // DS1302相关函数 /*********************************************************/ // 初始化DS1302 void DS1302_Init(void) { RST_P=0; // RST脚置低,开始初始化DS1302 SCK_P=0; // SCK脚置低 SDA_P=0; // SDA脚置低 } // 从DS1302读出函数 uchar DS1302_Read_Byte(uchar addr) { uchar i; uchar temp; RST_P=1;// 将RST引脚置高,准备进行数据读取操作 for(i=0;i<8;i++) //写入目标地址:addr { if(addr&0x01) SDA_P=1;// 根据地址的每一位决定将SDA置为高电平或低电平 else SDA_P=0; SCK_P=1;// 在SCK引脚上产生时钟脉冲 _nop_(); SCK_P=0;// 将SCK引脚置低,准备下一次时钟脉冲 _nop_(); addr=addr>> 1; // 逐位右移地址,准备下一个位的操作 } for(i=0;i<8;i++) // 读出该地址的数据 { temp=temp>>1;// 右移一位,准备接收新的数据 if(SDA_P) // 如果SDA引脚为高电平,则将temp最高位设置为1 temp|= 0x80; else // 如果SDA引脚为低电平,则将temp最高位设置为0 temp&=0x7F; SCK_P=1; _nop_(); SCK_P=0; _nop_(); } RST_P=0;// 数据读取结束,将RST引脚置低,结束通信 return temp; } // 向DS1302写入函数 void DS1302_Write_Byte(uchar addr, uchar dat) { uchar i; RST_P = 1; for(i=0;i<8;i++) //写入目标地址:addr { if(addr&0x01) SDA_P=1; else SDA_P=0; SCK_P=1; _nop_(); SCK_P=0; _nop_(); addr=addr>>1; } for(i=0;i<8;i++) //写入数据:dat { if(dat&0x01) SDA_P=1; else SDA_P=0; SCK_P=1; _nop_(); SCK_P=0; _nop_(); dat=dat>>1; } RST_P=0; } // 向DS1302写入时间数据 void DS1302_Write_Time() { uchar i; uchar temp1; uchar temp2; for(i=0;i<7;i++) // 十进制转BCD码 { temp1=(TimeBuff[i]/10)<<4; temp2=TimeBuff[i]%10; TimeBuff[i]=temp1+temp2; } DS1302_Write_Byte(0x8E,0x00); // 关闭写保护 DS1302_Write_Byte(0x80,0x80); // 暂停时钟 DS1302_Write_Byte(0x8C,TimeBuff[0]); // 年 DS1302_Write_Byte(0x88,TimeBuff[1]); // 月 DS1302_Write_Byte(0x86,TimeBuff[2]); // 日 DS1302_Write_Byte(0x8A,TimeBuff[3]); // 星期 DS1302_Write_Byte(0x84,TimeBuff[4]); // 时 DS1302_Write_Byte(0x82,TimeBuff[5]); // 分 DS1302_Write_Byte(0x80,TimeBuff[6]); // 秒 DS1302_Write_Byte(0x80,TimeBuff[6]&0x7F);// 运行时钟 DS1302_Write_Byte(0x8E,0x80); // 打开写保护 } // 从DS1302读出时间数据 void DS1302_Read_Time() { uchar i; TimeBuff[0]=DS1302_Read_Byte(0x8D); // 年 TimeBuff[1]=DS1302_Read_Byte(0x89); // 月 TimeBuff[2]=DS1302_Read_Byte(0x87); // 日 TimeBuff[3]=DS1302_Read_Byte(0x8B); // 星期 TimeBuff[4]=DS1302_Read_Byte(0x85); // 时 TimeBuff[5]=DS1302_Read_Byte(0x83); // 分 TimeBuff[6]=(DS1302_Read_Byte(0x81))&0x7F; // 秒 for(i=0;i<7;i++)// BCD转十进制 { TimeBuff[i]=(TimeBuff[i]/16)*10+TimeBuff[i]%16; } } /*********************************************************/ // 按键相关函数 /*********************************************************/ // 按键扫描(设置时间) void KeyScanf1() { if(KeySet_P==0) { LcdWriteCmd(0x0f); // 启动光标闪烁 LcdGotoXY(0,3); // 定位光标到年份闪烁 DelayMs(10); // 延时等待,消除按键按下的抖动 while(!KeySet_P); // 等待按键释放 DelayMs(10); // 延时等待,消除按键松开的抖动 /* 调整年份 */ while(1) { if(KeyDown_P==0) // 如果减按键被下去 { if(TimeBuff[0]>0) // 判断年份是否大于0 TimeBuff[0]--; // 是的话就减去1 LcdGotoXY(0,2); // 光标定位到年份的位置 LcdPrintNum(TimeBuff[0]);// 刷新显示改变后的年份 LcdGotoXY(0,3); // 定位光标到年份闪烁 DelayMs(300); // 延时0.3秒左右 } if(KeyUp_P==0) // 如果加按键被下去 { if(TimeBuff[0]<99) // 判断年份是否小于99 TimeBuff[0]++; // 是的话就加上1 LcdGotoXY(0,2); // 光标定位到年份的位置 LcdPrintNum(TimeBuff[0]);// 刷新显示改变后的年份 LcdGotoXY(0,3); // 定位光标到年份闪烁 DelayMs(300); // 延时0.3秒左右 } if(KeySet_P==0) { break; } } LcdGotoXY(0,6); // 定位光标到月份闪烁 DelayMs(10); // 延时等待,消除按键按下的抖动 while(!KeySet_P); // 等待按键释放 DelayMs(10); // 延时等待,消除按键松开的抖动 /* 调整月份 */ while(1) { if(KeyDown_P==0) // 如果减按键被下去 { if(TimeBuff[1]>1) // 判断月份是否大于1 TimeBuff[1]--; // 是的话就减去1 LcdGotoXY(0,5); // 光标定位到月份的位置 LcdPrintNum(TimeBuff[1]);// 刷新显示改变后的月份 LcdGotoXY(0,6); // 定位光标到月份闪烁 DelayMs(300); // 延时0.3秒左右 } if(KeyUp_P==0) // 如果加按键被下去 { if(TimeBuff[1]<12) // 判断月份是否小于12 TimeBuff[1]++; // 是的话就加上1 LcdGotoXY(0,5); // 光标定位到月份的位置 LcdPrintNum(TimeBuff[1]); // 刷新显示改变后的月份 LcdGotoXY(0,6); // 定位光标到月份闪烁 DelayMs(300); // 延时0.3秒左右 } if(KeySet_P==0) { break; } } LcdGotoXY(0,9); // 定位光标到日期闪烁 DelayMs(10); // 延时等待,消除按键按下的抖动 while(!KeySet_P); // 等待按键释放 DelayMs(10); // 延时等待,消除按键松开的抖动 /* 调整日期 */ while(1) { if(KeyDown_P==0) // 如果减按键被下去 { if(TimeBuff[2]>1) // 判断日期是否大于1 TimeBuff[2]--; // 是的话就减去1 LcdGotoXY(0,8); // 光标定位到日期的位置 LcdPrintNum(TimeBuff[2]);// 刷新显示改变后的日期 LcdGotoXY(0,9); // 定位光标到日期闪烁 DelayMs(300); // 延时0.3秒左右 } if(KeyUp_P==0) // 如果加按键被下去 { if(TimeBuff[2]<31) // 判断日期是否小于31 TimeBuff[2]++; // 是的话就加上1 LcdGotoXY(0,8); // 光标定位到日期的位置 LcdPrintNum(TimeBuff[2]);// 刷新显示改变后的日期 LcdGotoXY(0,9); // 定位光标到日期闪烁 DelayMs(300); // 延时0.3秒左右 } if(KeySet_P==0) { break; } } LcdGotoXY(0,15); // 定位光标到星期闪烁 DelayMs(10); // 延时等待,消除按键按下的抖动 while(!KeySet_P); // 等待按键释放 DelayMs(10); // 延时等待,消除按键松开的抖动 /* 调整星期 */ while(1) { if(KeyDown_P==0) // 如果减按键被下去 { if(TimeBuff[3]>1) // 判断星期是否大于1 TimeBuff[3]--; // 是的话就减去1 LcdGotoXY(0,12); // 光标定位到星期的位置 LcdPrintWeek(TimeBuff[3]); // 刷新显示改变后的星期 LcdGotoXY(0,15); // 定位光标到星期闪烁 DelayMs(300); // 延时0.3秒左右 } if(KeyUp_P==0) // 如果加按键被下去 { if(TimeBuff[3]<7) // 判断星期是否小于6 TimeBuff[3]++; // 是的话就加上1 LcdGotoXY(0,12); // 光标定位到星期的位置 LcdPrintWeek(TimeBuff[3]); // 刷新显示改变后的星期 LcdGotoXY(0,15); // 定位光标到星期闪烁 DelayMs(300); // 延时0.3秒左右 } if(KeySet_P==0) { break; } } LcdGotoXY(1,5); // 定位光标到小时闪烁 DelayMs(10); // 延时等待,消除按键按下的抖动 while(!KeySet_P); // 等待按键释放 DelayMs(10); // 延时等待,消除按键松开的抖动 /* 调整小时 */ while(1) { if(KeyDown_P==0) // 如果减按键被下去 { if(TimeBuff[4]>0) // 判断小时是否大于0 TimeBuff[4]--; // 是的话就减去1 LcdGotoXY(1,4); // 光标定位到小时的位置 LcdPrintNum(TimeBuff[4]); // 刷新显示改变后的小时 LcdGotoXY(1,5); // 定位光标到小时闪烁 DelayMs(300); // 延时0.3秒左右 } if(KeyUp_P==0) // 如果加按键被下去 { if(TimeBuff[4]<23) // 判断小时是否小于23 TimeBuff[4]++; // 是的话就加上1 LcdGotoXY(1,4); // 光标定位到小时的位置 LcdPrintNum(TimeBuff[4]); // 刷新显示改变后的小时 LcdGotoXY(1,5); // 定位光标到小时闪烁 DelayMs(300); // 延时0.3秒左右 } if(KeySet_P==0) { break; } } LcdGotoXY(1,8); // 定位光标到分钟闪烁 DelayMs(10); // 延时等待,消除按键按下的抖动 while(!KeySet_P); // 等待按键释放 DelayMs(10); // 延时等待,消除按键松开的抖动 /* 调整分钟 */ while(1) { if(KeyDown_P==0) // 如果减按键被下去 { if(TimeBuff[5]>0) // 判断分钟是否大于0 TimeBuff[5]--; // 是的话就减去1 LcdGotoXY(1,7); // 光标定位到分钟的位置 LcdPrintNum(TimeBuff[5]);// 刷新显示改变后的分钟 LcdGotoXY(1,8); // 定位光标到分钟闪烁 DelayMs(300); // 延时0.3秒左右 } if(KeyUp_P==0) // 如果加按键被下去 { if(TimeBuff[5]<59) // 判断分钟是否小于59 TimeBuff[5]++; // 是的话就加上1 LcdGotoXY(1,7); // 光标定位到分钟的位置 LcdPrintNum(TimeBuff[5]);// 刷新显示改变后的分钟 LcdGotoXY(1,8); // 定位光标到分钟闪烁 DelayMs(300); // 延时0.3秒左右 } if(KeySet_P==0) { break; } } LcdGotoXY(1,11); // 定位光标到秒钟闪烁 DelayMs(10); // 延时等待,消除按键按下的抖动 while(!KeySet_P); // 等待按键释放 DelayMs(10); // 延时等待,消除按键松开的抖动 /* 调整秒钟 */ while(1) { if(KeyDown_P==0) // 如果减按键被下去 { if(TimeBuff[6]>0) // 判断秒钟是否大于0 TimeBuff[6]--; // 是的话就减去1 LcdGotoXY(1,10); // 光标定位到秒钟的位置 LcdPrintNum(TimeBuff[6]);// 刷新显示改变后的秒钟 LcdGotoXY(1,11); // 定位光标到秒钟闪烁 DelayMs(300); // 延时0.3秒左右 } if(KeyUp_P==0) // 如果加按键被下去 { if(TimeBuff[6]<59) // 判断秒钟是否小于59 TimeBuff[6]++; // 是的话就加上1 LcdGotoXY(1,10); // 光标定位到秒钟的位置 LcdPrintNum(TimeBuff[6]);// 刷新显示改变后的秒钟 LcdGotoXY(1,11); // 定位光标到秒钟闪烁 DelayMs(300); // 延时0.3秒左右 } if(KeySet_P==0) { break; } } /* 退出前的设置 */ LcdWriteCmd(0x0C); // 关闭光标闪烁 DS1302_Write_Time(); // 把新设置的时间值存入DS1302芯片 DelayMs(10); // 延时等待,消除按键按下的抖动 while(!KeySet_P); // 等待按键释放 DelayMs(10); // 延时等待,消除按键松开的抖动 } } /*********************************************************/ // 闹钟相关函数 /*********************************************************/ // 按键扫描(设置闹钟) void KeyScanf2() { if(KeyClock_P==0) { LcdGotoXY(0,0); // 液晶显示为闹钟设置的界面 LcdPrintStr("Alarm Clock Set "); LcdGotoXY(1,0); LcdPrintStr(" : "); LcdGotoXY(1,3); // 显示闹钟的小时 LcdPrintNum(Clock_Hour); LcdGotoXY(1,6); // 显示闹钟的分钟 LcdPrintNum(Clock_Minute); LcdGotoXY(1,10); // 显示闹钟状态 if(Clock_Swt==0) { LcdPrintStr("OFF"); } else { LcdPrintStr(" ON"); } LcdGotoXY(1,4); // 光标定位 LcdWriteCmd(0x0f); // 光标闪烁 DelayMs(10); // 延时等待,消除按键按下的抖动 while(!KeyClock_P); // 等待按键释放 DelayMs(10); // 延时等待,消除按键松开的抖动 /* 调整闹钟小时 */ while(1) { if(KeyDown_P==0) // 如果减按键被下去 { if(Clock_Hour>0) // 判断闹钟小时是否大于0 Clock_Hour--; // 是的话就减去1 LcdGotoXY(1,3); // 光标定位到闹钟小时的位置 LcdPrintNum(Clock_Hour);// 刷新显示改变后的闹钟小时 LcdGotoXY(1,4); // 定位光标到闹钟小时闪烁 DelayMs(300); // 延时0.3秒左右 } if(KeyUp_P==0) // 如果加按键被下去 { if(Clock_Hour<23) // 判断闹钟小时是否小于23 Clock_Hour++; // 是的话就加上1 LcdGotoXY(1,3); // 光标定位到闹钟小时的位置 LcdPrintNum(Clock_Hour);// 刷新显示改变后的闹钟小时 LcdGotoXY(1,4); // 定位光标到闹钟小时闪烁 DelayMs(300); // 延时0.3秒左右 } if(KeyClock_P==0) { break; } } LcdGotoXY(1,7); // 定位光标到闹钟分钟的闪烁 DelayMs(10); // 延时等待,消除按键按下的抖动 while(!KeyClock_P); // 等待按键释放 DelayMs(10); // 延时等待,消除按键松开的抖动 /* 调整分钟 */ while(1) { if(KeyDown_P==0) // 如果减按键被下去 { if(Clock_Minute>0) // 判断闹钟分钟是否大于0 Clock_Minute--; // 是的话就减去1 LcdGotoXY(1,6); // 光标定位到闹钟分钟的位置 LcdPrintNum(Clock_Minute); // 刷新显示改变后的闹钟分钟 LcdGotoXY(1,7); // 定位光标到闹钟分钟闪烁 DelayMs(300); // 延时0.3秒左右 } if(KeyUp_P==0) // 如果加按键被下去 { if(Clock_Minute<59) // 判断闹钟分钟是否小于59 Clock_Minute++; // 是的话就加上1 LcdGotoXY(1,6); // 光标定位到闹钟分钟的位置 LcdPrintNum(Clock_Minute); // 刷新显示改变后的闹钟分钟 LcdGotoXY(1,7); // 定位光标到闹钟分钟闪烁 DelayMs(300); // 延时0.3秒左右 } if(KeyClock_P==0) { break; } } LcdGotoXY(1,12);// 定位光标到闹钟开关的位置闪烁 DelayMs(10); // 延时等待,消除按键按下的抖动 while(!KeyClock_P); // 等待按键释放 DelayMs(10); // 延时等待,消除按键松开的抖动 /* 闹钟开关 */ while(1) { if(KeyDown_P==0)// 如果减按键被下去 { if(Clock_Swt==1) // 判断闹钟是否开启 Clock_Swt=0; // 关闭闹钟 LcdGotoXY(1,10); // 光标定位到秒钟开关的位置 LcdPrintStr("OFF"); // 液晶显示“OFF” LcdGotoXY(1,12); // 定位光标到闹钟开关的位置闪烁 DelayMs(300); // 延时0.3秒左右 } if(KeyUp_P==0) // 如果加按键被下去 { if(Clock_Swt==0) // 判断闹钟是否关闭 Clock_Swt=1; // 启动闹钟 LcdGotoXY(1,10); // 光标定位到秒钟开关的位置 LcdPrintStr(" ON"); // 液晶显示“ ON” LcdGotoXY(1,12); // 定位光标到闹钟开关的位置闪烁 DelayMs(300); // 延时0.3秒左右 } if(KeyClock_P==0) { break; } } /* 退出前的设置 */ LcdWriteCmd(0x0C); // 关闭光标闪烁 LcdShowInit(); // 液晶显示内容恢复为检测界面的 DelayMs(10); // 延时等待,消除按键按下的抖动 while(!KeyClock_P); // 等待按键释放 DelayMs(10); // 延时等待,消除按键松开的抖动 Sector_Erase(0x2000); EEPROM_Write(0x2000,Clock_Hour); // 往0x2000这个地址写入闹钟的小时 EEPROM_Write(0x2001,Clock_Minute);// 往0x2001这个地址写入闹钟的分钟 EEPROM_Write(0x2002,Clock_Swt); // 往0x2002这个地址写入闹钟的开关 } } // 闹钟判断 void ClockJudge() { if(Clock_Swt==1) // 判断闹钟的开关是否开启 { if((Clock_Hour==TimeBuff[4])&&(Clock_Minute==TimeBuff[5])) // 当前小时和分钟,和闹钟的小时和分钟是否一致 { if(TimeBuff[6]==0) // 秒数是否等于0 { Buzzer_Flag=1; // 开启蜂鸣器报警标志 } } } if(TimeBuff[6]==59) // 如果当前秒数为59秒 { Buzzer_Flag=0; // 关闭蜂鸣器报警标志 } if((KeyDown_P==0)||(KeyUp_P==0))// 如果加按键或减按键被按下 { Buzzer_Flag=0; // 关闭蜂鸣器报警标志 } if(Buzzer_Flag==1) // 如果蜂鸣器报警标志为启动 { Buzzer_P=0; // 启动蜂鸣器 DelayMs(100); // 延时0.1秒 Buzzer_P=1; // 关闭蜂鸣器 DelayMs(100); // 延时0.1秒 } } /*********************************************************/ // 主函数 /*********************************************************/ void main() { LcdInit(); // 执行液晶初始化 DS1302_Init(); // 时钟芯片的初始化 LcdShowInit(); // 液晶显示内容的初始化 if(DS1302_Read_Byte(0x81)>=128) // 判断时钟芯片是否正在运行 { DS1302_Write_Time(); // 如果没有,则初始化一个时间 } Clock_Hour=EEPROM_Read(0x2000); // 读取0x2000这个地址的内容,赋值给闹钟的小时变量 if(Clock_Hour>23) // 如果读取到的闹钟小时数值不正常,则重新赋值 { Clock_Hour=12; } Clock_Minute=EEPROM_Read(0x2001); // 读取0x2001这个地址的内容,赋值给闹钟的分钟变量 if(Clock_Minute>59) // 如果读取到的闹钟分钟数值不正常,则重新赋值 { Clock_Minute=30; } Clock_Swt=EEPROM_Read(0x2002); // 读取0x2002这个地址的内容,赋值给闹钟的开关变量 if(Clock_Swt>1) // 如果读取到的闹钟开关数值不正常,则重新赋值 { Clock_Swt=0; } while(1) { DS1302_Read_Time(); // 获取当前时钟芯片的时间,存在数组time_buf中 ClockJudge(); // 闹钟工作的判断 FlashTime(); // 刷新时间显示 KeyScanf1(); // 按键扫描(时间的设置) KeyScanf2(); // 按键扫描(闹钟的设置) DelayMs(100); // 延时0.1秒 } }