Bootstrap

【51单片机学习笔记】DS1302实时时钟程序

实验现象

将程序烧录到单片机中后,lcd1602显示屏将从预设时间开始进行时钟功能。

在lcd1602显示屏第一行分别显示年,月,日,星期;在第二行显示时,分,秒。

在这里插入图片描述
在这里插入图片描述

DS1302介绍

ds1302简介

DS1302 是 DALLAS 公司推出的涓流充电时钟芯片,内含有一个实时时钟/日历和 31 字节静态 RAM,通过简单的串行接口与单片机进行通信。实时时钟/日历电路提供秒、分、时、日、周、月、年的信息,每月的天数和闰年的天数可自动调整。

DS1302 与单片机之间可以通过三根数据线进行同步串行的方式进行通信:RES,IO数据,SCLK串行时钟。实时时钟具有能计算 2100 年之前的秒、分、时、日、星期、月、年的能力,还有闰年调整的能力。

引脚

  • Vcc2 主电源
  • Vcc1 备用电源
  • X 32.768khz晶振引脚
  • SCLK串行时钟
    - I/O 数据
    - CE(res) 复位引脚
  • GND 接地

工作流程

  1. 预设的时间以bcd码形式写入ds1302的寄存器中,单片机开启后ds1302将从这个时间开始运作
  2. ds1302开始运作后,寄存器里的值会自己按时间变化。不断地取出其中的值,并由bcd码转化成十进制
  3. 取到的十进制按一定格式打印在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写数据,要做以下操作:

  1. 将CE至高,SCLK置低
  2. 将要写入的数据的最低位存入IO
  3. 将SCLK置高,这样产生一个上升沿,上升沿会使IO数据写入芯片,置高一段时间后置低
  4. 将数据右移一位(使次低位变成最低位)
  5. 重复2-4,直到数据写完
  6. 要写数据,首先要用2-5步骤写入对应的控制命令,再用相同的方法写要写的数据
  7. 完成命令与数据输入后,CE置低

与写不同,在输入了读的执行命令后,会在SCLK的下降沿将对应寄存器的值从低位开始传给IO,因此,读寄存器的操作是:

  1. 按照写数据的方法,将执行指令写入芯片
  2. 传完8位指令后,在SCLK为低电平时使用变量保存IO的状态,然后置高电平
  3. 循环读取八次,会读取到从低到高八位数据。将这八次得到的数据做成一个八位的变量
  4. 返回得到的这个八位变量(是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单片机学习者,本文是自己学习的总结。自己水平十分有限。若有错误望请大家多多指教!

本文的代码是跟着官方教程打的,经自己测试没有问题。

;