51单片机学习--DS1302可调时钟

news2024/9/20 7:52:24

之前学习过用定时器做的时钟,但是那样不仅误差大还费CPU,接下来利用DS1302时钟模块做一个可调实时时钟



这一次直接编写DS1302模块,首先要在DS1392.c 中根据下面的模块原理图进行位声明:

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

在这里插入图片描述
在这里插入图片描述



命令字: 命令字确定了是要写还是要读,以及操作的是时还是分还是秒
在这里插入图片描述
首先需要一个初始化函数:

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



在这里插入图片描述
工作时CE必须置1,上升沿的时候可以读, 下降沿的时候可以写
可以理解为0是写入模式,1是读取模式
IO口从左往右是由低位到高位
要注意时序图中Read比Write少一个脉冲因为它上升到1完成了最后一个命令行位的写入之后马上要回到0开始进行读取功能了
单字节写入函数:
按照时序图进行模拟,Command:命令行,Data:写入的数据

void DS1302_WriteByte(unsigned char Command, Data)
{
	unsigned char i;
	DS1302_CE = 1;
	
	for(i = 0; i < 8; i ++) {
		DS1302_IO = Command & (0x01 << i); //IO非0即1,从低到高每次取出一位
		DS1302_SCLK = 1;
		Delay(10);
		DS1302_SCLK = 0;
	}
	
	for(i = 0; i < 8; i ++) {
		DS1302_IO = Data & (0x01 << i); //IO非0即1,从低到高每次取出一位
		DS1302_SCLK = 1;
		Delay(10);
		DS1302_SCLK = 0;
	}
	
	DS1302_CE = 0;
}

在这里插入图片描述
单字节读出函数

unsigned char DS1302_ReadByte(unsigned char Command)
{
	unsigned char i, Data = 0x00;
	DS1302_CE = 1;
	
	for(i = 0; i < 8; i ++) {
		DS1302_IO = Command & (0x01 << i); //IO非0即1,从低到高每次取出一位
		DS1302_SCLK = 0; //写入
		Delay(10);
		DS1302_SCLK = 1; //这样在不改变写入时序的同时还能保证最后是1
	}
	
	Delay(10);
	
	//接下来要读的数据已经在IO口上了已经可以读了
	for(i = 0; i < 8; i ++) {
		DS1302_SCLK = 1; //读入
		Delay(10);
		DS1302_SCLK = 0;
		if(DS1302_IO) {Data |= (0x01 << i);} //把IO口的数据由低位到高位复现在Data上
	}
		
	DS1302_IO = 0;
	DS1302_CE = 0;
	return Data;
}

要注意在main.c中使用时需要在DS1302初始化后,调用:DS1302_WriteByte(0x8E, 0x00); //关闭写入保护
再进行正常的写入



但其实,在DS1302模块的寄存器存储的数据都是BCD码
所以时钟的秒会从1, 2, ····9然后直接跳到16
9 = 0000 1001
根据BCD的进位原则,四位二进制数达到10就要清零进位了,下一个BCD码是:
0001 0000 这个数以十进制显示在LCD上就是16
此时只要把ShowNum改成ShowHexNum即可正常显示10, 11, 12·····
在这里插入图片描述
也可以利用公式来用十进制显示:

LCD_ShowNum(2, 1, Sec / 16 * 10 + Sec % 16, 3 );


接下来就可以编写一个完整的时钟模块了

#include <REGX52.H>
#include "Delay.h"

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

//其实写的地址 或上 0x01 就是读的地址了
//所以下面只要重定义写的地址就行了
#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  //写入保护的地址

unsigned char DS1302_Time[] = {23, 8, 2, 10, 28, 50, 3};

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); //IO非0即1,从低到高每次取出一位
		DS1302_SCLK = 1;
		Delay(10);
		DS1302_SCLK = 0;
	}
	
	for(i = 0; i < 8; i ++) {
		DS1302_IO = Data & (0x01 << i); //IO非0即1,从低到高每次取出一位
		DS1302_SCLK = 1;
		Delay(10);
		DS1302_SCLK = 0;
	}
	
	DS1302_CE = 0;
}

unsigned char DS1302_ReadByte(unsigned char Command)
{
	unsigned char i, Data = 0x00;
	DS1302_CE = 1;
	
	for(i = 0; i < 8; i ++) {
		DS1302_IO = Command & (0x01 << i); //IO非0即1,从低到高每次取出一位
		DS1302_SCLK = 0; //写入
		Delay(10);
		DS1302_SCLK = 1; //这样在不改变写入时序的同时还能保证最后是1
	}
	
	Delay(10);
	
	//接下来要读的数据已经在IO口上了已经可以读了
	for(i = 0; i < 8; i ++) {
		DS1302_SCLK = 1; //读入
		Delay(10);
		DS1302_SCLK = 0;
		if(DS1302_IO) {Data |= (0x01 << i);} //把IO口的数据由低位到高位复现在Data上
	}
		
	DS1302_IO = 0;
	DS1302_CE = 0;
	return Data;
}

