大家好,欢迎来到IT知识分享网。
51单片机-DS1302
一、DS1302概述
- DS1302是由美国DALLAS公司推出的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时,且具有闰年补偿等功能
- RTC:实时时钟,是一种集成电路,通常称为时钟芯片,有备用电池,可以掉电运行
定时器也可以实现实时时钟的功能,采用DS1302是因为定时器实现精度不高、占用单片机CPU的时间且定时器时钟不可以掉电运行
1.1 BCD码
- RTC内部的数据不是以正常的二进制数来存储时间数据的,而是以BCD码来存储时间数据的
- BCD码用4位二进制数来表示1位十进制数。8位二进制数,高4位表示十进制的十位,低4位表示十进制的个位(例:0001 0011表示13,1000 0101表示85,0001 1010不合法)
- BCD码转十进制:DEC = BCD/16*10(十位) + BCD/16(个位)
- 十进制转BCD码:BCD = DEC/10*16 + DEC%10
二、DS1302电路
下面为DS1302的工作电路
- VCC2/1:主电源/备用电池
- GND:接地
- CE:芯片使能
- IO:数据输入/输出
- SCLK:串行时钟
- X1、X2:32.768KHz晶振,时钟部分
三、DS1302寄存器和数据时序
3.1 寄存器
上图前两列是命令字,地址/命令字组成下图所示:总共8位,最高位默认为1,第6位是决定操作RAM还是时钟,A4-A0是地址,最后一位是读写位(秒寄存器为例,秒寄存器地址是00000,读操作时,8位为1000 0001,刚好是81,写操作时,8位位1000 0000,刚好是80)
3.2 数据时序
单字节读:CE使能后,SCLK每来一个上升沿,单片机向DS1302发送地址命令字,确定在哪个地方读;然后每来一个下降沿,DS1302自动把数据放在IO线上,即DS1302发送时间数据给单片机,单片机读时间
单字节写:CE使能后,SCLK每来一个上升沿,单片机向DS1302发送地址命令字,确定在哪个地方写;然后继续每来一个上升沿,单片机向DS1302发送时间数据,用以设置时间
四、程序编写
4.1 DS1302时钟
下面为DS1302.c
#include <REGX52.H> sbit DS1302_SCLK = P3^6;//时钟线 sbit DS1302_IO = P3^4;//数据线 sbit DS1302_CE = P3^5;//使能 #define DS1302_SEC 0x80 //秒寄存器 #define DS1302_MIN 0x82 //分寄存器 #define DS1302_HOUR 0x84 //小时寄存器 #define DS1302_DATE 0x86 //日寄存器 #define DS1302_MON 0x88 //月寄存器 #define DS1302_DAY 0x8A //星期寄存器 #define DS1302_YEAR 0x8C //年寄存器 #define DS1302_WP 0x8E //写保护寄存器 unsigned char Time[] = {
24,2,18,21,20,50,7}; //时间初始值 /* 函数功能:时钟初始化 */ void DS1302_Init(void) {
DS1302_CE = 0; DS1302_SCLK = 0; } /* 函数功能:MCU写地址命令字和时间数据给DS1302 形式参数:地址命令字,时间数据 */ void DS1302_WriteByte(unsigned char Command,Data) {
unsigned char i; DS1302_CE = 1; //DS1302使能 for(i=0;i<8;i++) //写命令字 {
DS1302_IO = Command & (0x01<<i); //低位在前 DS1302_SCLK = 1; //上升沿写 DS1302_SCLK = 0; //清零 } for(i=0;i<8;i++) //写时间数据 {
DS1302_IO = Data & (0x01<<i);//低位在前 DS1302_SCLK = 1; DS1302_SCLK = 0; } DS1302_CE = 0; //使能初始化 } /* 函数功能:MCU从固定地址里读时间数据 形式参数:地址命令字 返回值:时间数据 */ unsigned char DS1302_ReadByte(unsigned char Command) {
unsigned char i,Data = 0x00; //局部变量初始值不一定是0 Command |= 0x01; //读命令字,最低位是1 DS1302_CE = 1; for(i=0;i<8;i++) //写命令字 {
DS1302_IO = Command & (0x01<<i); //上升沿写8位命令字 DS1302_SCLK = 0; DS1302_SCLK = 1; //先0再1上升沿写 } for(i=0;i<8;i++) //读数据 {
DS1302_SCLK = 1; //重复给(周期) DS1302_SCLK = 0; if(DS1302_IO){
Data |= (0x01<<i);} //下降沿读8位数据,IO口上已经有数据(DS1302是自动把数据放在IO线上) } DS1302_CE = 0; DS1302_IO = 0; //清零 return Data; } /* 函数功能:调用函数表示开始设置时间:年 月 日 小时 分 秒 星期 */ void DS1302_SetTime(void) {
DS1302_WriteByte(DS1302_WP,0x00); //关闭写保护,写时间时,需要关闭写保护 DS1302_WriteByte(DS1302_YEAR,Time[0]/10*16+Time[0]%10); //写入年(十进制转为8位BCD码) DS1302_WriteByte(DS1302_MON,Time[1]/10*16+Time[1]%10); DS1302_WriteByte(DS1302_DATE,Time[2]/10*16+Time[2]%10); DS1302_WriteByte(DS1302_HOUR,Time[3]/10*16+Time[3]%10); DS1302_WriteByte(DS1302_MIN,Time[4]/10*16+Time[4]%10); DS1302_WriteByte(DS1302_SEC,Time[5]/10*16+Time[5]%10); DS1302_WriteByte(DS1302_DAY,Time[6]/10*16+Time[6]%10); DS1302_WriteByte(DS1302_WP,0x80);//打开写保护 } /* 函数功能:调用函数表示读时间:年 月 日 小时 分 秒 星期 */ void DS1302_ReadTime(void) {
unsigned char Temp; Temp = DS1302_ReadByte(DS1302_YEAR); //读年寄存器的数据 Time[0] = Temp/16*10+Temp%16; //将读的数据,转为十进制,并存入数组 Temp = DS1302_ReadByte(DS1302_MON); Time[1] = Temp/16*10+Temp%16; Temp = DS1302_ReadByte(DS1302_DATE); Time[2] = Temp/16*10+Temp%16; Temp = DS1302_ReadByte(DS1302_HOUR); Time[3] = Temp/16*10+Temp%16; Temp = DS1302_ReadByte(DS1302_MIN); Time[4] = Temp/16*10+Temp%16; Temp = DS1302_ReadByte(DS1302_SEC); Time[5] = Temp/16*10+Temp%16; Temp = DS1302_ReadByte(DS1302_DAY); Time[6] = Temp/16*10+Temp%16; }
注意其中的Time[]数组要在声明文件中声明,以保证外部可以调用,并且在主函数中显示时间数据时,要注意解除写保护状态
4.2 DS1302可调时钟
#include <REGX52.H> #include "LCD1602.h" #include "DS1302.h" #include "Key.h" #include "Timer0.h" unsigned char KeyNum; //按键键码 unsigned char MODE; //模式(1为设置模式 0为显示模式) unsigned char TimeSetSelect; //设置位数(年 月 日 小时 分 秒 星期) unsigned char TimeSetFlashFlag; //设置确定or进入设置状态 /* 函数功能:调用函数,显示时间 */ void TimeShow(void) {
DS1302_ReadTime(); //读取时间,单片机从RTC读 LCD_ShowNum(1,1,Time[0],2); //会自动+1 LCD_ShowNum(1,4,Time[1],2); LCD_ShowNum(1,7,Time[2],2); LCD_ShowNum(2,1,Time[3],2); LCD_ShowNum(2,4,Time[4],2); LCD_ShowNum(2,7,Time[5],2); LCD_ShowNum(2,10,Time[6],1); } /* 函数功能:设置时钟 */ void TimeSet(void) {
if(KeyNum ==2) //选择设置第几位,年月日时分秒星期 {
TimeSetSelect++; TimeSetSelect%=7; //0-6 } if(KeyNum ==3) //加1 {
Time[TimeSetSelect]++; //数据++ //下面要进行合法判断 if(Time[0]>99){
Time[0]=0;} //年合法判断 if(Time[1]>12){
Time[1]=1;} //月合法判断 if(Time[1]==1 || Time[1]==3 || Time[1]==5 || Time[1]==7 || Time[1]==8 || Time[1]==10 || Time[1]==12) {
if(Time[2]>31){
Time[2]=1;} //大月份 } else if(Time[1]==4 || Time[1]==6 || Time[1]==9 || Time[1]==11) {
if(Time[2]>30){
Time[2]=1;} //小月份 } else if(Time[1]==2) //2月 {
if(Time[0]%4==0){
if(Time[2]>29){
Time[2]=1;}} //闰年的二月 29天 else{
if(Time[2]>28){
Time[2]=1;}} //平年给28天 } if(Time[3]>23){
Time[3]=0;} //时合法判断 if(Time[4]>59){
Time[4]=0;} //分合法判断 if(Time[5]>59){
Time[5]=0;} //秒合法判断 if(Time[6]>7){
Time[6]=1;} //星期合法判断 } if(KeyNum ==4) //减1 {
Time[TimeSetSelect]--; //数据-- //下面要进行合法判断 if(Time[0]<0){
Time[0]=99;} //年合法判断 if(Time[1]<1){
Time[1]=12;} //月合法判断 if(Time[1]==1 || Time[1]==3 || Time[1]==5 || Time[1]==7 || Time[1]==8 || Time[1]==10 || Time[1]==12) {
if(Time[2]<1){
Time[2]=31;} //大月份 if(Time[2]>31){
Time[2]=1;} //由于12月31日直接减月份会减到11月31日,11月没有31号,存在bug } else if(Time[1]==4 || Time[1]==6 || Time[1]==9 || Time[1]==11) {
if(Time[2]<1){
Time[2]=30;} //小月份 if(Time[2]>30){
Time[2]=1;} //过大判断 } else if(Time[1]==2) //如果是2月 {
if(Time[0]%4==0) {
if(Time[2]<1){
Time[2]=29;} if(Time[2]>29){
Time[2]=1;} } //闰年的二月 29天 else {
if(Time[2]<1){
Time[2]=28;} if(Time[2]>28){
Time[2]=1;} } //平年给28天 } if(Time[3]<0){
Time[3]=23;} //时合法判断 if(Time[4]<0){
Time[4]=59;} //分合法判断 if(Time[5]<0){
Time[5]=59;} //秒合法判断 if(Time[6]<1){
Time[6]=7;} //星期合法判断 } if(TimeSetSelect ==0 && TimeSetFlashFlag == 1) //设置位在年,Flag会0 1 500ms循环交替 {
LCD_ShowString(1,1," ");} else {
LCD_ShowNum(1,1,Time[0],2);} //更新年显示(闪烁版) if(TimeSetSelect ==1 && TimeSetFlashFlag == 1) {
LCD_ShowString(1,4," ");} else {
LCD_ShowNum(1,4,Time[1],2);} //更新月显示(闪烁版) if(TimeSetSelect ==2 && TimeSetFlashFlag == 1) {
LCD_ShowString(1,7," ");} else {
LCD_ShowNum(1,7,Time[2],2);} //更新日显示(闪烁版) if(TimeSetSelect ==3 && TimeSetFlashFlag == 1) {
LCD_ShowString(2,1," ");} else {
LCD_ShowNum(2,1,Time[3],2);} //更新时显示(闪烁版) if(TimeSetSelect ==4 && TimeSetFlashFlag == 1) {
LCD_ShowString(2,4," ");} else {
LCD_ShowNum(2,4,Time[4],2);} //更新分显示(闪烁版) if(TimeSetSelect ==5 && TimeSetFlashFlag == 1) {
LCD_ShowString(2,7," ");} else {
LCD_ShowNum(2,7,Time[5],2);} //更新秒显示(闪烁版) if(TimeSetSelect ==6 && TimeSetFlashFlag == 1) {
LCD_ShowString(2,10," ");} else {
LCD_ShowNum(2,10,Time[6],1);} //更新星期显示(闪烁版) } void main() {
LCD_Init(); DS1302_Init(); Timer0_Init(); //初始化 LCD_ShowString(1,3,"-"); LCD_ShowString(1,6,"-"); LCD_ShowString(2,3,":"); LCD_ShowString(2,6,":"); DS1302_WriteByte(0x8E,0x00); //解除芯片写保护 DS1302_SetTime(); //MCU写入时间:将往RTC里写 while(1) {
KeyNum = Key(); if(KeyNum == 1) //按键1为模式设置(设置or显示) {
if(MODE==0){
MODE = 1;TimeSetSelect = 0;} else if(MODE ==1){
MODE = 0;DS1302_SetTime();} } switch(MODE) {
case 0:TimeShow();break; //MODE=0 显示时间 case 1:TimeSet();break; //MODE=1 设置时间 } } } //1ms void Timer0_Routine() interrupt 1 {
static unsigned int T0Count; TL0 = 0x66; TH0 = 0xFC; T0Count++; if(T0Count>=500) //500ms {
T0Count = 0; TimeSetFlashFlag = !TimeSetFlashFlag; } }
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/120397.html




