51单片机入门_江协科技_23~24_OB记录的笔记DS1302时钟

23. DS1302实时时钟

  • 23.1. DS1302介绍

    •DS1302是由美国DALLAS公司推出的具有涓细电流充电能力的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时,且具有闰年补偿等多种功能

    •RTC(Real Time Clock):实时时钟,是一种集成电路,通常称为时钟芯片

  • 23.2. DS1302引脚定义和应用电路

  • 23.3. DS1302内部结构框图

  • 23.4. 寄存器定义

  • 23.5. 时序定义

  • 23.6. BCD码

    •BCD码(Binary Coded Decimal‎),用4位二进制数来表示1位十进制数

    •例:0001 0011表示13,1000 0101表示85,0001 1010不合法

    •在十六进制中的体现:0x13表示13,0x85表示85,0x1A不合法

    •BCD码转十进制:DEC=BCD/1610+BCD%16; (此公式只有2位BCD可用)
    •十进制转BCD码:BCD=DEC/10
    16+DEC%10; (此公式只有2位BCD可用)

  • 23.7. Proteus中仿真环境搭建

    • 在目前的仿真环境中插入 DS1302时钟芯片,其引脚连接如图,同时注意晶振直接搜索Crystal找到通用晶振插入,插入后将震动频率设置为32768Hz

    • 为了防止后续程序仿真过程中调用LCD1602与之前一节8x8点阵屏出现问题,此处我在点阵屏接P0口之前加了一组8位DIPSW手动开关用于切断点阵屏参与本节实验;

  • 23.8. 示例程序,指定一个初始的时间并显示在LCD1602屏上面,包括第一行的年月日,第二行开始的时分秒。需要之前程序样例中的LCD1602.c与LCD1602.h的模块化程序,并新建DS1302.c与DS1302.h的针对DS1302时钟芯片的模块化程序,用于时钟芯片初始化,写入数据函数,读入数据函数,设定时间,读取时间的操作

    • DS1302.h的程序如下:

      #ifndef _DS1302_H_ 
      

      #define DS1302_H

      extern unsigned char DS1302_Time[];//数组声明外部可调用

      void DS1302_Init(void);
      void DS1302_WriteByte(unsigned char Command,Data);
      unsigned char DS1302_ReadByte(unsigned char Command); //返回
      void DS1302_SetTime(void);
      void DS1302_ReadTime(void);

      #endif

  • DS1302.c的程序如下:

    #include <REGX52.H>

    sbit DS1302_SCLK=P3^6;
    sbit DS1302_IO=P3^4;
    sbit DS1302_CE=P3^5;

    #define DS1302_SECOND 0x80 //定义DS1302秒的写入地址
    #define DS1302_MINUTE 0x82 //定义DS1302分钟的写入地址
    #define DS1302_HOUR 0x84 //定义DS1302小时的写入地址
    #define DS1302_DATE 0x86 //定义DS1302天的写入地址
    #define DS1302_MONTH 0x88 //定义DS1302月份的写入地址
    #define DS1302_DAY 0x8A //定义DS1302星期的写入地址
    #define DS1302_YEAR 0x8C //定义DS1302年的写入地址
    #define DS1302_WP 0x8E //定义DS1302清空

    char DS1302_Time[]={24,04,06,22,56,56,6};//初始化时钟2024年4月6日22时56分56秒周六数组

    void DS1302_Init(void) //DS1302芯片初始化
    {
    DS1302_CE=0;
    DS1302_SCLK=0;
    }

    void DS1302_WriteByte(unsigned char Command,Data) //DS1302写入1字节函数
    {
    unsigned char i;
    DS1302_CE=1;
    for(i=0;i<8;i++)
    {
    DS1302_IO=Command&(0x01<<i); //Command逐位写入,前8个上升沿脉冲
    DS1302_SCLK=1;
    DS1302_SCLK=0;
    }

      for(i=0;i<8;i++)
      {
      	DS1302_IO=Data&(0x01<<i); //Data逐位写入,后8个上升沿脉冲
      	DS1302_SCLK=1;
      	DS1302_SCLK=0;
      }
      DS1302_CE=0; //DS1302关闭
    

    }

    unsigned char DS1302_ReadByte(unsigned char Command)
    {
    unsigned char i,Data=0x00;//必须先声明变量,如果放在Command后就会出错;
    Command|=0x01; //将指令转换为读指令
    DS1302_CE=1;
    for(i=0;i<8;i++) //前8个脉冲上升沿读入命令Command
    {
    DS1302_IO=Command&(0x01<<i);
    DS1302_SCLK=0;
    DS1302_SCLK=1;
    }
    for(i=0;i<8;i++) //借用第8个脉冲的下降沿开始读入数据给Data
    {
    DS1302_SCLK=1;
    DS1302_SCLK=0;
    if(DS1302_IO){Data|=(0x01<<i);}
    }
    DS1302_CE=0;
    DS1302_IO=0; //读取后将IO设置为0,否则读出的数据会出错
    return Data; //返回读入的数据
    }

    void DS1302_SetTime(void)
    {
    DS1302_WriteByte(DS1302_WP,0x00);//关闭写保护;
    DS1302_WriteByte(DS1302_YEAR,DS1302_Time[0]/1016+DS1302_Time[0]%10);
    DS1302_WriteByte(DS1302_MONTH,DS1302_Time[1]/10
    16+DS1302_Time[1]%10);
    DS1302_WriteByte(DS1302_DATE,DS1302_Time[2]/1016+DS1302_Time[2]%10);
    DS1302_WriteByte(DS1302_HOUR,DS1302_Time[3]/10
    16+DS1302_Time[3]%10);
    DS1302_WriteByte(DS1302_MINUTE,DS1302_Time[4]/1016+DS1302_Time[4]%10);
    DS1302_WriteByte(DS1302_SECOND,DS1302_Time[5]/10
    16+DS1302_Time[5]%10);
    DS1302_WriteByte(DS1302_DAY,DS1302_Time[6]/1016+DS1302_Time[6]%10);
    DS1302_WriteByte(DS1302_WP,0x80);//打开写保护;
    }
    void DS1302_ReadTime(void)
    {
    unsigned char temp;
    temp=DS1302_ReadByte(DS1302_YEAR);
    DS1302_Time[0]=temp/16
    10+temp%16; //BCD码高4位和低4位分别转10进制并相加
    temp=DS1302_ReadByte(DS1302_MONTH);
    DS1302_Time[1]=temp/1610+temp%16;
    temp=DS1302_ReadByte(DS1302_DATE);
    DS1302_Time[2]=temp/16
    10+temp%16;
    temp=DS1302_ReadByte(DS1302_HOUR);
    DS1302_Time[3]=temp/1610+temp%16;
    temp=DS1302_ReadByte(DS1302_MINUTE);
    DS1302_Time[4]=temp/16
    10+temp%16;
    temp=DS1302_ReadByte(DS1302_SECOND);
    DS1302_Time[5]=temp/1610+temp%16;
    temp=DS1302_ReadByte(DS1302_DAY);
    DS1302_Time[6]=temp/16
    10+temp%16;

    }

  • main.c主程序如下:

    #include <REGX52.h>
    #include "LCD1602.h"
    #include "DS1302.h"

    unsigned char sec,min,hour;

    void main()
    {

      LCD_Init();
      DS1302_Init();
      LCD_ShowString(1,1,"  -  -  ");
      LCD_ShowString(2,1,"  :  :  ");
      DS1302_SetTime();
      
      while(1)
      {
      	DS1302_ReadTime();
      	LCD_ShowNum(1,1,DS1302_Time[0],2);
      	LCD_ShowNum(1,4,DS1302_Time[1],2);
      	LCD_ShowNum(1,7,DS1302_Time[2],2);
      	LCD_ShowNum(2,1,DS1302_Time[3],2);
      	LCD_ShowNum(2,4,DS1302_Time[4],2);
      	LCD_ShowNum(2,7,DS1302_Time[5],2);
    
      }
    

    }

  • Proteus程序仿真测试如下:

