【单片机】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;
}相关文章:
【单片机】13-实时时钟DS1302
1.RTC的简介 1.什么是实时时钟(RTC) (rtc for real time clock) (1)时间点和时间段的概念区分 (2)单片机为什么需要时间点【一定的时间点干什么事情】 (3)RTC如何存在于…...
springboot和vue:十三、VueX简介与安装与推荐视频+前端数据模拟MockJS
VueX简介与安装与推荐视频 VueX用于管理分散在vue各个组件中的数据。每一个VueX的核心都是一个store,当store中的状态发生变化时,与之绑定的视图也将重新渲染。store中的状态不允许被直接修改,只能显示提交mutationVueX中有五个重要的概念&a…...
[React] Zustand状态管理库
文章目录 1.Zustand介绍2.创建一个store3.使用方法3.1 获取状态3.2 更新状态3.3 访问存储状态3.4 处理异步数据3.5 在状态中访问和存储数组3.6 持续状态 4.总结 1.Zustand介绍 状态管理一直是现代程序应用中的重要组成部分, Zustand使用 hooks 来管理状态无需样板代码。 更少…...
【ChatGPT】ChatGPT发展历史
更多优质文章请看底部:ChatGPT与日本首相交流核废水事件-精准Prompt... hello,我是小索奇,在AI日益庞大的环境下,接下来将为大家不断的ChatGPT学习 ChatGPT使用了 Transformer 结构,建立在 OpenAI的 GPT-3.5 大型语言模…...
分布式文件存储系统Minio实战
分布式文件系统应用场景 互联网海量非结构化数据的存储需求电商网站:海量商品图片视频网站:海量视频文件网盘 : 海量文件社交网站:海量图片 1. Minio介绍 MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存…...
【MySQL】MySQL 官方安装包形式
MySQL 官方提供3种包: 1. 源码包 mysql-5.7.42.tar.gz mysql-5.7.42-aarch64.tar.gz http://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-5.6.34.tar.gz http://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.42.tar.gz需要用户根据自己的CPU架构选择对应的…...
使用sqlmap获取数据步骤
文章目录 1.使用sqlmap获取所有数据库2.使用sqlmap获取当前连接数据库3.使用sqlmap获取当前数据库下所有表名4.使用sqlmap获取当前数据库下某个表下所有列名5.使用sqlmap获取当前数据库下某个表下指定字段的数据6.测试当前用户是否是管理员7.使用burpsqlmap批量检测8.脱库命令9…...
[论文笔记]GLM
引言 今天带来论文GLM: General Language Model Pretraining with Autoregressive Blank Infilling的笔记。论文中文标题为 通用语言模型预训练与自回归填空。 有很多不同类型的预训练架构,包括自编码模型(BERT、RoBERTa、ALBERT)、自回归模型(GPT系列)以及编码器-解码器模型…...
漏洞扫描环境:win10系统用VMware Workstation打开虚拟机若干问题
win10系统用VMware Workstation打开虚拟机若干问题 一 .VMware打开虚拟机就蓝屏重启怎么解决?一. VMware打开虚拟机就蓝屏重启怎么解决?方法一:1、同时按下CTRLSHIFTESC打开任务管理器功能,之后依次点击-详细信息-性能后出现下列界…...
OpenCV实现模板匹配和霍夫线检测,霍夫圆检测
一,模板匹配 1.1代码实现 import cv2 as cv import numpy as np import matplotlib.pyplot as plt from pylab import mplmpl.rcParams[font.sans-serif] [SimHei]#图像和模板的读取 img cv.imread("cat.png") template cv.imread(r"E:\All_in\o…...
消息队列实现进程之间通信方式
1. snd 源代码 #include <myhead.h>#define ERR_MSG(msg) do{\fprintf(stderr,"__%d__:",__LINE__);\perror(msg);\ }while(0)typedef struct{ long msgtype; //消息类型char data[1024]; //消息正文 }Msg;#define SIZE sizeof(Msg)-sizeof(long)int main(i…...
用简单例子讲清楚webgl模板测试
文章目录 搭建简易的webgl环境绘制简单三角形(不带stencilTest)绘制另一个三角形(不带模板测试)加入模板测试总结调参练习 搭建简易的webgl环境 一直以来,我只是想通过搭建纯webgl环境,进行开发,来清楚地了…...
区块链(8):p2p去中心化之websoket服务端实现业务逻辑
1 业务逻辑 例如 peer1和peer2之间相互通信 peer1通过onopen{ write(Mesage(QUERY_LATEST))} 向peer2发送消息“我要最新的区块”。 peer2通过onMessage收到消息,通过handleMessage方法对消息进行处理。 handleMessage根据消息类型进行处理 RESPONSE_BLOCKCHAIN:返回区块链…...
composer安装与设置
1、到官网下载 composer.phar。下载地址:Composer 2、将下载的composer.phar 复制到 composer 文件夹中 3、在composer文件夹中新建文件 composer.bat,内容为 php "%~dp0composer.phar" %* 5、设置环境变量的path,添加composer文件夹...
unordered_map/unordered_set的学习[unordered系列]
文章目录 1.老生常谈_遍历2.性能测试3.OJ训练3.1存在重复元素3.2两个数组的交集Ⅱ3.3两句话中的不常见单词3.4两个数组的交集3.5在长度2N的数组中找出重复N次的元素 1.老生常谈_遍历 #pragma once #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <l…...
C++位图—布隆过滤器
目录 位图概念位图应用 布隆过滤器简介布隆过滤器的优缺点布隆过滤器应用场景布隆过滤器实现布隆过滤器误判率分析 总结 位图概念 位图是一种数据结构,用于表示一组元素的存在或不存在,通常用于大规模数据集的快速查询。它基于一个位数组(或位…...
SQL SELECT 语句进阶
之前探讨了SQL SELECT 语句的基础内容,包括语法、字段选择、记录限制和数据源指定。今天将进一步深入,探讨多表连接、过滤结果集和逻辑运算等高级主题,还有LIKE 模糊查询、ORDER BY 对结果集排序、运用聚合函数汇总结果以及 GROUP BY 子句与相关应用。 本文将继续使用《三国…...
Mac程序坞美化工具 uBar
uBar是一款为Mac用户设计的任务栏增强软件,它可以为您提供更高效和更个性化的任务管理体验。 以下是uBar的一些主要特点和功能: 更直观的任务管理:uBar改变了Mac上传统的任务栏设计,将所有打开的应用程序以类似于Windows任务栏的方…...
【数据结构】排序之插入排序和选择排序
🔥博客主页:小王又困了 📚系列专栏:数据结构 🌟人之为学,不日近则日退 ❤️感谢大家点赞👍收藏⭐评论✍️ 目录 一、排序的概念及其分类 📒1.1排序的概念 📒1.2排序…...
6.html表单
HTML表单(HTML form)是网页中用于收集用户输入数据的一种方式。表单由多个表单元素组成,通常包括输入框,复选框,单选按钮,下拉列表和提交按钮等。 HTML表单元素的基本结构如下: <form acti…...
AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...
鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南
1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...
适应性Java用于现代 API:REST、GraphQL 和事件驱动
在快速发展的软件开发领域,REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名,不断适应这些现代范式的需求。随着不断发展的生态系统,Java 在现代 API 方…...
Chrome 浏览器前端与客户端双向通信实战
Chrome 前端(即页面 JS / Web UI)与客户端(C 后端)的交互机制,是 Chromium 架构中非常核心的一环。下面我将按常见场景,从通道、流程、技术栈几个角度做一套完整的分析,特别适合你这种在分析和改…...
前端开发者常用网站
Can I use网站:一个查询网页技术兼容性的网站 一个查询网页技术兼容性的网站Can I use:Can I use... Support tables for HTML5, CSS3, etc (查询浏览器对HTML5的支持情况) 权威网站:MDN JavaScript权威网站:JavaScript | MDN...
海云安高敏捷信创白盒SCAP入选《中国网络安全细分领域产品名录》
近日,嘶吼安全产业研究院发布《中国网络安全细分领域产品名录》,海云安高敏捷信创白盒(SCAP)成功入选软件供应链安全领域产品名录。 在数字化转型加速的今天,网络安全已成为企业生存与发展的核心基石,为了解…...

