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驱动补充——突发模式