24. DS1302 可调时钟程序

  • 24.1. 复制-黏贴 "10-1 DS1302时钟"的程序文件夹,重命名为" 10-2 DS1302可调时钟 "

  • 24.2. 将之前"按键控制LED流水灯模式" 程序文件夹中的模块程序 Four_Key的.c与.h文件拷贝到10-2的程序文件夹,定时器时钟项目中的TimeR0的.c与.h文件拷贝到10-2的程序文件夹,后续程序需要使用。

  • 24.3. 本节需要最终实现的程序功能是借助4独立按键S1~S4,实现类似真实时钟的功能,S1切换时钟的显示与进入调整模式,S2切换调整的时间(年月日还是时分秒),S3增加相应调整数字,S4减少相应调整数字,LCD1602用于显示输出;

  • 24.4. 示例程序1,加入独立按键模块程序后,

    #include <REGX52.h>
    #include "LCD1602.h"
    #include "DS1302.h"
    #include "delay_xms.h"
    #include "Four_Key.h"
    #include "TimeR0.h"

    unsigned char KeyNum;
    unsigned char sec,min,hour;

    void main()
    {

      LCD_Init();
      DS1302_Init();
      LCD_ShowString(1,1,"  -  -  ");
      LCD_ShowString(2,1,"  :  :  ");
      DS1302_SetTime();
      
      while(1)
      {
      	KeyNum=Four_Key();
      	if(KeyNum)
      	{
      		LCD_ShowNum(2,10,KeyNum,2);
      	}
      	DS1302_ReadTime();
      	LCD_ShowNum(1,1,DS1302_Time[0],2);
      	LCD_ShowNum(1,4,DS1302_Time[1],2);
      	LCD_ShowNum(1,7,DS1302_Time[2],2);
      	LCD_ShowNum(2,1,DS1302_Time[3],2);
      	LCD_ShowNum(2,4,DS1302_Time[4],2);
      	LCD_ShowNum(2,7,DS1302_Time[5],2);
    
      }
    

    }

  • Proteus的仿真测试无误,注意独立按键区域S1与S2位置返的

  • 24.5. 正式程序功能,第一行显示年-月-日,第二行显示时:分:秒,K1按键切换时钟进入显示或者调时模式,K2按键切换需要调整的年-月-日或者时:分:秒,相应调整位置被选中后间隔500ms闪烁,表示该为被选中,因为这一节教程笔者主要针对程序逻辑进行讲解且这段教程比较长,建议对照视频仔细看比较好;

  • 最终程序如下:

    #include <REGX52.h>
    #include "LCD1602.h"
    #include "DS1302.h"
    #include "delay_xms.h"
    #include "Four_Key.h"
    #include "TimeR0.h"

    //MODE类程序选择运行方式思维
    //定时器,按键,时钟综合应用

    unsigned char KeyNum,MODE,TimeSetSelect,TimeSetFlashFlag;

    void TimeShow(void) //
    {
    DS1302_ReadTime();
    LCD_ShowNum(1,1,DS1302_Time[0],2);
    LCD_ShowNum(1,4,DS1302_Time[1],2);
    LCD_ShowNum(1,7,DS1302_Time[2],2);
    LCD_ShowNum(2,1,DS1302_Time[3],2);
    LCD_ShowNum(2,4,DS1302_Time[4],2);
    LCD_ShowNum(2,7,DS1302_Time[5],2);

    }

    void TimeSet(void)
    {
    if(KeyNum==2)//按键2按下
    {
    TimeSetSelect++;//设置选择加1
    TimeSetSelect%=6;//越界清零,因为只有年月日,时分秒6个调整项,数字到6后%6余0
    }
    LCD_ShowNum(2,10,TimeSetSelect,2);

      if(KeyNum==3)//按键3按下
      {
      	DS1302_Time[TimeSetSelect]++;
      	if(DS1302_Time[0]>99){DS1302_Time[0]=0;} //年的越界判断++
      	if(DS1302_Time[1]>12){DS1302_Time[1]=0;} //月的越界判断++
      	if(DS1302_Time[1]==1 || DS1302_Time[1]==3 || DS1302_Time[1]==5
      		|| DS1302_Time[1]==7 || DS1302_Time[1]==8 || DS1302_Time[1]==10
      		|| DS1302_Time[1]==12) //大月判断++,||逻辑或
      	{
      		if(DS1302_Time[2]>31){ DS1302_Time[2]=1;}//大月天数越界判断++
      	}
      	else if(DS1302_Time[1]==4 || DS1302_Time[1]==6 || DS1302_Time[1]==9
      		 || DS1302_Time[1]==11)					//小月天数越界判断++
      	{
      		if(DS1302_Time[2]>30){ DS1302_Time[2]=1;}	
      	}
      	else if(DS1302_Time[1]==2)					//2月天数越界判断++
      	{
      		if(DS1302_Time[0]%4==0)
      		{
      			if(DS1302_Time[2]>29){DS1302_Time[2]=1;}	//闰年2月天数越界
      		}
      		else
      		{
      			if(DS1302_Time[2]>28){DS1302_Time[2]=1;}	//其他年2月天数越界
      		}
      	}		
    
      	if(DS1302_Time[3]>23){DS1302_Time[3]=0;} //小时越界判断
      	if(DS1302_Time[4]>59){DS1302_Time[4]=0;} //分钟越界判断
      	if(DS1302_Time[5]>59){DS1302_Time[5]=0;} //秒钟越界判断		
      	
      }	
      
      if(KeyNum==4)
      {
      	DS1302_Time[TimeSetSelect]--;
      	
      if(DS1302_Time[0]<0){DS1302_Time[0]=99;} //年越界判断--,DS1302_Time[]数组改为有符号char
      if(DS1302_Time[1]<1){DS1302_Time[1]=12;} //月份越界判断--
      if(DS1302_Time[1]==1 || DS1302_Time[1]==3 || DS1302_Time[1]==5
      	|| DS1302_Time[1]==7 || DS1302_Time[1]==8 || DS1302_Time[1]==10
      	|| DS1302_Time[1]==12 )
      		{
      	if(DS1302_Time[2]<1){ DS1302_Time[2]=31;} //大月日数越界判断--
      	if(DS1302_Time[2]>31){ DS1302_Time[2]=1;} //--对大于进行判断,否则会有11月31日情况
      		}
      	else if(DS1302_Time[1]==4 || DS1302_Time[1]==6 || DS1302_Time[1]==9
      		 || DS1302_Time[1]==11)
      		{
      		if(DS1302_Time[2]<1){ DS1302_Time[2]=30;}
      		if(DS1302_Time[2]>30){ DS1302_Time[2]=1;}
      		}
      	else if(DS1302_Time[1]==2)
      		{
      			if(DS1302_Time[0]%4==0)
      			{
      				if(DS1302_Time[2]<1){DS1302_Time[2]=29;}
      				if(DS1302_Time[2]>29){DS1302_Time[2]=1;}
      			}
      			else
      			{
      				if(DS1302_Time[2]<1){DS1302_Time[2]=28;}
      				if(DS1302_Time[2]>28){DS1302_Time[2]=1;}
      			}
      		}	
    
      	if(DS1302_Time[3]<0){DS1302_Time[3]=23;}
      	if(DS1302_Time[4]<0){DS1302_Time[4]=59;}
      	if(DS1302_Time[5]<0){DS1302_Time[5]=59;}	
      }	
      if(TimeSetSelect==0 && TimeSetFlashFlag==1){LCD_ShowString(1,1,"  ");}
      else {LCD_ShowNum(1,1,DS1302_Time[0],2);}
      
      if(TimeSetSelect==1 && TimeSetFlashFlag==1){LCD_ShowString(1,4,"  ");}
      else {LCD_ShowNum(1,4,DS1302_Time[1],2);}
      
      if(TimeSetSelect==2 && TimeSetFlashFlag==1){LCD_ShowString(1,7,"  ");}
      else {LCD_ShowNum(1,7,DS1302_Time[2],2);}
      
      if(TimeSetSelect==3 && TimeSetFlashFlag==1){LCD_ShowString(2,1,"  ");}
      else {LCD_ShowNum(2,1,DS1302_Time[3],2);}
      
      if(TimeSetSelect==4 && TimeSetFlashFlag==1){LCD_ShowString(2,4,"  ");}
      else {LCD_ShowNum(2,4,DS1302_Time[4],2);}
      
      if(TimeSetSelect==5 && TimeSetFlashFlag==1){LCD_ShowString(2,7,"  ");}
      else {LCD_ShowNum(2,7,DS1302_Time[5],2);}
      	
      LCD_ShowNum(2,10,TimeSetSelect,2);//上述设置完毕后更新显示
    

    }

    void main()
    {

      LCD_Init();
      DS1302_Init();
      TimeR0_Init();
      LCD_ShowString(1,1,"  -  -  ");
      LCD_ShowString(2,1,"  :  :  ");
      DS1302_SetTime();
      
      while(1)
      {
      	KeyNum=Four_Key();
    
      	if(KeyNum==1) //按键按下改变MODE,根据MODE值切换TIMESHOW与TIMESET交替运行
      	{
      		if(MODE==0){MODE=1;}
      		else if(MODE==1){MODE=0;DS1302_SetTime();}	//settime将修改好的数据写入DS1302
      	}
      	switch(MODE) //按键1用来切换时钟显示模式还是设置模式
      	{
      		case 0:TimeShow();break;
      		case 1:TimeSet();break;
      	}
      }
    

    }

    void TimeR0_Routine() interrupt 1 //中断子函数
    {
    static unsigned int T0Count; //设置静态子函数用T0Count,防止T0Count丢失
    TL0=0x18; //设置定时初始值
    TH0=0xFC; //设置定时初始值
    T0Count++;
    if(T0Count>=500)
    {
    T0Count=0;
    TimeSetFlashFlag=!TimeSetFlashFlag;//!逻辑取反,~按位取反
    }
    }

  • DS1302.c 程序如下:

    #include <REGX52.H>

    sbit DS1302_SCLK=P3^6;
    sbit DS1302_IO=P3^4;
    sbit DS1302_CE=P3^5;

    #define DS1302_SECOND 0x80
    #define DS1302_MINUTE 0x82
    #define DS1302_HOUR 0x84
    #define DS1302_DATE 0x86
    #define DS1302_MONTH 0x88
    #define DS1302_DAY 0x8A
    #define DS1302_YEAR 0x8C
    #define DS1302_WP 0x8E

    char DS1302_Time[]={24,04,06,22,56,56,6};

    void DS1302_Init(void)
    {
    DS1302_CE=0;
    DS1302_SCLK=0;
    }

    void DS1302_WriteByte(unsigned char Command,Data)
    {
    unsigned char i;
    DS1302_CE=1;
    for(i=0;i<8;i++)
    {
    DS1302_IO=Command&(0x01<<i);
    DS1302_SCLK=1;
    DS1302_SCLK=0;
    }

      for(i=0;i<8;i++)
      {
      	DS1302_IO=Data&(0x01<<i);
      	DS1302_SCLK=1;
      	DS1302_SCLK=0;
      }
      DS1302_CE=0;
    

    }

    unsigned char DS1302_ReadByte(unsigned char Command)
    {
    unsigned char i,Data=0x00;//必须先声明变量,如果放在Command后就会出错;
    Command|=0x01; //将指令转换为读指令,因为写的地址末尾是0,加1后是读的地址
    DS1302_CE=1;
    for(i=0;i<8;i++)
    {
    DS1302_IO=Command&(0x01<<i);
    DS1302_SCLK=0;
    DS1302_SCLK=1;
    }
    for(i=0;i<8;i++)
    {
    DS1302_SCLK=1;
    DS1302_SCLK=0;
    if(DS1302_IO){Data|=(0x01<<i);}
    }
    DS1302_CE=0;
    DS1302_IO=0; //读取后将IO设置为0,否则读出的数据会出错
    return Data;
    }

    void DS1302_SetTime(void) //将数组中的数据写入芯片
    {
    DS1302_WriteByte(DS1302_WP,0x00);//关闭写保护;
    DS1302_WriteByte(DS1302_YEAR,DS1302_Time[0]/1016+DS1302_Time[0]%10);
    DS1302_WriteByte(DS1302_MONTH,DS1302_Time[1]/10
    16+DS1302_Time[1]%10);
    DS1302_WriteByte(DS1302_DATE,DS1302_Time[2]/1016+DS1302_Time[2]%10);
    DS1302_WriteByte(DS1302_HOUR,DS1302_Time[3]/10
    16+DS1302_Time[3]%10);
    DS1302_WriteByte(DS1302_MINUTE,DS1302_Time[4]/1016+DS1302_Time[4]%10);
    DS1302_WriteByte(DS1302_SECOND,DS1302_Time[5]/10
    16+DS1302_Time[5]%10);
    DS1302_WriteByte(DS1302_DAY,DS1302_Time[6]/1016+DS1302_Time[6]%10);
    DS1302_WriteByte(DS1302_WP,0x80);//打开写保护;
    }
    void DS1302_ReadTime(void) //将芯片中的数据读出
    {
    unsigned char temp;
    temp=DS1302_ReadByte(DS1302_YEAR);
    DS1302_Time[0]=temp/16
    10+temp%16;
    temp=DS1302_ReadByte(DS1302_MONTH);
    DS1302_Time[1]=temp/1610+temp%16;
    temp=DS1302_ReadByte(DS1302_DATE);
    DS1302_Time[2]=temp/16
    10+temp%16;
    temp=DS1302_ReadByte(DS1302_HOUR);
    DS1302_Time[3]=temp/1610+temp%16;
    temp=DS1302_ReadByte(DS1302_MINUTE);
    DS1302_Time[4]=temp/16
    10+temp%16;
    temp=DS1302_ReadByte(DS1302_SECOND);
    DS1302_Time[5]=temp/1610+temp%16;
    temp=DS1302_ReadByte(DS1302_DAY);
    DS1302_Time[6]=temp/16
    10+temp%16;

    }

  • DS1302.h程序如下:

    #ifndef DS1302_H
    #define DS1302_H

    extern char DS1302_Time[];//数组声明外部可调用,char型变量用于小于0的判断

    void DS1302_Init(void);
    void DS1302_WriteByte(unsigned char Command,Data);
    unsigned char DS1302_ReadByte(unsigned char Command);
    void DS1302_SetTime(void);
    void DS1302_ReadTime(void);

    #endif

