当前位置: 首页 > article >正文

【51单片机】DS1302实时时钟

学习使用的开发板:STC89C52RC/LE52RC
编程软件:Keil5
烧录软件:stc-isp

开发板实图:
在这里插入图片描述

文章目录

  • DS1302
    • BCD码
    • 读出/写入时间信息原理
    • 时序定义
  • 编码
    • DS1302模块
    • 小程序——实时时钟
    • 小程序——可调节实时时钟

DS1302

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

在这里插入图片描述


原理图如下:
在这里插入图片描述
左侧部分为晶振部分,提供晶振源

  • VCC2:主电源
  • VCC1:备用电池
  • GND:电源接地
  • X1、X2:接晶振部分,32.768KHz晶振
  • CS:芯片使能
  • I/O:数据输入/输出
  • SCLK:上升沿串行时钟

内部结构图如下:
在这里插入图片描述

  • 电源控制:提供电源
  • 晶振源:实现实时时钟的时间改变
  • 时钟存储:存储年、月、日、周、时、分、秒信息
  • 命令控制逻辑:控制本次操作为读取/写入,选择对象(年、月、日…)
  • 输入移位寄存器:输入命令/数据

DS1302实现实时时钟的原理如下:

时钟存储有年月日等信息,晶振源提供晶振频率,让时钟存储的信息每秒加一

我们可以通过命令控制逻辑,告诉DS1302我们是要读出时钟存储的时间信息,还是覆写时间信息。覆写后的时间依然可以依靠晶振频率实现实时

读出和输入覆写都通过I/O口

BCD码

时钟存储的时间信息并不是十六进制数,而是BCD码

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

例:0001 0011表示13,前四个二进制为1,后四个二进制为3,组合在一起就是13
例:1000 0101表示85,前四个二进制为8,后四个二进制位为5,组合在一起为85
例:0001 1010不合法,因为BCD码规定4位二进制只能表示0 ~ 9

BCD码转十进制:DEC = BCD / 16 * 10 + BCD % 16(2位BCD)
十进制转BCD码:BCD = DEC / 10 * 16 + DEC % 10(2位BCD)

读出/写入时间信息原理

读出还是写入,通过命令控制逻辑,对年月日哪个时间信息进行操作,也是通过命令控制逻辑

命令控制逻辑通过命令字控制

在这里插入图片描述

命令字启动每一次数据传输
MSB(位 7) 必须是逻辑 1,如果是 0,则禁止对 DS1302 写入
位 6 在逻辑 0 时规定为时钟/日历数据,逻辑 1 时为 RAM 数据。
位 1 到 位 5 表示输入输出的指定寄存器,例如 00000 为秒寄存器,00001 为分钟寄存器,00010 为小时寄存器
LSB(位 0) 在逻辑 0 时为写操作逻辑 1 时为读操作


下图给出控制寄存器地址定义:
在这里插入图片描述

左侧两列分别是相应寄存器的读/写命令字

例如,秒寄存器写操作
在这里插入图片描述
位 7固定为1,位6 = 0 表示时钟模式,A0 ~ A5: 00000 选择秒寄存器,位0 = 0 表示读操作

因为同一寄存器的读/写操作只有在位 0 有所区别,写操作 = 1,读操作 = 0,所以写操作的命令字 = 读操作的命令字 |= 0x01


存储使用BCD码形式
例如秒寄存器。秒的范围为 0 ~ 59,用BCD码拆分就是4位表示 0 ~ 5,4 位表示 0 ~ 9。而 0 ~ 5 只要用 3 位就可以表示,所以就像下图的存储

在这里插入图片描述

时序定义

命令字和写入的时间数据都要通过I/O口,I/O口单次传输的数据是一位,然后通过 SCLK = 1,将数据写入移位寄存器

时序控制图如下:

在这里插入图片描述

  • CE:所有数据传输开始驱动 CE 输入高,即 CE = 1。CE实现两个功能:第一,CE 开启允许对地址/命令序列的移位寄存器进行读写的控制逻辑;第二,CE 信号为单字节和多字节 CE 数据数据传输提供了终止的方法
  • 当 SCLK = 1 上升沿时,I/O口的数据会被写入移位寄存器
  • 数据输入:输入写命令后,接下来的 8 个 SCLK 周期的上升沿数据字节被输入。
    • 数据输入以 位 0 开始。如存储15,BCD码 = 15 = 0001 0101。从最低位的1开始传输
  • 数据输出:输入读命令后的 8 个 SCLK 周期,下降沿输出一个数据字节。
    • 注意第一个数据位的传送发生在命令字节被写完后的第一个下降沿
    • 数据输出从位 0 开始

