实时时钟DS1302

1 RTC有关的背景知识

1.1 什么是实时时钟(rtc for real time clock)

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

(2)单片机为什么需要时间点。

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

1.2 DS1302介绍

(1)数据手册。

(2)SPI数字接口访问。

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

1.3 RTC学习的关键点

(1)SPI接口及其相关概念:3线or4线、同步、主从、串行等。

(2)时序的理解和编程实现。

2 原理图和接线

2.1 原理图分析

(1)DS1302引脚介绍。

(2)原理图中跳线设计对接线的影响。

实时时钟DS1302_第1张图片

2.2 接线

(1)正确的接线要点:
JP595断开,是为了让P3.4在控制DS1302的时候,不影响74HC595工作;
JP1302接上,是为了让P3.4能够控制到DS1302;
J11断开,是为了让P3.5在控制DS1302的时候,不影响NE555模块的工作。

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

3 数据手册

DS1302 涓流充电计时芯片包含一个实时时钟/日历和 31 字节的静态 RAM,通过简单的串行接口与微处理器通讯。这个实时时钟/日历提供年月日,时分秒信息。对于少于 31 天的月份月末会自动调整,还有闰年校正。由于有一个 AM/PM 指示器,时钟可以工作在 12 小时制或者 24小时制。

使用同步串行通讯简化了 DS1302 与微处理器的接口。与时钟/RAM 通讯只需要三根线: CE,I/O (数据线), and SCLK (串行时钟).。数据输出输入时钟/RAM 一次 1字节或者在脉冲串中多达 31 字节,DS1302 被设计工作在非常低的电能下,在低于 1μW 时还能保持数据和时钟信息。

串行计时器的主要元素:移位寄存器,控制逻辑,振荡器,实时时钟,还有 RAM。

实时时钟DS1302_第2张图片

4 时序图的读法

4.1 时序图关键点

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

(2)静态与动态2个角度去看。

(3)注意SCLK的边沿处。

实时时钟DS1302_第3张图片

4.2 结合示例代码来理解时序

/*******************************************************************************
* 函 数 名         : Ds1302Write
* 函数功能         : 向DS1302命令(地址+数据)
* 输    入         : addr,dat
* 输    出         : 无
*******************************************************************************/

void Ds1302Write(uchar addr, uchar dat)
{
    uchar n;
    RST = 0;
    _nop_();

    SCLK = 0;//先将SCLK置低电平。
    _nop_();
    RST = 1; //然后将RST(CE)置高电平。
    _nop_();

    for (n=0; n<8; n++)//开始传送八位地址命令
    {
        DSIO = addr & 0x01;//数据从低位开始传送
        addr >>= 1;
        SCLK = 1;//数据在上升沿时,DS1302读取数据
        _nop_();
        SCLK = 0;
        _nop_();
    }
    for (n=0; n<8; n++)//写入8位数据
    {
        DSIO = dat & 0x01;
        dat >>= 1;
        SCLK = 1;//数据在上升沿时,DS1302读取数据
        _nop_();
        SCLK = 0;
        _nop_();    
    }   

    RST = 0;//传送数据结束
    _nop_();
}

/*******************************************************************************
* 函 数 名         : Ds1302Read
* 函数功能         : 读取一个地址的数据
* 输    入         : addr
* 输    出         : dat
*******************************************************************************/

uchar Ds1302Read(uchar addr)
{
    uchar n,dat,dat1;
    RST = 0;
    _nop_();

    SCLK = 0;//先将SCLK置低电平。
    _nop_();
    RST = 1;//然后将RST(CE)置高电平。
    _nop_();

    for(n=0; n<8; n++)//开始传送八位地址命令
    {
        DSIO = addr & 0x01;//数据从低位开始传送
        addr >>= 1;
        SCLK = 1;//数据在上升沿时,DS1302读取数据
        _nop_();
        SCLK = 0;//DS1302下降沿时,放置数据
        _nop_();
    }
    _nop_();
    for(n=0; n<8; n++)//读取8位数据
    {
        dat1 = DSIO;//从最低位开始接收
        dat = (dat>>1) | (dat1<<7);
        SCLK = 1;
        _nop_();
        SCLK = 0;//DS1302下降沿时,放置数据
        _nop_();
    }

    RST = 0;
    _nop_();    //以下为DS1302复位的稳定时间,必须的。
    SCLK = 1;
    _nop_();
    DSIO = 0;
    _nop_();
    DSIO = 1;
    _nop_();
    return dat; 
}

4.3 时序之上的东西

(1)大小端:一个字节发出去,先发高位还是低位。

(2)如何读写寄存器。

4.4 总结SPI的时序特征

(1)低位在前。

(2)DS1302在上升沿读取,下降沿写入。

(3)注意SCLK工作频率。

5 编程实践

5.1 建立工程

5.2 编写ds1302_write_reg函数

(1)引脚定义。

(2)delay。

(3)根据时序写代码。

5.3 编写ds1302_read_reg函数

5.4 比对自写代码和官方例程

5.5 读取时间

(1)DS1302的时间寄存器的地址。

(2)移植串口输出代码,将读取到的时间通过串口输出显示。
如果要读取秒寄存器,地址是:0b10000001(0x81)。
如果要写入秒寄存器,地址是:0b10000000(0x80)。

5.6 添加串口调试

移植串口代码
(1)注意波特率设置和晶振设置。

(2)注意串口相关的接线设置。

(3)测试串口输出效果。

(4)注意二进制显示和文本方式显示。

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

串口输出时间信息
(1)写代码。

(2)问题解决:
状况:
1、代码确实得到了一系列的时间数据。
2、秒确实在变化,而且变化的规律似乎是正确的。
3、时间数据中有一些FF是不合理的,不应该出现的。

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

解决方法有2个:
1、硬件上在IO线上设置10K的电阻做弱上拉处理。

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

5.7 DS1302的时间格式详解

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

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

综合来讲:BCD码其实就是看起来很像十进制数的十六进制数。意思就是:BCD码本质上是十六进制数,但是因为他没有ABCDEF,所以看起来很像十进制数。

(2)BCD码的意义:方便显示。

(3)区别BCD码、16进制、10进制三种数。

(4)BCD码转16进制、16进制转BCD码。

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

5.8 向DS1302写入时间

写时间函数
(1)写保护。

(2)写入地址和读出地址不同。

(3)注意写入时序。

(4)先写入时间然后读取验证。

5.9 及时对程序进行规整

程序规整介绍
(1)何为规整。

(2)为什么要规整。

(3)如何规整:多文件、分c文件和h文件。

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

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

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

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

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

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

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

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

代码

uart.h

#ifndef __UART_H__
#define __UART_H__

#include 

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

#endif

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;
}

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





ds1302.c

#include 
#include 
#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();
    }   
}















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();
}

你可能感兴趣的:(51单片机,--------【常见外设】)