DS1302Nebula Pi·

第 17 章 实时时钟 DS1302 实验

ronger

ronger

4075 0

前言

在许多系统当中都需要精确的时钟功能,因此时钟芯片孕育而生。其中美国达拉斯 DALLAS 公司设计的 DS1302 是一款非常流行的数字时钟芯片。 DS1302 是一款具有涓细电流充电能力的低功耗实时时钟芯片。它可以对年、月、日、星期、时、分、秒进行计时,并且具有闰年功能。年计数可达到 2100 年。

17.1 DS1302 功能简介

DS1302 内部包含 31 字节的通用 RAM,实现设置备用电池功能。采用3线制的串行数据通信接口,并且适用于大多数的微处理器。工作电压范围达到2~ 5V,与 5V TTL 电平完全兼容。支持单字节或多字节时钟、RAM 数据读、写操作。当工作电压为 2V 时,工作电流低至 320nA。工业级 DS1302 正常工作温度范围为:-40℃~85℃,芯片包括直插和贴片两种封装模式,封装示意图如下所示:

图 17-1 DS1302 封装示意图

DS1302 典型通信电路如下图所示,

图 17-2 DS1302 典型通信图

如上图所示,只需3根线 CE、I/O、SCLK 便可实现处理器与 DS1302 之间通信,上图中 X1, X2 之间为外接时钟晶振, VCC2 为电源供电端, VCC1 为备用电池端。各管脚定义及功能如下所示:

表 17-1 DS1302 管脚定义

管脚号管脚名称功能介绍
1VCC2为芯片的主供电端, VCC1 为芯片的备用供电端。当 VCC2 端电压值比 VCC1 电压值大0.2V 以上时,由 VCC2 供电,否则由 VCC1 供电。通常情况下在 VCC1 处接入备用电池,当系统主电源掉电后,芯片切换到 VCC1 供电,保证芯片时钟继续运行。
2X1两端直接与 32.768KHz 的石英晶振相连接,或者 X1 连接外部 32.768KHz 时钟, X2 悬空。
3X2两端直接与 32.768KHz 的石英晶振相连接,或者 X1 连接外部 32.768KHz 时钟, X2 悬空。
4GND地引脚
5CE读或者写数据期间 CE 必须被拉高,称为片选信号
6I/O双相数据输入/输出口
7SCLK数据传输的同步时钟,由外部处理器提供
8VCC1备用电池接口

17.2 单字节操作模式

与 DS1302 进行数据通信时,首先得向 DS1302 传输一个字节的控制指令,控制指令位定义如下所示。

图 17-3 控制指令字节

字节的最高位 bit7 必须为1,否则无法向 DS1302 写入数据。 Bit6 为1时,表示后续对 31 字节的 RAM 进行读写操作。为0时,表示后续将对时钟寄存器进行读写操作。 Bit5~bit1 为后续操作的时钟寄存器或 RAM 的地址。最低位 bit0 为1时,表示读取 DS1302 的数据,为0时,表示向 DS1302 写入数据。对 DS1302 进行单字节模式的读、写操作时序如下图所示。

图 17-4 单字节读写时序

单字节写操作(Single-Byte Write)时序为首先向 DS1302 写入控制指令,紧接着写入一个字节的数据。写入的顺序为低位在前,高位在后的传输方式,要求在时钟上升沿准备好数据。

单字节读操作(Single-Byte Read)时序为首先向 DS1302 写入控制指令,紧接着 SCLK 的下降沿 DS1302 有数据 D0 输出,因此在接下来的上升沿前读取稳定的 D0 值,依次类推至 D7。根据上图时序要求,往 DS1302 写入一个字节和读取一个字节的函数如下图所示:

#define uchar unsigned char  
 #define  uint unsigned int  
   
 sbit    CE_1302 = P0^5;  //DS1302 通信引脚 CE, I/O, SCLK 定义
sbit   IO_1302 = P0^4;
sbit SCLK_1302 = P0^3;
  
// 写字节
void WrByte_1302(uchar dat)
{
   uchar j;
   bit flag;
 
   for(j=1;j<=8;j++)
   {   // 从低到高依次将 1Byte 数据写入 DS1302
       flag = dat&0x01;
 
       IO_1302 = flag;// 将要写的位放到总线
       SCLK_1302 = 0;
       SCLK_1302 = 1;// 产生一个上升沿,完成1位数据写入
 
       dat=dat>>1;// 将数据移到下一位
   }
}
// 读字节
uchar RdByte_1302(void)
{
   uchar dat,flag,j;
   for(j=1;j<=8;j++)
   {
       SCLK_1302 = 1;// 产生一个下降沿
       SCLK_1302 = 0;
 
       flag = IO_1302;// 读取 DS1302 发出的一位数据
       dat=(dat>>1)|(flag<<7);// 读出的值最低位在前面
   }
   return dat;
}

