(九)51单片机——DS1302时钟
时间:2023-09-07 17:07:00
目录
DS1302介绍
引脚定义及应用电路
寄存器定义
命令字
时序定义
代码编写
今天,我们的任务是用51开发板编写一个可调小时钟。让我们一步一步地实现它!
DS1302介绍
- DS1302是由美国DALLAS公司推出了具有涓细电流充电能力的低功耗实时钟芯片。可计时年、月、日、周、时、分、秒,具有闰年补偿等多种功能
- RTC(Real Time Clock):实时时钟是一种集成电路,通常称为时钟芯片
我们51单片机就是这样一个DS1302芯片。
引脚定义和应用电路
这边是DS1302与CPU之间的关系,我先列一个表格来介绍各个部分的功能。
引脚名 | 作用 |
---|---|
VCC2 |
主电源 |
VCC1 |
备用电池 |
GND |
接地 |
X1、X2 |
32.768KHz晶振 |
CE |
芯片使能 |
IO |
数据输入/输出 |
SCLK |
串行时钟 |
一是电源部分,VCC2连接外部电源,2连接外部电源VCC1连接内置电源(51开发板上没有内置电源,掉电后不能继续走,但我的闹钟掉电后可以走,hahaha!),之后就是GND接地。
之后是晶振部分,连接32.768KH单片机本身是11.0592MHZ,要注意区分,主要功能是为计时提供稳定的计数脉冲。
之后的CE是芯片使能,不能读取,但时钟仍然可以运行;IO用于获取和修改数据的数据输入输出;SCLK串行时钟,以前的SERCLK是类似的。
这是更详细的图片:
寄存器定义
DS1302有很多寄存器。让我们一一介绍一下:
从上往下,依次是秒,分,时,天,月,星期几(1~7),年WP它是写入保护,一个写入无效,但也可以读数。以下是涓流充电模式,即内置电源。如果没有51台单片机,我们就不会先写这个了。
注:CH是时钟停止引脚
命令字
命令字要解决的问题是我想去哪里操作,我想读还是写这两个问题。第一位,就是看是读出还是写入,当为0时,是写入,为1是读出;2~6位是地址,7位是时钟还是时钟RAM;8位都是1。其实命令字已经在前两列表达出来了。
时序定义
这部分内容是数电的第六章内容,我还没学到,呜呜呜,就只能先浅显的理解一下了。
首先,我们需要写好刚刚介绍的命令字部分,每一个上升沿就是一次写入,而下降沿就是输出,所以输入输出主要的区别就是后面部分,从D0~D7,就是我们要操作的数据。
代码编写
接下来,我们就开始着手来编写一下代码了,先把原理图放上来。
// DS1302.c的代码 #include
// 根据原理图,应该可以写出下面几个引脚定义 sbit DS1302_SCLK = P3^6; sbit DS1302_IO = P3^4; sbit DS1302_CE = P3^5; // 初始化函数,因为单片机接电后默认为1 void DS1302_Init(void) { DS1302_CE = 0; DS1302_SCLK = 0; } // 写入函数 void DS1302_WriteByte(unsigned char Command, Data) { unsigned char i; DS1302_CE = 1; // 读取低位 for(i=0;i<8;i++){ DS1302_IO = Command&(0x01< // DS1302.h的代码 #ifndef __DS1302_H__ #define __DS1302_H__ unsigned char DS1302_ReadByte(unsigned char Command); void DS1302_WriteByte(unsigned char Command, Data); void DS1302_Init(void); #endif
// 主函数代码 #include
#include "LCD1602.h" #include "DS1302.h" #include "Delay.h" unsigned char Second; void main(){ LCD_Init(); DS1302_Init(); LCD_ShowString(1, 1,"RTC"); // 解除写入保护 DS1302_WriteByte(0x8E,0x00); DS1302_WriteByte(0x80,0x03); while(1){ Second = DS1302_ReadByte(0x81); LCD_ShowNum(2,1,Second,3); } } 如果这样写的话,代码是可以运行的,但是会遇到问题,如下所示,直接从9跳到了16,主要原因就是码制的原因,这个地方用到了数电里的BCD码,如有不清楚的小伙伴,可以去看看我的数电笔记。
所以我们要对代码进行修改,如下所示:
#include
#include "LCD1602.h" #include "DS1302.h" #include "Delay.h" unsigned char Second; void main(){ LCD_Init(); DS1302_Init(); LCD_ShowString(1, 1,"RTC"); // 解除写入保护 DS1302_WriteByte(0x8E,0x00); DS1302_WriteByte(0x80,0x03); while(1){ Second = DS1302_ReadByte(0x81); // 将BCD码变成十进制 LCD_ShowNum(2,1,Second/16*10+Second%16,3); } } 最后,我们把最终优化的代码写出来。(其实还可以优化,但是会使代码不容易理解,因为代码还是要使人看懂,所以就不优化了)
#include
// 根据原理图,应该可以写出下面几个引脚定义 sbit DS1302_SCLK = P3^6; sbit DS1302_IO = P3^4; sbit DS1302_CE = P3^5; //寄存器写入地址/指令定义 #define DS1302_SECOND 0x80 #define DS1302_MINUTE 0x82 #define DS1302_HOUR 0x84 #define DS1302_DATE 0x86 #define DS1302_MONTH 0x88 #define DS1302_DAY 0x8A #define DS1302_YEAR 0x8C #define DS1302_WP 0x8E //时间数组,索引0~6分别为年、月、日、时、分、秒、星期 unsigned char DS1302_Time[]={22,7,3,11,57,55,7}; // 初始化函数,因为单片机接电后默认为1 /** * @brief DS1302初始化 * @param 无 * @retval 无 */ void DS1302_Init(void) { DS1302_CE = 0; DS1302_SCLK = 0; } // 写入函数 /** * @brief DS1302写一个字节 * @param Command 命令字/地址 * @param Data 要写入的数据 * @retval 无 */ void DS1302_WriteByte(unsigned char Command, Data) { unsigned char i; DS1302_CE = 1; // 读取低位 for(i=0;i<8;i++){ DS1302_IO = Command&(0x01< #ifndef __DS1302_H__ #define __DS1302_H__ //外部可调用时间数组,索引0~6分别为年、月、日、时、分、秒、星期 extern unsigned char DS1302_Time[]; void DS1302_Init(void); void DS1302_WriteByte(unsigned char Command,Data); unsigned char DS1302_ReadByte(unsigned char Command); void DS1302_SetTime(void); void DS1302_ReadTime(void); #endif
#include
#include "LCD1602.h" #include "DS1302.h" void main(){ LCD_Init(); DS1302_Init(); LCD_ShowString(1,1," - - ");//静态字符初始化显示 LCD_ShowString(2,1," : : "); DS1302_SetTime();//设置时间 while(1){ DS1302_ReadTime();//读取时间 LCD_ShowNum(1,1,DS1302_Time[0],2);//显示年 LCD_ShowNum(1,4,DS1302_Time[1],2);//显示月 LCD_ShowNum(1,7,DS1302_Time[2],2);//显示日 LCD_ShowNum(2,1,DS1302_Time[3],2);//显示时 LCD_ShowNum(2,4,DS1302_Time[4],2);//显示分 LCD_ShowNum(2,7,DS1302_Time[5],2);//显示秒 } } 运行结果如下所示:
最后呢,就是可调时钟的实现了,运用到了之前学的独立按键以及定时器,还有比较多的逻辑判断,不了解的同学也不要紧,我们就简单地把代码给出,同学们自己去看。主要的思想就是,不同的按键控制不同的功能。
#include
#include "LCD1602.h" #include "DS1302.h" #include "Key.h" #include "Time0.h" unsigned char KeyNum, MODE, TimeSetSelect, TimeSetFlashFlag; void TimeShow(void) { DS1302_ReadTime();//读取时间 LCD_ShowNum(1,1,DS1302_Time[0],2);//显示年 LCD_ShowNum(1,4,DS1302_Time[1],2);//显示月 LCD_ShowNum(1,7,DS1302_Time[2],2);//显示日 LCD_ShowNum(2,1,DS1302_Time[3],2);//显示时 LCD_ShowNum(2,4,DS1302_Time[4],2);//显示分 LCD_ShowNum(2,7,DS1302_Time[5],2);//显示秒 } void TimeSet(void) { if(KeyNum==2)//按键2按下 { TimeSetSelect++;//设置选择位加1 TimeSetSelect%=6;//越界清零 } if(KeyNum==3)//按键3按下 { DS1302_Time[TimeSetSelect]++;//时间设置位数值加1 if(DS1302_Time[0]>99){DS1302_Time[0]=0;}//年越界判断 if(DS1302_Time[1]>12){DS1302_Time[1]=1;}//月越界判断 if( DS1302_Time[1]==1 || DS1302_Time[1]==3 || DS1302_Time[1]==5 || DS1302_Time[1]==7 || DS1302_Time[1]==8 || DS1302_Time[1]==10 || DS1302_Time[1]==12)//日越界判断 { if(DS1302_Time[2]>31){DS1302_Time[2]=1;}//大月 } else if(DS1302_Time[1]==4 || DS1302_Time[1]==6 || DS1302_Time[1]==9 || DS1302_Time[1]==11) { if(DS1302_Time[2]>30){DS1302_Time[2]=1;}//小月 } else if(DS1302_Time[1]==2) { if(DS1302_Time[0]%4==0) { if(DS1302_Time[2]>29){DS1302_Time[2]=1;}//闰年2月 } else { if(DS1302_Time[2]>28){DS1302_Time[2]=1;}//平年2月 } } if(DS1302_Time[3]>23){DS1302_Time[3]=0;}//时越界判断 if(DS1302_Time[4]>59){DS1302_Time[4]=0;}//分越界判断 if(DS1302_Time[5]>59){DS1302_Time[5]=0;}//秒越界判断 } if(KeyNum==4)//按键4按下 { DS1302_Time[TimeSetSelect]--;//时间设置位数值减1 if(DS1302_Time[0]<0){DS1302_Time[0]=99;}//年越界判断 if(DS1302_Time[1]<1){DS1302_Time[1]=12;}//月越界判断 if( DS1302_Time[1]==1 || DS1302_Time[1]==3 || DS1302_Time[1]==5 || DS1302_Time[1]==7 || DS1302_Time[1]==8 || DS1302_Time[1]==10 || DS1302_Time[1]==12)//日越界判断 { if(DS1302_Time[2]<1){DS1302_Time[2]=31;}//大月 if(DS1302_Time[2]>31){DS1302_Time[2]=1;} } else if(DS1302_Time[1]==4 || DS1302_Time[1]==6 || DS1302_Time[1]==9 || DS1302_Time[1]==11) { if(DS1302_Time[2]<1){DS1302_Time[2]=30;}//小月 if(DS1302_Time[2]>30){DS1302_Time[2]=1;} } else if(DS1302_Time[1]==2) { if(DS1302_Time[0]%4==0) { if(DS1302_Time[2]<1){DS1302_Time[2]=29;}//闰年2月 if(DS1302_Time[2]>29){DS1302_Time[2]=1;} } else { if(DS1302_Time[2]<1){DS1302_Time[2]=28;}//平年2月 if(DS1302_Time[2]>28){DS1302_Time[2]=1;} } } if(DS1302_Time[3]<0){DS1302_Time[3]=23;}//时越界判断 if(DS1302_Time[4]<0){DS1302_Time[4]=59;}//分越界判断 if(DS1302_Time[5]<0){DS1302_Time[5]=59;}//秒越界判断 } //更新显示,根据TimeSetSelect和TimeSetFlashFlag判断可完成闪烁功能 if(TimeSetSelect==0 && TimeSetFlashFlag==1){LCD_ShowString(1,1," ");} else {LCD_ShowNum(1,1,DS1302_Time[0],2);} if(TimeSetSelect==1 && TimeSetFlashFlag==1){LCD_ShowString(1,4," ");} else {LCD_ShowNum(1,4,DS1302_Time[1],2);} if(TimeSetSelect==2 && TimeSetFlashFlag==1){LCD_ShowString(1,7," ");} else {LCD_ShowNum(1,7,DS1302_Time[2],2);} if(TimeSetSelect==3 && TimeSetFlashFlag==1){LCD_ShowString(2,1," ");} else {LCD_ShowNum(2,1,DS1302_Time[3],2);} if(TimeSetSelect==4 && TimeSetFlashFlag==1){LCD_ShowString(2,4," ");} else {LCD_ShowNum(2,4,DS1302_Time[4],2);} if(TimeSetSelect==5 && TimeSetFlashFlag==1){LCD_ShowString(2,7," ");} else {LCD_ShowNum(2,7,DS1302_Time[5],2);} } void main(){ LCD_Init(); DS1302_Init(); Timer0Init(); LCD_ShowString(1,1," - - ");//静态字符初始化显示 LCD_ShowString(2,1," : : "); DS1302_SetTime();//设置时间 while(1) { KeyNum=Key();//读取键码 if(KeyNum==1)//按键1按下 { if(MODE==0){MODE=1;TimeSetSelect=0;}//功能切换 else if(MODE==1){MODE=0;DS1302_SetTime();} } switch(MODE)//根据不同的功能执行不同的函数 { case 0:TimeShow();break; case 1:TimeSet();break; } } } void Timer0_Routine() interrupt 1 { static unsigned int T0Count; TL0 = 0x18; //设置定时初值 TH0 = 0xFC; //设置定时初值 T0Count++; if(T0Count>=500)//每500ms进入一次 { T0Count=0; TimeSetFlashFlag=!TimeSetFlashFlag;//闪烁标志位取反 } } 运行结果如下所示:
好了,DS1302的知识点就介绍这么多了。