# 8 DS1302实时时钟
[toc]
注:笔记主要参考B站江科大自化协教学视频“[51单片机入门教程-2020版 程序全程纯手打 从零开始入门](https://www.bilibili.com/video/BV1Mb411e7re?spm_id_from=333.1007.top_right_bar_window_custom_collection.content.click)”。
注:工程及代码文件放在了本人的[Github仓库](https://github.com/jjejdhhd/Learn_STC89C52)。
***
## 8.1 芯片介绍:DS1302
RTC(Real Time Clock)实时时钟,是一种集成电路,通常称为时钟芯片。常见的时钟芯片有DS1302,是由美国DALLAS公司推出的具有涓细电流充电能力的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时,且具有闰年补偿等多种功能。**本节的关键在于DS1302的时序。**
<div align=center>
<img src="https://raw.githubusercontent.com/jjejdhhd/Git_img2023/main/8051chip/DS1302%E5%AE%9E%E7%89%A9%E5%9B%BE.png" width=80%>
</div><div align=center>
图8-1 DS1302实物图
</div>
那为什么不使用单片机上的定时器来驱动实时时钟呢?
> 1. CPU内的定时器精度不高。
> 2. 使用定时器会占用CPU的内部资源。
> 3. 单片机定时器掉电不能继续工作。
>
<div align=center>
<img src="https://raw.githubusercontent.com/jjejdhhd/Git_img2023/main/8051chip/DS1302%E5%8E%9F%E7%90%86%E5%9B%BE.png" width=40%>
</div><div align=center>
图8-2 DS1302原理图
</div>
> 芯片引脚说明:
> CE:输入,芯片使能引脚。
> SCLK:输入,串行接口时钟。
> I/O:双向,输入输出的串行数据,SCLK上升沿触发。
基本逻辑是利用下面的寄存器地址说明(图8-4),写入的时候按需更改相应的秒数,读出的时候则依次进行读出。前八位是读写的选择和读写的地址,后八位才是具体要进行读写的数据,且**先传输低位**。前八位也被称为<u>命令字,最高位写保护,控制能否串行写入(1/0),但一直可以读;R/C选择是RAM数据/时钟数据(1/0);A4~A0则为5位寄存器地址;R/W位则控制读写(1/0)。</u>下面则给出了DS1602的读/写传输时序、寄存器操作地址:
<div align=center>
<img src="https://raw.githubusercontent.com/jjejdhhd/Git_img2023/main/8051chip/DS1602%E6%95%B0%E6%8D%AE%E4%BC%A0%E8%BE%93%E6%97%B6%E5%BA%8F.png" width=60%>
</div><div align=center>
图8-3 DS1302数据传输时序
</div>
<div align=center>
<img src="https://raw.githubusercontent.com/jjejdhhd/Git_img2023/main/8051chip/DS1602%E5%AF%84%E5%AD%98%E5%99%A8%E5%9C%B0%E5%9D%80.png" width=70%>
</div><div align=center>
图8-4 RTC相关寄存器
注:左侧的八位是直接给出了**命令字**。
</div>
值得注意的是,上面寄存器所显示的数据,都采用BCD码(Binary Coded Decimal)进行编码:
> - 用4位二进制数来表示1位十进制数
> 例:0001 0011表示13,1000 0101表示85,0001 1010不合法
> 在十六进制中的体现:0x13表示13,0x85表示85,0x1A不合法
> - BCD码转十进制:DEC=BCD/16\*10+BCD%16; (2位BCD)
> 十进制转BCD码:BCD=DEC/10\*16+DEC%10; (2位BCD)
> - CH位:时停控制位,写入此位为高电平,则时钟暂停。
> - 12/24位:设置12小时/24小时模式位。12小时模式,BIT5表示AM/PM,BIT4表示十位;24小时模式,[BIT5,BIT4]表示十位。
> - 关于星期:注意这个星期不是实际的星期,而是“处理日”,所以需要用户指定初始的星期偏置。
## 8.2 实验:DS1302时钟显示
需求:在LCD显示屏第一行显示日期“年-月-日”,第二行显示时间“时-分-秒”,24小时制。
<div align=center>
<img src="https://raw.githubusercontent.com/jjejdhhd/Git_img2023/main/8051chip/%E4%BB%A3%E7%A0%81%E8%B0%83%E7%94%A8%E5%85%B3%E7%B3%BB-%E5%AE%9E%E6%97%B6%E6%97%B6%E9%92%9F.png" width=20%>
</div><div align=center>
图8-5 “实时时钟”代码调用关系
</div>
代码展示:
**- main.c**
```c
#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
unsigned char DS1302_info[7];
void main(){
unsigned char i=0;
// 初始化LCD显示屏
LCD_Init();
LCD_ShowString(1,1,"2023-01-01");
LCD_ShowString(2,1,"00:00:00");
DS1302_Init();//初始化DS1302
while(1){
DS1302_ReadAll(&DS1302_info);
LCD_ShowNum(1,3,DS1302_info[0],2);
LCD_ShowNum(1,6,DS1302_info[1],2);
LCD_ShowNum(1,9,DS1302_info[2],2);
LCD_ShowDay(1,14,DS1302_info[3]);
LCD_ShowNum(2,1,DS1302_info[4],2);
LCD_ShowNum(2,4,DS1302_info[5],2);
LCD_ShowNum(2,7,DS1302_info[6],2);
}
}
```
**- DS1302.h**
```c
#ifndef __DS1302_H__
#define __DS1302_H__
void DS1302_Init(void);//初始化DS1302
unsigned char DS1302_ReadByte(unsigned char read_comd);//从DS1302中读出数据
void DS1302_WriteByte(unsigned char wri_comd,wri_byte);//向DS1302中写入数据
void DS1302_ReadAll(unsigned char *info);//读出所有信息
#endif
```
**- DS1302.c**
```c
#include <REGX52.H>
// 将端口重新定义
sbit DS1302_SCLK = P3^6;
sbit DS1302_IO = P3^4;
sbit DS1302_CE = P3^5;
#define DS1302_RSEC 0x81
#define DS1302_RMIN 0x83
#define DS1302_RHOU 0x85
#define DS1302_RDAT 0x87
#define DS1302_RMON 0x89
#define DS1302_RDAY 0x8B
#define DS1302_RYEA 0x8D
#define DS1302_RWP 0x8F
#define DS1302_WSEC 0x80
#define DS1302_WMIN 0x82
#define DS1302_WHOU 0x84
#define DS1302_WDAT 0x86
#define DS1302_WMON 0x88
#define DS1302_WDAY 0x8A
#define DS1302_WYEA 0x8C
#define DS1302_WWP 0x8E
/**
* @brief :从DS1302中读出数据
* @param :需要操作的命令字read_comd。
* @retval :读出的数据read_byte。
*/
unsigned char DS1302_ReadByte(unsigned char read_comd){
unsigned char read_byte = 0x00;
unsigned char i;
DS1302_CE = 1;
// 写入命令字
for(i=0;i<8;i++){
DS1302_IO = read_comd&(0x01<<i);
DS1302_SCLK = 0;
DS1302_SCLK = 1; // 经过测试,不需延时
}
// 读出数据
for(i=0;i<8;i++){
DS1302_SCLK = 1;
DS1302_SCLK = 0;
read_byte = DS1302_IO ? (read_byte|(0x01<<i)) : read_byte;
}
DS1302_IO = 0; //注意最后一定要将数据线清零
DS1302_CE = 0;
return read_byte;
}
/**
* @brief :向DS1302中写入数据
* @param :需要操作的命令字wri_comd,需要写入的数据wri_byte。
* @retval :无。
*/
void DS1302_WriteByte(unsigned char wri_comd,wri_byte){
unsigned char i;
DS1302_CE = 1;
// 写入命令字
for(i=0;i<8;i++){
DS1302_IO = wri_comd&(0x01<<i);
DS1302_SCLK = 0;
DS1302_SCLK = 1; // 经过测试,不需延时
}
// 写入8位数据
for(i=0;i<8;i++){
DS1302_IO = wri_byte&(0x01<<i);
DS1302_SCLK = 0;
DS1302_SCLK = 1; // 经过测试,不需延时
}
DS1302_SCLK = 0;
DS1302_CE = 0;
}
/**
* @brief :初始化控制DS1302的引脚。
* @param :无。
* @retval :无。
*/
void DS1302_Init(void){
DS1302_SCLK = 0;
DS1302_IO = 0;
DS1302_CE = 0;
DS1302_WriteByte(DS1302_WWP,0x00);//解除写保护
DS1302_WriteByte(DS1302_WSEC,0x00);//解除时钟暂停
// 默认为24小时模式
}
/**
* @brief :从单片机中读出年/月/日/星期/时/分/秒
* @param :数组指针,存储年/月/日/星期/时/分/秒。
* @retval :无。程序中通过指针解引用更改信息。
*/
void DS1302_ReadAll(unsigned char *info){
unsigned char temp;
// 年
temp = DS1302_ReadByte(DS1302_RYEA);
*info = (temp>>4)*10 + (temp&0x0f);
// 月
temp = DS1302_ReadByte(DS1302_RMON);
*(info+1) = (temp>>4)*10 + (temp&0x0f);
// 日
temp = DS1302_ReadByte(DS1302_RDAT);
*(info+2) = (temp>>4)*10 + (temp&0x0f);
// 星期
*(info+3) = DS1302_ReadByte(DS1302_RDAY);
// 时
temp = DS1302_ReadByte(DS1302_RHOU);
if(!(temp&0x80)){*(info+4) = (temp>>4)*10 + (temp&0x0f);}
else {*(info+4) = ((temp&0x10)>>4)*10 + (temp&0x0f);}
// 分
temp = DS1302_ReadByte(DS1302_RMIN);
*(info+5) = ((temp&0x70)>>4)*10 + (temp&0x0f);
// 秒
temp = DS1302_ReadByte(DS1302_RSEC);
*(info+6) = ((temp&0x70)>>4)*10 + (temp&0x0f);
}
```
**- LCD1602.h*