图 17-5 字节读、写函数

如上图所示,写字节函数 WrByte_1302() 中,要求单片机在时钟 SCLK_1302 上升沿前将数据放到数据总线 IO_1302 上,然后产生一个 SCLK_1302 上升,完成 1bit 数据的写入,同时要求 1Byte 的数据低位在前,高位在后依次发送。读字节函数 RdByte_1302() 中,要求在时钟 SCLK_1302 下降沿之后,将总线 IO_1302 数据读出,同时要求 1Byte 的数据低位在前,高位在后依次读取。

单字节操作模式的读、写函数如下图所示:

// 单字节写模式
void WrSingle_1302(uchar addr,uchar dat)
{
    CE_1302 = 1;// 拉高片选
    WrByte_1302(addr);// 写入地址及控制指令
    WrByte_1302(dat);// 写入数据
    CE_1302 = 0;// 拉低片选
    SCLK_1302 = 0;// 释放始终总线,满足下次操作时序要求(非常重要)
  
}
// 单字节读模式
uchar RdSingle_1302(uchar addr)
{
   uchar dat;
 
   CE_1302 = 1;// 拉高片选
   WrByte_1302(addr);// 写入地址及控制指令
   dat = RdByte_1302();// 读取一个字节数据
   CE_1302 = 0;// 拉低片选
 
   return dat;
}

图 17-6 单字节读、写模式函数

我们这里重点讲述 DS1302 的时钟功能,因此与涓流充电有关的 31 字节 RAM 操作这里不做详细的介绍。与实时时钟有关的寄存器如图所示。

图 17-7 实时时钟寄存器定义

与时钟有关的寄存器总共有9个如上图所示,除了第一行,每一行均为一个寄存器。前7个分别为:秒、分、时、日、月、星期、年,均为8位寄存器。以秒寄存器为例介绍时间的表示法,其中 bit6~bit4 为秒的十位, bit3~bit0 为秒的个位。 59 秒时, bit6~bit4="101", bit3~bit0 ="1001",其它依此类推。另外,设置"时"寄存器的 bit7 可以设置为 12 小时或 24 小时制。上述寄存器的读写控制指令字节分别如图左侧两列所示。

秒寄存器的 CH(bit7)定义为时钟运行标志位,当该位被设置成1时,时钟计时停止,并且 DS1302 进入低功耗模式。当设置为0时,启动计时。上电初始状态时,该位状态不定,因此在时钟初始化时确保该位被清0,保证后续时钟芯片正常运行。

第8个寄存器为控制寄存器, WP (bit7)为写保护位,当设置为1时,无法向 DS1302 写入数据,上电时该位状态不定,因此,需要对 DS1302 其它寄存器进行写操作之前,务必先将 WP 设置为0。第9个寄存器不影响时钟功能,暂不做介绍。

因此,在 DS1302 应用时要对它进行初始化,首先解除写保护,然后将与时间有关的7个寄存器赋初值,将初始化的内容放到函数 Init_1302(Uchar *SetTime) 中。另外,我们将从 DS1302 读取7个时间值的操作放到函数 GetTime(*CurrentTime)中,如下图所示。

//1302 初始化
void Init_1302(uchar *SetTime)
{
      uchar j;
  
      CE_1302 = 0;// 初始化通信引脚
    SCLK_1302 = 0;
  
    WrSingle_1302(0x8E,0x00);// 解除写保护(WP=0)
     
   for(j=0;j<=6;j++)
   {
       WrSingle_1302(0x80+2*j,SetTime[j]);// 写入7个时钟数据
   }
   // Wr Burst _1302(SetTime);// 当采用 Burst 模式时,使用此语句替代上面 for 循环语句
}
// 获取当前时间值
void GetTime(uchar *CurrentTime)
{
     uchar j;
 
     CE_1302 = 0;// 初始化通信引脚
   SCLK_1302 = 0;
 
   for(j=0;j<=6;j++)
   {
        *CurrentTime = RdSingle_1302(0x81+2*j);// 读取7个时钟数据
        CurrentTime++;
   }
     
   // Rd Burst _1302(CurrentTime); // 当采用 Burst 模式时,使用此语句替代上面 for 循环语句
}

