51单片机之DS1302实时时钟

news2024/11/23 18:59:47

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.coloradmin.cn/o/1601013.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于stm32_h5的freertos编程示例

目录 基于stm32_h5的freertos编程示例实验目的添加FreeRTOS配置FreeRTOS测试工程本文中使用的测试工程 基于stm32_h5的freertos编程示例 本文目标&#xff1a;基于stm32_h5的freertos编程示例 按照本文的描述&#xff0c;应该可以在对应的硬件上通实验并举一反三。 先决条件…

类的加载,反射和注解详解

文章目录 类的加载概述类加载器作用分类获取类加载器的方式 双亲委派机制3种加载器的关系工作机制 类加载器的应用 反射概述关键获取类对象获取构造器对象获取方法对象获取成员变量对象作用 注解概述作用自定义注解格式属性类型 元注解常见的元注解 注解解析概述方法技巧 类的加…

LabVIEW仪器信息管理系统

LabVIEW仪器信息管理系统 在计量检测实验室的日常工作中&#xff0c;仪器检定校准是一项基础而重要的任务。随着科技的进步和实验室工作量的增加&#xff0c;传统的人工管理方式已经难以满足现代实验室对效率和准确性的要求。开发一套基于LabVIEW的仪器信息管理系统显得尤为必…

还有同学开题报告没写吗?

引言 作为一名在软件技术领域深耕多年的专业人士&#xff0c;我不仅在软件开发和项目部署方面积累了丰富的实践经验&#xff0c;更以卓越的技术实力获得了&#x1f3c5;30项软件著作权证书的殊荣。这些成就不仅是对我的技术专长的肯定&#xff0c;也是对我的创新精神和专业承诺…

Jmeter 场景测试:登录--上传--下载--登出

为了练习Jmeter的使用&#xff0c;今天我要测试的场景是“登录--上传--下载--登出”这样一个过程. 测试的目标是我曾经练手写的一个文件分享系统&#xff0c;它要求用户只有登录后才可以下载想要的文件。 Jmeter总体结构&#xff1a; 第一步&#xff1a;添加HTTP Cookie管理器…

聊聊最近两星期的学习吧!

今天是4月14号。 自从我3月份回到学校之后&#xff0c;我每天都有记录自己的学习时长。今天晚上&#xff0c;我在复盘我自己学习时长的时候&#xff0c;我发现&#xff0c;在整个四月份&#xff0c;我平均每天的有效学习时长只有6h&#xff0c;而且到今天为止&#xff0c;整个四…

Java报表是什么?盘点2023最实用的四款Java报表

从字面义就可以推知&#xff0c;Java报表指的是在Java环境下开发或使用的报表工具。Java语言因其功能强大和简单易用的特点&#xff0c;是静态面向对象编程语言的代表&#xff0c;在Java环境开发使用的这些报表工具&#xff0c;可以通过提供可视化操作界面制作报表&#xff0c;…

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

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

LabVIEW光学探测器板级检测系统

LabVIEW光学探测器板级检测系统 特种车辆乘员舱的灭火抑爆系统广泛采用光学探测技术来探测火情。光学探测器作为系统的关键部件&#xff0c;其探测灵敏度、响应速度和准确性直接关系到整个系统的运行效率和安全性。然而&#xff0c;光学探测器在长期使用过程中可能会因为灰尘污…

怎么用手机远程控制电脑 远程控制怎么用

怎么用手机远程控制电脑&#xff1a;远程控制怎么用 在这个科技日新月异的时代&#xff0c;远程控制电脑已经成为了很多人的需求。有时&#xff0c;我们可能在外出时突然需要访问家中的电脑&#xff0c;或者在工作中需要远程操控办公室的电脑。这时&#xff0c;如果能用手机远…

力扣:141. 环形链表

力扣&#xff1a;141. 环形链表 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾…

解决Linux根分区空间不足的方法:利用Home分区进行扩容

前言 在进行系统安装时&#xff0c;一个常见的困扰是默认分区设置可能导致home分区拥有过多的空间&#xff0c;而root分区却显得十分紧缺。这种情况下&#xff0c;用户往往会陷入无法继续安装软件或存储文件的困境。本文将向您展示如何通过合理的调整&#xff0c;将home分区中多…

贪吃蛇(C语言版--1)

在我们学完C语言之后 我们准备写一个贪吃蛇的代码 但在我们写贪吃蛇代码之前 我们需要铺垫一些数据结构的知识 顺序表基于顺序表实现通讯录项目单链表双向链表 顺序表&#xff08;底层是数组&#xff09; 顺序表其实是线性表&#xff08;具有相同特性的数据结构的集合&…

【网络编程】UDP实现回显服务器

一.网络编程的基本术语. 客户端 客户端是为用户提供本地服务的程序&#xff0c;通常位于用户设备上。也称为用户端&#xff0c;是相对于服务器而言的。它主要指安装在用户设备上的程序&#xff0c;这些程序能够与服务器进行通信&#xff0c;从而获取服务或者执行特定功能。在…

window轻松使用k8s

Docker Desktop安装篇 1、win安装 1、下载安装包 https://www.docker.com/products/docker-desktop/ 官网下载安装包 2、配置win支持虚拟化 不勾选Hyper-V&#xff0c;它和Windows Subsystem for Linux (WSL) 是两套功能&#xff0c;这里不选他 3、安装WSL配置window支持lin…

6-pytorch - 网络的保存和提取

前言 我们训练好的网络&#xff0c;怎么保存和提取呢&#xff1f; 总不可以一直不关闭电脑吧&#xff0c;训练到一半&#xff0c;想结束到明天再来训练&#xff0c;这就需要进行网络的保存和提取了。 本文以前面博客3-pytorch搭建一个简单的前馈全连接层网络&#xff08;回归问…

Level protection and deep learning

1.模拟生成的数据 import randomdef generate_data(level, num_samples):if level not in [2, 3, 4]:return Nonedata_list []for _ in range(num_samples):# 构建指定等级的数据data str(level)for _ in range(321):data str(random.randint(0, 9))data_list.append(data)…

2.4G漂移小车电子方案 酷得智能科技

漂移高速遥控车是一种专门设计用于执行高速漂移动作的遥控车模型。以下是一些关于漂移高速遥控车的功能介绍&#xff1a; 1、高速性能&#xff1a;漂移车通常配备有强力的电机和电池&#xff0c;以便在保持高速的同时进行漂移动作。 2、漂移能力&#xff1a;漂移车的轮胎和悬挂…

操作系统—实现可变式分区分配算法

文章目录 实现可变式分区分配算法1.实验环境2.如何在xv6中实现分区分配算法&#xff1f;(1).xv6的内存管理机制(2).实现思路 3.最佳适应算法(1).基本思路(2).步骤(3).测试&Debug 总结参考资料 实现可变式分区分配算法 1.实验环境 因为这一次的实验仍然是在xv6中进行&#…

【AIGC】AIGC在虚拟数字人中的应用:塑造未来互动体验的革新力量

&#x1f680; &#x1f680; &#x1f680;随着科技的快速发展&#xff0c;AIGC已经成为引领未来的重要力量。其中&#xff0c;AIGC在虚拟数字人领域的应用更是引起了广泛关注。虚拟数字人作为一种先进的数字化表达形式&#xff0c;结合了3D建模、动画技术、人工智能等多种先进…