【单片机】13-实时时钟DS1302

news2024/12/28 18:20:39

1.RTC的简介

1.什么是实时时钟(RTC)

(rtc for real time clock)

(1)时间点和时间段的概念区分

(2)单片机为什么需要时间点【一定的时间点干什么事情】

(3)RTC如何存在于系统中(单片机内部集成 or 单片机外部扩展【DS1302】)

2.DS1302

1.数据手册

DS1302中文数据手册 - 豆丁网

2.SPI数字接口访问

SPI通信协议【DS1302也使用这个协议】,两个芯片之间的通信

3.内部存着一个时间点(年月日时分秒星期几)信息,可以读写,上电自动走表

3.RTC学习关键点

1.SPI接口的特征

(1)3线(SCLK,RST,IO)或者4线(SCLK,RST,I,O)

(2)同步:SPI是同步通信(表示主机【产生CLK】和从机【接受CLK】使用同一个SCLK)【同步通信有SCLK,异步没有SCLK】

(3)主从:有主机和从机

(4)串行:数据都从一根线进出

2.时序的理解

3.编程实现

2.原理图和接线

1.原理图分析

(1)DS1302引脚介绍

JP595断开,是为了让P3.4在控制DS1302的时候,不影响74HC595工作

JP1302接上,是为了让P3.4能控制到DS1302

J11断开,是为了让P3.5在控制DS1302的时候,不影响NE555模块工作

2.接线

(1)详解接线设置的原理和必要性

正常的产品一般不会这样设计,正常产品一般接线都是确定的,一般不会复用。

开发板来说,主要是为了学习,所以会放很多给模块,所以在这个时候GPIO就不够使用,这时候就需要复用设计。一个引脚接多个模块就会互相影响(有2种可能:一个是A模块工作时B模块莫名其妙的工作,二是有时候B模块会影响到A模块的正常工作)。对于复用引脚的情况,接线的关键是确认目标模块接线OK时还不会影响到其他模块。

3.数据手册带读

https://www.dianyuan.com/upload/community/2014/02/22/1393058389-67878.pdf

DS1302中文数据手册 - 豆丁网

3.时序图的读法

1.时序图的关键

(1)横轴表示时间,纵轴表示同一个时间各个通信线的状态

(2)静态或动态2个角度去看

(3)主要SCLK的边缘--->会影响IO的电平状态【如果为上升沿,代表IO端口应该在快上升沿和结束上升沿时应该保持高电平】

2.结合时序图的代码来理解时序

写入数据

/*******************************************************************************
* 函 数 名       : ds1302_write_byte
* 函数功能		 : DS1302写单字节
* 输    入       : addr:地址/命令
				   dat:数据
* 输    出    	 : 无
*******************************************************************************/
void ds1302_write_byte(u8 addr,u8 dat)
{
	u8 i=0;
	//出于安全期间,在进入之前要将SCLK和RST进行初始化为0
	DS1302_RST=0;
	_nop_();	
	DS1302_CLK=0;//CLK低电平
	_nop_();
	DS1302_RST=1;//RST由低到高变化,表示要开始工作
	_nop_();

	//开始传送八位数据
	for(i=0;i<8;i++)
	{
		//将数据放入IO口中
		DS1302_IO=addr&0x01;//数据从低位开始传送
		addr>>=1;
		DS1302_CLK=1; //上升沿
		_nop_();//delay()函数
		DS1302_CLK=0;//下降沿
		_nop_();		
	}
	for(i=0;i<8;i++)//循环8次,每次写1位,先写低位再写高位
	{
		DS1302_IO=dat&0x01;
		dat>>=1;
		DS1302_CLK=1;
		_nop_();
		DS1302_CLK=0;
		_nop_();		
	}
	//表示时序结束了
	DS1302_RST=0;//RST拉低
	_nop_();	
}

读数据

/*******************************************************************************
* 函 数 名       : ds1302_read_byte
* 函数功能		 : DS1302读单字节
* 输    入       : addr:地址/命令
* 输    出    	 : 读取的数据
*******************************************************************************/
u8 ds1302_read_byte(u8 addr)
{
	u8 i=0;
	u8 temp=0;
	u8 value=0;

	DS1302_RST=0;
	_nop_();	
	DS1302_CLK=0;//CLK低电平
	_nop_();
	DS1302_RST=1;//RST由低到高变化
	_nop_();
	
	for(i=0;i<8;i++)//循环8次,每次写1位,先写低位再写高位
	{
		DS1302_IO=addr&0x01;
		addr>>=1;	
		DS1302_CLK=1;
		_nop_();
		DS1302_CLK=0;//CLK由低到高产生一个上升沿,从而写入数据
		_nop_();		
	}
	for(i=0;i<8;i++)//循环8次,每次读1位,先读低位再读高位
	{
		temp=DS1302_IO;
		value=(temp<<7)|(value>>1);//先将value右移1位,然后temp左移7位,最后或运算
		DS1302_CLK=1;
		_nop_();
		DS1302_CLK=0;
		_nop_();		
	}
	DS1302_RST=0;//RST拉低
	_nop_();	
	DS1302_CLK=1;//对于实物中,P3.4口没有外接上拉电阻的,此处代码需要添加,使数据口有一个上升沿脉冲。
	_nop_();
	DS1302_IO = 0;
	_nop_();
	DS1302_IO = 1;
	_nop_();	
	return value;		
}

