DS1302 是个实时时钟芯片,我们可以用单片机写入时间或者读取当前的时间数据,下面带着大家通过阅读这个芯片的数据手册来学习和掌握这个器件。

DS1302的特点DS1302 是 DALLAS(达拉斯)公司推出的一款涓流充电时钟芯片,2001 年 DALLAS被 MAXIM(美信)收购,因此我们看到的 DS1302 的数据手册既有 DALLAS 的标志,又有MAXIM 的标志,大家了解即可。

DS1302 实时时钟芯片广泛应用于电话、传真、便携式仪器等产品领域,它的主要性能指标如下:

1) DS1302 是一个实时时钟芯片,可以提供秒、分、小时、日期、月、年等信息,并且还有软件自动调整的能力,可以通过配置 AM/PM 来决定采用 24 小时格式还是 12 小时格式。

2) 拥有 31 字节数据存储 RAM。

3) 串行 I/O 通信方式,相对并行来说比较节省 IO 口的使用。

4) DS1302 的工作电压比较宽,在 2.0~5.5V 的范围内都可以正常工作。

5) DS1302 这种时钟芯片功耗一般都很低,它在工作电压 2.0V 的时候,工作电流小于300nA。

6) DS1302 共有 8 个引脚,有两种封装形式,一种是 DIP-8 封装,芯片宽度(不含引脚)是 300mil,一种是 SOP-8 封装,有两种宽度,一种是 150mil,一种是 208mil。我们看一下DS1302 的引脚封装图,如图 15-3 所示。

图 15-3  DS1302 封装图


所谓的 DIP(Dual In-line Package)封装,就是双列直插式封装技术,就如同我们开发板上的 STC89C52 单片机,就是个典型的 DIP 封装,当然这个 STC89C52 还有其它的封装样式,为了方便学习使用,我们采用的是 DIP 封装。而 74HC245、74HC138、24C02、DS1302 我们用的都是 SOP(Small Out-Line Package)封装,是一种芯片两侧引出 L 形引脚的封装技术,大家可以看看开发板上的芯片,了解一下这些常识性知识。

7) 当供电电压是 5V 的时候,兼容标准的 TTL 电平标准,这里的意思是,可以完美的和单片机进行通信。

8) 由于 DS1302 是 DS1202 的升级版本,所以所有的功能都兼容 DS1202。此外 DS1302有两个电源输入,一个是主电源,另外一个是备用电源,比如可以用电池或者大电容,这样做是为了在系统掉电的情况下,我们的时钟还会继续走。如果使用的是充电电池,还可以在正常工作时,设置充电功能,给我们的备用电池进行充电。

DS1302 的特点第二条“拥有 31 字节数据存储 RAM”,这是 DS1302 额外存在的资源。这 31 字节的 RAM 相当于一个存储器一样,我们编写单片机程序的时候,可以把我们想存储的数据存储在 DS1302 里边,需要的时候读出来,这块功能和 EEPROM 有点类似,相当于一个掉电丢失数据的“EEPROM”,如果我们的时钟电路加上备用电池,那么这 31 个字节的RAM 就可以替代 EEPROM 的功能了。这 31 字节的 RAM 功能使用很少,所以在这里我们就不讲了,大家了解即可。

DS1302的硬件信息
我们平时所用的不管是单片机,还是其它一些电子器件,根据使用条件的约束,可以分为商业级和工业级,主要是工作温度范围的不同,DS1302 的购买信息如下图 15-4 所示。

图 15-4  DS1302 订购信息


我们在订购 DS1302 的时候,就可以根据图 15-4 所标识的来跟销售厂家沟通,商业级的工作温度范围略窄,是 0~70 摄氏度,而工业级可以工作在零下 40~85 摄氏度。TOP MARK就是指在芯片上印的字。

DS1302 一共有 8 个引脚,下边要根据引脚分布图和典型电路图来介绍一下每个引脚的功能,如图 15-5 和图 15-6 所示。

图 15-5  DS1302 引脚图

  
图 15 -6  DS1302典型电路


