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

news/2024/11/23 22:11:08/

之前学习过用定时器做的时钟,但是那样不仅误差大还费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.ppmy.cn/news/998777.html

相关文章

openEuler22.03安装 filebeat启动失败

报错详情 runtime/cgo: pthread_create failed: Operation not permitted runtime/cgo: pthread_create failed: Operation not permitted SIGABRT: abort PC=0x7faeea51af1f m=8 sigcode=18446744073709551610goroutine 0 [idle]: runtime: unknown pc 0x7faeea51af1f stack:…

ES6及以上新特性

ES6&#xff08;ECMAScript 2015&#xff09;及以上版本引入了许多新特性&#xff0c;每个版本都有不同的增强和改进。以下是 ES6 及以上版本的新特性的详细描述&#xff1a; ES6&#xff08;ECMAScript 2015&#xff09;&#xff1a; let 和 const 声明&#xff1a;引入块级作…

C# Blazor 学习笔记(8):row/col布局开发

文章目录 前言相关文章代码row和col组件B_rowB_col结构 使用 前言 可能是我用的element ui和 uView这种第三方组件用的太多了。我上来就希望能使用这些组件。但是目前Blazor目前的生态其实并不完善&#xff0c;所以很多组件要我们自己写。 我们对组件的要求是 我们在组件化一共…

代码随想录训练营Day57动态规划Part17|647.回文子串|516.最长回文子序

Part17 647.回文子串 虽然花了很多时间&#xff0c;但是自己写出来了定义dp[i][j]为布尔类型&#xff0c;记录起始位置为i&#xff0c;终止位置为j的字符串是否为回文子串起始、终止位置字符串不同则FALSE&#xff1b;若相同&#xff0c;有三种情况&#xff1a;1- ij&#xf…

C++ 可变参数模板

可变参数模板&#xff1a;允许模板中有0个到任意个模板参数&#xff0c;在语法上和传统模板不一样&#xff0c;多了一个... 下面是简单的示例 template<typename... T> void func1(T... a) // T&#xff1a;一包类型&#xff0c; a: 一包形参 {cout << sizeof...…

力扣-94、144、145-前中后序遍历

二叉树遍历方法总结 二叉树的遍历总体上分为深度优先遍历和广度优先遍历。常见的前中后序三种遍历方式就属于深度优先遍历&#xff0c;遍历过程中是顺着一条路径一直遍历到空节点然后向上回溯继续顺着遍历上一个节点的其他方向。层序遍历属于广度优先遍历&#xff0c;先遍历完同…

【104协议】【光伏电站】电站系统中的104协议学习

文章目录 104协议学习I帧S帧U帧ASDU总结&#xff1a;关于各类帧的通俗描述建立流程详细分析 104协议学习 起始一个apdu的总长度不会超过255个字节&#xff1b; 在协议中的第二个字节会记录本apdu的长度&#xff0c;但是这个记录的长度数是除开前面两个字节之外的长读数&#xf…

【三等奖方案】Web攻击检测与分类识别赛题「Cyan」团队解题思路

2022 CCF BDCI 数字安全公开赛「Web攻击检测与分类识别」赛题Cyan战队获奖方案 地址&#xff1a;http://go.datafountain.cn/4Zj 团队简介 团队成员来自中国科学院大学&#xff0c;对数据挖掘与网络安全有浓厚兴趣&#xff0c;曾多次获得大数据安全分析等比赛的Top名次。希望…