写保护寄存器

在这里插入图片描述

控制寄存器的位 7 是写保护位,前 7位(位 0至位 6)被强制为 0且读取时总是读 0。在任何对时钟或 RAM 的写操作以前,位 7必须为 0。当为高时,写保护位禁止任何寄存器的写操作。初始加电状态未定义,因此,在试图写器件之前应该清除 WP 位。
WP = 0 时允许写入,WP = 1 时禁止写入

编码

DS1302模块

首先对串口号和命令字进行定义,方便使用

#include <REGX52.h>
//引脚定义
sbit DS1302_IO = P3^4;		//数据输入/输出
sbit DS1302_CE = P3^5;		//使能
sbit DS1302_SCLK = P3^6;	//串行时钟上升沿,分隔相邻数据

//寄存器写入指令定义
#define DS1302_SECOND		0x80
#define DS1302_MINUTE		0x82
#define DS1302_HOUR			0x84
#define DS1302_DATE			0x86
#define DS1302_MOUTH		0x88
#define DS1302_DAY			0x8A
#define DS1302_YEAR			0x8C
//写保护寄存器
#define DS1302_WP			0x8E

此处只定义了写命令字,读命令字 = (写命令字 |= 0x01)

接下来封装向DS1302写数据和读数据的操作

写数据
根据时序图,要先写入命令字,再写入数据
每一位数据都通过I/O口输入,在 SCLK = 1 上升沿时写入移位寄存器
写入的数据要由十进制转为BCD码

/**
  * @brief		往DS1302中写数据
  * @parm		Command(命令字):写秒or分钟...		范围:0 ~ 255
  * @parm		Data:要写入的数据			范围:0 ~ 255
  * @retval		无
  */
void DS1302_WriteByte(unsigned char Command, unsigned char Data)
{
	int i = 0;
	//打开使能
	DS1302_CE = 1;
	//先写命令Command
	//8位数据, 从低位开始
	for(i = 0; i < 8; ++i)
	{
		DS1302_IO = Command & (0x01 << i);
		//给串行时钟上升沿
		DS1302_SCLK = 1;
		DS1302_SCLK = 0;
	}
	//再写数据,数据要由十进制转为BCD码
	Data = (Data / 10 * 16) + (Data % 10);
	for(i = 0; i < 8; ++i)
	{
		DS1302_IO = Data & (0x01 << i);
		//给串行时钟上升沿
		DS1302_SCLK = 1;
		DS1302_SCLK = 0;
	}
	DS1302_CE = 0;//使能复位
}

读数据
根据时序图,要先写入命令字,再读出数据
命令字数据通过I/O口输入,在 SCLK = 1 上升沿时写入移位寄存器
读出数据通过I/O口输出,在 SCLK = 0 下降沿读出
读出的数据要由BCD码转为十进制

/**
  * @brief		DS1302读取数据
  * @parm		Command(命令字): 读秒or分钟...		范围:0 ~ 255
  * @retval		返回读到的数据						范围:0 ~ 255
  */
unsigned char DS1302_ReadByte(unsigned char Command)
{
	unsigned char Data = 0;
	int i = 0;
	//传入写指令,转化为读指令
	Command |= 0x01;
	DS1302_CE = 1;
	//8位数据, 从低位开始
	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,否则读出的数据会出错
	//读出的数据要由BCD码转为十进制
	Data = (Data / 16 * 10) + (Data % 16);
	return Data;
}

到此向DS1302写数据和读数据的操作就封装完毕了
接下来是复用上述操作,实现设置时间和读取时间

使用一个数组存储时间信息

//实时时钟初始时间
//							   24年11月2日11时59分55秒周6
unsigned char DS1302_Time[] = {24, 11, 2, 12, 59, 55, 6};

设置时间
设置时间需要先关闭写保护,然后将数组的时间信息写入DS1302

/**
  * @brief		将DS1302_Time的时间设置进DS1302
  * @parm		无
  * @retval		无
  */
void DS1302_SetTime()
{
	//关闭写保护
	DS1302_WriteByte(DS1302_WP, 0x00);
	DS1302_WriteByte(DS1302_YEAR, DS1302_Time[0]);//年
	DS1302_WriteByte(DS1302_MOUTH, DS1302_Time[1]);//月
	DS1302_WriteByte(DS1302_DATE, DS1302_Time[2]);//日
	DS1302_WriteByte(DS1302_HOUR, DS1302_Time[3]);//时
	DS1302_WriteByte(DS1302_MINUTE, DS1302_Time[4]);//分
	DS1302_WriteByte(DS1302_SECOND, DS1302_Time[5]);//秒
	DS1302_WriteByte(DS1302_DAY, DS1302_Time[6]);//周几
	//打开写保护
	DS1302_WriteByte(DS1302_WP, 0x80);
}