1 脚 VCC2 是主电源正极的引脚,2 脚 X1 和 3 脚 X2 是晶振输入和输出引脚,4 脚 GND是负极,5 脚 CE 是使能引脚,接单片机的 IO 口,6 脚 I/O 是数据传输引脚,接单片机的 IO口,7 脚 SCLK 是通信时钟引脚,接单片机的 IO 口,8 脚 VCC1 是备用电源引脚。考虑到KST-51 开发板是一套以学习为目的的板子,加上备用电池对航空运输和携带不方便,所以 8脚没有接备用电池,而是接了一个 10uF 的电容,这个电容就相当于一个电量很小的电池,经过试验测量得出其可以在系统掉电后仍维持 DS1302 运行 1 分钟左右,如果大家想运行时间再长,可以加大电容的容量或者换成备用电池,如果掉电后不需要它再维持运行,也可以干脆悬空,如图 15-7 和图 15-8 所示。

图 15-7  DS1302 电容作备用电源


图 15 -8  DS1302无备用电源


涓流充电功能,基本也用不到,因为实际应用中很少会选择可充电电池作为备用电源,成本太高,本课程也不讲了,大家作为选学即可。我们使用的时候直接用 5V 电源接一个二极管,在主电源上电的情况下给电容充电,在主电源掉电的情况下,二极管可以防止电容向主电路放电,而仅用来维持 DS1302 的供电,这种电路的最大用处是在电池供电系统中更换主电池的时候保持实时时钟的运行不中断,1 分钟的时间对于更换电池足够了。此外,通过我们的使用经验,在 DS1302 的主电源引脚串联一个 1K 电阻可以有效的防止电源对 DS1302的冲击,R6 就是这个电阻,而 R9、R26、R32 都是上拉电阻。

我们把 8 个引脚功能分别介绍,如表 15-1 所示。

表 15-1 DS1302 引脚功能图引脚编号引脚名称引脚功能
1Vcc2主电源引脚,当 Vcc2 比 Vcc1 高 0.2V 以上时,DS1302 由 Vcc2
供电,当 Vcc2 低于 Vcc1 时,由 Vcc1 供电。
2X1这两个引脚需要接一个 32.768K 的晶振,给 DS1302 提供一个基
准。特别注意,要求这个晶振的引脚负载电容必须是 6pF,而不
是要加 6pF 的电容。如果使用有源晶振的话,接到 X1 上即可,
X2 悬空。
3X2
4GND接地。
5CEDS1302 的使能输入引脚。当读写 DS1302 的时候,这个引脚必须
是高电平,DS1302 这个引脚内部有一个 40k 的下拉电阻。
6I/O这个引脚是一个双向通信引脚,读写数据都是通过这个引脚完成。
DS1302 这个引脚的内部含有一个 40k 的下拉电阻。
7SCLK输入引脚。SCLK 是用来作为通信的时钟信号。DS1302 这个引脚
的内部含有一个 40k 的下拉电阻。
8Vcc1备用电源引脚。
DS1302 电路的一个重点就是晶振电路,它所使用的晶振是一个 32.768k 的晶振,晶振外部也不需要额外添加其它的电容或者电阻了。时钟的精度,首先取决于晶振的精度以及晶振的引脚负载电容。如果晶振不准或者负载电容过大或过小,都会导致时钟误差过大。在这一切都搞定后,最终一个考虑因素是晶振的温漂。随着温度的变化,晶振的精度也会发生变化,因此,在实际的系统中,其中一种方法就是经常校对。比如我们所用的电脑的时钟,通常我们会设置一个选项“将计算机设置与 internet 时间同步”。选中这个选项后,一般过一段时间,我们的计算机就会和 internet 时间校准同步一次。

DS1302寄存器介绍
DS1302 的一条指令一个字节共 8 位,其中第 7 位(即最高位)固定为 1,这一位如果是0 的话,那写进去也是无效的。第 6 位是选择 RAM 还是 CLOCK 的,我前边说过,我们这里主要讲 CLOCK 时钟的使用,它的 RAM 功能我们不用,所以如果选择 CLOCK 功能,第 6位是 0,如果要用 RAM,那第 6 位就是 1。从第 5 到第 1 位,决定了寄存器的 5 位地址,而第 0 位是读写位,如果要写,这一位就是 0,如果要读,这一位就是 1。指令字节直观位分配如图 15-9 所示。