17-8 初始化与时间值获取函数

按照惯例将上述代码打包放入 DS1302 驱动文件中, Drive_ DS1302 .h, Drive_ DS1302 .c。

到目前为止,我们已经学习了时钟芯片 DS1302 的功能介绍,以及初始化和时钟获取函数的编写, Nebula Pi 单片机开发板上 DS1302 电路原理图如下所示,三根通信线分别与单片机的 I/O 口相连接。

图 17-9 DS1302 原理图

我们利用上面编写的函数以及学习的单片机知识,开始编写一个小的时钟显示综合应用程序。程序的功能为:

上电时由单片机对 DS1302 进行初始化,设置时间为 2021 年、星期二、6月1日、23 时 58 分 56 秒。初始化完成后,每隔 500ms 获取 DS1302 的时间,并将时间显示到 1602 液晶显示器上,主函数程序如下图所示:

/*******************************************************************
*             实时时钟 DS1302 显示测试
* ******************************************************************
* 【主芯片】:STC89SC52/STC12C5A60S2
* 【主频率】: 11.0592MHz
*
* 【版  本】: V1.0
* 【作  者】: stephenhugh
* 【网  站】:https://rymcu.taobao.com/
* 【邮  箱】:
*
* 【版  权】All Rights Reserved
* 【声  明】此程序仅用于学习与参考,引用请注明版权和作者信息!
*
*******************************************************************/
#include <reg52.h>  
#include <Drive_1602.h>  
#include <Drive_DS1302.h>  
 
#define uchar unsigned char  
#define  uint unsigned int  
 
#define FOSC 11059200 // 单片机晶振频率  
#define T_1ms (65536 - FOSC/12/1000)  // 定时器初始值计算  
 
sbit FM = P0^0;// 蜂鸣器
sbit DU = P0^6;// 数码管段选、位选引脚定义
sbit WE = P0^7;
     
uchar T_flag  = 0;// 定时 500ms 标志位
uchar str[23]=0;  // 字符临时存储变量
 
unsigned char code SetTime[7]={//2021 年,星期二, 06 月 01 日,23 时 58 分 56 秒,时间初始值
                           //0x56,0x58,0x23,0x31,0x12,0x07,0x17};
                           0x56,0x58,0x23,0x01,0x06,0x02,0x21};
uchar CurrentTime[7]={0};// 存储时间变量
 
void main()
{
   Init_1602();//1602 初始
 
   P2 = 0xff;// 关闭所有数码管
   WE = 1;
   WE = 0;
 
   TMOD = 0x01;     // 定时器工作模式配置
   TL0  = T_1ms;   // 装载初始值
   TH0  = T_1ms>>8;
   TR0  = 1;        // 启动定时器
   ET0  = 1;        // 允许定时器中断
   EA   = 1;        // 开总中断
 
 
   Init_1302(SetTime);//1302 初始化
   while(1)
   {
       if(T_flag)//500ms 定时
       {
           T_flag = 0;
 
           GetTime(CurrentTime);// 获取时间
 
           str[0] = '2';
           str[1] = '0';
           str[2] = (CurrentTime[6]>>4)+'0';  // 年
           str[3] = (CurrentTime[6]& 0x0F)+'0';
           str[4] = '-';
           str[5] = (CurrentTime[4]>>4)+'0';  // 月
           str[6] = (CurrentTime[4]& 0x0F)+'0';
           str[7] = '-';
           str[8] = (CurrentTime[3]>>4)+'0';  // 日
           str[9] = (CurrentTime[3]& 0x0F)+'0';
          str[10] = '\0';
          str[11] = (CurrentTime[2]>>4)+'0';  // 时
          str[12] = (CurrentTime[2]& 0x0F)+'0';
          str[13] = ':';
          str[14] = (CurrentTime[1]>>4)+'0';  // 分
          str[15] = (CurrentTime[1]& 0x0F)+'0';
          str[16] = ':';
          str[17] = (CurrentTime[0]>>4)+'0';  // 秒
          str[18] = (CurrentTime[0]& 0x0F)+'0';
          str[19] = ' ';
          str[20] = (CurrentTime[5]>>4)+'0';  // 星期
          str[21] = (CurrentTime[5]& 0x0F)+'0';
          str[22] = '\0';
             
           Disp_ 1602 _str(1,4,str); // 将获得的时间分别显示到 1602 的第一二行
           Disp_1602_str(2,3,str+11);
       }
   }
}
 
