DS3231概述:
数据:
DS3231是一个超高精度I2C实时时钟芯片,带有集成的温度补偿晶振。误差范围:温度范围为0摄氏度到40摄氏度(±2PPM),温度范围为-40摄氏度到85摄氏度(±3.5PPM,每天±0.432秒)。包含时钟(24小时格式或12小时格式)、日历(年,月,日,星期)、两个可编程时间报警和一个可编程方波输出。
供电:
DS3231采用两组供电,一组主电,由外部供电(2.3V~5.5V),一组备电,一般由纽扣电池供电。当主电存在时,由主电供电,当主电不存在时由备电供电。
功能:
DS3231,通过内部带有温补的晶振自动对时间进行计数,对月末日期进行调整,以及闰年矫正。MCU可对其时间日期进行设置及读取,显示正确的当前时间。
连接:
与MCU通过标准I2C总线进行连接(注意,非标准I2C设备会占用I2C总线全部设备地址,导致标准设备不可用。),多个标准I2C设备可连接到同一条I2C总线上。
DS3231型号说明:
DS3231主要有两种型号,一种是16pin的DS3231SN,一种是8pin的DS3231MZ。主要是提醒,8pin的DS3231MZ,守时精度为±5PPM。
DS3231引脚说明:
引脚 | 名称 | 功能 |
---|---|---|
1 | 32kHz | 32.768kHz输出。开漏输出,需要外接上拉电阻。可在任何电源上工作,不使用需保持开路。 |
2 | VCC | 初级供电引脚。需使用0.1uF~1uF电容解耦,不使用请接地。 |
3 | INT/SQW | 主动下拉中断或方波输出。开漏输出,需要外接5.5V以下的上拉电阻。由多功能引脚控制寄存器的INTCN位决定。 |
4 | RST | 低电平复位引脚。同时可指示VCC和VPF的大小。VCC<VPF,引脚低电平;VCC>VPF,引脚高电平。不使用悬空。 |
5~12 | N.C. | 无连接,必须接地。 |
13 | GND | 电源接地。 |
14 | VBAT | 备用电源输入。需使用0.1uF~1uF电容解耦,不使用请接地。不会对VCC反向充电。 |
15 | SDA | I2C串行数据输入输出引脚。 |
16 | SCL | I2C串行时钟输入引脚。 |
DS3231电气性能:
V~PF~断电电流,VCC小于该电流时且小于VBAT时,由VBAT供电。
保持电流1uA,读取写入电流150uA,温度转换(64s周期)电流650uA,数据保持(关闭晶振)电流100nA。
DS3231电源控制:
DS3231对存储设备的电池消耗进行了优化,在第一次安装电池时,内部晶振不会启动 ,直到VCC超过上述VFP数值时,或将有效的I2C地址写入时,在1s内,晶振启动。大约2秒,设备进行测温,并应用于补偿,测温周期为64秒。晶振在激活后一直有效。
DS3231 MCU连接:
如图所示:
左侧为MCU接口,SCL和SDA接上拉电阻R~PU~,R~PU~取值可为10k,RST引脚可接MCU,如不使用,可悬空。
右侧VCC和VBAT各需要一个去耦电容,==其中VBAT的去耦电容如果无外部供电时不会进行读写操作,则可以取消。==去耦电容大小0.1uF~1uF。~~INT~~/SQW和32kHz接上拉电阻,如不使用,均可悬空,其中32kHz引脚输出32.768kHz方波可作为单片机时钟源进行精确延时。
DS3231寄存器定义:
DS3231设备地址:0xD0
时间寄存器:
- 寄存器读取长度超过12h,则超过部分从00h开始。读取指针指向00h时,外部寄存器数值自动与内部寄存器同步。
- 在数据读写时复位,则数据会丢失。
- 时间数据和其他实时时钟芯片一样使用二进制编码的十进制(BCD)格式。
- 小时寄存器的第6位为12/24小时格式选择位,第5位为AM/PM标志位,0为AM,1为PM。
- 月份寄存器的第七位为世纪位。
报警寄存器:
报警间隔设置寄存器:
注意报警1秒钟匹配时警报,报警2分钟匹配时警报。
- DS3231包含两个时间/日期报警。
- 07h到0Ah为报警1时间;0Bh到0Dh为报警2时间。
- 通过INTCN和A1IE和A2IE控制INT/SQW引脚输出。
- 报警可编程为每隔1秒、1分钟、1小时、1天、或日期重复。
- DY/DT用于设置是日期还是星期。
- 当RTC寄存器的值与报警寄存器设置匹配时,相应的警报标志"A1F"或"A2F"位被设置为1。如果相应的警报中断使能"A1IE"或"A2IE"也被设置为1,并且INTCN位设置为1,则激活INT/SQW信号。时间和日期寄存器的每秒匹配一次。
控制寄存器:
- BIT 7:启动晶振控制位。为0时启动晶振;为1时当VCC供电时晶振启动,当VBAT供电时关闭晶振。默认为1,首次通电时清0。
- BIT 6:电池供电方波使能。条件苛刻,用处不大。
- BIT 5: 转换温度控制位。设为1,强制更新一次温补。使用前先检查BSY位,避免与64s周期转换冲突。温补转换完成,BIT5和BSY位自动复位为0。
- BIT 4 和 BIT 3:控制方波输出频率。方波启用时可用。
- BIT 2:方波报警控制位。当RTC寄存器的值与报警寄存器设置匹配时,相应的警报标志"A1F"或"A2F"位被设置为1。如果相应的警报中断使能"A1IE"或"A2IE"也被设置为1,并且INTCN位设置为1,则激活INT/SQW信号。时间和日期寄存器的每秒匹配一次。
- BIT 1:报警2中断使能,VCC上电清0。
- BIT 0:报警1中断使能,VCC上电清0。
状态寄存器:
- BIT 7:晶振停止标志位。为1时表示晶振已停止。
- BIT 3:启用32.768kHz控制位。
- BIT 2:温补操作状态位,为1时表示正在温度转换。
- BIT 1:报警2报警标志位。该位为0时,同时A2IE为1,INTCN为1,此时当Alarm2时间匹配时才会触发 INT/SQW引脚的主动下拉中断(如果中断触发后,需要重新将其清0,才可以再次触发中断。)。
- BIT 0:报警1报警标志位。该位为0时,同时A1IE为1,INTCN为1,此时当Alarm1时间匹配时才会触发 INT/SQW引脚的主动下拉中断(如果中断触发后,需要重新将其清0,才可以再次触发中断。)。
- BIT 1和BIT 0有1,则INT/SQW引脚为低电平,都为零则为低电平。
老化偏置寄存器:
- 二进制补码,调整电容值以进一步校准时钟。
- 一般,25℃时,最小调整量为0.1ppm。
温度寄存器:
- 温度寄存器为10bit,分辨率为0.25℃。使用二进制补码形式。上8位为整数部分在11h,下2位为小数部分在12h。例如00011001 01b=+25.25摄氏度,即25+0.25*1。
DS3231寄存器读写:
DS3231可在标准模式100kHz和快速模式400kHz下工作。
写时序:(写入的寄存器地址,后跟数据)
读时序:(写入的寄存器地址,再重新开启I2C通讯,再跟设备读取地址)
DS3231注意事项:
- DS3231封装包含石英音叉晶体。可以使用拾取-贴装设备但是必须谨慎小心,以确保避免过度冲击。避免使用超声波清理,以免损坏晶体。
- 除非封装与信号线之间有地层隔开,否则避免在器件下面走信号线。所有N.C.(无连接)引脚必须接地。
- 潮湿敏感封装出厂时采用防潮包装。必须遵循封装标签上列出的操作说明,以防止回流焊过程中损坏器件。
- 潮湿敏感器件(MSD)的分类和回流焊温度曲线请参考IPC/JEDEC J-STD-020标准。允许的回流焊次数最多2次。
DS3231样例代码: (基于HC32L130)
ds3231.h
#ifndef __DS3231_H__
#define __DS3231_H__
/* Includes ------------------------------------------------------------------*/
#include "gpio.h"
/* Exported types ------------------------------------------------------------*/
typedef struct
{
uint8_t yearH; //年千百位
uint8_t yearL; //年
uint8_t month; //月
uint8_t date; //日
uint8_t hour; //时
uint8_t minute; //分
uint8_t second; //秒
uint8_t week; //周
}_calendar_obj;
extern _calendar_obj calendar; //日历结构体
/* DS3231 地址定义 */
/* ADDR Pin Conect to VSS */
#define DS3231_ADDR 0xD0
#define DS3231_ADDR_WRITE 0xD0
#define DS3231_ADDR_READ 0xD1
/* DS3231寄存器地址 */
typedef enum
{
DS3231_SECOND = 0x00, //秒
DS3231_MINUTE = 0x01, //分
DS3231_HOUR = 0x02, //时
DS3231_WEEK = 0x03, //星期
DS3231_DAY = 0x04, //日
DS3231_MONTH = 0x05, //月
DS3231_YEAR = 0x06, //年
/* 闹铃1 */
DS3231_ALARM1SECOND = 0x07, //秒
DS3231_ALARM1MINUTE = 0x08, //分
DS3231_ALARM1HOUR = 0x09, //时
DS3231_ALARM1DATE = 0x0A, //星期/日
/* 闹铃2 */
DS3231_ALARM2MINUTE = 0x0b, //分
DS3231_ALARM2HOUR = 0x0c, //时
DS3231_ALARM2DAY = 0x0d, //星期/日
DS3231_CONTROL = 0x0e, //控制寄存器
DS3231_STATUS = 0x0f, //状态寄存器
BSY = 2, //忙
OSF = 7, //振荡器停止标志
DS3231_XTAL = 0x10, //晶体老化寄存器
DS3231_TEMPERATUREH = 0x11, //温度寄存器高字节(8位)
DS3231_TEMPERATUREL = 0x12, //温度寄存器低字节(高2位)
} DS3231_CMD;
/* Exported functions ------------------------------------------------------- */
void DS3231_Init(void);
en_result_t I2C_DS3231_WriteCmd(M0P_I2C_TypeDef* I2CX,DS3231_CMD pu8Addr, uint8_t pu8Data);
en_result_t I2C_MasterRead_DS3231Data(M0P_I2C_TypeDef* I2CX,DS3231_CMD pu8Addr,uint8_t*pu8Data,uint32_t u32Len);
void I2C_DS3231_SetTime(uint8_t year,uint8_t month,uint8_t day,uint8_t week,uint8_t hour,uint8_t minute,uint8_t second);
void I2C_DS3231_ReadTime(void);
void I2C_DS3231_SetAlarm_1(boolean_t en,uint8_t date,uint8_t hour,uint8_t minute,uint8_t second);
void I2C_DS3231_SetAlarm_2(boolean_t en,uint8_t hour,uint8_t minute);
uint8_t I2C_DS3231_ReadTime_Hour(void);
uint8_t I2C_DS3231_ReadTime_Minute(void);
uint8_t I2C_DS3231_getTemperature(void);
#endif /* __DS3231_H__ */
ds3231.c
#include "ds3231.h"
#include "i2c.h"
#include "bsp_delay.h"
// BCD(8421)转DEC
uint8_t BCD_DEC(uint8_t val)
{
uint8_t i;
i= val&0x0f;
val >>= 4;
val &= 0x0f;
val *= 10;
i += val;
return i;
}
// DEC转BCD(8421)
uint8_t DEC_BCD(uint8_t val)
{
uint8_t i,j,k;
i=val/10;
j=val%10;
k=j+(i<<4);
return k;
}
//DS3231初始化
void DS3231_Init(void)
{
I2C_DS3231_WriteCmd(M0P_I2C0,DS3231_CONTROL,0x04); //闹钟中断允许,初始化禁用闹钟1闹钟2
I2C_DS3231_WriteCmd(M0P_I2C0,DS3231_STATUS,0x00); //32KHZ输出禁止,闹钟标志位清零
}
// DS3231写命令函数,只进行写命令操作
en_result_t I2C_DS3231_WriteCmd(M0P_I2C_TypeDef* I2CX,DS3231_CMD pu8Addr, uint8_t pu8Data)
{
en_result_t enRet = Error;
//超时重启值
uint32_t u32TimeOut = 0x00FFFFu;
uint8_t sendCount = 0, u8State = 0;
I2C_SetFunc(I2CX,I2cStop_En); ///发送停止条件
I2C_ClearIrq(I2CX);
I2C_SetFunc(I2CX,I2cStart_En); ///发送起始条件
while(1)
{
while(0 == I2C_GetIrq(I2CX))
{
while(0 == I2C_GetIrq(I2CX))
{
//超时I2C还未启动
if(0 == u32TimeOut--)
{
//软重启
NVIC_SystemReset();
}
}
}
u8State = I2C_GetState(I2CX);
switch(u8State)
{
case 0x08: ///已发送起始条件
I2C_ClearFunc(I2CX, I2cStart_En);
I2C_WriteByte(I2CX, DS3231_ADDR_WRITE); ///发送设备地址+W写标志0
break;
case 0x18: ///已发送SLW+W,已接收ACK
case 0x28: ///已发送I2Cx_DATA中的数据,已接收ACK
switch(sendCount)
{
case 0:
I2C_WriteByte(I2CX,pu8Addr); ///发送地址
break;
case 1:
I2C_WriteByte(I2CX,pu8Data); ///发送数据
break;
}
sendCount++;
break;
case 0x20: ///已发送SLW+W,已接收非ACK
break;
case 0x30: ///已发送I2Cx_DATA中的数据,已接收非ACK,将传输一个STOP条件
I2C_SetFunc(I2CX,I2cStop_En); ///发送停止条件
break;
case 0x58: ///< 已接收到最后一个数据,NACK已返回
I2C_SetFunc(I2CX,I2cStop_En); ///< 发送停止条件
break;
case 0x38: ///< 在发送地址或数据时,仲裁丢失
I2C_SetFunc(I2CX,I2cStart_En); ///< 当总线空闲时发起起始条件
break;
case 0x48: ///< 发送SLA+R后,收到一个NACK
I2C_SetFunc(I2CX,I2cStop_En); ///< 发送停止条件
I2C_SetFunc(I2CX,I2cStart_En); ///< 发送起始条件
break;
default:
break;
}
if(sendCount>2)
{
I2C_SetFunc(I2CX,I2cStop_En); ///此顺序不能调换,出停止条件
I2C_ClearIrq(I2CX);
break;
}
I2C_ClearIrq(I2CX); ///清除中断状态标志位
}
enRet = Ok;
return enRet;
}
// 主机读取数据函数,只进行读数据操作
en_result_t I2C_MasterRead_DS3231Data(M0P_I2C_TypeDef* I2CX,DS3231_CMD pu8Addr,uint8_t *pu8Data,uint32_t u32Len)
{
en_result_t enRet = Error;
//超时重启值
uint32_t u32TimeOut = 0x00FFFFu;
uint8_t u8State=0;
uint8_t receiveCount=0;
uint8_t sendAddrCount=0;
I2C_SetFunc(I2CX,I2cStop_En); ///发送停止条件
I2C_ClearIrq(I2CX);
I2C_SetFunc(I2CX,I2cStart_En); ///发送起始条件
while(1)
{
while(0 == I2C_GetIrq(I2CX))
{
//超时I2C还未启动
if(0 == u32TimeOut--)
{
//软重启
NVIC_SystemReset();
}
}
u8State = I2C_GetState(I2CX);
switch(u8State)
{
case 0x08: ///< 已发送起始条件,将发送SLA+W
sendAddrCount++;
I2C_ClearFunc(I2CX,I2cStart_En);
I2C_WriteByte(I2CX,DS3231_ADDR_WRITE);
break;
case 0x18: ///< 已发送SLA+W,并接收到ACK
I2C_WriteByte(I2CX, pu8Addr); ///< 读取寄存器位置
break;
case 0x28: ///< 已发送数据,接收到ACK, 此处是已发送从机内存地址u8Addr并接收到ACK
I2C_SetFunc(I2CX,I2cStart_En);
break;
case 0x10: ///< 已发送重复起始条件
I2C_ClearFunc(I2CX,I2cStart_En);
I2C_WriteByte(I2CX,DS3231_ADDR_READ);///< 发送SLA+R,开始从从机读取数据
break;
case 0x40: ///< 已发送SLA+R,并接收到ACK
if(u32Len>=1)
{
I2C_SetFunc(I2CX,I2cAck_En); ///< 使能主机应答功能
}
break;
case 0x50: ///< 已接收数据字节,并已返回ACK信号
pu8Data[receiveCount] = I2C_ReadByte(I2CX);
receiveCount++;
if(receiveCount==u32Len)
{
I2C_ClearFunc(I2CX,I2cAck_En); ///< 已接收到倒数第二个字节,关闭ACK应答功能
}
break;
case 0x58: ///< 已接收到最后一个数据,NACK已返回
I2C_ClearFunc(I2CX,I2cStart_En);
I2C_SetFunc(I2CX,I2cStop_En); ///< 发送停止条件
I2C_SetFunc(I2CX,I2cStart_En);
break;
case 0x38: ///< 在发送地址或数据时,仲裁丢失
I2C_SetFunc(I2CX,I2cStart_En); ///< 当总线空闲时发起起始条件
break;
case 0x48: ///< 发送SLA+R后,收到一个NACK
I2C_SetFunc(I2CX,I2cStop_En); ///< 发送停止条件
I2C_SetFunc(I2CX,I2cStart_En); ///< 发送起始条件
break;
default:
I2C_SetFunc(I2CX,I2cStart_En); ///< 其他错误状态,重新发送起始条件
break;
}
I2C_ClearIrq(I2CX); ///< 清除中断状态标志位
if(receiveCount==u32Len) ///< 数据全部读取完成,跳出while循环
{
break;
}
}
enRet = Ok;
return enRet;
}
// DS3231设置时间日期
void I2C_DS3231_SetTime(uint8_t year,uint8_t month,uint8_t day,uint8_t week,uint8_t hour,uint8_t minute,uint8_t second)
{
uint8_t readData = 0x04;
// DS3231忙则等待
while((readData&0x04) > 0)
{
I2C_MasterRead_DS3231Data(M0P_I2C0,DS3231_STATUS,&readData,1);
}
I2C_DS3231_WriteCmd(M0P_I2C0, DS3231_SECOND,DEC_BCD(second));
I2C_DS3231_WriteCmd(M0P_I2C0, DS3231_MINUTE,DEC_BCD(minute));
I2C_DS3231_WriteCmd(M0P_I2C0, DS3231_HOUR,DEC_BCD(hour));
I2C_DS3231_WriteCmd(M0P_I2C0, DS3231_WEEK,DEC_BCD(week));
I2C_DS3231_WriteCmd(M0P_I2C0, DS3231_DAY,DEC_BCD(day));
I2C_DS3231_WriteCmd(M0P_I2C0, DS3231_MONTH,DEC_BCD(month));
I2C_DS3231_WriteCmd(M0P_I2C0, DS3231_YEAR,DEC_BCD(year));
}
// DS3231读取时间日期
void I2C_DS3231_ReadTime()
{
uint8_t readTimeList[7]= {0};
uint8_t readData = 0x04;
// DS3231忙则等待
while((readData&0x04) > 0)
{
I2C_MasterRead_DS3231Data(M0P_I2C0,DS3231_STATUS,&readData,1);
}
I2C_MasterRead_DS3231Data(M0P_I2C0,DS3231_SECOND,readTimeList,7);
calendar.yearH = 20; //年千百位
calendar.yearL = BCD_DEC(readTimeList[6]); //年
calendar.month = BCD_DEC(readTimeList[5]); //月
calendar.date = BCD_DEC(readTimeList[4]); //日
calendar.hour = BCD_DEC(readTimeList[2]); //时
calendar.minute = BCD_DEC(readTimeList[1]); //分
calendar.second = BCD_DEC(readTimeList[0]); //秒
calendar.week = BCD_DEC(readTimeList[3]); //周
}
//DS3231设置Alarm_1
void I2C_DS3231_SetAlarm_1(boolean_t en,uint8_t date,uint8_t hour,uint8_t minute,uint8_t second)
{
uint8_t readData = 0x04;
// DS3231忙则等待
while((readData&0x04) > 0)
{
I2C_MasterRead_DS3231Data(M0P_I2C0,DS3231_STATUS,&readData,1);
}
if(en)
{
I2C_DS3231_WriteCmd(M0P_I2C0, DS3231_ALARM1SECOND,DEC_BCD(second));
I2C_DS3231_WriteCmd(M0P_I2C0, DS3231_ALARM1MINUTE,DEC_BCD(minute));
I2C_DS3231_WriteCmd(M0P_I2C0, DS3231_ALARM1HOUR,DEC_BCD(hour));
I2C_DS3231_WriteCmd(M0P_I2C0, DS3231_ALARM1DATE,DEC_BCD(date));
I2C_MasterRead_DS3231Data(M0P_I2C0,DS3231_CONTROL,&readData,1);
readData |= 0x01;
I2C_DS3231_WriteCmd(M0P_I2C0,DS3231_CONTROL,readData); //复用引脚设置闹钟中断
}
else
{
I2C_MasterRead_DS3231Data(M0P_I2C0,DS3231_CONTROL,&readData,1);
readData &= 0xFE;
I2C_DS3231_WriteCmd(M0P_I2C0,DS3231_CONTROL,readData); //复用引脚禁用闹钟中断
}
}
//DS3231设置Alarm_2
void I2C_DS3231_SetAlarm_2(boolean_t en, uint8_t hour, uint8_t minute)
{
uint8_t readData = 0x04;
// DS3231忙则等待
while((readData&0x04) > 0)
{
I2C_MasterRead_DS3231Data(M0P_I2C0,DS3231_STATUS,&readData,1);
}
//设置日期匹配寄存器A2M4位为1,即每日进行时分匹配
I2C_DS3231_WriteCmd(M0P_I2C0, DS3231_ALARM2DATE,0x80);
//写入时分匹配数据
I2C_DS3231_WriteCmd(M0P_I2C0, DS3231_ALARM2MINUTE,DEC_BCD(minute));
I2C_DS3231_WriteCmd(M0P_I2C0, DS3231_ALARM2HOUR,DEC_BCD(hour));
if(en)
{
I2C_MasterRead_DS3231Data(M0P_I2C0,DS3231_CONTROL,&readData,1);
readData |= 0x02;
I2C_DS3231_WriteCmd(M0P_I2C0,DS3231_CONTROL,readData); //复用引脚设置闹钟中断
}
else
{
I2C_MasterRead_DS3231Data(M0P_I2C0,DS3231_CONTROL,&readData,1);
readData &= 0xFD;
I2C_DS3231_WriteCmd(M0P_I2C0,DS3231_CONTROL,readData); //复用引脚禁用闹钟中断
}
}
// DS3231读取小时数据
uint8_t I2C_DS3231_ReadTime_Hour()
{
uint8_t readData = 0x04;
// DS3231忙则等待
while((readData&0x04) > 0)
{
I2C_MasterRead_DS3231Data(M0P_I2C0,DS3231_STATUS,&readData,1);
}
I2C_MasterRead_DS3231Data(M0P_I2C0,DS3231_HOUR,&readData,1);
return readData;
}
//DS3231读取分钟数据
uint8_t I2C_DS3231_ReadTime_Minute()
{
uint8_t readData = 0x04;
// DS3231忙则等待
while((readData&0x04) > 0)
{
I2C_MasterRead_DS3231Data(M0P_I2C0,DS3231_STATUS,&readData,1);
}
I2C_MasterRead_DS3231Data(M0P_I2C0,DS3231_MINUTE,&readData,1);
return readData;
}
// 获取温度整数部分
uint8_t I2C_DS3231_getTemperature(void)
{
uint8_t readData = 0x04;
// DS3231忙则等待
while((readData&0x04) > 0)
{
I2C_MasterRead_DS3231Data(M0P_I2C0,DS3231_STATUS,&readData,1);
}
I2C_MasterRead_DS3231Data(M0P_I2C0,DS3231_TEMPERATUREH,&readData,1);
return readData;
}