图15-9  DS1302 命令字节


DS1302 时钟的寄存器,其中 8 个和时钟有关的,5 位地址分别是 0b00000~0b00111,还有一个寄存器的地址是 01000,这是涓流充电所用的寄存器,我们这里不讲。在 DS1302 的数据手册里的地址,直接把第 7 位、第 6 位和第 0 位值给出来了,所以指令就成了 0x80、0x81那些了,最低位是 1,那么表示读,最低位是 0 表示写,如图 15-10 所示。

图 15-10  DS1302 的时钟寄存器


寄存器 0:最高位 CH 是一个时钟停止标志位。如果时钟电路有备用电源,上电后,我们要先检测一下这一位,如果这一位是 0,那说明时钟芯片在系统掉电后,由于备用电源的供给,时钟是持续正常运行的;如果这一位是 1,那么说明时钟芯片在系统掉电后,时钟部分不工作了。如果 Vcc1 悬空或者是电池没电了,当我们下次重新上电时,读取这一位,那这一位就是 1,我们可以通过这一位判断时钟在单片机系统掉电后是否还正常运行。剩下的7 位高 3 位是秒的十位,低 4 位是秒的个位,这里再提请注意一次,DS1302 内部是 BCD 码,而秒的十位最大是 5,所以 3 个二进制位就够了。

寄存器 1:最高位未使用,剩下的 7 位中高 3 位是分钟的十位,低 4 位是分钟的个位。

寄存器 2:bit7 是 1 的话代表是 12 小时制,0 代表是 24 小时制;bit6 固定是 0,bit5 在12 小时制下 0 代表的是上午,1 代表的是下午,在 24 小时制下和 bit4 一起代表了小时的十位,低 4 位代表的是小时的个位。

寄存器 3:高 2 位固定是 0,bit5 和 bit4 是日期的十位,低 4 位是日期的个位。

寄存器 4:高 3 位固定是 0,bit4 是月的十位,低 4 位是月的个位。

寄存器 5:高 5 位固定是 0,低 3 位代表了星期。

寄存器 6:高 4 位代表了年的十位,低 4 位代表了年的个位。请特别注意,这里的 00~99 指的是 2000 年~2099 年。

寄存器 7:最高位一个写保护位,如果这一位是 1,那么是禁止给任何其它寄存器或者那 31 个字节的 RAM 写数据的。因此在写数据之前,这一位必须先写成 0。

DS1302通信时序介绍
DS1302 我们前边也有提起过,是三根线,分别是 CE、I/O 和 SCLK,其中 CE 是使能线,SCLK 是时钟线,I/O 是数据线。前边我们介绍过了 SPI 通信,同学们发现没发现,这个 DS1302的通信线定义和 SPI 怎么这么像呢?

事实上,DS1302 的通信是 SPI 的变异种类,它用了 SPI 的通信时序,但是通信的时候没有完全按照 SPI 的规则来,下面我们一点点解剖 DS1302 的变异 SPI 通信方式。先看一下单字节写入操作,如图 15-11 所示。

图 15-11  DS1302 单字节写操作


然后我们再对比一下 CPOL=0/CPHA=0 情况下的 SPI 的操作时序,如图 15-12 所示。

图 15-12  CPOL=0/CPHA=0 通信时序



图 15-11 和图 15-12 的通信时序,其中 CE 和 SSEL 的使能控制是反的,对于通信写数据,都是在 SCK 的上升沿,从机进行采样,下降沿的时候,主机发送数据。DS1302 的时序里,单片机要预先写一个字节指令,指明要写入的寄存器的地址以及后续的操作是写操作,然后再写入一个字节的数据。

对于单字节读操作,我就不做对比了,把 DS1302 的时序图贴出来,大家自己看一下即可,如图 15-13 所示。

图 15-13  DS1302 单字节读操作


