51单片机之DS1302实时时钟

1.DS1302时钟芯片介绍

  • DS1302是由美国DALLAS公司推出的具有涓细电流充电能力的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时,且具有闰年补偿等多种功能
  • RTC(Real Time Clock):实时时钟,是一种集成电路,通常称为时钟芯片

        这个时钟芯片的应用十分广泛,而且使用它作为时钟计时也是很常见的操作。

        我们51单片机上采用的也是这个芯片,有人认为,我们的定时器也可以做一个时钟计时,但其实,我们的定时器毕竟不是专业的,在长时间的积累下还是会产生一定的误差,而且我们对定时器的采样和各种操作都是会占用一定的的CPU时间的,因为CPU要去处理这个定时器的信息。并且,很重要的一点就是:我们的单片机断电就会让定时器暂停,再次重启单片机的时候,计时才会继续,所以这需要我们一直供电,并且单片机的高频率调用定时器还是挺耗电的,一点都不节能。

        让专业的人做专业的事——我们使用DS1302芯片就是为了解决这个问题的:高精度计时,无惧误差;自带内置电池,不怕断电,断电计时还在继续计时,开启继续显示。

        这里是这个芯片的引脚图:

        这里要注意的是芯片有两个VCC供电,其中VCC2是主电源,就是我们单片机的直接供电,VCC1就是芯片内部的备用电池,让芯片在断电的情况下还可以继续运作。

这个就是芯片的内部结构图:

        

        在芯片手册里面还有这样的一张图:

        这个就是时序图,什么是时序图?你可以理解为单片机内部一个周期内发生的事情用一个电波来表示,这里有三个芯片上的引脚:CE(芯片使能),SCLK(时钟沿),I/O(输入输出)。CE比较简单,置为1就开始工作,置为0就停止工作。SCLK就是时钟周期,图上的箭头表示上升沿有效和下降沿有效。IO就是经典的寄存器,R/W到A0到1这个方向就是从低位到高位的数据。

        R/W表示Read和Write(读和写)。R是高电平有效,W是低电平有效,这个位置决定了调用哪个模式。后面的D0到D7就是芯片内部对应的区域了,代入你想要写入哪个地址,这个就是最后的写入的数据。

        芯片手册里还有这个图片:

        这个图最左边就是读和写对应的地址,中间是对应地址的对应位表示的数据,最后就是数据的范围,第一行是秒,第二行是分钟,第三行是小时(对应有12和24小时制),第四行是日,第五行是月份,第六行是星期,第七行就是年,第八行主要看到WP(即Write Protect),写入保护(WP为1的时候生效,此时所有写入的操作无效),最后一行是电池充电,这个不需要我们配置,保持默认就好。

        然后这个表格中的读写地址是根据这个图的出来的:

        这里就不多解释了,看上面的详细的表格更好一点。它对应的运作模式就是上面的时序图。

2.代码实现时钟

        这里主要用到LCD1602显示屏和这个时钟芯片相互配合实现,有人可能会疑惑为什么不用数码管,其实数码管也是可以的,这里主要是为了显示更多的数字和信息,数码管可以显示的位太少了,所以不使用数码管。

        我们配置寄存器前还要看一下原理图:

        可以看到,SCLK,IO,CE三个引脚都有定义,我们要在代码里使用sbit把它们重新定义一下,以便我们以后调用程序的时候一眼看出写的是什么。

sbit DS1302_SCLK =	P3^6;
sbit DS1306_IO   =	P3^4;
sbit DS1306_CE 	 =	P3^5;

        这里我们要配置初始的时间数值,我们按照时序图先写一个写入函数:

        这里我们看到每次开始前SCLK和CE都是低电平,所以我们在写写入函数前,还要再写一个初始化函数,先把SCLK和CE先初始化为低电平

void DS1602_Init()
{
	DS1306_CE = 0;
	DS1302_SCLK = 0;
}

        看时序图我们可以知道:一个上升沿表示一个数据的写入,和之前我们LED点阵屏的寄存器一样,使用SCLK控制读写数据,先把准备好的数据0/1放在IO口,当时钟上升沿生效之后,这个数据就被读入。并且,有一点要注意的是:数据是从低位(R/W)到高位(1),这个顺序,也就是上面图中从左到右对应输入数据的从低到高,这样我们就可以写出一个IO从低到高输入我们传入指令的代码了(按照时钟周期,先读入前面那8个控制位,后读入后面那八个数据位):