3.时序之上的东西

1.大小端

一个字节发出去,先发高位还是低位【IO=addr&0x10】表示先发低位

        【IO=addr&0x80】先发高位

2.如何读写寄存器

void ds1302_write_byte(u8 addr,u8 dat)

addr:寄存器的地址

dat:寄存器数据

4.SPI时序特征

1.低位在前

2.DS1302在上升沿读取,下降沿写入

上升沿:CLK=0;CLK=1;

下降沿:CLK=1;CLK=0

3.注意SCLK工作频率

延时长短,太短则单片机来不及读取

4.编程实践

1.编写ds1302_write_reg

//****************************************************
//向ds1302的内部寄存器addr写入一个值value
/**
addr:内部寄存器的地址
value:内部寄存器的值
*/
void ds1302_write_reg(unsigned char addr,unsigned char value){
	
	unsigned char i=0;
	unsigned char dat;
	
	//【第一步】起始部分 SCLK和RST为低电平,IO无所谓
	SCLK=0;
	delay();
	RST=0;
	delay();
	RST=1;		//SCLK为低时,RST由低变高,意味着一个大的周期的开始
	delay();
	
	
	//【第二步】写入第一个字节,addr
	for(i=0;i<8;i++){
		dat=addr&0x01;  //SPI是从低位开始传输,此时取出最低位
		
		addr=addr>>1; //把addr右移一位,将原来的数值移回去
		delay();
		DSIO=dat;  //表示将取出的二进制字符输入到IO口,把要发送的Bit数据丢到IO引脚上去准备
		//一个循环写入一个字节
		SCLK=1;   //意味着有一个上升沿
		delay();
		SCLK=0;  //读走之后,一个小周期就结束,把SCLK拉低,是为了下一个小周期做准备
	}
	
	
	//【第三步】写入第二个字节,value
	for(i=0;i<8;i++){
		dat=value&0x01;  //SPI是从低位开始传输,此时取出最低位
		
		value=value>>1; //把addr右移一位,将原来的数值移回去
		
		DSIO=dat;  //表示将取出的二进制字符输入到IO口,把要发送的Bit数据丢到IO引脚上去准备
		delay();
		//一个循环写入一个字节
		SCLK=1;   //意味着有一个上升沿
		delay();
		SCLK=0;  //读走之后,一个小周期就结束,把SCLK拉低,是为了下一个小周期做准备
	}
	
	
	//【第四步】时序结束,IO无所谓
	SCLK=0;  //SCLK拉低是为了后面的周期时初始状态是正确的
	delay();
	RST=0;// 表示一个大周期的结束
	delay();
}

2.编写ds1302_read_reg

//****************************************************
//向ds1302的内部寄存器addr读入一个值,作为返回值
/**
addr:内部寄存器的地址
value:内部寄存器的值
*/
unsigned char ds1302_read_reg(unsigned char addr)
{
	unsigned char i = 0;
	unsigned char dat = 0; 		// 用来存储读取到的一字节数据的
	unsigned char tmp = 0;

	// 第1部分: 时序起始
	SCLK = 0;
	delay();
	RST = 0;
	delay();
	RST = 1;  		// SCLK为低时,RST由低变高,意味着一个大的周期的开始
	delay();
	// 第2部分: 写入要读取的寄存器地址,addr
	for (i=0; i<8; i++)
	{
		dat = addr & 0x01;	 	// SPI是从低位开始传输的
		DSIO = dat;	 			// 把要发送的bit数据丢到IO引脚上去准备好
		SCLK = 1;		 		// 制造上升沿,让DS1302把IO上的值读走
		delay();				// 读走之后,一个小周期就完了
		SCLK = 0;				// 把SCLK拉低,是为了给下一个小周期做准备
		delay();
		addr >>= 1;	   			// 把addr右移一位
	}
	// 第3部分: 读出一字节DS1302返回给我们的值【SPI下降沿才可以进行读取】
	dat = 0;
	for (i=0; i<8; i++)
	{
	// 在前面向ds1302写入addr的最后一个bit后,ds1302就会将读取到的寄存器值
	// 的第一个bit放入到IO引脚上,所以我们应该先读取IO再制造下降沿然后继续
	// 读取下一个bit
		tmp = DSIO;
		dat |= (tmp << i);		// 读出来的数值是低位在前的
		SCLK = 1;  				// 由于上面SCLK是低,所以要先拉到高
		delay();
		SCLK = 0;				// 拉低SCLK制造一个下降沿
		delay();
	}
	// 第4部分: 时序结束
	SCLK = 0;				  	// SCLK拉低为了后面的周期时初始状态是对的
	delay();
	RST = 0;					// RST拉低意味着一个大周期的结束
	delay();

	// 第5部分:解决读取时间是ff的问题
	DSIO = 0;

	return dat;
}

3.读取时间

1.DS1302的时间寄存器的地址

如果要读取秒寄存器,地址是:0b 1000 0001(0x81)

如果要写入秒寄存器 ,地址是:0b 1000 0000(0x80)

2.移植串口输出代码,将读取到的时间通过串口输出显示