读操作有两处需要特别注意的地方。第一,DS1302 的时序图上的箭头都是针对 DS1302来说的,因此读操作的时候,先写第一个字节指令,上升沿的时候 DS1302 来锁存数据,下降沿我们用单片机发送数据。到了第二个字数据,由于我们这个时序过程相当于CPOL=0/CPHA=0,前沿发送数据,后沿读取数据,第二个字节是 DS1302 下降沿输出数据,我们的单片机上升沿来读取,因此箭头从 DS1302 角度来说,出现在了下降沿。

第二个需要注意的地方就是,我们的单片机没有标准的 SPI 接口,和 I2C 一样需要用 IO口来模拟通信过程。在读 DS1302 的时候,理论上 SPI 是上升沿读取,但是程序是用 IO 口模拟的,所以数据的读取和时钟沿的变化不可能同时了,必然就有一个先后顺序。通过实验发现,如果先读取 IO 线上的数据,再拉高 SCLK 产生上升沿,那么读到的数据一定是正确的,而颠倒顺序后数据就有可能出错。这个问题产生的原因还是在于 DS1302 的通信协议与标准SPI 协议存在的差异造成的,如果是标准 SPI 的数据线,数据会一直保持到下一个周期的下降沿才会变化,所以读取数据和上升沿的先后顺序就无所谓了;但 DS1302 的 IO 线会在时钟上升沿后被 DS1302 释放,也就是撤销强推挽输出变为弱下拉状态,而此时在 51 单片机引脚内部上拉的作用下,IO 线上的实际电平会慢慢上升,从而导致在上升沿产生后再读取 IO 数据的话就可能会出错。因此这里的程序我们按照先读取 IO 数据,再拉高 SCLK 产生上升沿的顺序。

