RTC芯片——DS1302驱动方式讲解(附代码)

RTC芯片——DS1302驱动方式讲解(附代码)

最近的一个项目中用到了DS1302rtc芯片,中间弯弯绕绕也费了点时间,好在最后还是成功搞定,现做一下总结,希望能让各位少走些弯路。

写代码前所需了解的DS1302知识

既然各位能搜索到这篇文章,那么关于DS1302是什么,可以用来干什么这里就不做过多描述了。我们直接奔着让DS1302跑起来的目标而去。

RTC寄存器

首先我们需要找到它的数据手册

如下图所示为DS1302的寄存器列表

在这里插入图片描述

所谓的驱动DS1302无非就是往对应的寄存器中读写数值。其中第一列为读取DS1302内部数据时需要访问的寄存器,例如我想读取其中的秒数,那么我就应该访问0x81,而第二列为写寄存器,例如当我们想要往DS1302中写入初始分钟数时,就需要往0x82写入相应数值。

每次读写数据共操作8位,其中每一位代表的含义也已经在给出。

例如秒钟:BIT7为时间暂停位,当该位为1时,时钟震荡器停止,DS1302将进入低功耗待机模式。一般为0;

BIT6,5,4:代表秒钟的十位;

BIT3,2,1,0:代表秒钟的个位。

如果我们向将秒钟初始设为32,则应该往寄存器0x80写入:0011 0010即 0x32

怎么写,怎么读

在明白了往哪写,从哪读之后,我们再来探讨一下具体的操作

MCU通过3个引脚与DS1302建立连接

在这里插入图片描述

进行读写操作时也需要这3个引脚相互配合。下图为对DS1302进行读写时各引脚的状态。

在这里插入图片描述

如图,上为从DS1302读取单个字节,下为向DS1302写入单个字节。粗略扫一眼不难得出以下三个重要结论:

1、读写数据过程中,CE需处于高电平状态。

2、写数据时,在SCLK为上升沿时写入数,读数据时在SCLK的下降沿时读取。

3、无论是写入一个字节,还是读取一个字节,都需要先向DS1302发送想要操作的寄存器地址。

时序调整

基于以上内容,我们已大致明白了该如何驱动DS1302。简单的来说

从DS1302读取数据:

1、发送数据所对应的读寄存器地址

2、在每个时钟的下降沿读取DS1302传来的数据。

向DS1302发送数据:

1、发送数据所对应的写寄存器地址

2、在每个时钟的上升沿发送数据

不过由于硬件速率等因素的限制,实际情况会比上述稍微复杂一些。举个例子,当读取数据时,我们理想的情况为,SCLK产生下降沿的那个瞬间,MCU就能接收到来自DS1302的数据,然而实际情况可能需要我们等上,几百纳秒(ns)。

所以,在编写代码过程中,我们需要在适当的地方加入一点延时,以满足DS1302的时序。(事实上,不加延时也不一定会出错,执行语句本身也需要一定时间。)

文档中也给出了详细的时序图

详细的时序图
时许要求

以上两张图需要搭配食用。

我们将其具体到代码中,如下为向DS1302写数据时的驱动。

static void Send_OneByte(uint8_t _data)
{
    uint8_t temp = _data;
    for (uint8_t i = 0; i < 8; i++)
    {
        if (temp & 0x01)
        {
            DATA_H;
        } // IO=1;
        else
        {
            DATA_L;
        } // IO=0;
        temp = temp >> 1;
        Delay_us(1); // for tcc
        SCLK_H;      // CLK=1;
        Delay_us(1); // for tch
        SCLK_L;      // CLK=0;
    }
}
/**
 * @description: 向指定寄存器发送一个字节数据
 * @event:
 * @param {uint8_t} _address
 * @param {uint8_t} _data
 * @return {*}
 */
static void DS1302_Wirte_Reg(uint8_t _address, uint8_t _data)
{
    uint8_t i, temp1, temp2;
    temp1 = _address;
    temp2 = _data;
    DS1302_DATAOUT_Init(); //配置IO为输出
    CE_H;                  // RET=1;
    //发送地址
    Send_OneByte(temp1);

    //发送数据
    Send_OneByte(temp2);
    DATA_L;
    CE_L; // RET=0;
}

