1.DS1302简介
实时时钟/日历和 31 字节的非易失性静态 RAM
实时时钟/日历可对秒,分,时,日,周,月,和年进行计数,对于小于31 天的月,月末的日期自动进行调整,还具有闰年校正的功能。
时钟可以采用 24 小时格式或带 AM(上午)/PM(下午)的 12 小时格式。
31 字节的 RAM 可以用来临时保存一些重要数据
与时钟/RAM 通信仅需 3 根线:
(1)RST(复位)
(2)I/O(数据线)
(3)SCLK(串行时钟)
数据可以以每次一个字节的单字节形式、多达 31 字节的多字节形式传输。
DS1302能在非常低的功耗下工作,消耗小于 1µW 的功率便能保存数据和时钟信息。
工作原理:
如图所示,RST 信号有效后,移位寄存器单元会在 SCLK 同步脉冲信号的控制下从 I/O 上串行接收 8 位指令字节,然后将 8 位指令字节进行串并转换并送至 ROM 指令译码单元。
由 ROM 指令译码单元对 8 位指令字节进行译码,以决定内部寄存器的地址以及读写状态。
然后在接下来的 SCLK 同步脉冲信号的控制下将 8 位数据写进或者读出相应的寄存器。
数据传送也可以采用多字节方式,先将 8 位相应的指令字节写入,然后在连续的 SCLK 的脉冲信号同步下,将数据字节连续写入或读出日历/时钟寄存器(或者RAM 单元)。SCLK 脉冲的个数在单字节方式下为 8 加 8,在多字节方式下为 8 加最大可达到 248 的数。
1、命令字节
命令字节示于图 2:每一数据传送由命令字节初始化,最高有效位 MSB(位 7)必须为逻辑 1。如果它是零,禁止写 DS1302。位 6 为逻辑 0 指定时钟/日历数据。逻辑 1 指定 RAM 数据。位 1 至 5 指定进行输入或输出的特定寄存器。最低有效位 LSB(位 0)为逻辑 0 指定进行写操作(输入);逻辑 1 指定进行读操作(输出)。命令字节总是从最低有效 LSB 位 0 开始输入**。**
2、复位和时钟控制
通过把 RST 输入驱动至高电平来启动所有的数据传送。RST 输入有两种功能。首先,RST 接通控制逻辑,允许地址命令序列送入移位寄存器。其次,RST 可以中止数据传送。数据输入时,在时钟的上升沿数据必须有效,而数据位在时钟的下降沿输出。如果 RST 输入为低电平,那么所有的数据传送中止,且 I/O引脚变为高阻。数据传送在图 3 中说明。上电时,在 VCC 大于或等于 2.5V 之前,RST 必须为逻辑 0,此外,当把 RST 驱动至逻辑 1 的状态时,SCLK 必须为逻辑 0。
3、数据输入
跟随在输入写命令字节的 8 个 SCLK 周期之后,在下 8 个 SCLK 周期的上升沿输入数据。如果有额外的SCLK 周期,它们将被忽略。输入从位 0 开始。
4、数据输出
跟随在输入读命令字节的 8 个 SCLK 周期之后,在随后的 8 个 SCLK 周期的下降沿输出数据字节。注意,被传送的每一个数据位发生在读命令字节的最后一位之后的第一个下降沿。只要 RST 保持为高电平,如果有额外的 SCLK 周期,它们将重新发送数据字节。这一操作使之具有连续的多字节方式的读能力。另外,在 SCLK 的每一个上升沿,I/O 引脚为三态。数据从位 0 开始输出。
5、多字节方式
通过对 31(十进制)位地址寻址(地址/命令位于 1 至 5=逻辑 1),可以把时钟/日历或 RAM 寄存器规定为多字节方式。如前所述,位 6 规定时钟或 RAM 而位 0 规定读或写。在时钟\日历寄存器中的地址 9 至 31或 RAM 寄存器中的地址 31 不能存储数据。在多字节方式中读或写从地址 0 的位 0 开始。当以多字节方式写时钟寄存器时,必须按数据传送的次序写最先 8 个寄存器。但是,当以多字节方式写 RAM 时,为了传送数据不必写所有 31 个字节。不管是否写了全部 31 个字节,所写的每一个字节都将传送至 RAM。
6、时钟/日历
如图 4 所示,时钟/日历包含在 7 个写/读寄存器内。包含在时钟/日历寄存器内的数据是二-十进制(BCD)码。
7、时钟暂停
秒寄存器的位 7 定义为时钟暂停位。当此位设置为逻辑 1 时,时钟振荡器停止,DS1302 被置入低功
率的备份方式,其电源消耗小于 100 纳安(nanoamp)。当把此位写成逻辑 0 时,时钟将启动。
8、AM-PM/12-24 方式
小时寄存器的位 7 定义为 12 或 24 小时方式选择位。当它为高电平时,选择 12 小时方式,在 12 小时
方式下,位 5 是 AM/PM 位,此位为逻辑高电平表示 PM。在 24 小时方式下,位 5 是第 2 个 10 小时位(20-23时)。
9、写保护寄存器
写保护寄存器的位 7 是写保护位。开始 7 位(位 0-6)置为零,在读操作时总是读出零。在对时钟或RAM 进行写操作之前,位 7 必须为零。当它为高电平时,写保护位禁止对任何其它寄存器进行写操作。
2.DS1302使用
2.1 单字节–读
从上图可以看出读写数据由RST、SCLK、I/O共同控制
1、启动:当RST为高电平时,可以进行数据读取;
2、时钟:分为两种时钟时序。一、写时序:上升沿写数据有效。二:读时序:下降沿读数据有效
3、数据读写。
可以将读数据操作分为两个部分,一、写命令。二、读数据。
上代码:
/**
* 读字节
* 返回区区的字节
*/
uint8_t readByte(void)
{
/* 1、将I/O端口配置为输入 */
DS1302_IO_IN;
uint8_t data = 0;
/* 2、循环读取I/O口的8位 */
for(int i = 0; i < 8; i++)
{
DS1302_SCLK(0); // 拉低 SCLK 产生下降沿读取一位
HAL_Delay_us(SCLK_DELAY);
data >>= 1; // 先读取的是低位数据
if((uint8_t)DS1302_IO_R) // 读取电平状态
{
data|=0x80;
}
DS1302_SCLK(1); // 拉高SCLK SCLK上升沿读取数据,确保上升沿为0
HAL_Delay_us(SCLK_DELAY);
}
return data;
}
/**
* 写字节
* data 要写入的字节
*/
void writeByte(uint8_t data)
{
DS1302_IO_OUT;
GPIO_PinState pinState; // 要写入的电平状态
for(int i = 0; i < 8; i++)
{
DS1302_SCLK(0); // 拉低SCLK,等待上升沿发送数据
HAL_Delay_us(SCLK_DELAY);
pinState = (GPIO_PinState)(data&0x01);
DS1302_IO_W(pinState); // 确定要写入的电平状态,确保在上升沿前发送
DS1302_SCLK(1); // 拉高SClK, 产生上升沿发送状态
HAL_Delay_us(SCLK_DELAY);
data>>=1; // 获取下一位要发送的数据
}
}
读数据也是分为两个步骤的,一、写命令字节。二是、读数据;写命令字节可使用void writeByte(uint8_t data),读数据可使用,uint8_t readByte(void),
写数据分为:一、写命令字节。二、写数据。
/**
* 读取时钟/RAM数据
* Addr 读取的地址
*/
uint8_t Read(uint8_t Addr)
{
uint8_t rec;
DS1302_NRST(0); // NRST = 0 禁止读写
DS1302_SCLK(0); // SCLK = 0 确保在写数据前,SCLK被拉低
DS1302_NRST(1); // NRST = 1 启动传输
HAL_Delay_us(SCLK_DELAY);
writeByte(Addr); // 写命令字节
rec = readByte(); // 读数据
DS1302_SCLK(0);
DS1302_NRST(0);
HAL_Delay_us(SCLK_DELAY);
return rec;
}
void Write(uint8_t Addr, uint8_t data)
{
DS1302_NRST(0); // NRST = 0 关闭数据
DS1302_SCLK(0); // SCLK = 0 确保在写数据前,SCLK被拉低
HAL_Delay_us(SCLK_DELAY);
DS1302_NRST(1); // 启动传输
HAL_Delay_us(SCLK_DELAY);
writeByte(Addr); // 写命令字节
writeByte(data); // 写数据
DS1302_SCLK(0);
DS1302_NRST(0);
HAL_Delay_us(SCLK_DELAY);
}
读取的地址可以分为时钟地址和RAM地址,手册中给出的寄存器地址格式:
每一个地址都是根据命令字节进行格式化的
1、最高有效位MSB(位7)必须为1,如果为0就禁止写DS130。
2、位6:、0:时钟、日历数据。1:RAM数据
3、位1-5、输入、输出寄存器。
4、最低有效位LSB(位0)、0:写。1:读
根据命令字节格式和寄存器地址图表可以看出时钟、日历寄存器和RAM寄存器中的每一个寄存器都有读命令地址和写命令地址
时钟:
名称 | 读地址 | 写地址 |
---|---|---|
秒 | 0x81 | 0x80 |
分 | 0x83 | 0x82 |
小时 | 0x85 | 0x84 |
日 | 0x87 | 0x86 |
月 | 0x89 | 0x88 |
星期 | 0x8B | 0x8A |
年 | 0x8D | 0x8C |
RAM:
名称 | 读地址 | 写地址 |
---|---|---|
0 | 0xC1 | 0xC0 |
1 | 0xC3 | 0xC2 |
. | . | . |
30 | 0xFD | 0xFC |
根据寄存器的读写命令读写情况可以分为读、写日历时钟,读、写RAM
typedef struct
{
uint8_t year; /* 年 */
uint8_t month; /* 月 */
uint8_t week; /* 周 */
uint8_t day; /* 日 */
uint8_t hour; /* 时 */
uint8_t minute; /* 分 */
uint8_t second; /* 秒 */
}DATE_STRUCT;
typedef enum
{
ENUM_DATE_TYPE_SECOND, /* 秒 */
ENUM_DATE_TYPE_MINUTE, /* 分 */
ENUM_DATE_TYPE_HOUR, /* 时 */
ENUM_DATE_TYPE_DAY, /* 日 */
ENUM_DATE_TYPE_MONTH, /* 月 */
ENUM_DATE_TYPE_WEEK, /* 星期 */
ENUM_DATE_TYPE_YEAR, /* 年 */
}DATE_TYPE_ENUM;
uint8_t readDateBit(ENUM_DATE_TYPE type)
{
uint8_t rec = 0;
uint8_t date = Read(0x81+ ((uint8_t)type * 2));
rec = ((date>>4)&0xF)*10 + (date&0xF);
return rec;
}
void writeDateBit(ENUM_DATE_TYPE type, uint8_t date)
{
uint8_t dateBuf = (((date/10)<<4)&0xF0) + ((date%10)&0x0F);
Write(0x80+ ((uint8_t)type * 2), dateBuf);
}
/**
* addr 0-30
*/
uint8_t readRambit(uint8_t addr)
{
return Read(0xC1+ ((uint8_t)addr * 2));
}
/**
* addr 0-30
*/
void writeRambit(uint8_t addr, uint8_t data)
{
Write(0xC0+ ((uint8_t)addr * 2), data);
}
void readDate(DATE_STRUCT *date)
{
date->year = readDateBit(ENUM_DATE_TYPE_YEAR);
date->month = readDateBit(ENUM_DATE_TYPE_MONTH);
date->week = readDateBit(ENUM_DATE_TYPE_WEEK);
date->day = readDateBit(ENUM_DATE_TYPE_DAY);
date->hour = readDateBit(ENUM_DATE_TYPE_HOUR);
date->minute = readDateBit(ENUM_DATE_TYPE_MINUTE);
date->second = readDateBit(ENUM_DATE_TYPE_SECOND);
}
void writeDate(DATE_STRUCT *date)
{
if(date == NULL)
return;
writeDateBit(ENUM_DATE_TYPE_SECOND,date->second);
writeDateBit(ENUM_DATE_TYPE_MINUTE,date->minute);
writeDateBit(ENUM_DATE_TYPE_HOUR, date->hour);
writeDateBit(ENUM_DATE_TYPE_DAY, date->day);
writeDateBit(ENUM_DATE_TYPE_MONTH, date->month);
writeDateBit(ENUM_DATE_TYPE_YEAR, date->year);
date->week = week(date->year, date->month, date->day);
writeDateBit(ENUM_DATE_TYPE_WEEK, date->week);
}
星期计算-蔡勒公式
uint8_t week(uint8_t y,uint8_t m,uint8_t d)
{
if(m==1||m==2) m+=12,y = y-1;
return (d+2*m+3*(m+1)/5+y+y/4-y/100+y/400+1)%7;
}