void DS1302_SetTime(void) //将数组中的时间写入芯片
{
	DS1302_WriteByte(DS1302_WP, 0x00); //关闭写保护
	DS1302_WriteByte(DS1302_YEAR, DS1302_Time[0]/10*16+DS1302_Time[0]%10);
	DS1302_WriteByte(DS1302_MONTH, DS1302_Time[1]/10*16+DS1302_Time[1]%10);
	DS1302_WriteByte(DS1302_DATE, DS1302_Time[2]/10*16+DS1302_Time[2]%10);
	DS1302_WriteByte(DS1302_HOUR, DS1302_Time[3]/10*16+DS1302_Time[3]%10);
	DS1302_WriteByte(DS1302_MINUTE, DS1302_Time[4]/10*16+DS1302_Time[4]%10);
	DS1302_WriteByte(DS1302_SECOND, DS1302_Time[5]/10*16+DS1302_Time[5]%10);
	DS1302_WriteByte(DS1302_DAY, DS1302_Time[6]/10*16+DS1302_Time[6]%10);
	DS1302_WriteByte(DS1302_WP, 0x80); //打开写保护
}

void DS1302_ReadTime(void) //把芯片中的时间读到数组中
{
	unsigned char temp;
	temp = DS1302_ReadByte(DS1302_YEAR | 0x01); //写的地址或上0x01就是读的地址
	DS1302_Time[0] = temp/16*10+temp%16;
	temp = DS1302_ReadByte(DS1302_MONTH | 0x01);
	DS1302_Time[1] = temp/16*10+temp%16;
	temp = DS1302_ReadByte(DS1302_DATE | 0x01);
	DS1302_Time[2] = temp/16*10+temp%16;
	temp = DS1302_ReadByte(DS1302_HOUR | 0x01);
	DS1302_Time[3] = temp/16*10+temp%16;
	temp = DS1302_ReadByte(DS1302_MINUTE | 0x01);
	DS1302_Time[4] = temp/16*10+temp%16;
	temp = DS1302_ReadByte(DS1302_SECOND | 0x01);
	DS1302_Time[5] = temp/16*10+temp%16;
	temp = DS1302_ReadByte(DS1302_DAY | 0x01);
	DS1302_Time[6] = temp/16*10+temp%16;
}

其实如果把BCD码与十进制相互转化的部分写成函数来处理,会大大减少代码量
要注意,这个封装好的DS1302.c要拿到外部调用的话,其中的DS1302_Time数组也需要在头文件中声明,外部可调用的变量要加上关键字extern

#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

最后给出main.c代码

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

unsigned char Sec;

void main()
{
	LCD_Init();
	DS1302_Init();
	DS1302_WriteByte(0x8E, 0x00); //关闭写入保护
	
	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);
		LCD_ShowNum(2, 10, DS1302_Time[6], 2);
	}
}

在这里插入图片描述



但是一个好的时钟远不止显示时间这么简单,还需要具有可调的功能。。
于是需要加入按键模块实现修改时间和定时器模块来实现光标闪烁效果

#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
#include "Key.h"
#include "Timer0.h"
 

unsigned char MODE, KeyNum, TimeSetSelect, TimeFlash;

void Time_Show(void) //在LCD显示数组时间
{
	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);
	LCD_ShowNum(2, 10, DS1302_Time[6], 2);
}

void Time_Set(void) //利用按键修改数组并重新读取数组显示在LCD
{
	
	if(KeyNum == 2) //选择修改的位置
	{
		TimeSetSelect ++;
		TimeSetSelect %= 7;
	}
	if(KeyNum == 3) //增加时间
	{
		DS1302_Time[TimeSetSelect] ++;
	}
	if(KeyNum == 4) //减少时间
	{
		DS1302_Time[TimeSetSelect] --;
	}
	
	//接下来更新显示	
	if(TimeFlash == 0 && TimeSetSelect == 0) LCD_ShowString(1, 1, "  ");
	//熄灭的时候用空格覆盖
	else LCD_ShowNum(1, 1, DS1302_Time[0], 2);
	
	if(TimeFlash == 0 && TimeSetSelect == 1) LCD_ShowString(1, 4, "  ");
	else LCD_ShowNum(1, 4, DS1302_Time[1], 2);
	
	if(TimeFlash == 0 && TimeSetSelect == 2) LCD_ShowString(1, 7, "  ");
	else LCD_ShowNum(1, 7, DS1302_Time[2], 2);
	
	if(TimeFlash == 0 && TimeSetSelect == 3) LCD_ShowString(2, 1, "  ");
	else LCD_ShowNum(2, 1, DS1302_Time[3], 2);
	
	if(TimeFlash == 0 && TimeSetSelect == 4) LCD_ShowString(2, 4, "  ");
	else LCD_ShowNum(2, 4, DS1302_Time[4], 2);
	
	if(TimeFlash == 0 && TimeSetSelect == 5) LCD_ShowString(2, 7, "  ");
	else LCD_ShowNum(2, 7, DS1302_Time[5], 2);
	
	if(TimeFlash == 0 && TimeSetSelect == 6) LCD_ShowString(2, 10, "  ");
	else LCD_ShowNum(2, 10, DS1302_Time[6], 2);
}