//********************************************************
//因为51单片机的设计本身RAM比较少而Flash比较多,像这里定义的数组内部
//的内容是不会变的(常量数组),我们就可以使用code关键字,让编译器帮我们
//把这个数组放在flash中而不是RAM,这样做可以省一些RAM
//判断要读取时分秒年月日星期几
unsigned char code READ_RTC_ADDR[7]={0x81,0x83,0x85,0x87,0x89,0x8b,0x8d};


//存储时间
unsigned char time[7];// 用来存储读取出来的时间,格式是:秒分时日月周年 


//****************************************************
//向ds1302的内部寄存器addr读入一个值,作为返回值
/**
addr:内部寄存器的地址
value:内部寄存器的值
*/
unsigned char ds1302_read_reg(unsigned char addr)
{
	unsigned char i = 0;
	unsigned char dat = 0; 		// 用来存储读取到的一字节数据的
	unsigned char tmp = 0;

	// 第1部分: 时序起始
	SCLK = 0;
	delay();
	RST = 0;
	delay();
	RST = 1;  		// SCLK为低时,RST由低变高,意味着一个大的周期的开始
	delay();
	// 第2部分: 写入要读取的寄存器地址,addr
	for (i=0; i<8; i++)
	{
		dat = addr & 0x01;	 	// SPI是从低位开始传输的
		DSIO = dat;	 			// 把要发送的bit数据丢到IO引脚上去准备好
		SCLK = 1;		 		// 制造上升沿,让DS1302把IO上的值读走
		delay();				// 读走之后,一个小周期就完了
		SCLK = 0;				// 把SCLK拉低,是为了给下一个小周期做准备
		delay();
		addr >>= 1;	   			// 把addr右移一位
	}
	// 第3部分: 读出一字节DS1302返回给我们的值【SPI下降沿才可以进行读取】
	dat = 0;
	for (i=0; i<8; i++)
	{
	// 在前面向ds1302写入addr的最后一个bit后,ds1302就会将读取到的寄存器值
	// 的第一个bit放入到IO引脚上,所以我们应该先读取IO再制造下降沿然后继续
	// 读取下一个bit
		tmp = DSIO;
		dat |= (tmp << i);		// 读出来的数值是低位在前的
		SCLK = 1;  				// 由于上面SCLK是低,所以要先拉到高
		delay();
		SCLK = 0;				// 拉低SCLK制造一个下降沿
		delay();
	}
	// 第4部分: 时序结束
	SCLK = 0;				  	// SCLK拉低为了后面的周期时初始状态是对的
	delay();
	RST = 0;					// RST拉低意味着一个大周期的结束
	delay();

	// 第5部分:解决读取时间是ff的问题
	DSIO = 0;

	return dat;
}

//******************************************************
//读取时间
void ds1302_read_time(void){
	
	unsigned char i=0;
	for(i=0;i<7;i++){
		time[i]=ds1302_read_reg(READ_RTC_ADDR[i]);
	}
	
}



void main(){
	ds1302_read_time();
}

 5.使用串口进行调试

1.注意波特率设置和晶振设置

2.注意串口相关的接线设置

3.测试串口输出效果

4.注意二进制显示和文本方式显示

5.注意串口助手打开时烧录软件是不能使用的

1.将读取到的时间输出到串口上


//*************************************************************
//通过串口将7个时间以二进制的方式输出到串口助手上
void debug_print_time(void)
{
	unsigned char i=0;
	while(1){
		
		//1.从ds1302读取时间
		ds1302_read_time();
		
		//2.for循环内打印一组7个时间
		for(i=0;i<7;i++){
			uart_send_byte(i);
		}
		
		//3.延时900ms后在继续下一个周期
		Delay900000us();
	}
}


//串口发送函数,发送一个字节【单个字节】
void uart_send_byte(unsigned char c){
	
	//【第一步】发送一个字节
	SBUF=c;
	//【第二步】先确认串口发送部分没有在忙
	while(!TI);//TI=0,表示在忙
	//【第三步】软件复位TI标志位---数据手册要求的
	TI=0;
}