DS1302采用5v供电,我一共使用了2次延时函数,每次延时约1us左右。

第一次延时主要为了tCC,即将CE拉高后需要等待tCC时间再将sclk拉高,查表可知5v供电下,tCC最少为1us。

第二次延时主要为了tCH,即SCLK高电平的持续时间,查表可知至少需要250ns才可被DS1302识别到。

内部涓流充电

DS1302中具有两个电源输入口,分别为VCC1,VCC2。根据手册描述,当VCC2>VCC1+0.2v时,DS1302会选择将VCC2作为供电电源,反之则选择VCC1供电。

在实际电路中我们会将,VCC2作为主要供电源,而在VCC1处连接可充电电池,或超级电容作为备用供电。

DS1302自带涓流充电器,通过控制内部寄存器开启。

在这里插入图片描述

开启方法即为超寄存器内写入特定的值,写入方法与写入时钟一致。

写入不同的值,可控制DS1302内部VCC2与VCC1之间二极管与电阻的连接方式,具体如下图所示。

在这里插入图片描述

在这里插入图片描述

若想开启涓流充电BIT7~BIT4必须为1010,BIT3和BIT2用以选择接入二极管的数量,BIT1和BIT0用以选择接入电阻的大小。

例:为使充电电流尽量大,这里选择只接入一个二极管和一个2K的电阻。即需要向寄存器0x90写入1010 0101(0xA5)

附录:代码

#include "drv_rtc.h"
struct TIMEData
{
    uint8_t year;
    uint8_t month;
    uint8_t day;
    uint8_t hour;
    uint8_t minute;
    uint8_t second;
    uint8_t week;
};
struct TIMEData TimeData_t;
uint16_t g_rtctick;

static void DS1302_DATAOUT_Init() //配置双向I/O端口为输出态
{
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = RTC_IO_PIN;        // Set SPI0 CS Pin
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;    // Set Output
    GPIO_InitStructure.GPIO_ODrv = GPIO_ODrv_NORMAL; // Low Speed
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;   // output Push-Pull
                                                     // GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
    GPIO_Init(RTC_IO_PORT, &GPIO_InitStructure);
    GPIO_PinAFConfig(RTC_IO_PORT, RTC_IO_PIN, GPIO_AF_GPIO);
    GPIO_ResetBits(RTC_IO_PORT, RTC_IO_PIN);
}

static void DS1302_DATAINPUT_Init() //配置双向I/O端口为输入态
{
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = RTC_IO_PIN;        // Set SPI0 CS Pin
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;     // Set Output
    GPIO_InitStructure.GPIO_ODrv = GPIO_ODrv_NORMAL; // Low Speed
                                                     // GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;   // output Push-Pull
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
    GPIO_Init(RTC_IO_PORT, &GPIO_InitStructure);
    GPIO_PinAFConfig(RTC_IO_PORT, RTC_IO_PIN, GPIO_AF_GPIO);
}
void DS1302_GPIO_Init() // IO,CE,SCLK端口初始化
{
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = RTC_CLK_PIN;       // Set SPI0 CS Pin
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;    // Set Output
    GPIO_InitStructure.GPIO_ODrv = GPIO_ODrv_NORMAL; // Low Speed
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;   // output Push-Pull
                                                     // GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
    GPIO_Init(RTC_CE_PORT, &GPIO_InitStructure);
    GPIO_PinAFConfig(RTC_CE_PORT, RTC_CLK_PIN, GPIO_AF_GPIO); //选择引脚功能为IO

    GPIO_InitStructure.GPIO_Pin = RTC_CE_PIN; // Set SPI0 CS Pin

    GPIO_Init(RTC_CLK_PORT, &GPIO_InitStructure);
    GPIO_PinAFConfig(RTC_CLK_PORT, RTC_CE_PIN, GPIO_AF_GPIO); //选择引脚功能为IO

    GPIO_ResetBits(RTC_CE_PORT, RTC_CLK_PIN);
    GPIO_ResetBits(RTC_CLK_PORT, RTC_CE_PIN);

    DS1302_DATAOUT_Init();
}

