实验现象
将程序烧录到单片机中后,lcd1602显示屏将从预设时间开始进行时钟功能。
在lcd1602显示屏第一行分别显示年,月,日,星期;在第二行显示时,分,秒。
DS1302介绍
ds1302简介
DS1302 是 DALLAS 公司推出的涓流充电时钟芯片,内含有一个实时时钟/日历和 31 字节静态 RAM,通过简单的串行接口与单片机进行通信。实时时钟/日历电路提供秒、分、时、日、周、月、年的信息,每月的天数和闰年的天数可自动调整。
DS1302 与单片机之间可以通过三根数据线进行同步串行的方式进行通信:RES,IO数据,SCLK串行时钟。实时时钟具有能计算 2100 年之前的秒、分、时、日、星期、月、年的能力,还有闰年调整的能力。
引脚
- Vcc2 主电源
- Vcc1 备用电源
- X 32.768khz晶振引脚
- SCLK串行时钟
- I/O 数据
- CE(res) 复位引脚 - GND 接地
工作流程
- 将预设的时间以bcd码形式写入ds1302的寄存器中,单片机开启后ds1302将从这个时间开始运作
- ds1302开始运作后,寄存器里的值会自己按时间变化。不断地取出其中的值,并由bcd码转化成十进制
- 取到的十进制按一定格式打印在1602显示屏上
从工作流程可知要使ds1302运转,首先要了解ds1302中的寄存器,然后设计写入数据与读取数据函数,最后进行调用。
寄存器
控制寄存器
在对ds1302进行读写操作的时候,第一步要向ds1302发送控制命令,用于后续对应的操作。在ds1302中,有这么一种寄存器,用于存放控制命令,这样ds1302就可以根据我们发送的指令,进行对应的操作。
一个控制命令有8位,输入一个八位的命令就可以进行操作。例如想要读取秒寄存器,就需要向ds1302发送命令10000001,例如想要写秒寄存器,就需要向ds1302发送命令10000000。
详情见以下表格:
操作 | 命令 |
---|---|
读秒寄存器 | 10000001 |
写秒寄存器 | 10000000 |
读分寄存器 | 10000011 |
写分寄存器 | 10000010 |
读小时寄存器 | 10000101 |
写小时寄存器 | 10000100 |
读日寄存器 | 10000111 |
写日寄存器 | 10000110 |
读月寄存器 | 10001001 |
写月寄存器 | 10001000 |
读星期寄存器 | 10001011 |
写星期寄存器 | 10001010 |
读年寄存器 | 10001101 |
写年寄存器 | 10001100 |
写写保护寄存器 | 10001111 |
时间寄存器
在ds1302中有着这种寄存器,用于保存时间或者其他数据。在秒,分,时,日,月,星期,年寄存器中,用bcd码保存着数据,高四位用于保存十进制的十位,低四位用于保存十进制的个位。例如想保存12月21日,就分别向日寄存器写0x21(00100001,高四位2,低四位1),向月寄存器写0x12。
秒的范围是0-59,因此对于高四位,其实用三位就足够了(最大为5,101),最高的那一位是ds1302的开关,为0时才能工作。
小时有12与24两种。最高位为0时表示24进制,此时剩下的位全是表示时间的数据;最高位为1时,表示12进制,此时A/P表示上午或者下午。
写保存寄存器的最高位WP是写入寄存器的通路的开关。当WP为0时,才可以向ds1302中写入数据。
读写与时序
写
前面提到,ds1302使用三根通信线CE,IO,SCLK。如何使用这三根线写入芯片呢?
IO用于接收一位数据,低电平为0,高电平为1。要向ds1302写数据,要做以下操作:
- 将CE至高,SCLK置低
- 将要写入的数据的最低位存入IO
- 将SCLK置高,这样产生一个上升沿,上升沿会使IO数据写入芯片,置高一段时间后置低
- 将数据右移一位(使次低位变成最低位)
- 重复2-4,直到数据写完
- 要写数据,首先要用2-5步骤写入对应的控制命令,再用相同的方法写要写的数据
- 完成命令与数据输入后,CE置低
读
与写不同,在输入了读的执行命令后,会在SCLK的下降沿将对应寄存器的值从低位开始传给IO,因此,读寄存器的操作是:
- 按照写数据的方法,将执行指令写入芯片
- 传完8位指令后,在SCLK为低电平时使用变量保存IO的状态,然后置高电平
- 循环读取八次,会读取到从低到高八位数据。将这八次得到的数据做成一个八位的变量
- 返回得到的这个八位变量(是bcd码)
ds1302的GPIO
IO,CE,SCLK分别连接着P34,35,36接口,因此要操作IO,CE,SCLK,在代码层仅需要对P34,35,36进行操作就行。
代码
ds1302.c
涉及到的操作最核心的部分是对ds1302的读与写,思路已在上文提及,读函数与写函数代码如下:
#include <REGX52.H>
#include "intrins.h"
//封装GPIO
#define CE P3_5
#define IO P3_4
#define SCLK P3_6
/*
* @brief 向ds1302芯片的寄存器写一个字节数据
* @details 提供控制指令与数据,将数据按位赋给IO,在SCLK出现上升沿的时候,
IO的值会发送给ds1302,先将控制命令用八次循环按位写入IO,经历
SCLK八次上升沿后命令送达,再用同样的方式将数据送入DS1302。
在整个期间,CE(RST)置高电平
* @param addr 写入的控制指令
* @param dat 向对应的寄存器要写的数据
* @retval 无
*/
void ds1302_write_byte(unsigned char addr,unsigned char dat){
unsigned char i;
CE = 0;
_nop_();
CE = 1;//将CE从低电平改为高电平
_nop_();
SCLK = 0;//SCLK置低电平
_nop_();
for (i = 0;i<8;i++){//输入指令
IO = addr&0x01; //取出最低位
addr = addr>>1; //右移一位,次低位变最低位
SCLK = 1; //产生上升沿,数据传入后再置低电平
_nop_();
SCLK = 0;
_nop_();
}
for (i = 0;i<8;i++){//输入数据
IO = dat&0x01;
dat = dat>>1;
SCLK = 1;
_nop_();
SCLK = 0;
_nop_();
}
CE = 0;
_nop_();
}
/*
* @brief 读取ds1302寄存器数据.
* @details 提供控制指令选择要读的寄存器之后,先将控制命令写入芯片,
此时sclk会完成八个上升沿,七个下降沿。
读的数据会在第八个下降沿开始返回,因此在将最后一个控制命令
的位数据写入后,置低电平时就要从IO开始读。
读到的时间数据是BCD码
* @param addr 写入的控制指令
* @retval 读出的寄存器数据
*/
unsigned char ds1302_read_byte(unsigned char addr){
unsigned char i=0,temp=0,value=0;
CE = 0;
_nop_();
CE = 1;
_nop_();
SCLK = 0;
_nop_();
for (i = 0;i<8;i++){ //输入指令
IO = addr&0x01;
addr = addr>>1;
SCLK = 1;
_nop_();
SCLK = 0;
_nop_();
}
for (i = 0;i<8;i++){ //下降沿读取数据
temp = IO;
/*先将 value 右移 1 位,然后 temp 左移 7 位, 最后或运算
这样可以将按低位拿到的数据变成八位数据
例如寄存器里为10001000,那么value在循环中的值是00000000,
00000000,00000000,10000000,01000000......10001000*/
value=(temp<<7)|(value>>1);
SCLK=1;
_nop_();
SCLK=0;
_nop_();
}
CE = 0;
_nop_();
/*
下面的代码在写函数里面没有,看官方教程解释也没搞明白,
删掉运行测试也没有异常...
*/
SCLK=1;
_nop_();
IO = 0;
_nop_();
IO = 1;
_nop_();
return value;
}
不仅需要读写操作的函数,还需要有调用这些函数的调用者。上文说过最开始要初始化时间,初始化后再继续不断读取。初始化的时间是以bcd码的形式保存在数组中的,得到的数据也是bcd码的形式。因此,还要写一个初始化函数,将初始值数组的值依次写入秒,分,时,日,月,星期,年寄存器;还要一个读取函数,依次读取这七个寄存器的值到数组。
代码如下:
//---DS1302 写入和读取时分秒的地址命令
//---秒分时日月周年 最低位读写位;
unsigned char gREAD_RTC_ADDR[7] = {0x81, 0x83, 0x85, 0x87, 0x89, 0x8b, 0x8d};
unsigned char gWRITE_RTC_ADDR[7] = {0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c};
//---DS1302 时钟初始化 2022 年 8 月 31 日星期三 17 点 22 分 25 秒。
//---存储顺序是秒分时日月周年,存储格式是用 BCD 码---//
unsigned char gDS1302_TIME[7] = {0x25, 0x22, 0x17, 0x31, 0x08, 0x03, 0x22};
/*
* @brief 初始化时钟时间
* @details 将时间数组保存的值用写函数写入ds1302
* @param 无
* @retval 无
*/
void ds1302_init(){
unsigned char i;
ds1302_write_byte(0x8E,0X00);//在写寄存器之前,关闭写寄存器保护
for (i = 0;i<7;i++){
ds1302_write_byte(gWRITE_RTC_ADDR[i],gDS1302_TIME[i]);
}
ds1302_write_byte(0x8E,0X80);//重新开启保护
}
/*
* @brief 将寄存器中的时间写入数组
* @details 将时间数组保存的值用写函数写入ds1302
* @param 无
* @retval 无
*/
void ds1302_read_time(void){
unsigned char i=0;
for(i=0;i<7;i++) {
gDS1302_TIME[i]=ds1302_read_byte(gREAD_RTC_ADDR[i]);
}
}
整个ds1302.c代码如下:
#include <REGX52.H>
#include "intrins.h"
//封装GPIO
#define CE P3_5
#define IO P3_4
#define SCLK P3_6
//---DS1302 写入和读取时分秒的地址命令
//---秒分时日月周年 最低位读写位;
unsigned char gREAD_RTC_ADDR[7] = {0x81, 0x83, 0x85, 0x87, 0x89, 0x8b, 0x8d};
unsigned char gWRITE_RTC_ADDR[7] = {0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c};
//---DS1302 时钟初始化 2022 年 8 月 31 日星期三 17 点 22 分 25 秒。
//---存储顺序是秒分时日月周年,存储格式是用 BCD 码---//
unsigned char gDS1302_TIME[7] = {0x25, 0x22, 0x17, 0x31, 0x08, 0x03, 0x22};
/*
* @brief 向ds1302芯片的寄存器写一个字节数据
* @details 提供控制指令与数据,将数据按位赋给IO,在SCLK出现上升沿的时候,
IO的值会发送给ds1302,先将控制命令用八次循环按位写入IO,经历
SCLK八次上升沿后命令送达,再用同样的方式将数据送入DS1302。
在整个期间,CE(RST)置高电平
* @param addr 写入的控制指令
* @param dat 向对应的寄存器要写的数据
* @retval 无
*/
void ds1302_write_byte(unsigned char addr,unsigned char dat){
unsigned char i;
CE = 0;
_nop_();
CE = 1;//将CE从低电平改为高电平
_nop_();
SCLK = 0;//SCLK置低电平
_nop_();
for (i = 0;i<8;i++){//输入指令
IO = addr&0x01; //取出最低位
addr = addr>>1; //右移一位,次低位变最低位
SCLK = 1; //产生上升沿,数据传入后再置低电平
_nop_();
SCLK = 0;
_nop_();
}
for (i = 0;i<8;i++){//输入数据
IO = dat&0x01;
dat = dat>>1;
SCLK = 1;
_nop_();
SCLK = 0;
_nop_();
}
CE = 0;
_nop_();
}
/*
* @brief 读取ds1302寄存器数据.
* @details 提供控制指令选择要读的寄存器之后,先将控制命令写入芯片,
此时sclk会完成八个上升沿,七个下降沿。
读的数据会在第八个下降沿开始返回,因此在将最后一个控制命令
的位数据写入后,置低电平时就要从IO开始读。
读到的时间数据是BCD码
* @param addr 写入的控制指令
* @retval 读出的寄存器数据
*/
unsigned char ds1302_read_byte(unsigned char addr){
unsigned char i=0,temp=0,value=0;
CE = 0;
_nop_();
CE = 1;
_nop_();
SCLK = 0;
_nop_();
for (i = 0;i<8;i++){ //输入指令
IO = addr&0x01;
addr = addr>>1;
SCLK = 1;
_nop_();
SCLK = 0;
_nop_();
}
for (i = 0;i<8;i++){ //下降沿读取数据
temp = IO;
/*先将 value 右移 1 位,然后 temp 左移 7 位, 最后或运算
这样可以将按低位拿到的数据变成八位数据
例如寄存器里为10001000,那么value在循环中的值是00000000,
00000000,00000000,10000000,01000000......10001000*/
value=(temp<<7)|(value>>1);
SCLK=1;
_nop_();
SCLK=0;
_nop_();
}
CE = 0;
_nop_();
SCLK=1;
_nop_();
IO = 0;
_nop_();
IO = 1;
_nop_();
return value;
}
/*
* @brief 初始化时钟时间
* @details 将时间数组保存的值用写函数写入ds1302
* @param 无
* @retval 无
*/
void ds1302_init(){
unsigned char i;
ds1302_write_byte(0x8E,0X00);//在写寄存器之前,关闭写寄存器保护
for (i = 0;i<7;i++){
ds1302_write_byte(gWRITE_RTC_ADDR[i],gDS1302_TIME[i]);
}
ds1302_write_byte(0x8E,0X80);//重新开启保护
}
/*
* @brief 将寄存器中的时间写入数组
* @details 将时间数组保存的值用写函数写入ds1302
* @param 无
* @retval 无
*/
void ds1302_read_time(void){
unsigned char i=0;
for(i=0;i<7;i++) {
gDS1302_TIME[i]=ds1302_read_byte(gREAD_RTC_ADDR[i]);
}
}
main.c
main程序中要做的是初始化,不断读取得到bcd码形式的时间值,转换成十进制后打印在LCD1602上。代码如下:
/*
实验名称:ds1302时钟
实现现象:从起点时间开始进行实时时钟
首先将保存在数组中的bcd码形式的起始时间值发送到ds1302,然后再在主函数中不断读取
ds1302寄存器中的时间数值,ds1302会不断进行时间增长。主函数读取到实时的时间后,通过
将bcd转换成10进制后打印在lcd1602上。
*/
#include "ds1302.h"
#include "LCD1602.h"
extern unsigned char gDS1302_TIME[7];
void main(){
unsigned char time_buf[7];
unsigned char i;
ds1302_init();//初始化 DS1302
LCD_Init();//初始化1602
while(1){
ds1302_read_time();//读取时间到数组
for (i = 0 ; i< 7 ; i++){
time_buf[i]=(gDS1302_TIME[i]/16)*10+gDS1302_TIME[i]%16;//将数组中的bcd形式的值转换成10进制并保存在其他数组中
}
//打印时间
LCD_ShowNum(1,1,20,2);
LCD_ShowNum(1,3,time_buf[6],2);
LCD_ShowChar(1,5,'-');
LCD_ShowNum(1,6,time_buf[4],2);
LCD_ShowChar(1,8,'-');
LCD_ShowNum(1,9,time_buf[3],2);
LCD_ShowNum(1,15,time_buf[5],1);
LCD_ShowNum(2,1,time_buf[2],2);
LCD_ShowChar(2,3,':');
LCD_ShowNum(2,4,time_buf[1],2);
LCD_ShowChar(2,6,':');
LCD_ShowNum(2,7,time_buf[0],2);
}
}
其中lcd1602操作的相关代码并不是本人所打,是使用的网络资源因此不在此放出。
备注
本人为51单片机学习者,本文是自己学习的总结。自己水平十分有限。若有错误望请大家多多指教!
本文的代码是跟着官方教程打的,经自己测试没有问题。