DS3231时钟芯片全解析——概况,性能,MCU连接,样例代码
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 | 主动下拉中断或方波输出。开漏输出,需要外接5.5V以下的上拉电阻。由多功能引脚控制寄存器的INTCN位决定。 | |
4 | 低电平复位引脚。同时可指示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电气性能:
VPF断电电流,VCC小于该电流时且小于VBAT时,由VBAT供电。
保持电流1uA,读取写入电流150uA,温度转换(64s周期)电流650uA,数据保持(关闭晶振)电流100nA。
DS3231电源控制:
DS3231对存储设备的电池消耗进行了优化,在第一次安装电池时,内部晶振不会启动 ,直到VCC超过上述VFP数值时,或将有效的I2C地址写入时,在1s内,晶振启动。大约2秒,设备进行测温,并应用于补偿,测温周期为64秒。晶振在激活后一直有效。
DS3231 MCU连接:
如图所示:
左侧为MCU接口,SCL和SDA接上拉电阻RPU,RPU取值可为10k,RST引脚可接MCU,如不使用,可悬空。
右侧VCC和VBAT各需要一个去耦电容,==其中VBAT的去耦电容如果无外部供电时不会进行读写操作,则可以取消。==去耦电容大小0.1uF1uF。INT~/SQW和32kHz接上拉电阻,如不使用,均可悬空,其中32kHz引脚输出32.768kHz方波可作为单片机时钟源进行精确延时。
DS3231寄存器定义:
DS3231设备地址:0xD0
时间寄存器:
报警寄存器:
报警间隔设置寄存器:
注意报警1秒钟匹配时警报,报警2分钟匹配时警报。
控制寄存器:

状态寄存器:
老化偏置寄存器:
温度寄存器:
DS3231寄存器读写:
DS3231可在标准模式100kHz和快速模式400kHz下工作。
写时序:(写入的寄存器地址,后跟数据)
读时序:(写入的寄存器地址,再重新开启I2C通讯,再跟设备读取地址)
DS3231注意事项:
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;
}
作者:Dunkle.T