【51单片机实战:使用DS1302实现实时时钟】

目录

  • 实验现象
  • DS1302介绍
  • ds1302简介
  • 引脚
  • 工作流程
  • 寄存器
  • 控制寄存器
  • 时间寄存器
  • 读写与时序
  • ds1302的GPIO
  • 代码
  • ds1302.c
  • main.c
  • 备注
  • 实验现象

    将程序烧录到单片机中后,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单片机学习者,本文是自己学习的总结。自己水平十分有限。若有错误望请大家多多指教!

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

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【51单片机实战:使用DS1302实现实时时钟】

    发表评论