void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count ++;
	if(T0Count >= 1000) //1s执行一次
	{
		T0Count = 0;
		
		TimeFlash = !TimeFlash; 
		//1的时候显示数字,0的时候熄灭,达成闪烁
    
	}
}

void main()
{
	LCD_Init();
	DS1302_Init();
	Timer0_Init();
	
	DS1302_WriteByte(0x8E, 0x00); //关闭写入保护
	
	LCD_ShowString(1, 1, "  -  -  ");
	LCD_ShowString(2, 1, "  :  :  ");
	
	DS1302_SetTime(); //先从数组中读取时间到芯片里
	
	
	while(1)
	{
		KeyNum = Key(); //读取按键
		
		if(KeyNum == 1) //按下按键1切换时钟模式
		{
			if(MODE == 1) {MODE = 0; DS1302_SetTime();} 
			//回到显示模式要重新读取数组到芯片里
			else MODE = 1;
		}
		switch(MODE)
		{
			case 0: Time_Show(); break;
			case 1: Time_Set(); break;
		}
	}
}

但是这个程序有个bug,就是修改时间的部分没有进行越界判断,可能会出现13月,32日这样的数据,这个修改起来就是逻辑上的事情,在Time++或者–的时候特判一下就行,比较容易,这里偷个懒就不改了

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

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

相关文章

05 Ubuntu下安装.deb安装包方式安装vscode,snap安装Jetbrains产品等常用软件

使用deb包安装类型 deb包指的其实就是debian系统&#xff0c;ubuntu系统是基于debian系统的发行版。 一般我们会到需要的软件官网下载deb安装包&#xff0c;然后你既可以采用使用“软件安装”打开的方法来进行安装&#xff0c;也可以使用命令行进行安装。我推荐后者&#xff…

电脑更新win10黑屏解决方法

电脑更新win10黑屏解决方法 电脑黑屏出现原因解决步骤 彻底解决 电脑黑屏 出现原因 系统未更新成功就关机&#xff0c;导致系统出故障无法关机 解决步骤 首先长安电源键10s关机 按电源键开机&#xff0c;出现logo时按F8进入安全模式。 进入自动修复环境后&#xff0c;单击…

快速排序——“数据结构与算法”

各位CSDN的uu们好呀&#xff0c;今天又是小雅兰的数据结构与算法专栏啦&#xff0c;下面&#xff0c;就让我们进入快速排序的世界吧&#xff01;&#xff01;&#xff01; 快速排序 快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法&#xff0c;其基本思想为&…

工序委外PR审批后,不允许按工单自动更新PR交期

工序委外PR审批后&#xff0c;不允许按工单自动更新PR交期 工序委外PR审批后&#xff0c;更改工单结束日期&#xff0c;PR的交期还是会按工单重新更新交货日期&#xff1f; 检查配置&#xff1a; 1.生产模块集成的配置没有找到这么细的配置点 2.PR审批标识调整也没有效果 没找…

webrtc的回声消除延迟时间估算

叫回声消除的延迟时间估算不太合理&#xff0c;这里核心就是估算调用webrtc的条件边界&#xff0c;都知道webrtc回声消除的生效的前提就是一定要拿到远端声音的信息&#xff0c;然后拿近端声音和远端声音对齐&#xff0c;从近端声音中&#xff0c;结合远端声音模拟出远端声音在…

MySQL安装 找不到 MSVCP100.dll

安装mysql5.6.51时&#xff0c;出现由于找不到 MSVCP100.dll&#xff0c;无法继续执行代码。重新安装程序可能会解决此问题。 这应该是缺少VS运行库文件导致的&#xff0c;运行库就是支持大部分程序运行的基础&#xff0c;由于很多常用软件都是采用 Microsoft Visual Studio 编…

Toolformer :让AI学会使用工具