unsigned char i = 0;
for(i = 0;i<8;i++)
{
	DS1306_IO = command&(0x01<<i);
	DS1302_SCLK = 1;
	DS1302_SCLK = 0;
}

        这样就可以读入指令集,然后我们可以看到,后面的部分和前面的部分都是一样的,所以我们仿照前面的循环代码,直接把数据输入到IO口:

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

        把两段代码合并,再加上把使能开启和关闭,就有了:

void DS1602_Write(unsigned char command,unsigned char Data)
{
	unsigned char i = 0;
	DS1306_CE = 1;
	for(i = 0;i<8;i++)
	{
		DS1306_IO = command&(0x01<<i);
		DS1302_SCLK = 1;
		DS1302_SCLK = 0;
	}
	for(i = 0;i<8;i++)
	{
		DS1306_IO = Data&(0x01<<i);
		DS1302_SCLK = 1;
		DS1302_SCLK = 0;
	}
	DS1306_CE = 0;
}

        然后我们就要按照时序图配置读数据的函数了:

        这里我们如果复用上面的读取指令的代码会出现一点问题:

        按照上面代码的写法,我们的函数会停留在时钟周期的这个红线的位置,这个时候我们发现:我们由触发了一次下降沿,也就是我们把后面数据又多读入了一位,这样其实不利于我们再写读数据部分的思路,我们就要把这两部分分离开来。怎么做?很简单,把SCLK置为0的步骤放在前面就好了:

	for(i = 0;i<8;i++)
	{
		DS1306_IO = command&(0x01<<i);
		DS1302_SCLK = 0;
		DS1302_SCLK = 1;
	}

        这样我们会发现,我们的停止位置红线到了这个地方:

        意满离,我们可以开始配置后面读取数据的代码了:

        这里我们先给SCLK一个下降沿,这个时候数据就到了IO口上了,我们就可以拿一个变量把它存起来,然后等下一个周期,直到全部读取完成。

        这里数一下,一共有8个下降沿,但是上升沿却只有7个,所以我们的代码还是要要使用循环,保证上升沿和下降沿的个数相同,我们就可以进入循环时(此时为高电平)先置为1,再置为0,再读取数据,就解决了前面的痛点了:

	for(i = 0;i<8;i++)
	{
		DS1302_SCLK = 1;
		DS1302_SCLK = 0;
		if(DS1306_IO)
		{
			Data |= 0x01 << i;
		}
	}

        然后就有下面的代码:

unsigned char DS1602_Read(unsigned char command)
{
	unsigned char i = 0;
	unsigned char Data = 0x00;
	DS1306_CE = 1;
	for(i = 0;i<8;i++)
	{
		DS1306_IO = command&(0x01<<i);
		DS1302_SCLK = 0;
		DS1302_SCLK = 1;
	}
	for(i = 0;i<8;i++)
	{
		DS1302_SCLK = 1;
		DS1302_SCLK = 0;
		if(DS1306_IO)
		{
			Data |= 0x01 << i;
		}
	}
	DS1306_CE = 0;
	return Data;
}

        至此,我们主要的函数就实现了,现在只要使用LCD1602把数据显示一下就好了。

        这里我们先演示一下把数据写入和读出:

unsigned char Second = 0x00;	
void main()
{
	LCD_Init();
	DS1602_Init();
	DS1602_Write(0x8E,0x00);
	
	DS1602_Write(0x80,0x03);
	Second = DS1602_Read(0x81);
	LCD_ShowNum(1,1,Second,3);
	
	while(1)
	{
		
	}
}

        这里有一句写入0x8e时比较重要的,不知道什么情况,这个只要使用一次下次这个就不需要加了,这句的作用就是取消写保护,在前面表格里我们介绍过的,如果没有这个,很可能你的显示出来就是128或者255这种没有初始化的值。

        那么,我们把显示数字和读取数字调用放在while循环前面的时候,我们可以显示出来一个数字,那么我们把它们放在while循环里面,他是不是就可以显示秒的变化了呢?没错!但是你直接这样做可能显示出来的数字时一个有点花的数字,这里猜测可能是循环进行太快,导致IO口上的数字串位,总之我们只要做一件事——在read函数返回前加上一个DS1306_IO = 0;

        然后我们就可以看到我们的显示出来的数字正常运作,但是仔细一看又有一点不对:9之后数字变成了16,这是为什么?

        我们的DS1302芯片使用的是BCD码,BCD码是什么?就是一个使用类似于十六进制格式十进制的一种格式这里举个例子:

        十六进制的0x18中,8占的是8的0次方的权重,1占的是8的1次方的权重,而在BCD码的表示里,4占的权重是10的0次方,2占的权重是10的1次方,使用2进制就还是正常表示但是又数据范围限制,换句话来说,其实BCD码可以用二进制表示,也可以用十六进制表示,但是它在二进制转化成16进制之后,它的每一位权重要改变一下,而且无法表示十六进制中A B C D E F这些位。

        因此,我们前面使用这样用BCD码表示的时候,它使用0000 1001/0x09,即数字9,但是它加1的时候,它就变成了0001 0000/0x10,即数字10,而在16进制中意思是16,我们使用LCD1602的时候调用的函数是普通的十进制使用函数,所以它显示就从9变成了16,我们这里有两种办法:1.把函数使用16进制转化,这个时候即便它从0x09变成0x10,在显示屏幕上显示的还是9和10;2.使用公式转换:

        这里为了更加清晰了解我们的逻辑,更多使用这个公式法,当然,我们可不能把这个参数直接就改了,这样会出大问题的,要借助一个临时变量来做这个动作,当然,我使用函数:

unsigned char BCDchange(unsigned char BCDNum)
{
	return BCDNum/16*10+BCDNum%16; 
}
void main()
{
	LCD_Init();
	DS1602_Init();
	DS1602_Write(0x8E,0x00);
	
	DS1602_Write(0x80,0x03);
	while(1)
	{
		Second = DS1602_Read(0x81);
		LCD_ShowNum(1,1,BCDchange(Second),3);
	}
}

        这样就很好,即不改变两种函数内部原本的运行逻辑,又把它们完美的串联在了一起。

        接下来就比较简单了,完成了上面的操作之后,依葫芦画瓢,做出一个时钟就跟喝水一样简单,但是完美这里想要把它们都集成为简单的函数,只要调用函数就可以实现写和读

        既然要追求简单,那就贯彻到底了,我们发现我们经常使用0x80,0x81...这样的东西,真的是很麻烦,难道我们每次都要查表看这个地址吗?

        读写模式之间只差了一个位,例如秒的写是0x80,读就是0x81,分的写是0x82,读就变成0x83了,其实就是最后一个位的区别,写为0,读为1

        知道这样的规律之后,我们就可以做一点不一样的了:

        从秒到年到周,这个都是有顺序的,从0x80到0x8C,我们为什么不按照这样的规律写我们的函数呢?

         这里我们再定义一个读写的缓存区,就是定义一个数组,我们把读取和写入的放在这个数组里,这样就OK了,所以我们就可以定义一个全局变量数组:充当缓存区

//0.second 1.minute 2.hour 3.data 4.month 5.week 6.year
unsigned char DS_Buffer[7];

        这样我们就不用频繁传参返回函数了,十分的简单

        然后,我们这里就使用上面的规律和这个数组,实现十分简单的读数函数:

void DS1302_ReadTime()
{
	unsigned char PreAdress = 0x80 + 1;
	unsigned char i = 0;
	for(i = 0;i < 7;i++)
	{
		DS_Buffer[i] = DS1602_Read(PreAdress + i*2);
	}
}

        这里前面加一是因为所有读数都要加一,这里后面i*2是因为每两个形式之间(比如秒和分)相隔2。

void DS1302_WriteTime()
{
	unsigned char PreAdress = 0x80;
	unsigned char i = 0;
	DS1602_Write(0x8E,0x00);
	for(i = 0;i < 7;i++)
	{
		DS1602_Write(PreAdress + i*2,DS_Buffer[i]);
	}
	DS1602_Write(0x8E,0x80);
}

        这里我们和读不同的是,我们需要在写入之前关闭写保护,然后出函数时再开启写保护,然后就是这里的缓存数组变成了我们读取信息的数组,我们就要把它先使用别的函数初始化这个信息,然后再调用该函数读取缓存数组里面的数。当然,别忘了初始地址,读写的地址是不一样的。

        然后我们就可以实现显示时间的函数,这里做了一个格式,直接使用这个函数就可以显示年月日,时分秒:

void Formate()
{
	LCD_ShowString(1,1,"  :  :  ");
	LCD_ShowString(2,1,"    -  -  ");
	DS1302_ReadTime();
	LCD_ShowNum(1,1,BCDchange(DS_Buffer[2]),2);
	LCD_ShowNum(1,4,BCDchange(DS_Buffer[1]),2);
	LCD_ShowNum(1,7,BCDchange(DS_Buffer[0]),2);
	LCD_ShowNum(2,1,20,2);
	LCD_ShowNum(2,3,BCDchange(DS_Buffer[6]),2);
	LCD_ShowNum(2,6,BCDchange(DS_Buffer[4]),2);
	LCD_ShowNum(2,9,BCDchange(DS_Buffer[3]),2);
}

        放在函数while循环内:

void main()
{
	LCD_Init();
	DS1602_Init();
	
	DS1302_WriteTime();
	DS1302_ReadTime();
	while(1)
	{
		Formate();
	}
}

       这里显示的格式是这样的:

21:11:50
2024-04-16 

         但是这里各位会发现一个问题:函数频闪的问题有点严重,这是因为我们有些不需要变化的数字也还是重新写入了,这里我们的解决方案:

使用Delay函数,大概需要0.5s以上的Delay才可以比较有效解决频闪,但是会导致有一点的误差(每个1s之间的读取都是靠芯片的,这里的误差只是刷新的误差,比如已经从5s到了6s,但是我们还在Delay中导致没有及时显示出来)

        还有别的实现吗?还在慢慢发掘中...

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/550632.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HTML段落标签、换行标签、文本格式化标签与水平线标签

目录 HTML段落标签 HTML换行标签 HTML格式化标签 加粗标签 倾斜标签 删除线标签 下划线标签 HTML水平线标签 HTML段落标签 在网页中&#xff0c;要把文字有条理地显示出来&#xff0c;就需要将这些文字分段显示。在 HTML 标签中&#xff0c;<p>标签用于定义段落…

【前端】1. HTML【万字长文】

HTML 基础 HTML 结构 认识 HTML 标签 HTML 代码是由 “标签” 构成的. 形如: <body>hello</body>标签名 (body) 放到 < > 中大部分标签成对出现. <body> 为开始标签, </body> 为结束标签.少数标签只有开始标签, 称为 “单标签”.开始标签和…

一次配置Docker环境的完整记录

一次配置Docker环境的完整记录 Docker环境搭建报错与解决报错一报错二报错三 Docker环境搭建 本节介绍了一次配置docker环境的完整记录&#xff1a; 编写Dockerfile文件&#xff1a; FROM pytorch/pytorch:1.10.0-cuda11.3-cudnn8-develRUN rm /etc/apt/sources.list.d/cuda.l…

C++设计模式|创建型 2.工厂模式

1.简单工厂思想 简单工厂模式不属于23种设计模式之⼀&#xff0c;更多的是⼀种编程习惯。它的核心思想是将产品的创建过程封装在⼀个⼯⼚类中&#xff0c;把创建对象的流程集中在这个⼯⼚类⾥⾯。卡码网将其结构描述为下图所示的情况&#xff1a; 简单⼯⼚模式包括三个主要⻆⾊…

zabbix 自动发现与自动注册 部署 zabbix 代理服务器

zabbix 自动发现&#xff08;对于 agent2 是被动模式&#xff09; zabbix server 主动的去发现所有的客户端&#xff0c;然后将客户端的信息登记在服务端上。 缺点是如果定义的网段中的主机数量多&#xff0c;zabbix server 登记耗时较久&#xff0c;且压力会较大。1.确保客户端…

uboot的移植

文章目录 一、官方uboot移植1.Uboot系统复制到Ubuntu系统2.解压Uboot系统3.编译Uboot系统4.生成可执行文件5.将u-boot.bin烧录到SD卡6.SD卡插入到板子&#xff0c;启动方式选择SD卡7.复位板子&#xff0c;查看打印信息&#xff0c;编译时间是否正常 二、根据官方提供的uboot添加…

frp 内网穿透配置(v0.55.1 版本)

注意&#xff1a;从 [v0.52.0] 版本开始&#xff0c;配置文件由 frps.ini 改成了 frps.toml 一种快速反向代理&#xff0c;可帮助您将 NAT 或防火墙后面的本地服务器暴露给 Internet。 GitHub 地址 &#xff1a; github.com/fatedier/fr… 下载之后如果碰到杀毒软件报毒&#x…

富文本在线编辑器 - tinymce

tinymce 项目是一个比较好的富文本编辑器. 这里有个小demo, 下载下来尝试一下, 需要配置个本地服务器才能够访问, 我这里使用的nginx, 下面是我的整个操作过程: git clone gitgitee.com:chick1993/layui-tinymce.git cd layui-tinymcewget http://nginx.org/download/nginx-1.…

00_Qt概述以及如何创建一个QT新项目

Qt概述 1.Qt概述1.1 什么是Qt1.2 Qt的发展史1.3 支持的平台1.4 Qt版本1.5 Qt的下载与安装1.6 Qt的优点 2.QT新项目创建3.pro文件4.主函数5.代码命名规范和快捷键 1.Qt概述 1.1 什么是Qt Qt是一个跨平台的C图形用户界面应用程序框架。它为应用程序开发者提供建立艺术级图形界面…

