Bootstrap

江科大51单片机学习——DS1302

二十、DS1302

之前用单片机定时器就能计时,为啥还要一个时钟芯片呢?

单片机定时器时钟的缺点:

1.精度低

2.不能掉电继续运行(这个是比较重要的原因)

而DS1302时钟芯片有个备用电池,当单片机断电的时候,这个时钟芯片就启用它的备用电池,让其在单片机不工作时候继续走时,下次单片机再接上电的时候,就是当下时间

1.介绍

在这里插入图片描述

2.引脚定义和应用电路

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qZrtTyRI-1684927363631)(51单片机学习.assets/image-20230518132323201.png)]

DIP是直插分装,引脚是立起来的,可以直接插在pcb开发板上的

SO是贴片分装,它就不是通孔插在电路板上的而是贴在电路板表面上,像开发板上的芯片就是贴片分装

实际上他们内部的芯片/集成电路是一模一样的,包括引脚定义都是一样的,只不过一种是大分装一种是小分装贴片的

右边就是它怎么接怎么装的,总共分为三部分:

1.首先是电源部分:VCC2是主电源,接在开发板的电源上,VCC1是备用电池,正极接在备用电池上,负极接到GND,上面介绍说具有涓细电流充电能力,这个备用电池在VCC2有电的时候可以对备用电池进行充电,但是具有涓细电流能力在断电之后应该也能用很长时间,不充电也行(具体情况自己百度)

2.计数脉冲:晶振是为我们实时时钟系统提供一个稳定的计数脉冲。X1,X2就是提供一个32.768KHz的晶振,晶振产生的时钟频率精度特别高,产生的叫石英晶体震荡器,稳定性也高

3.通信引脚:CE,IO,SCLK,利用这三个引脚,单片机就可以把芯片内部的时钟给读出来

3.内部结构框图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1oFfrO2K-1684927363632)(51单片机学习.assets/image-20230518134730187.png)]

X1,X2就是晶振,输出的实时时钟就放在RAM中

4.寄存器定义

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wa6SRWL7-1684927363633)(51单片机学习.assets/image-20230518142209690.png)]

RTC:

第一个是秒寄存器,第二个是装分钟的,嗲是那个是装小时的,第四个装日期,第五给装月份,第六个装周几(1-7),第七个装年份

WP是Write Protect,写保护,也是一个使能的标志,当置1的时候,写入就是无效的,但是这时还是可以读数的

TCS时用来存储涓流充电的,如果不需要的话这个寄存器也不需要配置

要完成的任务:在哪儿 写入/读出 什么数据

图中展示了地址/命令字, 就是展示在哪儿&读入/写出

第七位:如果是0,表示禁止对DS1302写入

第六位:给1操作RAM,给0操作CT(时钟)

第五位-第一位:就是地址,要是要操作秒寄存器,上面那个图中,秒寄存器是00000

最后一位是读写判断读写的操作,上面一个杠代表低电平使能,WR是write和read,所以当给低电平的时候,是写的操作,反之即是读

5.时序定义

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PRwfgC7J-1684927363633)(51单片机学习.assets/image-20230518145450193.png)]

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码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UeviExrl-1684927363635)(51单片机学习.assets/image-20230519151433449.png)]

高四位表示十进制的十位,低四位表示十进制的个位

看寄存器定义也是

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zScO0ys0-1684927363636)(51单片机学习.assets/image-20230519153118754.png)]

低四位写的是秒,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)]

ds1302读取数据时,时常读取到0xff-CSDN社区

这个解决了问题,问题在于数字线拉低,告诉器件,释放总线

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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里面的时候,一按独立按键,时钟就复位???这是什么原因??

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;