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

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)串行:数据都从一根线进出【数据都是从IO进出】

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进行初始化为0DS1302_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();//【第二步】写入第一个字节,addrfor(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拉低,是为了下一个小周期做准备}//【第三步】写入第二个字节,valuefor(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部分: 写入要读取的寄存器地址,addrfor (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再制造下降沿然后继续// 读取下一个bittmp = 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部分: 写入要读取的寄存器地址,addrfor (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再制造下降沿然后继续// 读取下一个bittmp = 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部分: 写入要读取的寄存器地址,addrfor (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再制造下降沿然后继续// 读取下一个bittmp = 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部分: 写入要读取的寄存器地址,addrfor (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再制造下降沿然后继续// 读取下一个bittmp = 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部分: 写入要读取的寄存器地址,addrfor (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再制造下降沿然后继续// 读取下一个bittmp = 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;  //对应24stime[1]=0x39;// 对应39mtime[2]=0x11;  //对应11htime[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字节,addrfor (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字节,valuefor (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部分: 写入要读取的寄存器地址,addrfor (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再制造下降沿然后继续// 读取下一个bittmp = 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;				// 对应 24stime[1] = 0x39;				// 对应 39mtime[2] = 0x11;				// 对应 11htime[3] = 0x06;				// 对应 6日time[4] = 0x12;				// 对应 12月time[5] = 0x02;				// 对应 星期2time[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)
{// 波特率9600SCON = 0x50;   	// 串口工作在模式1(8位串口)、允许接收PCON = 0x00;	// 波特率不加倍// 通信波特率相关的设置TMOD = 0x20;	// 设置T1为模式2TH1 = 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.rhkb.cn/news/147499.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis中Hash类的操作

Redis中Hash类型是键值对的形式保存数据&#xff0c;其中键被称为字段&#xff08;field&#xff09;&#xff0c;值称为字段值&#xff08;value&#xff09;。在一个key中&#xff0c;字段不能重复&#xff0c;而值可以重复。无论是字段还是值都是无序的&#xff08;保存的次…

Java 大厂八股文面试专题-JVM相关面试题 垃圾回收算法 GC JVM调优

Java 大厂八股文面试专题-JVM相关面试题 类加载器_软工菜鸡的博客-CSDN博客 3 垃圾收回 3.1 简述Java垃圾回收机制&#xff1f;&#xff08;GC是什么&#xff1f;为什么要GC&#xff09; 难易程度&#xff1a;☆☆☆ 出现频率&#xff1a;☆☆☆ 为了让程序员更专注于代码的实现…

【Flink】

事件驱动型应用 核心目标&#xff1a;数据流上的有状态计算 Apache Flink是一个框架和分布式处理引擎&#xff0c;用于对无界或有界数据流进行有状态计算。 运行逻辑 状态 把流处理需要的额外数据保存成一个“状态”,然后针对这条数据进行处理,并且更新状态。这就是所谓的“…

MySQL explain SQL分析工具详解与最佳实践

目录 一、explain工具介绍二、添加示例表和数据用于后续演示三、explain中的列3.1、id列3.2、select_type列3.3、table列3.4、partitions列3.5、type列NULLsystemconsteq_refrefrangeindexALL 3.6、possible_keys列3.7、key列3.8、key_len列3.9、ref列3.10、rows列3.11、filter…

HDFS最基础使用

文章目录 一、简介1、定义2、HDFS优缺点3、HDFS组成架构4、HDFS文件块大小 二、HDFS的读写流程1、HDFS写数据流程2、HDFS读数据流程3、网络拓扑-节点距离计算4、机架感知&#xff08;副本存储节点选择&#xff09; 三、NameNode和SecondaryNameNode1、NN和2NN工作机制2、Fsimag…

Linux关于gittee的远端仓库的连接和git三板斧

目录 1.网页操作 2.Linux操作 查看Linux系统中是否安装git指令 安装git指令 链接远端仓库 设置 .gitignore文件 3.git三板斧 1.网页操作 首先我们要在gittee建立一个仓库 这是我自己的勾选方案&#xff0c;大家可以参考一下。 这个方案勾选最下面的三个选项才有&#x…

java图书管理系统

一、 引言 图书管理系统是一个用于图书馆或书店管理图书信息、借阅记录和读者信息的应用程序。本系统使用Java Swing框架进行开发&#xff0c;提供直观的用户界面&#xff0c;方便图书馆管理员或书店工作人员对图书信息进行管理。以下是系统的设计、功能和实现的详细报告。 二…

最新整理源码面试题

经典框架源码面试题 Lecture&#xff1a;波哥 1.谈谈你对框架的理解 1.1 框架的作用 JavaWeb中的框架是一种开发工具或者平台&#xff0c;它提供了一系列的功能和组件&#xff0c;用于简化和加速Web应用的开发过程。框架可以提供一些基础设施&#xff0c;如数据库访问、用户认…

ISP图像信号处理——白平衡校正和标定介绍以及C++实现

从数码相机直接输出的未经过处理过的RAW图到平常看到的JEPG图有一系列复杂的图像信号处理过程&#xff0c;称作ISP&#xff08;Image Signal Processing&#xff09;。这个过程会经过图像处理和压缩。 参考文章1&#xff1a;http://t.csdn.cn/LvHH5 参考文章2&#xff1a;htt…

基于蜉蝣优化的BP神经网络(分类应用) - 附代码

基于蜉蝣优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于蜉蝣优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.蜉蝣优化BP神经网络3.1 BP神经网络参数设置3.2 蜉蝣算法应用 4.测试结果&#xff1a;5.M…

AAD基础知识(identity/token/PRT)

简介 AAD(Azure Active Directory/Azure AD)是微软基于云身份验证和访问控制的解决方案&#xff0c;通过SSO登录其他o365应用(word/outlook/teams…) 微软在2023年7月把AAD重命名为Microsoft Entra ID&#xff0c;官网&#xff1a;https://www.microsoft.com/zh-cn/security/b…

【多任务案例:猫狗脸部定位与分类】

【猫狗脸部定位与识别】 1 引言2 损失函数3 The Oxford-IIIT Pet Dataset数据集4 数据预处理4 创建模型输入5 自定义数据集加载方式6 显示一批次数据7 创建定位模型8 模型训练9 绘制损失曲线10 模型保存与预测 1 引言 猫狗脸部定位与识别分为定位和识别&#xff0c;即定位猫狗…

通过 HelpLook ChatBot AI自动问答机器人降低客户服务成本

在当今竞争激烈的商业环境中&#xff0c;提供卓越的客户服务对于维持忠诚的客户群和推动业务增长至关重要。客户服务涵盖了公司与其客户之间的所有互动&#xff0c;包括解答问题、解决问题和提供支持。它在塑造客户对品牌的看法方面起着关键作用&#xff0c;并且可以显著影响他…

C语言判断语句

判断结构要求程序员指定一个或多个要评估或测试的条件&#xff0c;以及条件为真时要执行的语句&#xff08;必需的&#xff09;和条件为假时要执行的语句&#xff08;可选的&#xff09;。 C 语言把任何非零和非空的值假定为 true&#xff0c;把零或 null 假定为 false。 下面…

react create-react-app v5 从零搭建(使用 npm run eject)

前言&#xff1a; 好久没用 create-react-app做项目了&#xff0c;这次为了个h5项目&#xff0c;就几个页面&#xff0c;决定自己搭建一个&#xff08;ps:mmp 好久没用&#xff0c;搭建的时候遇到一堆问题&#xff09;。 我之前都是使用 umi 。后台管理系统的项目 使用 antd-…

DevExpress ChartControl 画间断线

效果如下&#xff1a; 解决办法&#xff1a;数据源间断位置加入double.NaN demo下载

动态链接那些事

1、为什么要动态链接 1.1 空间浪费 对于静态链接来说&#xff0c;在程序运行之前&#xff0c;会将程序所需的所有模块编译、链接成一个可执行文件。这种情况下&#xff0c;如果 Program1 和 Program2 都需要用到 Lib.o 模块&#xff0c;那么&#xff0c;内存中和磁盘中实际上就…

Sui第五轮资助: 17个项目共获105万美元资助金

近日&#xff0c;Sui基金会宣布17个项目获得了105万美元的资助&#xff0c;用于建设项目以推动Sui的采用和发展。要获得资助&#xff0c;项目必须提交详细的提案&#xff0c;说明构建的项目、预算明细、关键里程碑、团队经验&#xff0c;以及对Sui社区的预期贡献。了解更多Sui资…

Azure Arc 概要:功能、管理和应用场景详解,AZ900 考点示例

文章目录 本文大纲一、什么是 Azure Arc二、使用 Azure Arc 可以做什么操作三、使用 Azure Arc 可以管理什么资源3.1 如何使用Azure Arc与服务器? 四、Azure Arc 支持的主要场景五、在 AZ900 中的考点示例5.1 示例题 15.2 示例题 2 本文大纲 本文思维导图概述的主要内容&…

已解决 Bug——IndexError: index 3 is out of bounds for axis 0 with size 3问题

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页: &#x1f405;&#x1f43e;猫头虎的博客&#x1f390;《面试题大全专栏》 &#x1f995; 文章图文并茂&#x1f996…