paper: 《Toolformer: Language Models Can Teach Themselves to Use Tools 》 核心思想&#xff0c; 1. Sampling API Calls &#xff1a;设计设计prompt,让模型生成含API调用的文本&#xff08;如图3&#xff09;&#xff0c;只保留K个概率最高的API调用 2. Executing API …

nvm安装和切换node版本

1、nvm list查看已安装的node版本 2、查看当前使用的npm和node版本 3、安装某版本的node 4、 切换node版本

一行命令删除tag为<none>的镜像

sudo docker images | grep none | awk {print $3;} | xargs sudo docker rmi

理解APP开发的法规和合规性问题

随着移动应用程序的快速普及&#xff0c;越来越多的用户选择使用这些应用程序。但它们也会带来一些问题&#xff0c;其中许多是由于应用程序开发过程中存在的法规和合规性问题。用户需要了解这些问题&#xff0c;以及如何解决它们。 APP开发是一个不断变化的领域&#xff0c;其…

我的创作纪念日——256天

机缘 最开始我写博客没有什么特别的原因&#xff0c;主要是因为以下几点&#xff1a; 练习自己的语言组织能力 记录自己学习生活中学到的知识 为和我同一个学习阶段的朋友提供帮助 事实上最开始我根本不指望我的博客有多少人看&#xff0c;主要是想找一个好的保存 Markdown 笔…

穷举深搜暴搜回溯剪枝(2)

一)电话号码的字母组合 17. 电话号码的字母组合 - 力扣&#xff08;LeetCode&#xff09; 1)画出决策树:只是需要对最终的决策树做一个深度优先遍历 把图画出来&#xff0c;知道每一层在干什么&#xff0c;代码就能写出来了 2)定义全局变量: 1)定义一个哈希表来处理数字和字符串…

【freespace】YOLOP: You Only Look Once for Panoptic Driving Perception论文解读

目录 Abstract 摘要 1、简介 2、 相关工作 2.1. 交通目标检测 2.2. 可驾驶区域分割 2.3. 车道检测 2.4. 多任务的方法 3. 方法 3.1. 编码器 3.1.1 骨干 3.1.2 脖子 3.2. 解码器 3.2.1 探测头 3.2.2 可驾驶区域分段头&车道线分段头 3.3. 损失函数 3.4. 训练模…

Windows同时安装两个版本的JDK并随时切换,以JDK6和JDK8为例,并解决相关存在的问题(亲测有效)

Windows同时安装两个版本的JDK并随时切换&#xff0c;以JDK6和JDK8为例&#xff0c;并解决相关存在的问题&#xff08;亲测有效&#xff09; 1.下载不同版本JDK 这里给出JDK6和JDK的百度网盘地址&#xff0c;具体安装过程&#xff0c;傻瓜式安装即可。 链接&#xff1a;http…

华为OD机试真题 JavaScript 实现【机器人活动区域】【2023Q1 200分】,附详细解题思路

目录 一、题目描述二、输入描述三、输出描述四、解题思路五、JavaScript算法源码六、效果展示1、输入2、输出 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测试&am…

EXCEL,多条件查询数字/文本内容的4种方法

目录 1 问题&#xff1a;如何根据多条件查询到想要的内容 2 方法总结 2.1 方法1&#xff1a; sumif() 和sumifs() 适合查找符合条件的多个数值之和 2.2 方法2&#xff1a;使用lookup(1,0/((区域1条件1)*(区域2条件2)*....),结果查询区域) 2.3 方法3&#xff1a;使用 ind…

rknn模型在rv1126开发板上跑

在前面&#xff0c;已经将 onnx模型转为 rknn模型。 yolov5 onnx模型 转为 rknn模型_爱钓鱼的歪猴的博客-CSDN博客 这里探讨的是&#xff1a;rknn模型在rv1126开发板上运行 目录 1、rknn模型在PC端进行推理测试&#xff0c;评估模型精度 2、模型预编译 3、rknn模型部署到r…

【LeetCode】根据二叉树创建字符串

根据二叉树创建字符串 题目描述算法分析编程代码 链接: 根据二叉树创建字符串 题目描述 算法分析 当单纯的按照前序遍历输出后&#xff0c;我们只要对&#xff08;&#xff09;进行一些修改就好 编程代码 /*** Definition for a binary tree node.* struct TreeNode {* …

偷懒神器-->花样的代码生成工具

1、CRUD代码生成&#xff1a; 根据MyBatisPlus逆向工程改造而来&#xff0c;添加了showDoc文档生成&#xff0c;数据库脚本生成&#xff0c;增删改查文件生成&#xff0c;Po、Vo、Request对象生成等。普通的增删改查一般搞定。并预调了部份判断逻辑。 效果示例&#xff1a; p…