void Delay900000us()		//@12.000MHz
{
	unsigned char i, j, k;

	_nop_();
	_nop_();
	i = 42;
	j = 10;
	k = 168;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

//****************************************************
//向ds1302的内部寄存器addr读入一个值,作为返回值
/**
addr:内部寄存器的地址
value:内部寄存器的值
*/
unsigned char ds1302_read_reg(unsigned char addr)
{
	unsigned char i = 0;
	unsigned char dat = 0; 		// 用来存储读取到的一字节数据的
	unsigned char tmp = 0;

	// 第1部分: 时序起始
	SCLK = 0;
	delay();
	RST = 0;
	delay();
	RST = 1;  		// SCLK为低时,RST由低变高,意味着一个大的周期的开始
	delay();
	// 第2部分: 写入要读取的寄存器地址,addr
	for (i=0; i<8; i++)
	{
		dat = addr & 0x01;	 	// SPI是从低位开始传输的
		DSIO = dat;	 			// 把要发送的bit数据丢到IO引脚上去准备好
		SCLK = 1;		 		// 制造上升沿,让DS1302把IO上的值读走
		delay();				// 读走之后,一个小周期就完了
		SCLK = 0;				// 把SCLK拉低,是为了给下一个小周期做准备
		delay();
		addr >>= 1;	   			// 把addr右移一位
	}
	// 第3部分: 读出一字节DS1302返回给我们的值【SPI下降沿才可以进行读取】
	dat = 0;
	for (i=0; i<8; i++)
	{
	// 在前面向ds1302写入addr的最后一个bit后,ds1302就会将读取到的寄存器值
	// 的第一个bit放入到IO引脚上,所以我们应该先读取IO再制造下降沿然后继续
	// 读取下一个bit
		tmp = DSIO;
		dat |= (tmp << i);		// 读出来的数值是低位在前的
		SCLK = 1;  				// 由于上面SCLK是低,所以要先拉到高
		delay();
		SCLK = 0;				// 拉低SCLK制造一个下降沿
		delay();
	}
	// 第4部分: 时序结束
	SCLK = 0;				  	// SCLK拉低为了后面的周期时初始状态是对的
	delay();
	RST = 0;					// RST拉低意味着一个大周期的结束
	delay();

	// 第5部分:解决读取时间是ff的问题
	DSIO = 0;

	return dat;
}

2.问题解决

状况:

(1)代码确实得到了一系列的时间数据

(2)秒确实在变化,而且规律正确

(3)时间数据中有一些FF是不合理的,不应该出现的。

总结规律:

FF总是出现在前一个周期数字是偶数时,前一个如果是奇数则不会出现

解决方法:解决读取时间为ff

1.硬件上在IO线上设置10k的电阻做弱上拉电阻处理

2.如果没有做弱上拉,也有解决方法。在代码的读取寄存器时序之后,加一个将IO置为低电平的代码进去就可以。

//****************************************************
//向ds1302的内部寄存器addr读入一个值,作为返回值
/**
addr:内部寄存器的地址
value:内部寄存器的值
*/
unsigned char ds1302_read_reg(unsigned char addr)
{
	
	
	unsigned char i = 0;
	unsigned char dat = 0; 		// 用来存储读取到的一字节数据的
	unsigned char tmp = 0;

	// 第1部分: 时序起始
	SCLK = 0;
	delay();
	RST = 0;
	delay();
	RST = 1;  		// SCLK为低时,RST由低变高,意味着一个大的周期的开始
	delay();
	// 第2部分: 写入要读取的寄存器地址,addr
	for (i=0; i<8; i++)
	{
		dat = addr & 0x01;	 	// SPI是从低位开始传输的
		DSIO = dat;	 			// 把要发送的bit数据丢到IO引脚上去准备好
		SCLK = 1;		 		// 制造上升沿,让DS1302把IO上的值读走
		delay();				// 读走之后,一个小周期就完了
		SCLK = 0;				// 把SCLK拉低,是为了给下一个小周期做准备
		delay();
		addr >>= 1;	   			// 把addr右移一位
	}
	// 第3部分: 读出一字节DS1302返回给我们的值【SPI下降沿才可以进行读取】
	dat = 0;
	for (i=0; i<8; i++)
	{
	// 在前面向ds1302写入addr的最后一个bit后,ds1302就会将读取到的寄存器值
	// 的第一个bit放入到IO引脚上,所以我们应该先读取IO再制造下降沿然后继续
	// 读取下一个bit
		tmp = DSIO;
		dat |= (tmp << i);		// 读出来的数值是低位在前的
		SCLK = 1;  				// 由于上面SCLK是低,所以要先拉到高
		delay();
		SCLK = 0;				// 拉低SCLK制造一个下降沿
		delay();
	}
	// 第4部分: 时序结束
	SCLK = 0;				  	// SCLK拉低为了后面的周期时初始状态是对的
	delay();
	RST = 0;					// RST拉低意味着一个大周期的结束
	delay();

	// 第5部分:解决读取时间是ff的问题
	DSIO = 0;

	return dat;
}

6.DS1302的时间格式详解

1.BCD码

上面显示的时间都是十六进制

1.什么是BCD码

(1)BCD码是一种数字编码,这种计数编码有个特点:很像十进制和十六进制的结合。看起来很像十进制(29下来是30而不是2A),BCD码实际是用十六进制来表示的。【BCD码的21其实在计算机中就是0x21】

BCD中只有0-9,而没有ABCDEF等字目。

综合来说:BCD码其实就是看起来很像十进制数的十六进制。

意思是:BCD码本质是十六进制数,但是因为它没有ABCDEF,所以看起来很像十进制数

(2)BCD码的意义:十六进制适合计算机进行计算,十进制适合人看和理解

2.区别BCD码,16进制,10进制,三种数

C语言:十进制、BCD码互换_51CTO博客_bcd码和十进制的互相转换

2.年份从2000开始

直接读出的数+2000就是当前的年份,比如读出的BCD码是16,对应0x16,其实就表示数字16,所以读出的是2016年。

7.向DS1302写入时间

1.读时间函数

//********************************************************
//因为51单片机的设计本身RAM比较少而Flash比较多,像这里定义的数组内部
//的内容是不会变的(常量数组),我们就可以使用code关键字,让编译器帮我们
//把这个数组放在flash中而不是RAM,这样做可以省一些RAM
//判断要读取时分秒年月日星期几
unsigned char code READ_RTC_ADDR[7]={0x81,0x83,0x85,0x87,0x89,0x8b,0x8d};


//****************************************************
//向ds1302的内部寄存器addr读入一个值,作为返回值
/**
addr:内部寄存器的地址
value:内部寄存器的值
*/
unsigned char ds1302_read_reg(unsigned char addr)
{
	
	
	unsigned char i = 0;
	unsigned char dat = 0; 		// 用来存储读取到的一字节数据的
	unsigned char tmp = 0;

	// 第1部分: 时序起始
	SCLK = 0;
	delay();
	RST = 0;
	delay();
	RST = 1;  		// SCLK为低时,RST由低变高,意味着一个大的周期的开始
	delay();
	// 第2部分: 写入要读取的寄存器地址,addr
	for (i=0; i<8; i++)
	{
		dat = addr & 0x01;	 	// SPI是从低位开始传输的
		DSIO = dat;	 			// 把要发送的bit数据丢到IO引脚上去准备好
		SCLK = 1;		 		// 制造上升沿,让DS1302把IO上的值读走
		delay();				// 读走之后,一个小周期就完了
		SCLK = 0;				// 把SCLK拉低,是为了给下一个小周期做准备
		delay();
		addr >>= 1;	   			// 把addr右移一位
	}
	// 第3部分: 读出一字节DS1302返回给我们的值【SPI下降沿才可以进行读取】
	dat = 0;
	for (i=0; i<8; i++)
	{
	// 在前面向ds1302写入addr的最后一个bit后,ds1302就会将读取到的寄存器值
	// 的第一个bit放入到IO引脚上,所以我们应该先读取IO再制造下降沿然后继续
	// 读取下一个bit
		tmp = DSIO;
		dat |= (tmp << i);		// 读出来的数值是低位在前的
		SCLK = 1;  				// 由于上面SCLK是低,所以要先拉到高
		delay();
		SCLK = 0;				// 拉低SCLK制造一个下降沿
		delay();
	}
	// 第4部分: 时序结束
	SCLK = 0;				  	// SCLK拉低为了后面的周期时初始状态是对的
	delay();
	RST = 0;					// RST拉低意味着一个大周期的结束
	delay();

	// 第5部分:解决读取时间是ff的问题
	DSIO = 0;

	return dat;
}


//存储时间
unsigned char time[7];// 用来存储读取出来的时间,格式是:秒分时日月周年 

//******************************************************
//读取时间
void ds1302_read_time(void){
	
	unsigned char i=0;
	for(i=0;i<7;i++){
		time[i]=ds1302_read_reg(READ_RTC_ADDR[i]);
	}
	
}

2.写时间函数

1.数组的设置

//读取时间用到的数组:因为是【读】所以最后一位是1
unsigned char code READ_RTC_ADDR[7]={0x81,0x83,0x85,0x87,0x89,0x8b,0x8d};
//写入时间用到的数组:因为是【写】所以最后一位是0,所以比READ_RTC_ADDR中的地址分别少1
unsigned char code WRITE_RTC_ADDR[7]={0x80,0x82,0x84,0x86,0x88,0x8a,0x8c};

2.“写保护”设置

	ds1302_write_reg(0x8E,0x00);  //去掉写保护
	for(i=0;i<7;i++){
		ds1302_write_reg(WRITE_RTC_ADDR[i],time[i]);
	}
	
	ds1302_write_reg(0x8E,0x80);//打开写保护

3.注意写入地址和读出地址不同

//******************************************************
//写入时间
void ds1302_write_time(void)
{
		unsigned char i=0;
	//准备好要写入的时间
	time[0]=0x24;  //对应24s
	time[1]=0x39;// 对应39m
	time[2]=0x11;  //对应11h
	time[3]=0x30;  //对应30日
	time[4]=0x11;		//对应12月
	time[5]=0x02;  //对应星期二
	time[6]=0x16;  //对应2016年
	
	ds1302_write_reg(0x8E,0x00);  //去掉写保护
	for(i=0;i<7;i++){
		ds1302_write_reg(WRITE_RTC_ADDR[i],time[i]);
	}
	
	ds1302_write_reg(0x8E,0x80);//打开写保护
}

8.对程序进行规整

1.如何规整

(1)多文件方式实现,意思是多个.c文件来实现

(2)多文件方式的目的是让各个功能模块分开实现,这样方便组织和查找

2.c文件和头文件

(1)c文件是c语言源文件,h文件是头文件

(2)源文件主要用来放:函数和全局变量的定义

(3)头文件主要用来存放:函数和全局变量的声明,宏定义,结构体共用体类型定义等

(4)一般是一个源文件就配一个头文件

(5)一般包含自己建立的头文件时用”“而不用<>

(6)头文件中还有固定格式

#ifndef __UART_H__
#define __UART_H__
#endif

uart.h

#ifndef __UART_H__
#define __UART_H__

#include <reg51.h>





void uart_init(void);
void uart_send_byte(unsigned char c);





#endif



ds1302.h

#ifndef __DS1302_H__
#define __DS1302_H__




void delay(void);
//void delay1s(void);
void delay900ms(void);
void ds1302_write_reg(unsigned char addr, unsigned char value);
unsigned char ds1302_read_reg(unsigned char addr);
void ds1302_read_time(void);
void ds1302_write_time(void);
void debug_print_time(void);





#endif





main.c

#include "uart.h"
#include "ds1302.h"


void main(void)
{
//	unsigned char i = 0;

	uart_init();

	ds1302_write_time();
/*
	// 测试串口工作
	for (i=0; i<255; i++)
	{
		uart_send_byte(i);
		delay1s();
	}
	while (1);
*/
	debug_print_time();
}

ds1302.c

#include <reg51.h>
#include <intrins.h>
#include "uart.h"
#include "ds1302.h"



/**************  全局变量定义  *************************************/

// 定义SPI的三根引脚
sbit DSIO	= P3^4;
sbit RST	= P3^5;
sbit SCLK	= P3^6;

// 因为51单片机的设计本身RAM比较少而Flash稍微多一些,像这里定义的数组内部
// 的内容是不会变的(常量数组),我们就可以使用code关键字,让编译器帮我们
// 把这个数组放在flash中而不是ram中,这样做可以省一些ram。
unsigned char code READ_RTC_ADDR[7] = {0x81, 0x83, 0x85, 0x87, 0x89, 0x8b, 0x8d}; 
unsigned char code WRITE_RTC_ADDR[7] = {0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c};
unsigned char time[7];		// 用来存储读取的时间的,格式是:秒分时日月周年




// 有用函数
void delay(void)
{
	unsigned char i;
	for (i=0; i<3; i++);
}
/*
void delay1s(void)   //误差 0us
{
    unsigned char a,c;
    for(c=167;c>0;c--)
            for(a=16;a>0;a--);
    _nop_();  //if Keil,require use intrins.h
}
*/

void delay900ms(void)   //误差 -0.000000000205us
{
    unsigned char a,b,c;
    for(c=127;c>0;c--)
        for(b=128;b>0;b--)
            for(a=24;a>0;a--);
}

// 向ds1302的内部寄存器addr写入一个值value
void ds1302_write_reg(unsigned char addr, unsigned char value)
{
	unsigned char i = 0;
	unsigned char dat = 0;

	// 第1部分: 时序起始
	SCLK = 0;
	delay();
	RST = 0;
	delay();
	RST = 1;  		// SCLK为低时,RST由低变高,意味着一个大的周期的开始
	delay();
	// 第2部分: 写入第1字节,addr
	for (i=0; i<8; i++)
	{
		dat = addr & 0x01;	 	// SPI是从低位开始传输的
		DSIO = dat;	 			// 把要发送的bit数据丢到IO引脚上去准备好
		SCLK = 1;		 		// 制造上升沿,让DS1302把IO上的值读走
		delay();				// 读走之后,一个小周期就完了
		SCLK = 0;				// 把SCLK拉低,是为了给下一个小周期做准备
		delay();
		addr >>= 1;	   			// 把addr右移一位
	}
	// 第3部分: 写入第2字节,value
	for (i=0; i<8; i++)
	{
		dat = value & 0x01;	 	// SPI是从低位开始传输的
		DSIO = dat;	 			// 把要发送的bit数据丢到IO引脚上去准备好
		SCLK = 1;		 		// 制造上升沿,让DS1302把IO上的值读走
		delay();				// 读走之后,一个小周期就完了
		SCLK = 0;				// 把SCLK拉低,是为了给下一个小周期做准备
		delay();
		value = value >> 1;	   	// 把addr右移一位
	}
	// 第4部分: 时序结束
	SCLK = 0;				  	// SCLK拉低为了后面的周期时初始状态是对的
	delay();
	RST = 0;					// RST拉低意味着一个大周期的结束
	delay();
}


// 从ds1302的内部寄存器addr读出一个值,作为返回值
unsigned char ds1302_read_reg(unsigned char addr)
{
	unsigned char i = 0;
	unsigned char dat = 0; 		// 用来存储读取到的一字节数据的
	unsigned char tmp = 0;

	// 第1部分: 时序起始
	SCLK = 0;
	delay();
	RST = 0;
	delay();
	RST = 1;  		// SCLK为低时,RST由低变高,意味着一个大的周期的开始
	delay();
	// 第2部分: 写入要读取的寄存器地址,addr
	for (i=0; i<8; i++)
	{
		dat = addr & 0x01;	 	// SPI是从低位开始传输的
		DSIO = dat;	 			// 把要发送的bit数据丢到IO引脚上去准备好
		SCLK = 1;		 		// 制造上升沿,让DS1302把IO上的值读走
		delay();				// 读走之后,一个小周期就完了
		SCLK = 0;				// 把SCLK拉低,是为了给下一个小周期做准备
		delay();
		addr >>= 1;	   			// 把addr右移一位
	}
	// 第3部分: 读出一字节DS1302返回给我们的值
	dat = 0;
	for (i=0; i<8; i++)
	{
	// 在前面向ds1302写入addr的最后一个bit后,ds1302就会将读取到的寄存器值
	// 的第一个bit放入到IO引脚上,所以我们应该先读取IO再制造下降沿然后继续
	// 读取下一个bit
		tmp = DSIO;
		dat |= (tmp << i);		// 读出来的数值是低位在前的
		SCLK = 1;  				// 由于上面SCLK是低,所以要先拉到高
		delay();
		SCLK = 0;				// 拉低SCLK制造一个下降沿
		delay();
	}
	// 第4部分: 时序结束
	SCLK = 0;				  	// SCLK拉低为了后面的周期时初始状态是对的
	delay();
	RST = 0;					// RST拉低意味着一个大周期的结束
	delay();

	// 第5部分:解决读取时间是ff的问题
	DSIO = 0;

	return dat;
}


void ds1302_read_time(void)
{
	unsigned char i = 0;
	for (i=0; i<7; i++)
	{
		time[i] = ds1302_read_reg(READ_RTC_ADDR[i]);
	}
}

void ds1302_write_time(void)
{
	unsigned char i = 0;

	// 准备好要写入的时间
	time[0] = 0x24;				// 对应 24s
	time[1] = 0x39;				// 对应 39m
	time[2] = 0x11;				// 对应 11h
	time[3] = 0x06;				// 对应 6日
	time[4] = 0x12;				// 对应 12月
	time[5] = 0x02;				// 对应 星期2
	time[6] = 0x16;				// 对应 2016年

	ds1302_write_reg(0x8E, 0x00);	// 去掉写保护
	for (i=0; i<7; i++)
	{
		ds1302_write_reg(WRITE_RTC_ADDR[i], time[i]);
	}
	ds1302_write_reg(0x8E, 0x80);	// 打开写保护
}




// 通过串口将7个时间以二进制方式输出在串口助手上
void debug_print_time(void)
{
	unsigned char i = 0;

	while (1)
	{
		// 1 从DS1302读取时间
		ds1302_read_time();

		// 2 for循环内打印一组7个时间
		for (i=0; i<7; i++)
		{
			uart_send_byte(time[i]);	
		}

		// 3 延时900ms后再继续下个周期
	   	delay900ms();
	}	
}

uart.c

#include "uart.h"


// 串口设置为: 波特率9600、数据位8、停止位1、奇偶校验无
// 使用的晶振是11.0592MHz的,注意12MHz和24MHz的不行
void uart_init(void)
{
	// 波特率9600
	SCON = 0x50;   	// 串口工作在模式1(8位串口)、允许接收
	PCON = 0x00;	// 波特率不加倍

	// 通信波特率相关的设置
	TMOD = 0x20;	// 设置T1为模式2
	TH1 = 253;
	TL1 = 253;	   	// 8位自动重装,意思就是TH1用完了之后下一个周期TL1会
					// 自动重装到TH1去

	TR1 = 1;		// 开启T1让它开始工作
	ES = 1;
	EA = 1;
}

// 通过串口发送1个字节出去
void uart_send_byte(unsigned char c)
{
   // 第1步,发送一个字节
   SBUF = c;

   // 第2步,先确认串口发送部分没有在忙
   while (!TI);

   // 第3步,软件复位TI标志位
   TI = 0;
}

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

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

相关文章

【Git】Git 原理和使用

Git 一、Git 本地仓库1. 本地仓库的创建2. 配置 Git3. 工作区、暂存区、版本库4. 添加文件5. 查看 .git 文件6. 修改文件7. 版本回退8. 撤销修改9. 删除文件 二、分支管理1. 理解分支2. 创建分支3. 切换分支4. 合并分支5. 删除分支6. 合并冲突7. 分支管理策略8. bug 分支9. 强制…

基于Java的厨艺交流平台设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

JavaScript Web APIs第三天笔记

Web APIs - 第3天 进一步学习 事件进阶&#xff0c;实现更多交互的网页特效&#xff0c;结合事件流的特征优化事件执行的效率 掌握阻止事件冒泡的方法理解事件委托的实现原理 事件流 事件流是对事件执行过程的描述&#xff0c;了解事件的执行过程有助于加深对事件的理解&…

数据结构刷题(三十三):完全背包最小值情况。322. 零钱兑换、279. 完全平方数

题目一&#xff1a; 322. 零钱兑换https://leetcode.cn/problems/coin-change/ 思路&#xff1a;完全背包问题&#xff0c;求解最小组合数。dp[j]&#xff1a;凑足总额为j所需钱币的最少个数为dp[j]。同时需要确保凑足总金额为0所需钱币的个数一定是0&#xff0c;那么dp[0] 0…

如果只是用php纯做api的话,给移动端做数据接口,是否需要用php框架?

API接口对接是现代软件开发中不可或缺的一部分&#xff0c;它允许不同的应用程序之间进行数据交换和服务调用。在PHP中&#xff0c;可以使用多种方式实现API接口的对接&#xff0c;包括基于HTTP协议的传统方法以及现代的API客户端库客户端库客户端库等。 一、实现API接口的对接…

Web开发-登录页面设计流程

目录 确定页面设计样式创建js文件jquery.min.jsbootstrap.min.js 创建css文件bootstrap.min.cssmaterialdesignicons.min.cssstyle.min.css 创建ftl文件header.ftlfooter.ftllogin.ftlcss部分html部分 确定页面设计样式 可以自己用“画图”等软件进行设计&#xff0c;也可以打…

步进电机只响不转

我出现问题的原因是相位线接错。 我使用的滑台上示17H的步进电机&#xff0c;之前用的是57的步进电机。 57步进电机的相位线是A黑、A-绿、B红、B-蓝。 17步进电机的相位线是A红、A-绿、B黑、B-蓝。 这两天被一个问题困扰了好久&#xff0c;在调试步进电机开发板的时候电机发生…

ubuntu安装ROS

进官网&#xff0c;选版本&#xff0c;操作系统 ROS: Home 开始安装&#xff1a; noetic/Installation/Ubuntu - ROS Wiki Installation Configure your Ubuntu repositories Configure your Ubuntu repositories to allow "restricted," "universe,"…

银行金融科技岗笔试题资料大总结

程序员进银行科技岗——简单总结_银行程序员 无水印&#xff0c;可直接打印使用。 中国银行 通用资料 视频资料

IPV6(IPV6,RIPng的配置以及手工配置IPV4隧道)

目录 实验一&#xff1a;IPv6的基本配置 实验二&#xff1a;RIPng基本配置 RIPng RIPng的工作机制 实验三&#xff1a;手工配置IPV4隧道 实验一&#xff1a;IPv6的基本配置 案例如下&#xff1a; 各部分配置如下 配置路由器RTA <Huawei>sys Enter system view, …

【论文笔记】DiffusionTrack: Diffusion Model For Multi-Object Tracking

原文链接&#xff1a;https://arxiv.org/abs/2308.09905 1. 引言 多目标跟踪通常分为两阶段的检测后跟踪&#xff08;TBD&#xff09;和一阶段的联合检测跟踪&#xff08;JDT&#xff09;。TBD对单帧进行目标检测后&#xff0c;使用跟踪器跨帧关联相同物体。使用的跟踪器包括使…

【STM32基础 CubeMX】外部中断

文章目录 前言一、中断是什么二、使用CubeMX配置你的第一个中断三、代码分析CubeMX四、中断函数按键中断点灯示例代码总结 前言 当涉及到STM32基础的外部中断时&#xff0c;我们进入了一个引人入胜的领域&#xff0c;它允许微控制器与外部世界进行互动并实时响应各种事件。外部…

【论文阅读】通过3D和2D网络的交叉示教实现稀疏标注的3D医学图像分割(CVPR2023)

目录 前言方法标注3D-2D Cross Teaching伪标签选择Hard-Soft Confidence Threshold Consistent Prediction Fusion 结论 论文&#xff1a;3D Medical Image Segmentation with Sparse Annotation via Cross-Teaching between 3D and 2D Networks 代码&#xff1a;https://githu…

2023年中国艺术涂料市场发展历程及趋势分析:艺术涂料市场规模将进一步扩大[图]

艺术涂料是一种用于绘画和装饰&#xff0c;具有各种纹理或通过涂装手段后具有高装饰性的新型涂料。由于具有高度饱和的颜色、良好的遮盖力和可塑性&#xff0c;呈现立体装饰效果好、色彩搭配适当、风格独具特色的特点&#xff0c;而使得涂装出的饰面自然贴合、更加美观漂亮&…

Centos7环境下安装MySQL8详细教程

目录 一、Xftp7下载二、MySQL8安装包的下载三、将MySQL8安装包上传至服务器四、解压mysql8安装包五、rpm包的安装六、依次安装下列文件七、对MySQL进行初始化和授权八、查看数据库初始密码九、启动MySQL服务十、使用初始密码登录MySQL关于MySQL的卸载 一、Xftp7下载 关于Xftp7…

力扣 -- 718. 最长重复子数组

解题步骤&#xff1a; 参考代码&#xff1a; class Solution { public:int findLength(vector<int>& nums1, vector<int>& nums2) {int m nums1.size();int n nums2.size();//多开一行&#xff0c;多开一列vector<vector<int>> dp(m 1, ve…

华为云云耀云服务器L实例评测 | 搭建企业级 Registry 服务器 Harbor

文章目录 您需要了解Harbor介绍Harbor特性和优势系统设置关闭防火墙安装Docker安装Docker Compose配置镜像加速器 Habor安装传包并解压配置Harbor安装Horbor登录Harbor 测试创建项目推送镜像拉取镜像 您需要了解 本次搭建采用 华为云耀云服务器 &#xff0c;一键部署、快速搭建…

Visual Studio 代码显示空格等空白符

1.VS2010: 快捷键&#xff1a;CtrlR,W 2.VS2017、VS2019、VS2022&#xff1a; 工具 -> 选项 -> 文本编辑器 -> 显示 -> 勾选查看空白

RWA分析通过10个问题。不要让数字和视觉欺骗您!(文章很长,请仔细阅读)

已经有30个小伙伴加入我们的星球了&#xff0c;如果你不介意的话&#xff0c;可以加入 我的知识星球主要分享 1. 分享区块链各种有价值的内容 2. 一起攻读一些有用的书籍 3. 财富密码&#xff08;不敢保证&#xff09; 4. 一些自我的感悟 1.什么是现实世界资产&#xff08;RWA&…

【Flutter】Flutter Web 开发 如何从 URL 中获取参数值

【Flutter】Flutter Web 开发 如何从 URL 中获取参数值 文章目录 一、前言二、Flutter Web 中的 URL 处理三、如何从 URL 中获取参数四、实际业务中的用法五、完整示例六、总结 一、前言 大家好&#xff01;我是小雨青年&#xff0c;今天我想和大家分享一下在 Flutter Web 开发…