DS1302/GC1302/1302系列时钟芯片驱动程序&注意事项&解析数据手册
开的这个专栏会一直记录自己用过的驱动,以及解析数据手册,有触摸芯片,RFID芯片,语音芯片,显示屏驱动,ADC,各种通讯模块等等,感兴趣的可以一起交流学习
一、简介
概述
DS1302 可慢速充电实时时钟芯片包含实时时钟/日历和 31 字节的非易失性静态 RAM。实时时钟/日历可对秒,分,时,日,周,月,和年进行计数,对于小于31 天的月,月末的日期自动进行调整,还具有闰年校正的功能。时钟可以采用 24 小时格式或带 AM(上午)/PM(下午)的 12 小时格式。31 字节的 RAM 可以用来临时保存一些重要数据。
使用同步串行通信,简化了 DS1302 与微处理器的通信。与时钟/RAM 通信仅需 3 根线:
(1)RST(复位)/又或者叫使能,
(2)I/O(数据线),
(3)SCLK(串行时钟)。
数据可以以每次一个字节的单字节形式或多达 31 字节的多字节形式传输。
DS1302能在非常低的功耗下工作,消耗小于 1µW 的功率便能保存数据和时钟信息。
工作原理
CE信号使能后,移位寄存器单元会在 SCLK 同步脉冲信号的控制下从 I/O 上串行接收 8 位指令字节,然后将 8 位指令字节进行串并转换并送至 ROM 指令译码单元。由 ROM 指令译码单元对 8 位指令字节进行译码,以决定内部寄存器的地址以及读写状态。然后在接下来的 SCLK 同步脉冲信号的控制下将 8 位数据写进或者读出相应的寄存器。数据传送也可以采用多字节方式,先将 8 位相应的指令字节写入,然后在连续的 SCLK 的脉冲信号同步下,将数据字节连续写入或读出日历/时钟寄存器(或者RAM 单元)。SCLK 脉冲的个数在单字节方式下为 8 加 8,在多字节方式下为 8 加最大可达到 248 的数。
二、硬件连接
引脚 | 说明 |
---|---|
VCC1 | 备用电源(纽扣电池,掉电使用) |
VCC2 | 主电源(设备电池,正常使用) |
SCLK | 时钟信号 |
I/O | 数据输入输出信号 |
CE | 使能信号(复位) |
X1/X2 | 晶振(不需要负载电容) |
GND | 地 |
tips:
- 三个通信引脚内部都有下拉电阻。
- VCC1为备用电源,当VCC1大于VCC2的电压0.3V时会自动切换为备用电源。(一般VCC2是恒压源,因此可认为VCC2(即设备电源)断电时自动切换使用VCC1)
- 内部有6pf负载电容,故只需要合适的32.768khz晶振即可,无需增加外部负载电容。
- CE高电平来启动所有的数据传送。CE低电平所有的数据传送中止,且 I/O
引脚变为高阻抗状态。
三、驱动程序
/* 一些宏 */
#define XX1302_DELAY() delay_us(5)
#define XX1302_WP_EN() XX1302_WriteByte(CMD_W_CTRL,0x80) //使能写保护
#define XX1302_WP_DISEN() XX1302_WriteByte(CMD_W_CTRL,0x00) //关闭写保护
1、引脚配置&初始化
/******* XX1302(RTC) *******/
/* 数据XX1302_SDA 芯片内部下拉*/
#define XX1302_SDA_PORT GPIOA
#define XX1302_SDA_PIN GPIO_PIN_4
#define XX1302_SDA_CLK RCC_APB2_PERIPH_GPIOA
#define XX1302_SDA_OUT_PP() outputIO_CFG(XX1302_SDA_PORT,XX1302_SDA_PIN,IO_OUT_PP)
#define XX1302_SDA_OUT_H() GPIO_SetBits(XX1302_SDA_PORT, XX1302_SDA_PIN)
#define XX1302_SDA_OUT_L() GPIO_ResetBits(XX1302_SDA_PORT, XX1302_SDA_PIN)
#define XX1302_SDA_IN_NP() inputIO_CFG(XX1302_SDA_PORT,XX1302_SDA_PIN,IO_IN_NP)
#define XX1302_SDA_READ() GPIO_ReadInputDataBit(XX1302_SDA_PORT,XX1302_SDA_PIN)
/* 时钟XX1302_SCL 芯片内部下拉*/
#define XX1302_SCL_PORT GPIOA
#define XX1302_SCL_PIN GPIO_PIN_3
#define XX1302_SCL_CLK RCC_APB2_PERIPH_GPIOA
#define XX1302_SCL_OUT_PP() outputIO_CFG(XX1302_SCL_PORT,XX1302_SCL_PIN,IO_OUT_PP)
#define XX1302_SCL_OUT_H() GPIO_SetBits(XX1302_SCL_PORT, XX1302_SCL_PIN)
#define XX1302_SCL_OUT_L() GPIO_ResetBits(XX1302_SCL_PORT, XX1302_SCL_PIN)
/* 使能XX1302_EN 芯片内部下拉*/
#define XX1302_EN_PORT GPIOA
#define XX1302_EN_PIN GPIO_PIN_5
#define XX1302_EN_CLK RCC_APB2_PERIPH_GPIOA
#define XX1302_EN_OUT_PP() outputIO_CFG(XX1302_EN_PORT,XX1302_EN_PIN,IO_OUT_PP)
#define XX1302_EN_OUT_H() GPIO_SetBits(XX1302_EN_PORT, XX1302_EN_PIN)
#define XX1302_EN_OUT_L() GPIO_ResetBits(XX1302_EN_PORT, XX1302_EN_PIN)
/*******************************************
* @brief void XX1302Init()
* XX1302初始化
* @param
* @return
* @note
*******************************************/
void XX1302Init(void)
{
XX1302_EN_OUT_PP(); //RTC1302使能配置
XX1302_SCL_OUT_PP(); //SCL配置推挽输出
XX1302_SDA_OUT_PP(); //SDA配置推挽输出
XX1302_EN_OUT_L(); //所有的数据传输终止,I/O引脚进入高阻抗状态。
XX1302_SCL_OUT_L(); //使能前,SCL必须为0
}
2、命令&寄存器
后面使用时再展开讲
/* 命令和寄存器宏定义 */
/* LSB先行: 1,RAM/nCLK,A4,A3,A2,A1,A0,R/nW */
#define CMD_W_SEC 0x80//写秒寄存器 1000 0000
#define CMD_W_MIN 0x82//写分 1000 0010
#define CMD_W_HOUR 0x84//写小时 1000 0100
#define CMD_W_DAY 0x86//写日 1000 0110
#define CMD_W_MONTH 0x88//写月 1000 1000
#define CMD_W_WEEK 0x8A//写星期 1000 1010
#define CMD_W_YEAR 0x8C//写年 1000 1100
#define CMD_W_CTRL 0x8E//写控制寄存器
#define CMD_R_SEC 0x81//读秒寄存器 1000 0001
#define CMD_R_MIN 0x83//读分 1000 0011
#define CMD_R_HOUR 0x85//读小时 1000 0101
#define CMD_R_DAY 0x87//读日 1000 0111
#define CMD_R_MONTH 0x89//读月 1000 1001
#define CMD_R_WEEK 0x8B//读星期 1000 1011
#define CMD_R_YEAR 0x8D//读年 1000 1101
3、写入一个字节
(1)拉高使能脚开启数据传输功能;
(2)SDA脚配置为输出;
(3)SCLK上升沿时,数据位有效,因此先将SDA脚置位(LSB先行),再拉高SCLK,然后延时后重新拉低SCLK,此为一个时序;
(4)右移数据切换最低位;
(5)8个时序写入一字节;
(6)使用时先写入命令,再写入数据,两者同为8位();
(7)结束时拉低使能脚终止数据传输;
/*******************************************
* @brief void XX1302_WriteByte()
* XX1302写入单字节数据
* @param
* @return
* @note
*******************************************/
static void XX1302_WriteByte(u8 cmd,u8 data){
u8 i;
XX1302_EN_OUT_H(); //启动数据传输
XX1302_SDA_OUT_PP();
XX1302_SCL_OUT_L();
for(i=0;i<8;i++){
if(cmd&0x01)
XX1302_SDA_OUT_H();
else
XX1302_SDA_OUT_L();
cmd>>=1;
XX1302_DELAY();
XX1302_SCL_OUT_H();
XX1302_DELAY();
XX1302_SCL_OUT_L();
}
for(i=0;i<8;i++){
if(data&0x01)
XX1302_SDA_OUT_H();
else
XX1302_SDA_OUT_L();
data>>=1;
XX1302_DELAY();
XX1302_SCL_OUT_H();
XX1302_DELAY();
XX1302_SCL_OUT_L();
}
XX1302_EN_OUT_L(); //所有的数据传输终止,I/O引脚进入高阻抗状态。
}
4、读出一个字节
注意上图读出时序和写入时的区别,读出数据时是在8个写命令周期后,再在下降沿读数据;
(1)拉高使能脚开启数据传输功能;
(2)SDA脚配置为输出;
(3)照写入数据的方式写入命令;
(4)注意紧跟命令写入的最后一个上升沿的下一个下降沿就开始输出数据了,因此有效的一共只有15个时序(不同于写入的16个时序);
(5)切换SDA脚配置为输入;
(6)要在下降沿后读数据(LSB先输出)下降沿后增加延时以保读数稳定;
(7)8个下降沿后读完数据;
(8)结束时拉低使能脚终止数据传输;
/*******************************************
* @brief u8 XX1302_ReadByte()
* XX1302读出单字节数据
* @param
* @return
* @note
*******************************************/
static u8 XX1302_ReadByte(u8 cmd){
u8 i;
u8 data = 0x00;
XX1302_EN_OUT_H(); //启动数据传输
XX1302_SDA_OUT_PP();
XX1302_SCL_OUT_L();
for(i=0;i<8;i++){
if(cmd&0x01)
XX1302_SDA_OUT_H();
else
XX1302_SDA_OUT_L();
cmd>>=1;
XX1302_DELAY();
XX1302_SCL_OUT_H();
XX1302_DELAY();
XX1302_SCL_OUT_L();
}
XX1302_SDA_IN_NP(); //切换为输入
for(i=0;i<8;i++){
XX1302_SCL_OUT_L();
XX1302_DELAY();
if(XX1302_SDA_READ())
data|=0x80; //最高位置1
else
data&=0x7F; //置0
if(i<7)
data>>=1; //右移
XX1302_SCL_OUT_H();
XX1302_DELAY();
}
XX1302_SCL_OUT_L();
XX1302_EN_OUT_L(); //所有的数据传输终止,I/O引脚进入高阻抗状态。
return data;
}
5、其他应用函数
BCD转换
存储在时钟寄存器中的数据是2-10BCD码,因此其实读出的时候用16进制即可,如下代码。
写入默认时间
/*******************************************
* @brief void XX1302_WriteDefaultTime(void)
* 写入默认时间
* @param
* @return
* @note 年 月 日 时 分
*******************************************/
void XX1302_WriteDefaultTime()
{
XX1302_WP_DISEN(); //关闭写保护
XX1302_WriteByte(CMD_W_YEAR,0x23); //2023
XX1302_WriteByte(CMD_W_MONTH,0x03); //03
XX1302_WriteByte(CMD_W_DAY,0x16); //16
XX1302_WriteByte(CMD_W_HOUR,0x09); //09
XX1302_WriteByte(CMD_W_MIN,0x21); //21
XX1302_WriteByte(CMD_W_SEC,0x00); //00
XX1302_WP_EN(); //使能写保护
}
注意:
-
写保护仅在写入时钟或者RAM时要关闭,因此写入时间前要关闭,但是读出时间时不用,因为读出时虽然写入命令,但没有写入时钟。
-
小时寄存器的位 7 定义为 12 或 24 小时方式选择位。当它为1时,选择 12 小时方式,在 12 小时方式下,位 5 是 AM/PM 位,此位为1表示 PM。在 24 小时方式下,位 5 是第 2 个 10 小时位(20-23时)。
-
秒寄存器的位 7 定义为时钟暂停位。当此位设置为逻辑 1 时,时钟振荡器停止,DS1302 被置入低功率的备份方式,其电源消耗小于 100 纳安(nanoamp)。当把此位写成逻辑 0 时,时钟将启动。
读出时间
/*******************************************
* @brief void XX1302_readTime(u8 *addr)
* 读出时间到数组
* @param
* @return
* @note 年 月 日 时 分 秒
*******************************************/
void XX1302_readTime(u8 *addr)
{
addr[0] = XX1302_ReadByte(CMD_R_YEAR);
addr[1] = XX1302_ReadByte(CMD_R_MONTH);
addr[2] = XX1302_ReadByte(CMD_R_DAY);
addr[3] = XX1302_ReadByte(CMD_R_HOUR);
addr[4] = XX1302_ReadByte(CMD_R_MIN);
addr[5] = XX1302_ReadByte(CMD_R_SEC);
}
main.c
/* 读出的时间的数组 年月日时分秒*/
u8 readTimeArr[6] = {0x22,0x10,0x02,0x06,0x06,0x06};
void main()
{
ALL_GPIO_RCC_EN(ENABLE); //GPIO/AFIO时钟开启
delay_init(SYSCLK_M); //含滴答定时器初始化
IWDG_Init();
Print_USART_Init(); //打印串口配置
XX1302Init(); //外部RTC初始化
IWDG_ReloadKey(); //喂狗
XX1302_WriteDefaultTime(); //写入默认时间
while(1)
{
IWDG_ReloadKey();
delay_ms(1000);
XX1302_readTime(readTimeArr);
printf("20%x年-%x月%x日-%x:%x:%x\n\r",readTimeArr[0],readTimeArr[1],readTimeArr[2],readTimeArr[3],readTimeArr[4],readTimeArr[5]);
}
}
串口打印数据:
结
该芯片重点就是写入和读出一字节的程序,个中算法因人而异,以上程序皆为个人实际调试程序,MCU是国民技术的G030,和ST的架构很像。
本人调试过程出现的问题分享
- 第一遍直接根据数据手册写代码的时候,出现了一点小问题,导致读出的数据全是0,后来接上逻辑分析仪发现整个程序只有128个脉冲(即8个写入周期),那很显然就只跑完了
XX1302_WriteDefaultTime
,而XX1302_readTime
函数不知什么情况没有运行或者说无效。但跑一遍流程发现不应该出现没运行的情况,因此将重点排查放在了XX1302_WriteDefaultTime
的最后一句XX1302_WP_EN();
上,很可能是写保护没有关掉,再看时序图,将高低电平解析发现,写入cmd
的部分都是正常的(这里要将前8个脉冲的位数据反序排列,因为是LSB先行),但是data
部分全部都是低电平,因此去查XX1302_WriteByte
的data
部分,发现data>>=1;
写成了cmd>>=1;
(因为两段逻辑一样,直接拷贝只改了上面的cmd),修改后,先是正常打印出时间,后来复位后就又全是0了。重新烧录依旧如此(短暂的正常)。 - 再次查时序可以发现:在读数据时,下降沿和数据位的上升沿完全重叠,如下图。因此可能就导致读出的都是0,解决方法就是调整
XX1302_ReadByte
函数后半段读数据的程序,在制造下降沿的时候增加延时,以稳定的读取到数据电平。再重新烧录,一切正常,不管断电还是复位。