下面我们就写一个程序,先将 2013 年 10 月 8 号星期二 12 点 30 分 00 秒这个时间写到DS1302 内部,让 DS1302 正常运行,然后再不停的读取 DS1302 的当前时间,并显示在我们的液晶屏上。
/***************************Lcd1602.c 文件程序源代码*****************************/
(此处省略,可参考之前章节的代码)
纯文本复制

  • /*****************************main.c 文件程序源代码******************************/
  • #include <reg52.h>

  • sbit DS1302_CE = P1^7;
  • sbit DS1302_CK = P3^5;
  • sbit DS1302_IO = P3^4;
  • bit flag200ms = 0; //200ms 定时标志
  • unsigned char T0RH = 0; //T0 重载值的高字节
  • unsigned char T0RL = 0; //T0 重载值的低字节

  • void ConfigTimer0(unsigned int ms);
  • void InitDS1302();
  • unsigned char DS1302SingleRead(unsigned char reg);
  • extern void InitLcd1602();
  • extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);

  • void main(){
  •     unsigned char i;
  •     unsigned char psec=0xAA; //秒备份,初值 AA 确保首次读取时间后会刷新显示
  •     unsigned char time[8]; //当前时间数组
  •     unsigned char str[12]; //字符串转换缓冲区
  •     EA = 1; //开总中断
  •     ConfigTimer0(1); //T0 定时 1ms
  •     InitDS1302(); //初始化实时时钟
  •     InitLcd1602(); //初始化液晶

  •     while (1){
  •         if (flag200ms){ //每 200ms 读取一次时间
  •             flag200ms = 0;
  •             for (i=0; i<7; i++){ //读取 DS1302 当前时间
  •                 time = DS1302SingleRead(i);
  •             }
  •             if (psec != time[0]){ //检测到时间有变化时刷新显示
  •                 str[0] = '2'; //添加年份的高 2 位:20
  •                 str[1] = '0';
  •                 str[2] = (time[6] >> 4) + '0'; //“年”高位数字转换为 ASCII 码
  •                 str[3] = (time[6]&0x0F) + '0'; //“年”低位数字转换为 ASCII 码
  •                 str[4] = '-'; //添加日期分隔符
  •                 str[5] = (time[4] >> 4) + '0'; //“月”
  •                 str[6] = (time[4]&0x0F) + '0';
  •                 str[7] = '-';
  •                 str[8] = (time[3] >> 4) + '0'; //“日”
  •                 str[9] = (time[3]&0x0F) + '0';
  •                 str[10] = '\0';
  •                 LcdShowStr(0, 0, str); //显示到液晶的第一行
  •                 str[0] = (time[5]&0x0F) + '0'; //“星期”
  •                 str[1] = '\0';
  •                 LcdShowStr(11, 0, "week");
  •                 LcdShowStr(15, 0, str); //显示到液晶的第一行
  •                 str[0] = (time[2] >> 4) + '0'; //“时”
  •                 str[1] = (time[2]&0x0F) + '0';
  •                 str[2] = ':'; //添加时间分隔符
  •                 str[3] = (time[1] >> 4) + '0'; //“分”
  •                 str[4] = (time[1]&0x0F) + '0';
  •                 str[5] = ':';
  •                 str[6] = (time[0] >> 4) + '0'; //“秒”
  •                 str[7] = (time[0]&0x0F) + '0';
  •                 str[8] = '\0';
  •                 LcdShowStr(4, 1, str); //显示到液晶的第二行
  •                 psec = time[0]; //用当前值更新上次秒数
  •             }
  •         }
  •     }
  • }
  • /* 发送一个字节到 DS1302 通信总线上 */
  • void DS1302ByteWrite(unsigned char dat){
  •     unsigned char mask;

  •     for (mask=0x01; mask!=0; mask<<=1){ //低位在前,逐位移出
  •         if ((mask&dat) != 0){ //首先输出该位数据
  •             DS1302_IO = 1;
  •         }else{
  •             DS1302_IO = 0;
  •         }
  •         DS1302_CK = 1; //然后拉高时钟
  •         DS1302_CK = 0; //再拉低时钟,完成一个位的操作
  •     }
  •     DS1302_IO = 1; //最后确保释放 IO 引脚
  • }
  • /* 由 DS1302 通信总线上读取一个字节 */
  • unsigned char DS1302ByteRead(){
  •     unsigned char mask;
  •     unsigned char dat = 0;

  •     for (mask=0x01; mask!=0; mask<<=1){ //低位在前,逐位读取
  •         if (DS1302_IO != 0){ //首先读取此时的 IO 引脚,并设置 dat 中的对应位
  •             dat |= mask;
  •         }
  •         DS1302_CK = 1; //然后拉高时钟
  •         DS1302_CK = 0; //再拉低时钟,完成一个位的操作
  •     }
  •     return dat; //最后返回读到的字节数据
  • }
  • /* 用单次写操作向某一寄存器写入一个字节,reg-寄存器地址,dat-待写入字节 */
  • void DS1302SingleWrite(unsigned char reg, unsigned char dat){
  •     DS1302_CE = 1; //使能片选信号
  •     DS1302ByteWrite((reg<<1)|0x80); //发送写寄存器指令
  •     DS1302ByteWrite(dat); //写入字节数据
  •     DS1302_CE = 0; //除能片选信号
  • }
  • /* 用单次读操作从某一寄存器读取一个字节,reg-寄存器地址,返回值-读到的字节 */
  • unsigned char DS1302SingleRead(unsigned char reg){
  •     unsigned char dat;
  •     DS1302_CE = 1; //使能片选信号
  •     DS1302ByteWrite((reg<<1)|0x81); //发送读寄存器指令
  •     dat = DS1302ByteRead()//读取字节数据
  •     DS1302_CE = 0; //除能片选信号
  •     return dat;
  • }
  • /* DS1302 初始化,如发生掉电则重新设置初始时间 */
  • void InitDS1302(){
  •     unsigned char i;
  •     unsigned char code InitTime[] = { //2013 年 10 月 8 日 星期二 12:30:00
  •         0x00,0x30,0x12, 0x08, 0x10, 0x02, 0x13
  •     };

  •     DS1302_CE = 0; //初始化 DS1302 通信引脚
  •     DS1302_CK = 0;
  •     i = DS1302SingleRead(0); //读取秒寄存器

  •     if ((i & 0x80) != 0){ //由秒寄存器最高位 CH 的值判断 DS1302 是否已停止
  •         DS1302SingleWrite(7, 0x00); //撤销写保护以允许写入数据
  •         for (i=0; i<7; i++){ //设置 DS1302 为默认的初始时间
  •             DS1302SingleWrite(i, InitTime);
  •         }
  •     }
  • }
  • /* 配置并启动 T0,ms-T0 定时时间 */
  • void ConfigTimer0(unsigned int ms){
  •     unsigned long tmp; //临时变量
  •     tmp = 11059200 / 12; //定时器计数频率
  •     tmp = (tmp * ms) / 1000; //计算所需的计数值
  •     tmp = 65536 - tmp; //计算定时器重载值
  •     tmp = tmp + 12; //补偿中断响应延时造成的误差
  •     T0RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节
  •     T0RL = (unsigned char)tmp;
  •     TMOD &= 0xF0; //清零 T0 的控制位
  •     TMOD |= 0x01; //配置 T0 为模式 1
  •     TH0 = T0RH; //加载 T0 重载值
  •     TL0 = T0RL;
  •     ET0 = 1; //使能 T0 中断
  •     TR0 = 1; //启动 T0
  • }
  • /* T0 中断服务函数,执行 200ms 定时 */
  • void InterruptTimer0() interrupt 1{
  •     static unsigned char tmr200ms = 0;
  •     TH0 = T0RH; //重新加载重载值
  •     TL0 = T0RL;
  •     tmr200ms++;
  •     if (tmr200ms >= 200){ //定时 200ms
  •         tmr200ms = 0;
  •         flag200ms = 1;
  •     }
  • }