// 定时器0中断子程序,定时 1ms
void timer0() interrupt 1
{
   static uint T_500ms = 0;
 
   TL0 = T_1ms;// 重装初始值
   TH0 = T_1ms>>8;
 
   T_500ms++;
   if(T_500ms>=500)//500ms,置位 T_flag
   {
       T_500ms = 0;
       T_flag = 1;
   }
}

图 17-10 主函数

显示结果如下图所示:

图 17-11 时间显示试验结果

17.3 突发操作模式

上面我们讲解的是以单字节的模式,从 1302 中连续读取时间数据。仔细的同学可能会发现一个问题,就是我们连续读7个时间寄存器是有先后顺序的,会有读错数据的风险。例如我们要读的时间为 23 时 59 分 59 秒,最开始时我们把 59 秒读出来了,如果刚好在你读完的时候 59 秒变成了 00 秒, 59 分变成了 00 分, 23 时变成了 00 时,接下来把分、时依次读出来,因此我们读出来的时间为 00 时 00 分 59 秒,很显然这个时间是不对的。下面我们讲解的突发操作模式有效的解决了这个问题。

在突发操作读模式下,当 DS1302 收到突发读数据指令, DS1302 首先会把8个时间寄存器的数据同时读出存放在8个二级时间寄存器中,然后依次把8个二级时间寄存器的数据输出给单片机,突发读专用指令为 0xBF。同样,当我们需要写 DS1302 时,当收到突发写指令后, DS1302 将接收到的8个连续数据存储到8个二级时间寄存器中,然后同时将8各数据写到时间寄存器中,突发写专用指令为 0xBE。根据上述原理,编写突发写模式和突发读模式函数如下图所示。

// 突发写模式
void WrBurst_1302(uchar *SetTime)
{
    uchar j;
  
    CE_1302 = 1;// 拉高片选
    WrByte_1302(0xBE);//Burst 模式写专用指令
    for(j=0;j<=6;j++)
    {
       WrByte_1302(SetTime[j]);// 写入7位时钟数据
   }
       WrByte_1302(0);// 写第8个寄存器,不写的话可能无法使用
   CE_1302 = 0;// 拉低片选
}
// 突发读模式
void RdBurst_1302(uchar *CurrentTime)
{
   uchar j;
 
   CE_1302 = 1;// 拉高片选
   WrByte_1302(0xBF);//Burst 模式读专用指令
   for(j=0;j<=6;j++)
   {
       *CurrentTime = RdByte_1302();// 读取一个字节数据;
       CurrentTime++;
   }
   CE_1302 = 0;// 拉低片选
}

图 17-12 突发模式读、写函数

如上图所示,首先向 DS1302 写入突发读或者写指令,然后紧接着读取或写入8个时间数据。将函数更新到 DS1302 的驱动文件中,头文件如下图所示。

#ifndef __DS1302_H__  
#define __DS1302_H__  
  
//1302 初始化
extern void Init_1302(unsigned char *SetTime);
// 获取时间
extern void GetTime(unsigned char *CurrentTime);
// 单字节模式写
void WrSingle_1302(unsigned char addr,unsigned char dat);
// 单字节模式读
unsigned char RdSingle_1302(unsigned char addr);
// 突发模式写
void WrBurst_1302(unsigned char *SetTime);
// 突发模式读
void RdBurst_1302(unsigned char *CurrentTime);
 
#endif

图 17-13 DS1302 的驱动头文件

突发模式的应用与单字节模式类似,只需用图 17-8 所示的第 15 行替代 11-14 行的 for 循环语句,第 31 行替代 25-29 行的 for 循环语句便可,这里不再赘述。

17.4 本章小结

本章详细讲解了实时时钟芯片 DS1302 的工作原理,并且编写了驱动函数以及实际应用,后续可以直接使用驱动文件,无需重复造轮子了。

所属系列

从当前文章继续阅读它所在合集中的前后内容。

关于我和 Hugh 学嵌入式开发这件事 —— 51 篇 第 19 / 22 篇
查看合集

> 本作品集内教程基于 [Hugh](https://rymcu.com/user/hugh) 的创作基础上进行修订发布 关于我~和昊楠君~(昊楠君已经阵亡了,现在和 Hugh )学嵌入式开发这件事。

相关文章

优先推荐同专题、同标签和同作者内容,补足热门文章。

评论 0

登录 后参与评论

评论

成为第一个评论的人