从零开始的单片机学习(十四)

十四、实时时钟DS1302
    1、RTC有关的背景知识
        a、什么是实时时钟(RTC for real time clock)
            (1)时间点和时间段的概念区分
            (2)单片机为什么需要时间点
            (3)RTC如何存在于系统中(单片机内部集成或者单片机外部扩展)

        b、DS1302介绍
            (1)数据手册
            (2)SPI数字接口访问
            (3)内部存在着一个时间点信息(年月日时分秒星期),可以读写,上电自动走表

        c、RTC学习的关键点
            (1)SPI接口及其相关概念:3线or4线、同步、主从、串行等
            (2)时序图的理解和编程实现

    2、原理图和接线
        a、原理图分析
            (1)DS1302引脚介绍
            (2)原理图中跳线设计对接线的影响

        b、跳线
            (1)正确的跳线要点
                JP595断开,是为了让P3.4控制DS1302的时候,不影响74HC595的工作
                JP1302接上,是为了让P3.4控制到DS1302
                J11断开是为了让P3.5在控制DS1302的时候,不影响NE555模块工作
            (2)详解接线设置的原理和必要性
                正常的产品都不会这么设计,正常产品接线一般都是确定的不会复用。
                开发板为了学习,会放置很多模块,因此GPIO就不够用了,这个时候就需要复用的设计了。一个引脚
             接多个模块就会相互影响(分两种情况:一个是A模块工作的时候B模块莫名其妙的工作,二是有的时候B
             模块会影响到A模块正常工作)。对于引脚复用的情况,接线的关键是确认目标模块接线正确时还不会影
             响到其他模块的工作。

    3、数据手册带读

    4、时序图的读法
        a、时序图关键点
            (1)横轴代表时间,纵轴是同一时间点各个通信线的状态
            (2)静态与动态两个角度去看
            (3)注意SCLK的边沿处

        b、结合示例代码来理解时序

        c、时序之上的东西
            (1)大小端:一个字节发出去,先发高位还是低位。
            (2)如何读写寄存器

        d、总结SPI的时序特征
            (1)地位在前
            (2)DS1302在上升沿读取,下降沿写入
            (3)注意SCLK的工作频率:51单片机速度快,DS1302速度慢,因此,在读取和写入数据的时候要加上
                一定的延时,让二者的速度相匹配。

    5、参考时序图编程
        a、建立工程

        b、编写ds1302_write_reg函数
            (1)引脚定义
                //定义SPI的三个引脚
                sbit DSIO     = P3^4;
                sbit CE       = P3^5;
                sbit SCLK     = P3^6;
            (2)delay
                延时使用了intrins头文件下声明的_nop_()函数。
                #include <INTRINS.H>
                void main(void)
                {
                    _nop_();
                }    
            (3)根据时序写代码
                #include <REGX51.H>
                #include <INTRINS.H>
                //定义SPI的三个引脚
                sbit DSIO     = P3^4;
                sbit CE       = P3^5;
                sbit SCLK     = P3^6;

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

                #include <REGX51.H>
                #include <INTRINS.H>
                //定义SPI的三个引脚
                sbit DSIO     = P3^4;
                sbit CE       = P3^5;
                sbit SCLK     = P3^6;

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

                    return date;
                }

    6、编程实践
        a、比对官方例程和自己写的代码
            在读取数据给一个返回值中应当注意在前面向DS1302写入addr的最后一个bit后,ds1302就会将读取到的寄存器值得第一个bit放入IO引脚上
         所以我们应该先读取IO再制造下降沿然后继续读取下一个bit。

        b、读取时间
            (1)DS1302的时间寄存器地址
                读秒寄存器地址:0x81h; 写入秒寄存器地址:0x80h;范围:00-59;
                读分寄存器地址:0x83h; 写入分寄存器地址:0x82h;范围:00-59;
                读时寄存器地址:0x85h; 写入时寄存器地址:0x84h;范围:1-12/0-23;
                PS:小时寄存器的BIT7决定是12小时制还是24小时制;
                读日寄存器地址:0x87h; 写入日寄存器地址:0x86h;范围:1-31;
                读月寄存器地址:0x89h; 写入月寄存器地址:0x88h;范围:1-12;
                读周寄存器地址:0x8Bh; 写入周寄存器地址:0x8Ah;范围:1-7;
                读年寄存器地址:0x8Dh; 写入年寄存器地址:0x8Ch。范围:00-99;
            (2)移植串口输出代码,将读取的时间通过串口输出显示

    7、添加串口调试
        a、移植串口代码
            (1)注意波特率设置和晶振设置
            (2)注意串口相关的接线设置
            (3)测试串口输出效果
            (4)注意二进制显示和文本方式显示
            (5)注意串口助手打开时烧录软件是不能使用的

        b、串口输出时间信息
            (1)写代码
            (2)问题解决
            (3)状况:
                1、代码确实得到了一些时间数据
                2、秒确实在变化,而且变化的规律似乎是正确的。
                3、时间数据总有一些FF是不合理的,不应该出现的
                总结规律:FF总是出现在前一个周期是偶数的时候,前一个如果是奇数那么久不会出现。
                解决方法:
                        (1)硬件在IO线上设置K的电阻做弱上拉处理。
                        (2)如果没有做弱上拉,也有办法解决。在代码的读取寄存器时序之后,加一个将IO置位
                     低电平的代码进去,就可以了。

    8、DS1302d的时间格式详解
        a、BCD码
            (1)什么是BCD码
                BCD码是一种数字编码,这种计数编码有个特点:很像16进制和10进制的结合。看起来很像十进制,
             譬如29下来是30而不是2A。因此BCD码中只有0-9而没有ABCDEF等字母,但是BCD吗实际上是用十六进
             制来表示的(BCD码的21在计算机中就是0x21)。
                 因此,综合来看BCD码就是看起来很像十进制数的十六进制数。意思就是:BCD码本质上是十六进制
             数,但是因为没有ABCDEF,所以看起来很像十进制数。
            (2)BCD码的意义
                人天生对十进制敏感,而计算机处理十六进制效率高,但是人对于十六进制不敏感。因此就诞生了
             BCD码,使得人对于BCD码有了一定的敏感,计算机处理效率也高。
            (3)区别BCD码、16进制、10进制三种数
                在(1)中有详解
            (4)BCD码转16进制、16进制转BCD码
                BCD转16:(BCD >> 4)*10+(BCD & 0x0F)
                16转BCD:((HEX/10) << 0x04)+(HEX%10)

        b、年份从2000年开始 
            DS1302的年寄存器的范围是00-99,其真实含义是2000-2099年。因此DS1302的年份是从2000年开
         始计数的。
             譬如读出的数是22,那么对应0x22,其实就是代表数字2,22+2000就是当前年份,所以就是2022年。

    9、向DS1302写入时间
        a、写时间函数
            (1)写保护:在年寄存器之后还有一个写保护寄存器,当这个寄存器最高位为1的时候,只能读取DS1302
         中的时间,而不能写入数据。当这个寄存器全为0的时候才可以写入数据。这个机制是为了保护系统的时间不
         被随意更改。
            (2)写入地址和读出地址的不同
                读取各个时间的寄存器地址:
                            READ_RTC_ADDR[7] = {0x81, 0x83, 0x85, 0x87, 0x89, 0x8b, 0x8d}; 
                写入各个时间的寄存器地址:
                            WRITE_RTC_ADDR[7] = {0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c};
            (3)注意写入的时序
                读取的时候在上升沿,而写入则是在下降沿写入。

        b、先写入时间然后进行读取验证
            代码:
                    #include <REGX51.H>
                    #include "ds1302_read_reg.H"
                    #include "ds1302_write_reg.H"
                    #include "uart_init.h"
                    #include "uart_send.h"
                    #include "delay.h"

                    //因为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] = {0x50, 0x33, 0x16, 0x08, 0x09, 0x04, 0x22};
                    unsigned char readtime[8];         //用来存储时间的,格式:秒分时日周月年

                    void ds1302_read_time(void);
                    void ds1302_write_time(void);

                    void main(void)
                    {
                    //    unsigned char i;
                        uart_init();
                        ds1302_write_time();
                        while(1)
                        {
                            ds1302_read_time();
                    //        for (i=0; i<7; i++)
                    //        {
                                uart_send_string(readtime);
                    //            delay(50);
                    //        }
                            delay(1000);
                        }
                    }
                    void ds1302_read_time(void)
                    {
                        unsigned char i;
                        for (i=0; i<7; i++)
                        {
                            readtime[i] = ds1302_read_reg(READ_RTC_ADDR[i]);
                        }
                    }
                    void ds1302_write_time(void)
                    {
                        unsigned char i;
                        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);                //打开写保护
                    }

    10、及时对程序进行规整
        a、程序规整介绍
            (1)何为规整
            (2)为什么要规整
            (3)如何规整:多文件、分C文件和H头文件

        b、多文件
            (1)多文件方式实现,意思是多个.c文件来实现
            (2)多文件的主要目的是让各个功能模块分开来实现,这样方便组织和查找。

        c、C文件和头文件
            (1)C文件是C语言的源文件,h文件是头文件
            (2)源文件主要用来放:函数和全局变量定义
            (3)头文件主要用来放:函数和全局变量的声明、宏定义、结构体共用体类型定义等等
            (4)一般是一个源文件一个头文件
            (5)一般包含自己建立的头文件的时候用""来引用而不是<>来引用。
            (6)头文件有个固定的格式:
                                    #ifndef __DS1302_H__
                                    #define __DS1302_H__

                                    #endif
 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值