前边学习了 I2C 和 EEPROM 的底层读写时序,那么 DS1302 的底层读写时序程序的实现方法是与之类似的,这里就不过多解释了,大家自己认真揣摩一下。

DS1302的BURST模式
进行产品开发的时候,逻辑的严谨性非常重要,如果一个产品或者程序逻辑上不严谨,就有可能出现功能上的错误。比如我们 15.3.4 节里的这个程序,我们再回顾一下,当单片机定时器时间到了 200ms 后,我们连续把 DS1302 的时间参数的 7 个字节读了出来。但是不管怎么读,都会有一个时间差,在极端的情况下就会出现这样一种情况:假如我们当前的时间是 00:00:59,我们先读秒,读到的秒是 59,然后再去读分钟,而就在读完秒到还未开始读分钟的这段时间内,刚好时间进位了,变成了 00:01:00 这个时间,我们读到的分钟就是 01,显示在液晶上就会出现一个 00:01:59,这个时间很明显是错误的。出现这个问题的概率极小,但却是实实在在可能存在的。

为了解决这个问题,芯片厂家肯定要给我们提供一种解决方案,这就是 DS1302 的突发模式。突发模式也分为 RAM 突发模式和时钟突发模式,RAM 部分我们不讲,我们只看和时钟相关的 clock burst mode。

当我们写指令到 DS1302 的时候,只要我们将要写的 5 位地址全部写 1,即读操作用 0xBF,写操作用 0xBE,这样的指令送给 DS1302 之后,它就会自动识别出来是 burst 模式,马上把所有的 8 个字节同时锁存到另外的 8 个字节的寄存器缓冲区内,这样时钟继续走,而我们读数据是从另外一个缓冲区内读取的。同样的道理,如果我们用 burst 模式写数据,那么我们也是先写到这个缓冲区内,最终 DS1302 会把这个缓冲区内的数据一次性送到它的时钟寄存器内。

要注意的是,不管是读还是写,只要使用时钟的 burst 模式,则必须一次性读写 8 个寄存器,要把时钟的寄存器完全读出来或者完全写进去。

