二十、DS1302
之前用单片机定时器就能计时,为啥还要一个时钟芯片呢?
单片机定时器时钟的缺点:
1.精度低
2.不能掉电继续运行(这个是比较重要的原因)
而DS1302时钟芯片有个备用电池,当单片机断电的时候,这个时钟芯片就启用它的备用电池,让其在单片机不工作时候继续走时,下次单片机再接上电的时候,就是当下时间
1.介绍
2.引脚定义和应用电路
DIP是直插分装,引脚是立起来的,可以直接插在pcb开发板上的
SO是贴片分装,它就不是通孔插在电路板上的而是贴在电路板表面上,像开发板上的芯片就是贴片分装
实际上他们内部的芯片/集成电路是一模一样的,包括引脚定义都是一样的,只不过一种是大分装一种是小分装贴片的
右边就是它怎么接怎么装的,总共分为三部分:
1.首先是电源部分:VCC2是主电源,接在开发板的电源上,VCC1是备用电池,正极接在备用电池上,负极接到GND,上面介绍说具有涓细电流充电能力,这个备用电池在VCC2有电的时候可以对备用电池进行充电,但是具有涓细电流能力在断电之后应该也能用很长时间,不充电也行(具体情况自己百度)
2.计数脉冲:晶振是为我们实时时钟系统提供一个稳定的计数脉冲。X1,X2就是提供一个32.768KHz的晶振,晶振产生的时钟频率精度特别高,产生的叫石英晶体震荡器,稳定性也高
3.通信引脚:CE,IO,SCLK,利用这三个引脚,单片机就可以把芯片内部的时钟给读出来
3.内部结构框图
X1,X2就是晶振,输出的实时时钟就放在RAM中
4.寄存器定义
RTC:
第一个是秒寄存器,第二个是装分钟的,嗲是那个是装小时的,第四个装日期,第五给装月份,第六个装周几(1-7),第七个装年份
WP是Write Protect,写保护,也是一个使能的标志,当置1的时候,写入就是无效的,但是这时还是可以读数的
TCS时用来存储涓流充电的,如果不需要的话这个寄存器也不需要配置
要完成的任务:在哪儿 写入/读出 什么数据
图中展示了地址/命令字, 就是展示在哪儿&读入/写出
第七位:如果是0,表示禁止对DS1302写入
第六位:给1操作RAM,给0操作CT(时钟)
第五位-第一位:就是地址,要是要操作秒寄存器,上面那个图中,秒寄存器是00000
最后一位是读写判断读写的操作,上面一个杠代表低电平使能,WR是write和read,所以当给低电平的时候,是写的操作,反之即是读
5.时序定义
SINGLE-BYTE READ单字节读, SINGLE-BYTE READ单字节写
CE是一个操作使能,不论是在读的操作中还是在写的操作中,全过程都是高电频,结束之后再置0,相当于一个开关
SCLK就是给一个固定的时钟,IO就是数据
那么数据到底是怎么被一位一位被移进去的?
有规定:在时钟的上升沿(就是SCLK从0–>1的时候),IO口上的数据将被写入,在时钟的下降沿(就是SCLK从1–>0的时候),DS1302就会把数据输出
简而言之,就是在时钟的上升沿,我要写入数据;在时钟的下降沿,时钟芯片向我写出数据(我读数据的过程)。
工作流程:当R/W这一位标志的读的时候,那么接收到这个命令之后,讲输出数据,当R/W这一位标志的写的时候,那么这个之后讲一直写下去,写完这个字节之后讲继续写下去,在整个过程中,只有R/W…1是DS1302操控的,其他的都是由单片机操控
单字节写的具体操作步骤:
1.把CE置高电频,即先把开关打开
2.设置R/W,从上上个图可以看出来RW是最低位,但是为啥先发最低位,这是规定,时序芯片就是这样设计的,所以这边设置1还是0 设置在我们的IO口上
3.SCLK给一个上升沿,那么命令字节的第一位就会被写入单片机
4.把时钟再置回0,然后把这个第二位放在线上,然后再置上升沿,这样A0也就会被写入
5.以此类推,直到循环8次,命令字节就会被写入,这时一个字节的写入已经完成
6.要想继续写,因为之前RW置的是R,所以继续写就可以了
7.直到数据写完,CE置0
单字节读的具体操作步骤:
1.把CE置高电频,即先把开关打开
2.设置R/W
3.SCLK给一个上升沿,那么命令字节的第一位就会被写入单片机
4.把时钟再置回0,然后把这个第二位放在线上,然后再置上升沿,这样A0也就会被写入
5.以此类推,直到循环8次,命令字节就会被写入,这时一个字节的写入已经完成
(前5步都是一样的,后面读的过程不一样)
6.下面就要读取数据,这个时候IO口就要置空,然后SCLK每一个下降沿,IO口就收到一个数据
7.读8个数据之hi,SCLK就置0了
8.操作完成,CE也置0
写入和读出的数据跟上面的寄存器(RTC)完全对应。
6.代码部分
(1)初步实现显示DS1302的代码
DS1302.c
#include <REGX52.H>
sbit DS1302_SCLK = P3^6;
sbit DS1302_CE = P3^5;
sbit DS1302_IO = P3^4;
void DS1302_Init(){
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);//取出它的第0位
//DS1302_IO也是遵循非零即一
DS1302_SCLK=1;
//这边看一下它的使用手册,里面有它需要的最大时间间隔和最小~
//因为我们的单片机传输比较慢,所以这里可以不加延迟,不然的话,这边刚置1就置0,会不太稳定?
DS1302_SCLK=0;
}
for(i = 0; i < 8; i++){
DS1302_IO = Data&(0x01<<i);//取出它的第0位
//DS1302_IO也是遵循非零即一
DS1302_SCLK=1;
//这边看一下它的使用手册,里面有它需要的最大时间间隔和最小~
//因为我们的单片机传输比较慢,所以这里可以不加延迟,不然的话,这边刚置1就置0,会不太稳定?
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);//取出它的第0位
DS1302_SCLK=0;
//这边的顺序要颠倒一下,因为之前一上一下算一个脉冲,然后循环八次
//这里是要读数据,最后一个下降沿下来的时候,data数据已经接收到了,所以一个低一个高算一个脉冲(循环),这样才能在第一个下降沿之前结束第一个Byte的接收工作
DS1302_SCLK=1;
}
for(i=0;i<8;i++){
DS1302_SCLK=1;//这边重复置1,对脉冲其实没啥影响,但是这样就可以去掉一个脉冲
//从图中可以看出读数据的脉冲比写数据的脉冲少一个
DS1302_SCLK=0;
if(DS1302_IO){
Data = Data|(0x01<<i);
}
}
DS1302_CE=0;
return Data;
}
DS1302.h
#ifndef __DS1302_H_
#define __DS1302_H_
void DS1302_Init();
void DS1302_WriteByte(unsigned char Command, Data);
unsigned char DS1302_ReadByte(unsigned char Command);
#endif
main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
#include "Delay.h"
void main(){
unsigned char Second;
LCD_Init();
DS1302_Init();
LCD_ShowString(1,1,"RTC");
DS1302_WriteByte(0x8E,0x00);
//在写寄存器之前必须先将写保护位清零,因此要加上这句,后面才不会出现错误数字
DS1302_WriteByte(0x80,0x03);
Second=DS1302_ReadByte(0x81);
LCD_ShowNum(2,1,Second,3);
while(1){
}
}
发现问题:
#include <REGX52.H> #include "LCD1602.h" #include "DS1302.h" #include "Delay.h" void main(){ unsigned char Second; LCD_Init(); DS1302_Init(); LCD_ShowString(1,1,"RTC"); DS1302_WriteByte(0x8E,0x00); //在写寄存器之前必须先将写保护位清零,因此要加上这句,后面才不会出现错误数字 DS1302_WriteByte(0x80,0x03); while(1){ DS1302_WriteByte(0x8E,0x00); //在每次读数据之前都要将保护位清零,不然每次都会在显示完数据结束之后显示一个255 Second=DS1302_ReadByte(0x81); LCD_ShowNum(2,1,Second,3); //Delay(50); } }
当把读数据放到while(1)里面的时候,显示就会有错误,甚至加上Delay也不管用
补充:BCD码
高四位表示十进制的十位,低四位表示十进制的个位
看寄存器定义也是
低四位写的是秒,BIT6-BIT4写的是10秒
BIT7是时钟暂停的意思,如果这一位设置为1,那么读秒就暂停了,如果给0,那么时钟始终是运行的
下面的分钟和小时也是同理,高四位是十进制
小时那里有个12小时/24小时模式选择,当设置为0时,是24H模式
改进后main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
#include "Delay.h"
void main(){
unsigned char Second;
LCD_Init();
DS1302_Init();
LCD_ShowString(1,1,"RTC");
DS1302_WriteByte(0x8E,0x00);
//在写寄存器之前必须先将写保护位清零,因此要加上这句,后面才不会出现错误数字
DS1302_WriteByte(0x80,0x03);
while(1){
DS1302_WriteByte(0x8E,0x00);
//在每次读数据之前都要将保护位清零,不然每次都会在显示完数据结束之后显示一个255
Second=DS1302_ReadByte(0x81);
//LCD_ShowHexNum(2,1,Second,3);
LCD_ShowNum(2,1,Second/16*10+Second%16,3)
Delay(1000);
}
}
(2)实现显示时钟的代码
改进地方:
1.首先:BCD码的相互转换
2.其次:日期数据的设置和读取
3.给RTC中的每个位置给予定义
4.加注释
DS1302.c
#include <REGX52.H>
//首先给每一个寄存器位置重新命名
sbit DS1302_SCLK = P3^6;
sbit DS1302_CE = P3^5;
sbit DS1302_IO = P3^4;
//给RTC中每个位置给予定义,这样就不用每次要记住位置赋值了
#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
//每次设置时间Second,Minute的话都比较麻烦,所以设置一个设置时间的数组
unsigned char DS1302_Time[]={19,11,16,12,59,55,6};
void DS1302_Init(){
DS1302_CE=0;
DS1302_SCLK=0;
DS1302_IO=0;
}
//写入的最低位WR肯定是0
void DS1302_WriteByte(unsigned char Command, Data){
unsigned char i;
DS1302_CE=1;//把开关打开
//这边在执行的就是写数据的前半段,读取命令Command过程
for(i = 0; i < 8; i++){
DS1302_IO = Command&(0x01<<i);//取出它的第0位
//DS1302_IO也是遵循非零即一
DS1302_SCLK=1;
//这边看一下它的使用手册,里面有它需要的最大时间间隔和最小~
//因为我们的单片机传输比较慢,所以这里可以不加延迟,不然的话,这边刚置1就置0,会不太稳定?
DS1302_SCLK=0;
}
//下面就要开始读入数据,执行写数据的后半段,并且可以一直写下去
for(i = 0; i < 8; i++){
DS1302_IO = Data&(0x01<<i);//取出它的第0位
//DS1302_IO也是遵循非零即一
DS1302_SCLK=1;
//这边看一下它的使用手册,里面有它需要的最大时间间隔和最小~
//因为我们的单片机传输比较慢,所以这里可以不加延迟,不然的话,这边刚置1就置0,会不太稳定?
DS1302_SCLK=0;
}
DS1302_CE=0;//把开关关闭
}
//读出的最低位WR肯定是1
unsigned char DS1302_ReadByte(unsigned char Command){
unsigned char i, Data=0x00;
Command |= 0x01;//用一个|=,就避免了读写使用两个参数,而只用一个变量就能够表示读和写两种命令
DS1302_CE=1;//把开关打开
//执行读数据的前半段(读取命令Command的过程),因为读数据的脉冲比写数据的脉冲要少一个,写数据是16个脉冲,读数据是15个脉冲,所以对于哪儿到哪儿是一个周期要调整一下
for(i = 0; i < 8; i++){
DS1302_IO = Command&(0x01<<i);//取出它的第0位
DS1302_SCLK=0;
//这边的顺序要颠倒一下,因为之前一上一下算一个脉冲,然后循环八次
//这里是要读数据,最后一个下降沿下来的时候,data数据已经接收到了,所以一个低一个高算一个脉冲(循环),这样才能在第一个下降沿之前结束第一个Byte的接收工作
DS1302_SCLK=1;
}
//之后就开始读数据,执行读数据后半段部分
for(i=0;i<8;i++){
DS1302_SCLK=1;//这边重复置1,对脉冲其实没啥影响,但是这样就可以去掉一个脉冲
//从图中可以看出读数据的脉冲比写数据的脉冲少一个
DS1302_SCLK=0;
if(DS1302_IO){
Data = Data|(0x01<<i);
}
}
DS1302_CE=0;
DS1302_IO=0;
return Data;
}
//将DS1302_Time数据写入到寄存器的时钟数据位置,同时要注意十进制和BCD码的转换
void DS1302_SetTime(){
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);
}
//将寄存器中的时钟数据读取到DS1302_Time数组中,同时要注意十进制和BCD码的转换
void DS1302_ReadTime(){
unsigned char Temp;
Temp = DS1302_ReadByte(DS1302_YEAR);
DS1302_Time[0] = Temp/16*10+Temp%16;
Temp = DS1302_ReadByte(DS1302_MONTH);
DS1302_Time[1] = Temp/16*10+Temp%16;
Temp = DS1302_ReadByte(DS1302_DATE);
DS1302_Time[2] = Temp/16*10+Temp%16;
Temp = DS1302_ReadByte(DS1302_HOUR);
DS1302_Time[3] = Temp/16*10+Temp%16;
Temp = DS1302_ReadByte(DS1302_MINUTE);
DS1302_Time[4] = Temp/16*10+Temp%16;
Temp = DS1302_ReadByte(DS1302_SECOND);
DS1302_Time[5] = Temp/16*10+Temp%16;
Temp = DS1302_ReadByte(DS1302_DAY);
DS1302_Time[6] = Temp/16*10+Temp%16;
}
DS1302.h
#ifndef __DS1302_H_
#define __DS1302_H_
//这边数组也要声明一下,并且一定要加extern
extern unsigned char DS1302_Time[];
//事实上,函数前面默认也有extern,只不过没有显示而已
void DS1302_Init();
void DS1302_WriteByte(unsigned char Command, Data);
unsigned char DS1302_ReadByte(unsigned char Command);
void DS1302_SetTime();
void DS1302_ReadTime();
#endif
main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
#include "Delay.h"
void main(){
LCD_Init();
DS1302_Init();
DS1302_SetTime();
while(1){
//DS1302_WriteByte(0x8e,0x00);
//在每次读数据之前都要将保护位清零,不然每次都会在显示完数据结束之后显示一个255
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);
}
}
//问题:偶数个位置显示65,并且时钟也是在走着的,就很奇怪,如果不加上DS1302_WriteByte(0x8E,0x00);这句,数字直接就叠在一起了
//============下面是弹幕提供的解决方案============
//显示乱码可以尝试把74HC595的OE和VCC短接
//有没有关闭写保护
//命令位最低为给1
//有没有把DS1302_IO清零
//秒显示一个固定值的,把写保护wp关掉
//也可能是接触不良,电池没电,始终模块线断了
//还是要解决之前的问题,为啥显示一下就要显示255/OFF(应该就是这个问题)
//============================================
发现(显示异常)问题:
偶数个位显示65,就比如显示屏上第一行显示的是19 65 16,第二行显示的是65 59 65,而且它时钟是在跑的,因为59分过了5秒后就变成00了,弹幕中的解答没有解决问题,去到网上搜
发现有同样问题,我的也是隔一个跳出0xff或者255
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GazDBBFD-1684927363637)(51单片机学习.assets/image-20230524105547350.png)]
这个解决了问题,问题在于数字线拉低,告诉器件,释放总线
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rMkGwRSx-1684927363637)(51单片机学习.assets/image-20230524105334916.png)]
所以只需要在DS1302_ReadByte函数最后加上将IO口清零即可,即加上DS1302_IO=0;
而且,加上这句之后,main.c循环中也不需要加保护位清零的那行代码了(DS1302_WriteByte(0x8e,0x00)😉
(3)实现独立按键控制DS1302可调时钟的代码
#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
#include "Delay.h"
unsigned char KeyNum;
void main(){
LCD_Init();
DS1302_Init();
DS1302_SetTime();
KeyNum = Key();
LCD_ShowNum(2,10,KeyNum,2);
while(1){
//DS1302_WriteByte(0x8e,0x00);
//在每次读数据之前都要将保护位清零,不然每次都会在显示完数据结束之后显示一个255
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);
}
}
这个板子不知道啥原因,四个独立按键识别不了
当KeyNum = Key();LCD_ShowNum(2,10,KeyNum,2);这两句放在while外面的时候,相应位置就是00,按独立按键没有反应,所以不能识别到独立按键,时钟正常走
当这两句放在while里面的时候,一按独立按键,时钟就复位???这是什么原因??