其他main中调用函数同之前项目中使用的程序文件,Proteus中测试没问题;

相关推荐
结衣结衣.12 分钟前
C++ 类和对象的初步介绍
java·开发语言·数据结构·c++·笔记·学习·算法
Starry_hello world3 小时前
二叉树实现
数据结构·笔记·有问必答
云卓科技10 小时前
无人机之数据提取篇
科技·安全·机器人·无人机·制造
唐·柯里昂79810 小时前
[3D打印]拓竹切片软件Bambu Studio使用
经验分享·笔记·3d
sml_542110 小时前
【笔记】连续、可导、可微的概念解析
笔记·线性代数
新手unity自用笔记10 小时前
项目-坦克大战学习-子弹的移动与销毁
笔记·学习·c#
Word码11 小时前
数据结构:栈和队列
c语言·开发语言·数据结构·经验分享·笔记·算法
我命由我1234511 小时前
SSL 协议(HTTPS 协议的关键)
网络·经验分享·笔记·学习·https·ssl·学习方法
丶Darling.12 小时前
代码随想录 | Day26 | 二叉树:二叉搜索树中的插入操作&&删除二叉搜索树中的节点&&修剪二叉搜索树
开发语言·数据结构·c++·笔记·学习·算法
结衣结衣.13 小时前
python中的函数介绍
java·c语言·开发语言·前端·笔记·python·学习