读取时间
将DS1302的所有时间信息读出,更新到时间数组中

/**
  * @brief		读取次数DS1302时间,并设置到DS1302_Time数组中
  * @parm		无
  * @retval		无
  */
void DS1302_ReadTime()
{
	unsigned char tmp;
	tmp = DS1302_ReadByte(DS1302_YEAR);//年
	DS1302_Time[0] = tmp;
	
	tmp = DS1302_ReadByte(DS1302_MOUTH);//月
	DS1302_Time[1] = tmp;
	
	tmp = DS1302_ReadByte(DS1302_DATE);//日
	DS1302_Time[2] = tmp;
	
	tmp = DS1302_ReadByte(DS1302_HOUR);//时
	DS1302_Time[3] = tmp;
	
	tmp = DS1302_ReadByte(DS1302_MINUTE);//分
	DS1302_Time[4] = tmp;
	
	tmp = DS1302_ReadByte(DS1302_SECOND);//秒
	DS1302_Time[5] = tmp;
	
	tmp = DS1302_ReadByte(DS1302_DAY);//周几
	DS1302_Time[6] = tmp;
}

还可以封装一个初始化函数,因为单片机启动和复位时,所有 I/O 都为高电平,但我们没有对DS1302操作时,CE 和 SCLK 都应为低电平

/**
  * @brief		初始化DS1302
  * @parm		无
  * @retval		无
  */
void DS1302_Init()
{
	DS1302_CE = 0;
	DS1302_SCLK = 0;
}

完整代码如下:
DS1302.h

#ifndef __DS1302_H__
#define __DS1302_H__

unsigned char DS1302_Time[];

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

#endif

DS1302.c

#include <REGX52.h>

//引脚定义
sbit DS1302_IO = P3^4;		//数据输入/输出
sbit DS1302_CE = P3^5;		//使能
sbit DS1302_SCLK = P3^6;	//串行时钟上升沿,分隔相邻数据

//寄存器写入指令定义
#define DS1302_SECOND		0x80
#define DS1302_MINUTE		0x82
#define DS1302_HOUR			0x84
#define DS1302_DATE			0x86
#define DS1302_MOUTH		0x88
#define DS1302_DAY			0x8A
#define DS1302_YEAR			0x8C
//写保护寄存器
#define DS1302_WP			0x8E

//实时时钟初始时间
//							   24年11月2日11时59分55秒周6
unsigned char DS1302_Time[] = {24, 11, 2, 12, 59, 55, 6};

/**
  * @brief		初始化DS1302
  * @parm		无
  * @retval		无
  */
void DS1302_Init()
{
	DS1302_CE = 0;
	DS1302_SCLK = 0;
}

/**
  * @brief		往DS1302中写数据
  * @parm		Command: 写秒or分钟...		范围:0 ~ 255
  * @parm		Data:要写入的数据			范围:0 ~ 255
  * @retval		无
  */
void DS1302_WriteByte(unsigned char Command, unsigned char Data)
{
	int i = 0;
	//打开使能
	DS1302_CE = 1;
	//先写命令Command
	//8位数据, 从低位开始
	for(i = 0; i < 8; ++i)
	{
		DS1302_IO = Command & (0x01 << i);
		//给串行时钟上升沿
		DS1302_SCLK = 1;
		DS1302_SCLK = 0;
	}
	//再写数据,数据要由十进制转为BCD码
	Data = (Data / 10 * 16) + (Data % 10);
	for(i = 0; i < 8; ++i)
	{
		DS1302_IO = Data & (0x01 << i);
		//给串行时钟上升沿
		DS1302_SCLK = 1;
		DS1302_SCLK = 0;
	}
	DS1302_CE = 0;//使能复位
}
/**
  * @brief		DS1302读取数据
  * @parm		Command(命令字): 读秒or分钟...		范围:0 ~ 255
  * @retval		返回读到的数据						范围:0 ~ 255
  */
unsigned char DS1302_ReadByte(unsigned char Command)
{
	unsigned char Data = 0;
	int i = 0;
	//传入写指令,转化为读指令
	Command |= 0x01;
	DS1302_CE = 1;
	//8位数据, 从低位开始
	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,否则读出的数据会出错
	//读出的数据要由BCD码转为十进制
	Data = (Data / 16 * 10) + (Data % 16);
	return Data;
}
/**
  * @brief		将DS1302_Time的时间设置进DS1302
  * @parm		无
  * @retval		无
  */