【一竞技CS2】VP战队官宣签下electroNic取代mir

1、近日VP战队官宣签下electroNic&#xff0c;以取代阵容中的mir。 electroNic自己也表示&#xff1a;“VP是一支顶级队伍。阵容核心曾赢得Major冠军&#xff0c;所有队员都处于巅峰状态并且时刻准备着去争夺冠军。我们有着一样的雄心壮志。 此外我还对和Jame很感兴趣&#xf…

解决nginx日志过大问题

1. 问题点 nginx默认的日志在logs/access.log&#xff0c;并且是一直累加写入&#xff0c;时间长了就会非常大&#xff0c;占用过多的硬盘&#xff0c;如果强行删除是很不友好的&#xff0c;需要重启服务&#xff1b; 2. 文件分割 上图文件已经达到了十个G左右 处理的思路肯定…

AI大模型探索之路-应用篇14:认识国产开源大模型GLM

目录 前言 一、国产主流大模型概览 1. 国内主流大模型清单 2. 主流大模型综合指数 3. 大语言模型评测榜单 二、GLM大模型介绍 三、GLM大模型发展历程 四、GLM家族之基座模型GLM-130B 五、GLM家族之ChatGLM3 六、GLM家族之WebGLM 七、GLM家族之CogVLM 1. CogVLM 2. …

2024五一杯数学建模A题思路分析

文章目录 1 赛题思路2 比赛日期和时间3 组织机构4 建模常见问题类型4.1 分类问题4.2 优化问题4.3 预测问题4.4 评价问题 5 建模资料 1 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 2 比赛日期和时间 报名截止时间&#xff1a;2024…

P9241 [蓝桥杯 2023 省 B] 飞机降落

原题链接&#xff1a;[蓝桥杯 2023 省 B] 飞机降落 - 洛谷 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 dfs全排列的变形题。 因为最后问飞机是否降落&#xff0c;并且一架飞机降落完毕时另一架飞机才能降落。所以我们设置dfs的两个变量cnt为安全…

解决EasyPoi导入Excel获取不到第一列的问题

文章目录 1. 复现错误2. 分析错误2.1 导入的代码2.2 DictExcel实体类2.2 表头和标题 3. 解决问题 1. 复现错误 使用EasyPoi导入数据时&#xff0c;Excel表格如下图&#xff1a; 但在导入时&#xff0c;出现如下错误&#xff1a; name为英文名称&#xff0c;在第一列&#xff0c…

Java代码基础算法练习-水仙花数-2024.04.17

任务描述&#xff1a; 水仙花数也被称为超完全数字不变数、自恋数、自幂数、阿姆斯壮数或阿姆斯特朗数。水仙花数是 指一个 3 位数&#xff0c;它的每个位上的数字的3次幂之和等于它本身。 例如: 1的3次方 5的3次方 …

计算机网络的七层模型

序 OSl(Open System Interconnect)&#xff0c;即开放式系统互联。一般都叫OSI参考模型。在网络编程中最重要的模型就是OSI七层网络模型和TCP/IP四层网络模型 一、OSI七层参考模型以及功能概述 二、各层的具体职能以及实际应用 1.应用层&#xff1a; OSI参考模型中最接近用…

最新的网易星球GEC挖矿系统修复版 章鱼星球挖矿系统源码 区块链虚拟币交易源码 基于ThinkPHP5开发

区块链系统介绍 2018.12.10更新增加聚合数据短信接口 2018.11.19更新增加短信宝接口 2018.08.17修复Linux系统搭建验证码不显示问题 2018.08.09修复后台某处溢出数据库账号密码BUG 2018.08.06修复票卷BUG 源码介绍&#xff1a; 区块链系统中用户共九个等级&#xff0c;依…

【Git】生成patch和应用patch

生成patch 将本地所有修改打成补丁 git diff > /tmp/xxx.patch将本地对某个文件的修改打成补丁 git diff test/1.txt > /tmp/1.patch将某一次提交的修改内容打成补丁 -1表示只为单个提交创建patch&#xff0c;-o表示输出patch的文件夹路径&#xff0c;默认是用提交的…

轻松查询车辆信息的全能接口

在当今社会&#xff0c;车辆已经成为人们出行的重要工具之一。当我们在二手车买卖、事故处理或者其他需要查询车辆详细信息的情况下&#xff0c;我们通常需要耗费大量时间和精力去收集相关的资料。幸好&#xff0c;有了车辆信息查询接口&#xff0c;我们可以通过输入车架号vin来…
最新文章