• 【单片机】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.时序图的关键

    RST:由低电平变为高电平表示要开始工作了

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

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

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

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

    写入数据

    1. /*******************************************************************************
    2. * 函 数 名 : ds1302_write_byte
    3. * 函数功能 : DS1302写单字节
    4. * 输 入 : addr:地址/命令
    5. dat:数据
    6. * 输 出 : 无
    7. *******************************************************************************/
    8. void ds1302_write_byte(u8 addr,u8 dat)
    9. {
    10. u8 i=0;
    11. //出于安全期间,在进入之前要将SCLK和RST进行初始化为0
    12. DS1302_RST=0;
    13. _nop_();
    14. DS1302_CLK=0;//CLK低电平
    15. _nop_();
    16. DS1302_RST=1;//RST由低到高变化,表示要开始工作
    17. _nop_();
    18. //开始传送八位数据
    19. for(i=0;i<8;i++)
    20. {
    21. //将数据放入IO口中
    22. DS1302_IO=addr&0x01;//数据从低位开始传送
    23. addr>>=1;
    24. DS1302_CLK=1; //上升沿
    25. //因为读取数据需要一段时间
    26. _nop_();//delay()函数
    27. DS1302_CLK=0;//下降沿
    28. _nop_();
    29. }
    30. for(i=0;i<8;i++)//循环8次,每次写1位,先写低位再写高位
    31. {
    32. DS1302_IO=dat&0x01;
    33. dat>>=1;
    34. DS1302_CLK=1;
    35. _nop_();
    36. DS1302_CLK=0;
    37. _nop_();
    38. }
    39. //表示时序结束了
    40. DS1302_RST=0;//RST拉低
    41. DS1302_CLK=0;//为下一次循环做准备
    42. _nop_();
    43. }

    读数据

    1. /*******************************************************************************
    2. * 函 数 名 : ds1302_read_byte
    3. * 函数功能 : DS1302读单字节
    4. * 输 入 : addr:地址/命令
    5. * 输 出 : 读取的数据
    6. *******************************************************************************/
    7. u8 ds1302_read_byte(u8 addr)
    8. {
    9. u8 i=0;
    10. u8 temp=0;
    11. u8 value=0;
    12. DS1302_RST=0;
    13. _nop_();
    14. DS1302_CLK=0;//CLK低电平
    15. _nop_();
    16. DS1302_RST=1;//RST由低到高变化
    17. _nop_();
    18. for(i=0;i<8;i++)//循环8次,每次写1位,先写低位再写高位
    19. {
    20. DS1302_IO=addr&0x01;
    21. addr>>=1;
    22. DS1302_CLK=1;
    23. _nop_();
    24. DS1302_CLK=0;//CLK由低到高产生一个上升沿,从而写入数据
    25. _nop_();
    26. }
    27. for(i=0;i<8;i++)//循环8次,每次读1位,先读低位再读高位
    28. {
    29. temp=DS1302_IO;
    30. value=(temp<<7)|(value>>1);//先将value右移1位,然后temp左移7位,最后或运算
    31. DS1302_CLK=1;
    32. _nop_();
    33. DS1302_CLK=0;
    34. _nop_();
    35. }
    36. DS1302_RST=0;//RST拉低
    37. _nop_();
    38. DS1302_CLK=1;//对于实物中,P3.4口没有外接上拉电阻的,此处代码需要添加,使数据口有一个上升沿脉冲。
    39. _nop_();
    40. DS1302_IO = 0;
    41. _nop_();
    42. DS1302_IO = 1;
    43. _nop_();
    44. return value;
    45. }

    3.时序之上的东西

    1.大小端

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

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

    2.如何读写寄存器

    void ds1302_write_byte(u8 addr,u8 dat)

    addr:寄存器的地址

    dat:寄存器数据

    4.SPI时序特征

    1.低位在前

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

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

    下降沿:CLK=1;CLK=0

    3.注意SCLK工作频率

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

    4.编程实践

    1.编写ds1302_write_reg

    DS1302是在上升沿读取【表示我们如果要写入数据,则应该在上升沿写入】

    1)先放入数据

    2)SCLK=1

    3)SCLK=0

    1. //****************************************************
    2. //向ds1302的内部寄存器addr写入一个值value
    3. /**
    4. addr:内部寄存器的地址
    5. value:内部寄存器的值
    6. */
    7. void ds1302_write_reg(unsigned char addr,unsigned char value){
    8. unsigned char i=0;
    9. unsigned char dat;
    10. //【第一步】起始部分 SCLK和RST为低电平,IO无所谓
    11. SCLK=0;
    12. delay();
    13. RST=0;
    14. delay();
    15. //此时开始工作
    16. RST=1; //SCLK为低时,RST由低变高,意味着一个大的周期的开始
    17. delay();
    18. //【第二步】写入第一个字节,addr
    19. for(i=0;i<8;i++){
    20. dat=addr&0x01; //SPI是从低位开始传输,此时取出最低位
    21. addr=addr>>1; //把addr右移一位,将原来的数值移回去
    22. delay();
    23. DSIO=dat; //表示将取出的二进制字符输入到IO口,把要发送的Bit数据丢到IO引脚上去准备
    24. //一个循环写入一个字节
    25. SCLK=1; //意味着有一个上升沿
    26. delay();
    27. SCLK=0; //读走之后,一个小周期就结束,把SCLK拉低,是为了下一个小周期做准备
    28. }
    29. //【第三步】写入第二个字节,value
    30. for(i=0;i<8;i++){
    31. dat=value&0x01; //SPI是从低位开始传输,此时取出最低位
    32. value=value>>1; //把addr右移一位,将原来的数值移回去
    33. DSIO=dat; //表示将取出的二进制字符输入到IO口,把要发送的Bit数据丢到IO引脚上去准备
    34. delay();
    35. //一个循环写入一个字节
    36. SCLK=1; //意味着有一个上升沿
    37. delay();
    38. SCLK=0; //读走之后,一个小周期就结束,把SCLK拉低,是为了下一个小周期做准备
    39. }
    40. //【第四步】时序结束,IO无所谓
    41. SCLK=0; //SCLK拉低是为了后面的周期时初始状态是正确的
    42. delay();
    43. RST=0;// 表示一个大周期的结束
    44. delay();
    45. }

    2.编写ds1302_read_reg

    下降沿写入【表示如果我们要读取数据,则应该在下降沿读取】

    1)SCLK=1;

    2)SCLC=0;

    3)在将数据取出

    1. //****************************************************
    2. //向ds1302的内部寄存器addr读入一个值,作为返回值
    3. /**
    4. addr:内部寄存器的地址
    5. value:内部寄存器的值
    6. */
    7. unsigned char ds1302_read_reg(unsigned char addr)
    8. {
    9. unsigned char i = 0;
    10. unsigned char dat = 0; // 用来存储读取到的一字节数据的
    11. unsigned char tmp = 0;
    12. // 第1部分: 时序起始
    13. SCLK = 0;
    14. delay();
    15. RST = 0;
    16. delay();
    17. //表示此时进入读取状态
    18. RST = 1; // SCLK为低时,RST由低变高,意味着一个大的周期的开始
    19. delay();
    20. // 第2部分: 写入要读取的寄存器地址,addr
    21. for (i=0; i<8; i++)
    22. {
    23. dat = addr & 0x01; // SPI是从低位开始传输的
    24. DSIO = dat; // 把要发送的bit数据丢到IO引脚上去准备好
    25. SCLK = 1; // 制造上升沿,让DS1302把IO上的值读走
    26. delay(); // 读走之后,一个小周期就完了
    27. SCLK = 0; // 把SCLK拉低,是为了给下一个小周期做准备
    28. delay();
    29. addr >>= 1; // 把addr右移一位
    30. }
    31. // 第3部分: 读出一字节DS1302返回给我们的值【SPI下降沿才可以进行读取】
    32. dat = 0;
    33. for (i=0; i<8; i++)
    34. {
    35. // 在前面向ds1302写入addr的最后一个bit后,ds1302就会将读取到的寄存器值
    36. // 的第一个bit放入到IO引脚上,所以我们应该先读取IO再制造下降沿然后继续
    37. // 读取下一个bit
    38. tmp = DSIO; //DSIO一次传输一个bit
    39. dat |= (tmp << i); // 读出来的数值是低位在前的
    40. SCLK = 1; // 由于上面SCLK是低,所以要先拉到高
    41. delay();
    42. SCLK = 0; // 拉低SCLK制造一个下降沿
    43. delay();
    44. }
    45. // 第4部分: 时序结束
    46. SCLK = 0; // SCLK拉低为了后面的周期时初始状态是对的
    47. delay();
    48. RST = 0; // RST拉低意味着一个大周期的结束
    49. delay();
    50. // 第5部分:解决读取时间是ff的问题
    51. DSIO = 0;
    52. return dat;
    53. }

    3.读取时间

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

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

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

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

    1. //**************************************
    2. //定义SPI的三个引脚
    3. sbit DSIO=P3^4;
    4. sbit RST=P3^5;
    5. sbit SCLK=P3^6;
    6. //加入delay,是为了防止速度过快,单片机感受不到
    7. void delay(){
    8. unsigned char i;
    9. for(i=0;i<3;i++){
    10. }
    11. }
    12. //********************************************************
    13. //因为51单片机的设计本身RAM比较少而Flash比较多,像这里定义的数组内部
    14. //的内容是不会变的(常量数组),我们就可以使用code关键字,让编译器帮我们
    15. //把这个数组放在flash中而不是RAM,这样做可以省一些RAM
    16. //判断要读取时分秒年月日星期几
    17. unsigned char code READ_RTC_ADDR[7]={0x81,0x83,0x85,0x87,0x89,0x8b,0x8d};
    18. //存储时间
    19. unsigned char time[7];// 用来存储读取出来的时间,格式是:秒分时日月周年
    20. //****************************************************
    21. //向ds1302的内部寄存器addr写入一个值value
    22. /**
    23. addr:内部寄存器的地址
    24. value:内部寄存器的值
    25. */
    26. void ds1302_write_reg(unsigned char addr,unsigned char value){
    27. unsigned char i=0;
    28. unsigned char dat;
    29. //【第一步】起始部分 SCLK和RST为低电平,IO无所谓
    30. SCLK=0;
    31. delay();
    32. RST=0;
    33. delay();
    34. RST=1; //SCLK为低时,RST由低变高,意味着一个大的周期的开始
    35. delay();
    36. //【第二步】写入第一个字节,addr
    37. for(i=0;i<8;i++){
    38. dat=addr&0x01; //SPI是从低位开始传输,此时取出最低位
    39. addr=addr>>1; //把addr右移一位,将原来的数值移回去
    40. delay();
    41. DSIO=dat; //表示将取出的二进制字符输入到IO口,把要发送的Bit数据丢到IO引脚上去准备
    42. //一个循环写入一个字节
    43. SCLK=1; //意味着有一个上升沿
    44. delay();
    45. SCLK=0; //读走之后,一个小周期就结束,把SCLK拉低,是为了下一个小周期做准备
    46. delay();
    47. }
    48. //【第三步】写入第二个字节,value
    49. for(i=0;i<8;i++){
    50. dat=value&0x01; //SPI是从低位开始传输,此时取出最低位
    51. value=value>>1; //把addr右移一位,将原来的数值移回去
    52. DSIO=dat; //表示将取出的二进制字符输入到IO口,把要发送的Bit数据丢到IO引脚上去准备
    53. delay();
    54. //一个循环写入一个字节
    55. SCLK=1; //意味着有一个上升沿
    56. delay();
    57. SCLK=0; //读走之后,一个小周期就结束,把SCLK拉低,是为了下一个小周期做准备
    58. }
    59. //【第四步】时序结束,IO无所谓
    60. SCLK=0; //SCLK拉低是为了后面的周期时初始状态是正确的
    61. delay();
    62. RST=0;// 表示一个大周期的结束
    63. delay();
    64. }
    65. //****************************************************
    66. //向ds1302的内部寄存器addr读入一个值,作为返回值
    67. /**
    68. addr:内部寄存器的地址
    69. value:内部寄存器的值
    70. */
    71. unsigned char ds1302_read_reg(unsigned char addr)
    72. {
    73. unsigned char i = 0;
    74. unsigned char dat = 0; // 用来存储读取到的一字节数据的
    75. unsigned char tmp = 0; //因为51单片机不允许DSIO进行移位,所以使用中间变量
    76. // 第1部分: 时序起始
    77. SCLK = 0;
    78. delay();
    79. RST = 0;
    80. delay();
    81. RST = 1; // SCLK为低时,RST由低变高,意味着一个大的周期的开始
    82. delay();
    83. // 第2部分: 写入要读取的寄存器地址,addr
    84. for (i=0; i<8; i++)
    85. {
    86. dat = addr & 0x01; // SPI是从低位开始传输的
    87. DSIO = dat; // 把要发送的bit数据丢到IO引脚上去准备好
    88. SCLK = 1; // 制造上升沿,让DS1302把IO上的值读走
    89. delay(); // 读走之后,一个小周期就完了
    90. SCLK = 0; // 把SCLK拉低,是为了给下一个小周期做准备
    91. delay();
    92. addr >>= 1; // 把addr右移一位
    93. }
    94. // 第3部分: 读出一字节DS1302返回给我们的值【SPI下降沿才可以进行读取】
    95. dat = 0;
    96. for (i=0; i<8; i++)
    97. {
    98. // 在前面向ds1302写入addr的最后一个bit后,ds1302就会将读取到的寄存器值
    99. // 的第一个bit放入到IO引脚上,所以我们应该先读取IO再制造下降沿然后继续
    100. // 读取下一个bit
    101. tmp = DSIO;
    102. dat |= (tmp << i); // 读出来的数值是低位在前的
    103. SCLK = 1; // 由于上面SCLK是低,所以要先拉到高
    104. delay();
    105. SCLK = 0; // 拉低SCLK制造一个下降沿
    106. delay();
    107. }
    108. // 第4部分: 时序结束
    109. SCLK = 0; // SCLK拉低为了后面的周期时初始状态是对的
    110. delay();
    111. RST = 0; // RST拉低意味着一个大周期的结束
    112. delay();
    113. // 第5部分:解决读取时间是ff的问题
    114. DSIO = 0;
    115. return dat;
    116. }
    117. //******************************************************
    118. //读取时间
    119. void ds1302_read_time(void){
    120. unsigned char i=0;
    121. for(i=0;i<7;i++){
    122. time[i]=ds1302_read_reg(READ_RTC_ADDR[i]);
    123. }
    124. }
    125. void main(){
    126. ds1302_read_time();
    127. }

     5.使用串口进行调试

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

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

    3.测试串口输出效果

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

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

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

    1. //*************************************************************
    2. //通过串口将7个时间以二进制的方式输出到串口助手上
    3. void debug_print_time(void)
    4. {
    5. unsigned char i=0;
    6. while(1){
    7. //1.从ds1302读取时间
    8. ds1302_read_time();
    9. //2.for循环内打印一组7个时间
    10. for(i=0;i<7;i++){
    11. uart_send_byte(i);
    12. }
    13. //3.延时900ms后在继续下一个周期
    14. Delay900000us();
    15. }
    16. }
    17. //串口发送函数,发送一个字节【单个字节】
    18. void uart_send_byte(unsigned char c){
    19. //【第一步】发送一个字节
    20. SBUF=c;
    21. //【第二步】先确认串口发送部分没有在忙
    22. while(!TI);//TI=0,表示在忙
    23. //【第三步】软件复位TI标志位---数据手册要求的
    24. TI=0;
    25. }
    26. void Delay900000us() //@12.000MHz
    27. {
    28. unsigned char i, j, k;
    29. _nop_();
    30. _nop_();
    31. i = 42;
    32. j = 10;
    33. k = 168;
    34. do
    35. {
    36. do
    37. {
    38. while (--k);
    39. } while (--j);
    40. } while (--i);
    41. }
    42. //****************************************************
    43. //向ds1302的内部寄存器addr读入一个值,作为返回值
    44. /**
    45. addr:内部寄存器的地址
    46. value:内部寄存器的值
    47. */
    48. unsigned char ds1302_read_reg(unsigned char addr)
    49. {
    50. unsigned char i = 0;
    51. unsigned char dat = 0; // 用来存储读取到的一字节数据的
    52. unsigned char tmp = 0;
    53. // 第1部分: 时序起始
    54. SCLK = 0;
    55. delay();
    56. RST = 0;
    57. delay();
    58. RST = 1; // SCLK为低时,RST由低变高,意味着一个大的周期的开始
    59. delay();
    60. // 第2部分: 写入要读取的寄存器地址,addr
    61. for (i=0; i<8; i++)
    62. {
    63. dat = addr & 0x01; // SPI是从低位开始传输的
    64. DSIO = dat; // 把要发送的bit数据丢到IO引脚上去准备好
    65. SCLK = 1; // 制造上升沿,让DS1302把IO上的值读走
    66. delay(); // 读走之后,一个小周期就完了
    67. SCLK = 0; // 把SCLK拉低,是为了给下一个小周期做准备
    68. delay();
    69. addr >>= 1; // 把addr右移一位
    70. }
    71. // 第3部分: 读出一字节DS1302返回给我们的值【SPI下降沿才可以进行读取】
    72. dat = 0;
    73. for (i=0; i<8; i++)
    74. {
    75. // 在前面向ds1302写入addr的最后一个bit后,ds1302就会将读取到的寄存器值
    76. // 的第一个bit放入到IO引脚上,所以我们应该先读取IO再制造下降沿然后继续
    77. // 读取下一个bit
    78. tmp = DSIO;
    79. dat |= (tmp << i); // 读出来的数值是低位在前的
    80. SCLK = 1; // 由于上面SCLK是低,所以要先拉到高
    81. delay();
    82. SCLK = 0; // 拉低SCLK制造一个下降沿
    83. delay();
    84. }
    85. // 第4部分: 时序结束
    86. SCLK = 0; // SCLK拉低为了后面的周期时初始状态是对的
    87. delay();
    88. RST = 0; // RST拉低意味着一个大周期的结束
    89. delay();
    90. // 第5部分:解决读取时间是ff的问题
    91. DSIO = 0;
    92. return dat;
    93. }

    2.问题解决

    状况:

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

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

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

    总结规律:

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

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

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

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

    1. //****************************************************
    2. //向ds1302的内部寄存器addr读入一个值,作为返回值
    3. /**
    4. addr:内部寄存器的地址
    5. value:内部寄存器的值
    6. */
    7. unsigned char ds1302_read_reg(unsigned char addr)
    8. {
    9. unsigned char i = 0;
    10. unsigned char dat = 0; // 用来存储读取到的一字节数据的
    11. unsigned char tmp = 0;
    12. // 第1部分: 时序起始
    13. SCLK = 0;
    14. delay();
    15. RST = 0;
    16. delay();
    17. RST = 1; // SCLK为低时,RST由低变高,意味着一个大的周期的开始
    18. delay();
    19. // 第2部分: 写入要读取的寄存器地址,addr
    20. for (i=0; i<8; i++)
    21. {
    22. dat = addr & 0x01; // SPI是从低位开始传输的
    23. DSIO = dat; // 把要发送的bit数据丢到IO引脚上去准备好
    24. SCLK = 1; // 制造上升沿,让DS1302把IO上的值读走
    25. delay(); // 读走之后,一个小周期就完了
    26. SCLK = 0; // 把SCLK拉低,是为了给下一个小周期做准备
    27. delay();
    28. addr >>= 1; // 把addr右移一位
    29. }
    30. // 第3部分: 读出一字节DS1302返回给我们的值【SPI下降沿才可以进行读取】
    31. dat = 0;
    32. for (i=0; i<8; i++)
    33. {
    34. // 在前面向ds1302写入addr的最后一个bit后,ds1302就会将读取到的寄存器值
    35. // 的第一个bit放入到IO引脚上,所以我们应该先读取IO再制造下降沿然后继续
    36. // 读取下一个bit
    37. tmp = DSIO;
    38. dat |= (tmp << i); // 读出来的数值是低位在前的
    39. SCLK = 1; // 由于上面SCLK是低,所以要先拉到高
    40. delay();
    41. SCLK = 0; // 拉低SCLK制造一个下降沿
    42. delay();
    43. }
    44. // 第4部分: 时序结束
    45. SCLK = 0; // SCLK拉低为了后面的周期时初始状态是对的
    46. delay();
    47. RST = 0; // RST拉低意味着一个大周期的结束
    48. delay();
    49. // 第5部分:解决读取时间是ff的问题
    50. DSIO = 0;
    51. return dat;
    52. }

    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.读时间函数

    1. //********************************************************
    2. //因为51单片机的设计本身RAM比较少而Flash比较多,像这里定义的数组内部
    3. //的内容是不会变的(常量数组),我们就可以使用code关键字,让编译器帮我们
    4. //把这个数组放在flash中而不是RAM,这样做可以省一些RAM
    5. //判断要读取时分秒年月日星期几
    6. unsigned char code READ_RTC_ADDR[7]={0x81,0x83,0x85,0x87,0x89,0x8b,0x8d};
    7. //****************************************************
    8. //向ds1302的内部寄存器addr读入一个值,作为返回值
    9. /**
    10. addr:内部寄存器的地址
    11. value:内部寄存器的值
    12. */
    13. unsigned char ds1302_read_reg(unsigned char addr)
    14. {
    15. unsigned char i = 0;
    16. unsigned char dat = 0; // 用来存储读取到的一字节数据的
    17. unsigned char tmp = 0;
    18. // 第1部分: 时序起始
    19. SCLK = 0;
    20. delay();
    21. RST = 0;
    22. delay();
    23. RST = 1; // SCLK为低时,RST由低变高,意味着一个大的周期的开始
    24. delay();
    25. // 第2部分: 写入要读取的寄存器地址,addr
    26. for (i=0; i<8; i++)
    27. {
    28. dat = addr & 0x01; // SPI是从低位开始传输的
    29. DSIO = dat; // 把要发送的bit数据丢到IO引脚上去准备好
    30. SCLK = 1; // 制造上升沿,让DS1302把IO上的值读走
    31. delay(); // 读走之后,一个小周期就完了
    32. SCLK = 0; // 把SCLK拉低,是为了给下一个小周期做准备
    33. delay();
    34. addr >>= 1; // 把addr右移一位
    35. }
    36. // 第3部分: 读出一字节DS1302返回给我们的值【SPI下降沿才可以进行读取】
    37. dat = 0;
    38. for (i=0; i<8; i++)
    39. {
    40. // 在前面向ds1302写入addr的最后一个bit后,ds1302就会将读取到的寄存器值
    41. // 的第一个bit放入到IO引脚上,所以我们应该先读取IO再制造下降沿然后继续
    42. // 读取下一个bit
    43. tmp = DSIO;
    44. dat |= (tmp << i); // 读出来的数值是低位在前的
    45. SCLK = 1; // 由于上面SCLK是低,所以要先拉到高
    46. delay();
    47. SCLK = 0; // 拉低SCLK制造一个下降沿
    48. delay();
    49. }
    50. // 第4部分: 时序结束
    51. SCLK = 0; // SCLK拉低为了后面的周期时初始状态是对的
    52. delay();
    53. RST = 0; // RST拉低意味着一个大周期的结束
    54. delay();
    55. // 第5部分:解决读取时间是ff的问题
    56. DSIO = 0;
    57. return dat;
    58. }
    59. //存储时间
    60. unsigned char time[7];// 用来存储读取出来的时间,格式是:秒分时日月周年
    61. //******************************************************
    62. //读取时间
    63. void ds1302_read_time(void){
    64. unsigned char i=0;
    65. for(i=0;i<7;i++){
    66. time[i]=ds1302_read_reg(READ_RTC_ADDR[i]);
    67. }
    68. }

    2.写时间函数

    1.数组的设置

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

    2.“写保护”设置

        ds1302_write_reg(0x8E,0x00);  //去掉写保护

        ds1302_write_reg(0x8E,0x80);//打开写保护

    1. ds1302_write_reg(0x8E0x00); //去掉写保护
    2. for(i=0;i<7;i++){
    3. ds1302_write_reg(WRITE_RTC_ADDR[i],time[i]);
    4. }
    5. ds1302_write_reg(0x8E,0x80);//打开写保护

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

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

    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

    1. #ifndef __UART_H__
    2. #define __UART_H__
    3. #include
    4. void uart_init(void);
    5. void uart_send_byte(unsigned char c);
    6. #endif

    ds1302.h

    1. #ifndef __DS1302_H__
    2. #define __DS1302_H__
    3. void delay(void);
    4. //void delay1s(void);
    5. void delay900ms(void);
    6. void ds1302_write_reg(unsigned char addr, unsigned char value);
    7. unsigned char ds1302_read_reg(unsigned char addr);
    8. void ds1302_read_time(void);
    9. void ds1302_write_time(void);
    10. void debug_print_time(void);
    11. #endif

    main.c

    1. #include "uart.h"
    2. #include "ds1302.h"
    3. void main(void)
    4. {
    5. // unsigned char i = 0;
    6. uart_init();
    7. ds1302_write_time();
    8. /*
    9. // 测试串口工作
    10. for (i=0; i<255; i++)
    11. {
    12. uart_send_byte(i);
    13. delay1s();
    14. }
    15. while (1);
    16. */
    17. debug_print_time();
    18. }

    ds1302.c

    1. #include
    2. #include
    3. #include "uart.h"
    4. #include "ds1302.h"
    5. /************** 全局变量定义 *************************************/
    6. // 定义SPI的三根引脚
    7. sbit DSIO = P3^4;
    8. sbit RST = P3^5;
    9. sbit SCLK = P3^6;
    10. // 因为51单片机的设计本身RAM比较少而Flash稍微多一些,像这里定义的数组内部
    11. // 的内容是不会变的(常量数组),我们就可以使用code关键字,让编译器帮我们
    12. // 把这个数组放在flash中而不是ram中,这样做可以省一些ram。
    13. unsigned char code READ_RTC_ADDR[7] = {0x81, 0x83, 0x85, 0x87, 0x89, 0x8b, 0x8d};
    14. unsigned char code WRITE_RTC_ADDR[7] = {0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c};
    15. unsigned char time[7]; // 用来存储读取的时间的,格式是:秒分时日月周年
    16. // 有用函数
    17. void delay(void)
    18. {
    19. unsigned char i;
    20. for (i=0; i<3; i++);
    21. }
    22. /*
    23. void delay1s(void) //误差 0us
    24. {
    25. unsigned char a,c;
    26. for(c=167;c>0;c--)
    27. for(a=16;a>0;a--);
    28. _nop_(); //if Keil,require use intrins.h
    29. }
    30. */
    31. void delay900ms(void) //误差 -0.000000000205us
    32. {
    33. unsigned char a,b,c;
    34. for(c=127;c>0;c--)
    35. for(b=128;b>0;b--)
    36. for(a=24;a>0;a--);
    37. }
    38. // 向ds1302的内部寄存器addr写入一个值value
    39. void ds1302_write_reg(unsigned char addr, unsigned char value)
    40. {
    41. unsigned char i = 0;
    42. unsigned char dat = 0;
    43. // 第1部分: 时序起始
    44. SCLK = 0;
    45. delay();
    46. RST = 0;
    47. delay();
    48. RST = 1; // SCLK为低时,RST由低变高,意味着一个大的周期的开始
    49. delay();
    50. // 第2部分: 写入第1字节,addr
    51. for (i=0; i<8; i++)
    52. {
    53. dat = addr & 0x01; // SPI是从低位开始传输的
    54. DSIO = dat; // 把要发送的bit数据丢到IO引脚上去准备好
    55. SCLK = 1; // 制造上升沿,让DS1302把IO上的值读走
    56. delay(); // 读走之后,一个小周期就完了
    57. SCLK = 0; // 把SCLK拉低,是为了给下一个小周期做准备
    58. delay();
    59. addr >>= 1; // 把addr右移一位
    60. }
    61. // 第3部分: 写入第2字节,value
    62. for (i=0; i<8; i++)
    63. {
    64. dat = value & 0x01; // SPI是从低位开始传输的
    65. DSIO = dat; // 把要发送的bit数据丢到IO引脚上去准备好
    66. SCLK = 1; // 制造上升沿,让DS1302把IO上的值读走
    67. delay(); // 读走之后,一个小周期就完了
    68. SCLK = 0; // 把SCLK拉低,是为了给下一个小周期做准备
    69. delay();
    70. value = value >> 1; // 把addr右移一位
    71. }
    72. // 第4部分: 时序结束
    73. SCLK = 0; // SCLK拉低为了后面的周期时初始状态是对的
    74. delay();
    75. RST = 0; // RST拉低意味着一个大周期的结束
    76. delay();
    77. }
    78. // 从ds1302的内部寄存器addr读出一个值,作为返回值
    79. unsigned char ds1302_read_reg(unsigned char addr)
    80. {
    81. unsigned char i = 0;
    82. unsigned char dat = 0; // 用来存储读取到的一字节数据的
    83. unsigned char tmp = 0;
    84. // 第1部分: 时序起始
    85. SCLK = 0;
    86. delay();
    87. RST = 0;
    88. delay();
    89. RST = 1; // SCLK为低时,RST由低变高,意味着一个大的周期的开始
    90. delay();
    91. // 第2部分: 写入要读取的寄存器地址,addr
    92. for (i=0; i<8; i++)
    93. {
    94. dat = addr & 0x01; // SPI是从低位开始传输的
    95. DSIO = dat; // 把要发送的bit数据丢到IO引脚上去准备好
    96. SCLK = 1; // 制造上升沿,让DS1302把IO上的值读走
    97. delay(); // 读走之后,一个小周期就完了
    98. SCLK = 0; // 把SCLK拉低,是为了给下一个小周期做准备
    99. delay();
    100. addr >>= 1; // 把addr右移一位
    101. }
    102. // 第3部分: 读出一字节DS1302返回给我们的值
    103. dat = 0;
    104. for (i=0; i<8; i++)
    105. {
    106. // 在前面向ds1302写入addr的最后一个bit后,ds1302就会将读取到的寄存器值
    107. // 的第一个bit放入到IO引脚上,所以我们应该先读取IO再制造下降沿然后继续
    108. // 读取下一个bit
    109. tmp = DSIO;
    110. dat |= (tmp << i); // 读出来的数值是低位在前的
    111. SCLK = 1; // 由于上面SCLK是低,所以要先拉到高
    112. delay();
    113. SCLK = 0; // 拉低SCLK制造一个下降沿
    114. delay();
    115. }
    116. // 第4部分: 时序结束
    117. SCLK = 0; // SCLK拉低为了后面的周期时初始状态是对的
    118. delay();
    119. RST = 0; // RST拉低意味着一个大周期的结束
    120. delay();
    121. // 第5部分:解决读取时间是ff的问题
    122. DSIO = 0;
    123. return dat;
    124. }
    125. void ds1302_read_time(void)
    126. {
    127. unsigned char i = 0;
    128. for (i=0; i<7; i++)
    129. {
    130. time[i] = ds1302_read_reg(READ_RTC_ADDR[i]);
    131. }
    132. }
    133. void ds1302_write_time(void)
    134. {
    135. unsigned char i = 0;
    136. // 准备好要写入的时间
    137. time[0] = 0x24; // 对应 24s
    138. time[1] = 0x39; // 对应 39m
    139. time[2] = 0x11; // 对应 11h
    140. time[3] = 0x06; // 对应 6日
    141. time[4] = 0x12; // 对应 12月
    142. time[5] = 0x02; // 对应 星期2
    143. time[6] = 0x16; // 对应 2016年
    144. ds1302_write_reg(0x8E, 0x00); // 去掉写保护
    145. for (i=0; i<7; i++)
    146. {
    147. ds1302_write_reg(WRITE_RTC_ADDR[i], time[i]);
    148. }
    149. ds1302_write_reg(0x8E, 0x80); // 打开写保护
    150. }
    151. // 通过串口将7个时间以二进制方式输出在串口助手上
    152. void debug_print_time(void)
    153. {
    154. unsigned char i = 0;
    155. while (1)
    156. {
    157. // 1 从DS1302读取时间
    158. ds1302_read_time();
    159. // 2 for循环内打印一组7个时间
    160. for (i=0; i<7; i++)
    161. {
    162. uart_send_byte(time[i]);
    163. }
    164. // 3 延时900ms后再继续下个周期
    165. delay900ms();
    166. }
    167. }

    uart.c

    1. #include "uart.h"
    2. // 串口设置为: 波特率9600、数据位8、停止位1、奇偶校验无
    3. // 使用的晶振是11.0592MHz的,注意12MHz和24MHz的不行
    4. void uart_init(void)
    5. {
    6. // 波特率9600
    7. SCON = 0x50; // 串口工作在模式1(8位串口)、允许接收
    8. PCON = 0x00; // 波特率不加倍
    9. // 通信波特率相关的设置
    10. TMOD = 0x20; // 设置T1为模式2
    11. TH1 = 253;
    12. TL1 = 253; // 8位自动重装,意思就是TH1用完了之后下一个周期TL1会
    13. // 自动重装到TH1去
    14. TR1 = 1; // 开启T1让它开始工作
    15. ES = 1;
    16. EA = 1;
    17. }
    18. // 通过串口发送1个字节出去
    19. void uart_send_byte(unsigned char c)
    20. {
    21. // 第1步,发送一个字节
    22. SBUF = c;
    23. // 第2步,先确认串口发送部分没有在忙
    24. while (!TI);
    25. // 第3步,软件复位TI标志位
    26. TI = 0;
    27. }
  • 相关阅读:
    代码随想录刷题Day55 | 392. 判断子序列 | 115. 不同的子序列
    (1)(1.16) Maxbotix I2C声纳
    MTK平台拍照录像如何实现Mirror效果
    Java项目:ssm+mysql+maven养老院管理系统
    day36-xml
    大数据存储与处理
    全能型开源数据库监控平台 - lepus
    2023年8月知识复习
    LTC4056/TP4056国产替代DP4056锂电池充电保护芯片
    快速熟悉C++之常用语法
  • 原文地址:https://blog.csdn.net/m0_63077733/article/details/133437970