void DS1302_SetTime()
{
	//关闭写保护
	DS1302_WriteByte(DS1302_WP, 0x00);
	DS1302_WriteByte(DS1302_YEAR, DS1302_Time[0]);//年
	DS1302_WriteByte(DS1302_MOUTH, DS1302_Time[1]);//月
	DS1302_WriteByte(DS1302_DATE, DS1302_Time[2]);//日
	DS1302_WriteByte(DS1302_HOUR, DS1302_Time[3]);//时
	DS1302_WriteByte(DS1302_MINUTE, DS1302_Time[4]);//分
	DS1302_WriteByte(DS1302_SECOND, DS1302_Time[5]);//秒
	DS1302_WriteByte(DS1302_DAY, DS1302_Time[6]);//周几
	//打开写保护
	DS1302_WriteByte(DS1302_WP, 0x80);
}

/**
  * @brief		读取次数DS1302时间,并设置到DS1302_Time数组中
  * @parm		无
  * @retval		无
  */
void DS1302_ReadTime()
{
	unsigned char tmp;
	tmp = DS1302_ReadByte(DS1302_YEAR);//年
	DS1302_Time[0] = tmp;
	
	tmp = DS1302_ReadByte(DS1302_MOUTH);//月
	DS1302_Time[1] = tmp;
	
	tmp = DS1302_ReadByte(DS1302_DATE);//日
	DS1302_Time[2] = tmp;
	
	tmp = DS1302_ReadByte(DS1302_HOUR);//时
	DS1302_Time[3] = tmp;
	
	tmp = DS1302_ReadByte(DS1302_MINUTE);//分
	DS1302_Time[4] = tmp;
	
	tmp = DS1302_ReadByte(DS1302_SECOND);//秒
	DS1302_Time[5] = tmp;
	
	tmp = DS1302_ReadByte(DS1302_DAY);//周几
	DS1302_Time[6] = tmp;
}

小程序——实时时钟

DS1302 模块实现对实时时钟的控制
再使用 LCD1602 模块显示时间
LCD1602模块代码:Gitee 的 LCD1602.hLCD1602.c

main.c

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

void main()
{
	LCD_Init();//LCD1602初始化
	DS1302_Init();//DS1302初始化
	//LCD1602显示实时时钟格式
	LCD_ShowString(1, 1, "  -  -  ");
	LCD_ShowString(2, 1, "  :  :  ");
	DS1302_SetTime();
	//DS1302_ReadTime();
    while(1)
    {
		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);//显示秒
		//读取DS1302时间信息,更新时间数组
		DS1302_ReadTime();
    }
}

完整项目代码:Gitee:实时时钟

效果如下:
在这里插入图片描述

小程序——可调节实时时钟

此处暂不讲解,项目链接:Gitee:可调节时钟

效果如下:

DS1302——可调时钟


以上就是本篇博客的所有内容,感谢你的阅读
如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。
在这里插入图片描述


http://www.kler.cn/a/381180.html

相关文章:

  • 动态规划<四> 回文串问题(含对应LeetcodeOJ题)
  • 探寻快速排序的局限性及其优化策略
  • 【C++语言】多态
  • JVM系列(十三) -常用调优工具介绍
  • YoloV8改进策略:Head改进|DynamicHead,利用注意力机制统一目标检测头部|即插即用
  • python round四舍五入和decimal库精确四舍五入
  • RabbitMQ最全教程-Part1(基础使用)
  • 活着就好20241106
  • 人脸检测之MTCNN算法网络结构
  • VUE+Vite之环境文件配置及使用环境变量
  • 全面解析:区块链技术及其应用
  • 轴承故障全家桶更新 | 基于时频图像的分类算法
  • 科技改变阅读习惯:最新研究揭示电子阅读器的普及趋势
  • WonderWorld: Interactive 3D Scene Generation from a Single Image 论文解读
  • 【go从零单排】在 Go 语言中,:= 是什么意思?
  • TOEIC 词汇专题:旅游计划篇
  • ClickHouse数据库SSL配置和SSL连接测试
  • C语言 | Leetcode C语言题解之第540题有序数组中的单一元素
  • MySQL详细安装教程
  • 【机器学习】25. 聚类-DBSCAN(density base)
  • 云计算Openstack 虚拟机调度策略
  • Docker-- cgroups资源控制实战
  • 【C++刷题】力扣-#705-设计哈希集合
  • 「Mac畅玩鸿蒙与硬件27」UI互动应用篇4 - 猫与灯的互动应用
  • Flink-Kafka-Connector
  • 第五次作业