/**
 * @description: 传递一个字节
 * @event:
 * @param {uint8_t} _data
 * @return {*}
 */
static void Send_OneByte(uint8_t _data)
{
    uint8_t temp = _data;
    for (uint8_t i = 0; i < 8; i++)
    {
        if (temp & 0x01)
        {
            DATA_H;
        } // IO=1;
        else
        {
            DATA_L;
        } // IO=0;
        temp = temp >> 1;
        Delay_us(1); // for tcc
        SCLK_H;      // CLK=1;
        Delay_us(1); // for tch
        SCLK_L;      // CLK=0;
    }
}

/**
 * @description: 向指定寄存器发送一个字节数据
 * @event:
 * @param {uint8_t} _address
 * @param {uint8_t} _data
 * @return {*}
 */
static void DS1302_Wirte_Reg(uint8_t _address, uint8_t _data)
{
    uint8_t i, temp1, temp2;
    temp1 = _address;
    temp2 = _data;
    DS1302_DATAOUT_Init(); //配置IO为输出
    CE_H;                  // RET=1;
    //发送地址
    Send_OneByte(temp1);

    //发送数据
    Send_OneByte(temp2);
    DATA_L;
    CE_L; // RET=0;
}

/**
 * @description: 从指定地址读取一字节数据
 * @event:
 * @param {uint8_t} _address
 * @return {*}
 */
uint8_t DS1302_Read_Reg(uint8_t _address)
{

    uint8_t recdata;
    uint8_t temp = _address;
    uint8_t retnum;

    DS1302_DATAOUT_Init(); //配置IO为输出,恢复正常状态
    CE_H;                  // RET=1;
                           //写地址
    for (uint8_t i = 0; i < 8; i++)
    {
        if (temp & 0x01)
        {
            DATA_H;
        } // IO=1;
        else
        {
            DATA_L;
        } // IO=0;
        temp = temp >> 1;
        Delay_us(1);
        SCLK_H; // CLK=1;
        Delay_us(1);
        if (i == 7)
        {
            DS1302_DATAINPUT_Init(); //在clk拉低之前执行,防止rtc数据传输过来时,引脚仍为输出
        }
        SCLK_L; // CLK=0;
    }
    //读数据

    for (uint8_t i = 0; i < 8; i++)
    {
        recdata = recdata >> 1; //读数据变量
        if (GPIO_ReadInputDataBit(RTC_IO_PORT, RTC_IO_PIN))
        {
            recdata = recdata | 0x80;
        }
        else // IO=0
        {
            recdata = recdata & 0x7f;
        }
        SCLK_H; // CLK=1;
        Delay_us(1);
        SCLK_L; // CLK=0;
    }
    CE_L;                  // RET=0;
    DS1302_DATAOUT_Init(); //配置IO为输出,恢复正常状态

    //数据处理转化十进制
    retnum = BCD_TO_DEC(recdata);
    return retnum;
}

void DS1302_Init()
{
    /*芯片上电后先读取一次RTC值*/
    TimeData_t.second = DS1302_Read_Reg(0x81); //读秒
    TimeData_t.minute = DS1302_Read_Reg(0x83); //读分
    TimeData_t.hour = DS1302_Read_Reg(0x85);   //读时
    TimeData_t.day = DS1302_Read_Reg(0x87);    //读日
    TimeData_t.month = DS1302_Read_Reg(0x89);  //读月
    TimeData_t.week = DS1302_Read_Reg(0x8B);   //读星期
    TimeData_t.year = DS1302_Read_Reg(0x8D);   //读年
    
  	DS1302_Wirte_rig(0x8e, 0x00);              // 关闭写保护
  	#ifdef CHARGE_INTERNAL /*开启内部充电*/
 	DS1302_Wirte_rig(0x90, 0xA5);
 	#endif
   	DS1302_Wirte_rig(0x80,(((TimeData_t.second/10)<<4)|(TimeData_t.second%10))&0x7f);
    DS1302_Wirte_rig(0x8e, 0x80); // 写保护
}