下边就提供一个 burst 模式的例程给大家学习一下,程序的功能还是与上一节一样的。
/***************************Lcd1602.c 文件程序源代码*****************************/
(此处省略,可参考之前章节的代码)
纯文本复制

  • /*****************************main.c 文件程序源代码******************************/
  • #include <reg52.h>

  • sbit DS1302_CE = P1^7;
  • sbit DS1302_CK = P3^5;
  • sbit DS1302_IO = P3^4;

  • bit flag200ms = 0; //200ms 定时标志
  • unsigned char T0RH = 0; //T0 重载值的高字节
  • unsigned char T0RL = 0; //T0 重载值的低字节

  • void ConfigTimer0(unsigned int ms);
  • void InitDS1302();
  • void DS1302BurstRead(unsigned char *dat);
  • extern void InitLcd1602();
  • extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);

  • void main(){
  •     unsigned char psec=0xAA; //秒备份,初值 AA 确保首次读取时间后会刷新显示
  •     unsigned char time[8]; //当前时间数组
  •     unsigned char str[12]; //字符串转换缓冲区

  •     EA = 1; //开总中断
  •     ConfigTimer0(1); //T0 定时 1ms
  •     InitDS1302(); //初始化实时时钟
  •     InitLcd1602(); //初始化液晶

  •     while (1){
  •         if (flag200ms){ //每 200ms 读取依次时间
  •             flag200ms = 0;
  •             DS1302BurstRead(time); //读取 DS1302 当前时间

  •             if (psec != time[0]){ //检测到时间有变化时刷新显示
  •                 str[0] = '2'; //添加年份的高 2 位:20
  •                 str[1] = '0';
  •                 str[2] = (time[6] >> 4) + '0'; //“年”高位数字转换为 ASCII 码
  •                 str[3] = (time[6]&0x0F) + '0'; //“年”低位数字转换为 ASCII 码
  •                 str[4] = '-'; //添加日期分隔符
  •                 str[5] = (time[4] >> 4) + '0'; //“月”
  •                 str[6] = (time[4]&0x0F) + '0';
  •                 str[7] = '-';
  •                 str[8] = (time[3] >> 4) + '0'; //“日”
  •                 str[9] = (time[3]&0x0F) + '0';
  •                 str[10] = '\0';
  •                 LcdShowStr(0, 0, str); //显示到液晶的第一行

  •                 str[0] = (time[5]&0x0F) + '0'; //“星期”
  •                 str[1] = '\0';
  •                 LcdShowStr(11, 0, "week");
  •                 LcdShowStr(15, 0, str); //显示到液晶的第一行

  •                 str[0] = (time[2] >> 4) + '0'; //“时”
  •                 str[1] = (time[2]&0x0F) + '0';
  •                 str[2] = ':'; //添加时间分隔符
  •                 str[3] = (time[1] >> 4) + '0'; //“分”
  •                 str[4] = (time[1]&0x0F) + '0';
  •                 str[5] = ':';
  •                 str[6] = (time[0] >> 4) + '0'; //“秒”
  •                 str[7] = (time[0]&0x0F) + '0';
  •                 str[8] = '\0';
  •                 LcdShowStr(4, 1, str); //显示到液晶的第二行

  •                 psec = time[0]; //用当前值更新上次秒数
  •             }
  •         }
  •     }
  • }

  • /* 发送一个字节到 DS1302 通信总线上 */
  • void DS1302ByteWrite(unsigned char dat){
  •     unsigned char mask;
  •     for (mask=0x01; mask!=0; mask<<=1){ //低位在前,逐位移出
  •         if ((mask&dat) != 0){ //首先输出该位数据
  •             DS1302_IO = 1;
  •         }else{
  •             DS1302_IO = 0;
  •         }
  •         DS1302_CK = 1; //然后拉高时钟
  •         DS1302_CK = 0; //再拉低时钟,完成一个位的操作
  •     }
  •     DS1302_IO = 1; //最后确保释放 IO 引脚
  • }
  • /* 由 DS1302 通信总线上读取一个字节 */
  • unsigned char DS1302ByteRead(){
  •     unsigned char mask;
  •     unsigned char dat = 0;

  •     for (mask=0x01; mask!=0; mask<<=1){ //低位在前,逐位读取
  •         if (DS1302_IO != 0){ //首先读取此时的 IO 引脚,并设置 dat 中的对应位
  •             dat |= mask;
  •         }
  •         DS1302_CK = 1; //然后拉高时钟
  •         DS1302_CK = 0; //再拉低时钟,完成一个位的操作
  •     }
  •     return dat; //最后返回读到的字节数据
  • }
  • /* 用单次写操作向某一寄存器写入一个字节,reg-寄存器地址,dat-待写入字节 */
  • void DS1302SingleWrite(unsigned char reg, unsigned char dat){
  •     DS1302_CE = 1; //使能片选信号
  •     DS1302ByteWrite((reg<<1)|0x80); //发送写寄存器指令
  •     DS1302ByteWrite(dat); //写入字节数据
  •     DS1302_CE = 0; //除能片选信号
  • }
  • /* 用单次读操作从某一寄存器读取一个字节,reg-寄存器地址,返回值-读到的字节 */
  • unsigned char DS1302SingleRead(unsigned char reg){
  •     unsigned char dat;

  •     DS1302_CE = 1; //使能片选信号
  •     DS1302ByteWrite((reg<<1)|0x81); //发送读寄存器指令
  •     dat = DS1302ByteRead(); //读取字节数据
  •     DS1302_CE = 0; //除能片选信号
  •     return dat;
  • }
  • /* 用突发模式连续写入 8 个寄存器数据,dat-待写入数据指针 */
  • void DS1302BurstWrite(unsigned char *dat){
  •     unsigned char i;

  •     DS1302_CE = 1;
  •     DS1302ByteWrite(0xBE); //发送突发写寄存器指令

  •     for (i=0; i<8; i++){ //连续写入 8 字节数据
  •         DS1302ByteWrite(dat);
  •     }
  •     DS1302_CE = 0;
  • }
  • /* 用突发模式连续读取 8 个寄存器的数据,dat-读取数据的接收指针 */
  • void DS1302BurstRead(unsigned char *dat){
  •     unsigned char i;

  •     DS1302_CE = 1;
  •     DS1302ByteWrite(0xBF); //发送突发读寄存器指令
  •     for (i=0; i<8; i++){ //连续读取 8 个字节
  •         dat = DS1302ByteRead();
  •     }
  •     DS1302_CE = 0;
  • }
  • /* DS1302 初始化,如发生掉电则重新设置初始时间 */
  • void InitDS1302(){
  •     unsigned char dat;
  •     unsigned char code InitTime[] = { //2013 年 10 月 8 日 星期二 12:30:00
  •         0x00,0x30,0x12, 0x08, 0x10, 0x02, 0x13
  •     };

  •     DS1302_CE = 0; //初始化 DS1302 通信引脚
  •     DS1302_CK = 0;
  •     dat = DS1302SingleRead(0); //读取秒寄存器

  •     if ((dat & 0x80) != 0){ //由秒寄存器最高位 CH 的值判断 DS1302 是否已停止
  •         DS1302SingleWrite(7, 0x00); //撤销写保护以允许写入数据
  •         DS1302BurstWrite(InitTime); //设置 DS1302 为默认的初始时间
  •     }
  • }
  • /* 配置并启动 T0,ms-T0 定时时间 */
  • void ConfigTimer0(unsigned int ms){
  •     unsigned long tmp; //临时变量

  •     tmp = 11059200 / 12; //定时器计数频率
  •     tmp = (tmp * ms) / 1000; //计算所需的计数值
  •     tmp = 65536 - tmp; //计算定时器重载值
  •     tmp = tmp + 12; //补偿中断响应延时造成的误差
  •     T0RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节
  •     T0RL = (unsigned char)tmp;
  •     TMOD &= 0xF0; //清零 T0 的控制位
  •     TMOD |= 0x01; //配置 T0 为模式 1
  •     TH0 = T0RH; //加载 T0 重载值
  •     TL0 = T0RL;
  •     ET0 = 1; //使能 T0 中断
  •     TR0 = 1; //启动 T0
  • }
  • /* T0 中断服务函数,执行 200ms 定时 */
  • void InterruptTimer0() interrupt 1{
  •     static unsigned char tmr200ms = 0;
  •     TH0 = T0RH; //重新加载重载值
  •     TL0 = T0RL;
  •     tmr200ms++;
  •     if (tmr200ms >= 200){ //定时 200ms
  •         tmr200ms = 0;
  •         flag200ms = 1;
  •     }
  • }