DS1302时钟芯片
DS1302 是一个实时时钟芯片,可以提供秒、分、小时、日期、月、年等信息,并且还有软件自动调整的能力,可以通过配置 AM/PM 来决定采用 24 小时格式还是 12 小时格式。拥有 31 字节数据存储 RAM。串行 I/O 通信方式,相对并行来说比较节省IO口的使用。DS1302 的工作电压比较宽,在 2.0~5.5V 的范围内都可以正常工作。DS1302 这种时钟芯片功耗一般都很低,它在工作电
目录
DS1302简介
前言
- DS1302 是一个实时时钟芯片,可以提供秒、分、小时、日期、月、年等信息,并且还有软件自动调整的能力,可以通过配置 AM/PM 来决定采用 24 小时格式还是 12 小时格式。
- 拥有 31 字节数据存储 RAM。
- 串行 I/O 通信方式,相对并行来说比较节省IO口的使用。
- DS1302 的工作电压比较宽,在 2.0~5.5V 的范围内都可以正常工作。
- DS1302 这种时钟芯片功耗一般都很低,它在工作电压 2.0V 的时候,工作电流小于 300nA。
- DS1302 共有 8 个引脚,有两种封装形式,一种是 DIP-8 封装,芯片宽度(不含引脚) 是 300mil,一种是 SOP-8 封装,有两种宽度,一种是 150mil,一种是 208mil。
DS1302内部结构
理解:
- 电源控制模块:DS1302 有两个电源输入,一个是主电源VCC2,另外一个是备用电源VCC1,比如可以用电池或者大电容,这样做是为了在系统掉电的情况下,我们的时钟还会继续走。如果使用的是充电电池,还可以在正常工作时设置充电功能,给我们的备用电池进行充电。
- 时钟电路:X1和X2接的是外部的晶振,并通过内部的电路进行一些设置(频率分频等)最终会输出1Hz的标准计时频率
- 实时时钟RAM:实时时钟就是我们的寄存器,我们内部的时间都是存在该寄存器里面的,里面有31*8个RAM,我们只需要对寄存器进行读写就可以访问时间了。
- 命令控制逻辑和输入移位寄存器模块:该模块决定了怎么去读写寄存器(SCLK每来一个上升沿,那么IO数据输入移位一次,数据到达命令控制逻辑后要经过CE开关,CE为高电平时数据移位才是有用的)。
DS1302引脚图
注意:
- 我们在上电时,时钟芯片会以VCC当做电源,同时会对备用电池进行冲电,一旦掉电他就会自动切换到备用电池,保障时钟继续运行。
- DS1302与单片机通信只需要RES(复位线)、IO(数据线)、SCLK(串行时钟)三根信号线(利用这三根引脚,单片机就可以把芯片内部的时钟读出来)
- VCC1为+3V供电;VCC2为+5V供电
DS1302常用寄存器
注意:
- 这里面日历寄存器的值采用BCD码的存储行式。
- BCD码转10进制:DEC=BCD/16*10+BCD%16;10进制转BCD码:BCD=DEC/10*16+DEC%10
- 秒寄存器中D7的CH就是时钟暂停,若把该位给1,那么这个秒就会停止,进而导致时钟整个都暂停了;若该位给0则时钟运行(秒钟导致分钟进位,分钟导致小时进位,以此类推)。
- 时寄存器中D7为0则设置的为24小时模式,D7为1则设置的为24小时模式;D5为0表示AM,D5为1表示PM
- 年寄存器只能记录到2000年到2099年(因为只有十位和个位可变)
- 写保护寄存器可以用来关闭写保护,通过发送数据0x00关闭写保护;发送数据0x80打开写保护
寄存器控制指令
解释:
- D7:固定为1
- D6:RAM/CK杠位,片内的RAM或日历、时钟寄存器选择位,1为片内RAM,0为日历时钟寄存器
- D5——D1:地址位,用于选择进行读写的日历、时钟寄存器或片内RAM
- D0:RD/W读写位,0为写,1为读
常用寄存器指令
注意:向寄存器写入时,那么写入的命令为特定寄存器的地址,若从寄存器读取时,那么读取的命令为寄存器的地址+1。
DS1302控制时序
单字节读
理解:首先将CE置位高电平开始读,将命令字的最低位设置到IO口中;之后时钟给个上升沿,那么命令字的最低位便会被写入单片机;将时钟再置为0后把数据的第2位放入IO口,时钟再给上升沿如此循环往复将最高位也写入IO口,之后时钟给低电平,此时便完成了对命令字的写入操作;DS1302接收到数据后,就会在紧跟着这个时钟的下降沿将这个数据放在IO线上,单片机此时将IO口释放掉,就开始读出DS1302发来的数据(每个下降沿来一个数据)读取完后时钟置0,CE置0,整个操作结束。
代码辅助理解
//单字节读
unsigned char DS1302_ReadByte(unsigned char Command){
DS1302_RST=0;
DS1302_SCLK=0;
unsigned char i,Data=0x00;
DS1302_RST=1;
//写命令字
for(i=0;i<8;i++){
DS1302_IO=Command&(0x01<<i); //取出第i位
//写入
DS1302_SCLK=0;
DS1302_SCLK=1;
//先0后1那么就会在最后致使DS1302_SCLK=1,进而保障了for循环内都与写入有关
}
//单片机开始读取数据
for(i=0;i<8;i++){
DS1302_SCLK=1;
DS1302_SCLK=0;
//若端口状态为1,那么就相当于把IO的1给了Data(先读低位,后读高位)
if(DS1302_IO==1){
Data=Data|(0x01<<i);
}
}
DS1302_RST=0;
DS1302_IO=0;
return Data;
}
单字节写
理解:首先将CE置高电平开始写;将命令字的最低位设置到IO口中;之后时钟给个上升沿,那么命令字的最低位便会被写入单片机;将时钟再置为0后把数据的第2位放入IO口,时钟再给上升沿如此循环往复将最高位也写入IO口,之后时钟给低电平,此时便完成了对命令字的写入操作(发第二个字节的数据和第一个字节命令字过程一样,只不过操作完后再将CE置0)
代码辅助理解
//单字节写
void DS1302_WriteByte(unsigned char Command,unsigned char Data){
DS1302_RST=0;
DS1302_SCLK=0;
unsigned char i;
DS1302_RST=1;
//写命令字
for(i=0;i<8;i++){
DS1302_IO=Command&(0x01<<i); //取出第i位
//写入
DS1302_SCLK=1;
DS1302_SCLK=0;
}
//写数据
for(i=0;i<8;i++){
DS1302_IO=Data&(0x01<<i); //取出第i位
//写入
DS1302_SCLK=1;
DS1302_SCLK=0;
}
DS1302_RST=0;
DS1302_IO=0;
}
注意:
- SCLK每震荡一次,那么就会读取或写入一位数据,在时钟的上升沿,IO口上的电平将会被写入DS1302,在时钟的下降沿,IO口的数据将会从DS1302读出。
- IO口中数据分两部分,第一部分为指定那一个地址,后面的阶段为真正的读取/写入数据
- 无论是命令还是数据,一个字节传送时都是低位在前,高位在后(见时序图)。
- 向芯片写入数据时,首先关闭写保护。
仿真案例
需求:根据数组设定,LCD液晶屏上显示年月日,时分秒(步进)。
电路图
keil文件
#include "reg51.h"
//注意:想看DS1302代码,请见61行后
sbit RS=P3^0;
sbit RW=P3^1;
sbit E=P3^2;
void delay(unsigned int n){
unsigned int i=0,j=0;
for(i=0;i<n;i++){
for(j=0;j<120;j++);
}
}
//写指令
void writecom(unsigned char com){
//写指令
RS=0;
RW=0;
E=0;
P2=com;
delay(5);
E=1;
E=0;
}
//写数据
void writedat(unsigned char dat){
//写指令
RS=1;
RW=0;
E=0;
P2=dat;
delay(5);
E=1;
E=0;
}
//初始化液晶屏
void initlcd(){
writecom(0x38);
writecom(0x0c);
writecom(0x06);
writecom(0x01);
}
int LCD_Pow(int X,int Y){
unsigned char i;
int Result=1;
for(i=0;i<Y;i++){
Result*=X;
}
return Result;
}
//展示数字
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length){
unsigned char i;
if(Line==1){
writecom(0x80|(Column-1));
}else{
writecom(0x80|(Column-1)+0x40);
}
for(i=Length;i>0;i--){
writedat('0'+Number/LCD_Pow(10,i-1)%10);
}
}
//DS1302篇
sbit DS1302_RST=P3^3;
sbit DS1302_SCLK=P3^4;
sbit DS1302_IO=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
unsigned char DS1302_Time[]={19,11,16,12,59,55,6}; //年月日时分秒,星期
//初始化DS1302
void DS1302_Init(){
DS1302_RST=0;
DS1302_SCLK=0;
}
//单字节写
void DS1302_WriteByte(unsigned char Command,unsigned char Data){
unsigned char i;
DS1302_RST=1;
//写命令字
for(i=0;i<8;i++){
DS1302_IO=Command&(0x01<<i); //取出第i位
//写入
DS1302_SCLK=1;
DS1302_SCLK=0;
}
//写数据
for(i=0;i<8;i++){
DS1302_IO=Data&(0x01<<i); //取出第i位
//写入
DS1302_SCLK=1;
DS1302_SCLK=0;
}
DS1302_RST=0;
DS1302_IO=0;
}
//单字节读
unsigned char DS1302_ReadByte(unsigned char Command){
unsigned char i,Data=0x00;
Command|=0x01; //因为我想给地址的时候给写的地址
DS1302_RST=1;
//写命令字
for(i=0;i<8;i++){
DS1302_IO=Command&(0x01<<i); //取出第i位
//写入
DS1302_SCLK=0;
DS1302_SCLK=1;
//先0后1那么就会在最后致使DS1302_SCLK=1,进而保障了for循环内都与写入有关
}
//单片机开始读取数据
for(i=0;i<8;i++){
DS1302_SCLK=1;
DS1302_SCLK=0;
//若端口状态为1,那么就相当于把IO的1给了Data(先读低位,后读高位)
if(DS1302_IO==1){
Data=Data|(0x01<<i);
}
}
DS1302_RST=0;
DS1302_IO=0;
return Data;
}
//10进制转化BCD码
unsigned char DecToBcd(unsigned char dec){
return (dec/10*16+dec%10);
}
//BCD码转10进制
unsigned char BcdToDec(unsigned char bcd){
return (bcd/16*10+bcd%16);
}
//设定时间
void DS1302_SetTime(){
DS1302_WriteByte(DS1302_WP,0x00); //关闭写保护
DS1302_WriteByte(DS1302_YEAR,DecToBcd(DS1302_Time[0]));
DS1302_WriteByte(DS1302_MONTH,DecToBcd(DS1302_Time[1]));
DS1302_WriteByte(DS1302_DATE,DecToBcd(DS1302_Time[2]));
DS1302_WriteByte(DS1302_HOUR,DecToBcd(DS1302_Time[3]));
DS1302_WriteByte(DS1302_MINUTE,DecToBcd(DS1302_Time[4]));
DS1302_WriteByte(DS1302_SECOND,DecToBcd(DS1302_Time[5]));
DS1302_WriteByte(DS1302_DAY,DecToBcd(DS1302_Time[6]));
DS1302_WriteByte(DS1302_WP,0x80); //打开写保护
}
//读取时间
void DS1302_ReadTime(){
unsigned char temp=0;
temp=DS1302_ReadByte(DS1302_YEAR);
DS1302_Time[0]=BcdToDec(temp);
temp=DS1302_ReadByte(DS1302_MONTH);
DS1302_Time[1]=BcdToDec(temp);
temp=DS1302_ReadByte(DS1302_DATE);
DS1302_Time[2]=BcdToDec(temp);
temp=DS1302_ReadByte(DS1302_HOUR);
DS1302_Time[3]=BcdToDec(temp);
temp=DS1302_ReadByte(DS1302_MINUTE);
DS1302_Time[4]=BcdToDec(temp);
temp=DS1302_ReadByte(DS1302_SECOND);
DS1302_Time[5]=BcdToDec(temp);
temp=DS1302_ReadByte(DS1302_DAY);
DS1302_Time[6]=BcdToDec(temp);
}
unsigned char Second=0;
void main()
{
initlcd();
DS1302_Init();
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);
}
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)