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内部结构框图
内部结构中第一块是电源控制;第二块是X1和X2时钟部分;第三块是RAM存储部分,用于存储时间数据;最后一块是命令控制部分,包括CE使能、输入移位寄存器和读写访问
三、DS1302寄存器和数据时序
3.1 寄存器
下面为RTC相关的寄存器
从上到下依次是秒寄存器、分寄存器,小时寄存器、日寄存器、月寄存器、星期寄存器、年寄存器、写保护寄存器(WP给1时、写入操作无效)和存储涓流充电寄存器
上图前两列是命令字,地址/命令字组成下图所示:总共8位,最高位默认为1,第6位是决定操作RAM还是时钟,A4-A0是地址,最后一位是读写位(秒寄存器为例,秒寄存器地址是00000,读操作时,8位为1000 0001,刚好是81,写操作时,8位位1000 0000,刚好是80)
3.2 数据时序
下图为数据时序
CE给1为使能RTC。SCLK是时钟线,每来一个上升沿,单片机将地址/数据写入到DS1302,每来一个下降沿时,单片机从DS1302里读数据,即DS1302自动将时间数据发送给单片机。IO是数据线,前8位为地址命令字,低位在前,后8位为时间数据
单字节读:CE使能后,SCLK每来一个上升沿,单片机向DS1302发送地址命令字,确定在哪个地方读;然后每来一个下降沿,DS1302自动把数据放在IO线上,即DS1302发送时间数据给单片机,单片机读时间
单字节写:CE使能后,SCLK每来一个上升沿,单片机向DS1302发送地址命令字,确定在哪个地方写;然后继续每来一个上升沿,单片机向DS1302发送时间数据,用以设置时间
四、程序编写
4.1 DS1302时钟
DS1302显示时钟需要编写两个主要函数
其一是单字节读,单片机给DS1302发送地址命令字,DS1302再给单片机发送时间数据,单片机读DS1302的时间数据。
其二是单字节写,用于设置时间,单片机给DS1302发送地址命令字和时间数据。
由于DS1302的时间数据是BCD码,所以写入时间时,需要转为BCD码写入;单片机读数据时,要将BCD转为十进制数,显示在显示屏上
下面为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可调时钟
可调时钟需要利用按键去设置时间,所以只需要加上调节年月日等等的逻辑判断,即可实现一个完整的可调时钟
下面是main.c
#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;
}
}