void DS1302_Read_Time()
{
    if (SysGetLapseTick(g_rtctick) > 1000)
    {
        SysSetCurrentTick(&g_rtctick);

        TimeData_t.second = DS1302_Read_Reg(0x81); //读秒
        TimeData_t.minute = DS1302_Read_Reg(0x83); //读分
        TimeData_t.hour = DS1302_Read_Reg(0x85);   //读时
        TimeData_t.day = DS1302_Read_Reg(0x87);    //读日
        TimeData_t.month = DS1302_Read_Reg(0x89);  //读月
        TimeData_t.week = DS1302_Read_Reg(0x8B);   //读星期
        TimeData_t.year = DS1302_Read_Reg(0x8D);   //读年
    }
}

/**
 * @description:
 * @event:
 * @param {TIMEDATA_E} _timedata_e
 * @param {uint8_t} _value bcd码
 *  * 例:设置日期为29号, DS1302_Set_Time(DAY, 0X29)
 * @return {*}
 */
void DS1302_Set_Time(TIMEDATA_E _timedata_e, uint8_t _value)
{
    uint8_t temp;
    temp =DEC_TO_BCD(_value);
    DS1302_Wirte_Reg(0x8e, 0x00); //关闭写保护
    switch (_timedata_e)
    {
    case YEAR:
        DS1302_Wirte_Reg(0x8c, temp);
        break;
    case MONTH:
        DS1302_Wirte_Reg(0x88, temp);
        break;
    case DAY:
        DS1302_Wirte_Reg(0x86, temp);
        break;
    case HOUR:
        DS1302_Wirte_Reg(0x84, temp);
        break;
    case MINUTE:
        DS1302_Wirte_Reg(0x82, temp);
        break;
    case SECOND:
        DS1302_Wirte_Reg(0x80, temp);
        break;
    case WEEK:
        DS1302_Wirte_Reg(0x8a, temp);
        break;
    default:
        break;
    }
    DS1302_Wirte_Reg(0x8e, 0x80); //打开写保护
}

void Delay_us(__IO uint32_t nCount) //简单的延时函数
{
    for (; nCount != 0; nCount--)
    {
        IWDG_ReloadCounter(); // feed dog IWDT必须喂狗
    }
}
#ifndef __ds1302_H
#define __ds1302_H
#include <stdint.h>

#define BCD_TO_DEC(X) ((X>>4)*10 + (X&0X0F)) 
#define DEC_TO_BCD(X) (((X/10)<<4) | (X%10))

#define CHARGE_INTERNAL 

#define RTC_IO_PIN GPIO_Pin_12
#define RTC_IO_PORT GPIOA

#define RTC_CLK_PIN GPIO_Pin_11
#define RTC_CLK_PORT GPIOA

#define RTC_CE_PIN GPIO_Pin_13
#define RTC_CE_PORT GPIOA

#define CE_L GPIO_ResetBits(RTC_CE_PORT, RTC_CE_PIN)     //拉低使能位
#define CE_H GPIO_SetBits(RTC_CE_PORT, RTC_CE_PIN)       //拉高使能位
#define SCLK_L GPIO_ResetBits(RTC_CLK_PORT, RTC_CLK_PIN) //拉低时钟线
#define SCLK_H GPIO_SetBits(RTC_CLK_PORT, RTC_CLK_PIN)   //拉高时钟线
#define DATA_L GPIO_ResetBits(RTC_IO_PORT, RTC_IO_PIN)   //拉低数据线
#define DATA_H GPIO_SetBits(RTC_IO_PORT, RTC_IO_PIN)     //拉高数据线

typedef enum
{
  YEAR = 0,
  MONTH,
  DAY,
  HOUR,
  MINUTE,
  SECOND,
  WEEK
} TIMEDATA_E;

                                                //创建TIMEData结构体方便存储时间日期数据
extern struct TIMEData TimeData;                        //全局变量
void DS1302_GPIO_Init(void);                            // ds1302端口初始化
uint8_t DS1302_Read_Reg(uint8_t _address);              //从指定寄存器读一字节数据
void DS1302_Init(void);                                 // ds1302初始化函数
void DS1302_Read_Time(void);
void Delay_us(__IO uint32_t nCount);
void DS1302_Set_Time(TIMEDATA_E _timedata_e, uint8_t _value);

#endif

扩展阅读:DS1302驱动补充——突发模式

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值