一、模块简介
DS3231是一款高精度I2C实时时钟器件,具有集成的温度补偿晶体振荡器。该器件包含电池输入端,断开主电源时仍可保持精确计时。集成的晶体振荡器可提高器件的长期精确度。DS3231的寄存器能保存秒、分、时、星期、日期、月、年和闹钟设置等信息。少于31天的月份,可自动调整月末日期,包括闰年补偿。时钟的工作格式为24小时或带AM/PM指示的12小时格式。DS3231提供两个可编程日历闹钟和一路可编程方波输出。DS3231与单片机通过I2C双向串行总线传输地址与数据。
DS3231时钟芯片结构原理
引脚图
VCC为电源引脚;
INT/SQW为低电平有效中断或方波输出:是低电平有效复位引脚;
N.C.表示无连接,外部必须接地;
GND为地;
VBAT为备用电源输入;
SDA为串行数据输入输出;
SCL为串行时钟输入。
内部结构图
DS3231典型应用电路
下图为DS3231典型应用电路,图中可看出,DS3231几乎不需要外部元件。
DS3231时钟寄存器介绍
如下图1所示,DS3231的主要组成部分有8个模块,划分为4个功能组:TCXO、电源控制、按钮复位和RTC。
1. 32 kHz的TCXO
TCXO包括温度传感器、振荡器和控制逻辑。控制器读取片上温度传感器输出,使用查表法确定所需的电容,加上AGE寄存器的老化修正。然后设置电容选择寄存器。仅在温度变化或者用户启动的温度转换完成时,才加载包括AGE寄存器变化的新值。VCC初次上电时就会读取温度值,然后每隔64 s读取一次。
2. DS3231的内部寄存器及功能
DS3231寄存器地址为00h~12h,分别用于存放秒、分、时、星期、日期及闹钟设置信息。在多字节访问期间,如果地址达到RAM空间的结尾12h处,将发生卷绕,此时定位到开始位置即00h单元。DS3231的时间和日历信息通过读取相应的寄存器来设置和初始化。用户辅助缓冲区用于防止内部寄存器更新时可能出现的错误。读取时间和日历寄存器时,用户缓冲区在任何START条件下或者寄存器指针返回到零时与内部寄存器同步。时间信息从这些辅助寄存器读取,此时时钟继续保持运行状态。这样在读操作期间发生主寄存器更新时可以避免重新读取寄存器。以控制寄存器(地址为0EH)为例,可以控制实时时钟、闹钟和方波输出。其各bit定义如下表。
BIT7位:使能振荡器(EOEC)。设定为逻辑0时,启动振荡器。如果设定为逻辑1,在DS3231电源切换至VBAT时,振荡器停止。初次上电时该位清零 (逻辑0) 。当DS3231由VCC供电时,振荡器与EOSC位的状态无关,始终保持工作状态。
BIT6位:电池备份的方波使能(BBSOW)。温度转换不影响内部64 s更新周期。用户启动的温度转换在大约2 ms内不会影响BSY位。CONV位从写入开始直到转换完成一直保持为1,转换完后,CONV和BSY均变为0。在监视用户启动转换状态时,应使用CONV位。
BIT4和BIT3位:频率选择(RS2和RS1),初次上电时,BIT
当设定为逻辑1并且DS3231由VBAT引脚供电时,在没有加载VCC的情况下,该位使能方波输出。当BB-SQW设定为逻辑0时,若VCC降至低于电源故障门限值,则INT/SQW引脚变为高阻抗。初次上电时,该位清零(逻辑0)。
BIT5位:转换温度(CONV)。该位置为1时,强制温度传感器将温度转换成数字,并执行TCXO算法更新振荡器的电容阵列。只在空闲期间有效。状态位BSY=1时,禁止设定转换温度位。用户在强制控制器开始新的TCXO操作之前。应检查状态位BSY。用户启动的
4和BIT3设置为逻辑1。方波使能时用于控制方波输出的频率。RS1、RS2的逻辑值与方波输出频率的关系如表2所列。
BIT2位:中断控制(INTCN)。该位控制INT/SQW信号。INTCN置为0时,INT/SQW引脚输出方波;INTCN置为1时,若计时寄存器与任一个闹钟寄存器相匹配,则会触发INT/SQW信号(如果也使能闹钟的话)。匹配时相应的闹钟标志总是置位,而与INTCN位的状态无关。初次上电时,INTCN位置为逻辑1。
BIT1位:闹钟2中断使能(A2IE)。该位置为逻辑1时,允许状态寄存器中的闹钟2标志位(A2F)触发INT/SQW信号(当INTCN=1时)。当A2IE位置为0或者INTCN置为0时,A2F位不启动中断信号。初次上电时,A2IE位清零(逻辑0)。
BIT0位:闹钟1中断使能(A1IE)。该位置为逻辑1时,允许状态寄存器中的闹钟1标志位(A1F)触发INT/SQW信号(当INTCN=1时)。当A1IE位置为0或者INTCN置为0时,A1F位不启动INT/SQW信号。初次上电时,A1IE位清零(逻辑0)。
3. DS3231的电源控制
电源控制功能由温度补偿电压基准(VPF)和监视VCC电平的比较器电路提供。当VCC高于VPF时,DS3231由VCC供电,当VCC低于VPF但高于VBAT时,DS3231由VCC供电;当VCC低于VPF并低于VBAT时,DS3231由VBAT供电。为保护电池,VBAT首次加到器件时振荡器并不启动,除非加载VCC,或者向器件写入一个有效的I2C地址。典型的振荡器启动时间在1 s以内。在VCC加电后或者有效的I2C地址写入后大约2 s,器件会测量一次温度,并使用计算的修正值校准振荡器。一旦振荡器运行,只要电源(VCC或者VBAT)有效就会一直保持工作状态。器件每隔64 s进行一次温度测量并校准振荡器频率。
4. DS3231的时钟和日历RTC
可以通过读取适当的寄存器字节获得时钟和日历信息。通过写入适当的寄存器字节设定或者初始化时钟和日历数据。时钟和日历寄存器的内容采用二-十进制编码(BCD)格式。DS3231运行于12小时或者24小时模式。小时寄存器的第6位定义为12或24小时模式选择位。该位为高时,选择12小时模式。在12小时模式下,第5位为AM/PM指示位,逻辑高时为PM。
5. DS3231的复位按钮
DS3231具有连接至RST输出引脚的按钮开关功能。若DS3231不在复位周期,会持续监视RST信号的下降沿。如果检测到一个边沿转换,DS3231通过拉低RST完成开关去抖。内部定时器定时结束后,DS3231继续监视RST信号。如果信号依旧保持低电平,DS3231持续监视信号线以检测上升沿。一旦检测到按钮释放,DS3231强制RST为低电平并保持tRST。RST还可用于指示电源故障报警情况。当VCC低于VPF时,产生内部电源故障报警信号,并强制拉低RST引脚。当VCC返回至超过VPF电平时。RST保持低电平大约250 ms(tREC),使供电电源达到稳定。如果在VCC加载时,振荡器不工作,将跳过tREC,RST立刻变为高电平。
二、代码实现
DS3231.c
#include "DS3231.h"
#include "Delay.h"
_calendar_obj calendar;
void DS3231_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);
//I2C2是APB1的外设,所以用APB1
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
//GPIO是APB2的外设,所以用APB2
GPIO_InitTypeDef GPIO_InitStructure;//初始化GPIO
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
//复用开漏模式 ,复用:控制权交给外设。开漏:协议要求
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
I2C_InitTypeDef I2C_InitStructure;//初始化I2C2
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;//是否给应答
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//响应7位地址
I2C_InitStructure.I2C_ClockSpeed = 400000;//时钟频率:100KHZ
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
//占空比 低:高 = 2:1;
//因为I2C总线采用上拉电阻,是弱上拉,弹回高电平速度比拉低电平速度慢,所以要给低电平更多资源
//让数据有足够的时间放到SDA总线上
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_OwnAddress1 = 0x0A ;
I2C_Init(I2C2,&I2C_InitStructure);
I2C_Cmd(I2C2,ENABLE);//使能I2C2;
}
void DS3231_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
uint32_t Timeout;
Timeout = 10000;
while(I2C_CheckEvent(I2Cx,I2C_EVENT) != SUCCESS)
{
Timeout--;
if(Timeout == 0)
{
break;
}
}
}
void DS3231_WriteReg(uint8_t RegAddress, uint8_t Data)
{//指定地址写寄存器 (写一个字节)
I2C_GenerateSTART(I2C2,ENABLE);//开始时序
//等待EV5事件(可参考上文图245);等不到(SUCCESS)就一直空循环,等到就跳出循环,执行下一步;
DS3231_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C2,DS3231_ADDRESS,I2C_Direction_Transmitter);//发送从机地址(点名从机)
//I2C2,从机地址,读写位置0(发送);
DS3231_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//等待EV6事件;
I2C_SendData(I2C2,RegAddress);//发送寄存器的地址(点名从机的寄存器)
DS3231_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING);//等待EV8事件;
I2C_SendData(I2C2,Data);
DS3231_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);
//连续发送字节时,等待EV8事件,当发送最后一个字节后,应等待EV8_2事件;
I2C_GenerateSTOP(I2C2,ENABLE);//停止时序
}
uint8_t DS3231_ReadReg(uint8_t RegAddress)
{//指定地址读寄存器 (读一个字节)
uint8_t Data;
while(I2C_GetFlagStatus(I2C2, I2C_FLAG_BUSY))
{
uint32_t Timeout;
Timeout = 10000;
if(Timeout == 0)
{
break;
}
Timeout--;
}
I2C_GenerateSTART(I2C2,ENABLE);//开始时序
DS3231_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);
//等待EV5事件(可参考上文图245);等不到(SUCCESS)就一直空循环,等到就跳出循环,执行下一步;
I2C_Send7bitAddress(I2C2,DS3231_ADDRESS,I2C_Direction_Transmitter);//发送从机地址(点名从机)
//I2C2,从机地址,读写位置0(发送);
DS3231_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//等待EV6事件;
I2C_SendData(I2C2,RegAddress);//发送寄存器的地址(点名从机的寄存器)
DS3231_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//等待EV8_2事件(等待发送完成再产生重复起始条件);
I2C_GenerateSTART(I2C2,ENABLE);//开始时序
DS3231_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//等待EV5事件
I2C_Send7bitAddress(I2C2,DS3231_ADDRESS,I2C_Direction_Receiver);//接收,读写位置1(接收)
DS3231_WaitEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);//等待EV6事件;
I2C_AcknowledgeConfig(I2C2,DISABLE);//不给应答,ACK置0;
I2C_GenerateSTOP(I2C2,ENABLE);//停止时序
DS3231_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);//等待EV7事件;
Data = I2C_ReceiveData(I2C2);
I2C_AcknowledgeConfig(I2C2,ENABLE);//ACK置1,方便产生下一个开始时序;
return Data;
}
/**
* @brief 等待 DS3231 到准备状态
* @param 无
* @retval 无
*/
void I2C_WaitDs3231StandbyState(void)
{
vu16 SR1_Tmp = 0;
do
{
/* 发送起始信号 */
I2C_GenerateSTART(I2C2, ENABLE);
/* 读 I2C1 SR1 寄存器 */
SR1_Tmp = I2C_ReadRegister(I2C2, I2C_Register_SR1);
/* 发送 DS3231 地址 + 方向 */
I2C_Send7bitAddress(I2C2, DS3231_ADDRESS, I2C_Direction_Transmitter);
}while(!(I2C_ReadRegister(I2C2, I2C_Register_SR1) & 0x0002));
/* 清除 AF 位 */
I2C_ClearFlag(I2C2, I2C_FLAG_AF);
/* 发送停止信号 */
I2C_GenerateSTOP(I2C2, ENABLE);
}
/**
* @brief BCD(8421)转DEC.//二进制转十进制
* @param val:BCD码.
* @retval i:DEC码.
*/
uint8_t BCD_DEC(u8 val)
{
u8 i;
i= val&0x0f;
val >>= 4;
val &= 0x0f;
val *= 10;
i += val;
return i;
}
//
/**
* @brief BCD(8421)转DEC.
* @param val:DEC码.
* @retval k:BCD码.
*/
uint8_t DEC_BCD(u8 val)
{
u8 i,j,k;
i=val/10;
j=val%10;
k=j+(i<<4);
return k;
}
/**
* @brief 时间设置
* @param
* @arg 分别输入 年 月 日 星期 时 分 秒
* @retval 无
*/
void I2C_DS3231_SetTime(u8 yea,u8 mon,u8 da,u8 we,u8 hou,u8 min,u8 sec)
{
// u8 yea = 0x23;//23年 0010 0011
//u8 mon = 0x10;//10月 0001 0000
//u8 da = 0x13;//13日 0001 0011
//u8 we = 0x06;//周6 0000 0110
//u8 hou = 0x08; //8时 0000 1000
//u8 min = 0x08;//8分
//u8 sec = 0x08;//8秒
while((DS3231_ReadReg(DS3231_STATUS)&0x04) == 0x04)
{
}
DS3231_WriteReg(DS3231_CONTROL,0x25);//001
DS3231_WriteReg(0x06,DEC_BCD(yea));
DS3231_WriteReg(0x05,DEC_BCD(mon));
DS3231_WriteReg(0x04,DEC_BCD(da));
DS3231_WriteReg(0x03,DEC_BCD(we));
DS3231_WriteReg(0x02,DEC_BCD(hou));
DS3231_WriteReg(0x01,DEC_BCD(min));
DS3231_WriteReg(0x00,DEC_BCD(sec));
}
/**
* @brief 获取时间
* @param
* @arg pBuffer:存放从DS3231读取的数据的缓冲区指针
* @arg ReadAddr:读取数据的DS3231的地址
* @arg NumByteToWrite:要从DS3231读取的字节数
* @retval 返回1,表示读取成功.
*/
void I2C_DS3231_getTime(void)
{
calendar.year= DS3231_ReadReg(0x06);
calendar.month= DS3231_ReadReg(0x05);
calendar.date= DS3231_ReadReg(0x04);
calendar.week= DS3231_ReadReg(0x03);
calendar.hour= DS3231_ReadReg(0x02);
calendar.hour&=0x3f;
calendar.min= DS3231_ReadReg(0x01);
calendar.sec= DS3231_ReadReg(0x00);
}
/**
* @brief 获取温度
* @param 无
* @retval 无
*/
void I2C_DS3231_getTemperature(void)
{
while((DS3231_ReadReg(DS3231_STATUS)&0x04) == 0x04)
{
}
DS3231_WriteReg(DS3231_CONTROL, 0x20|0x05);//0010 0000
//0000 0101 0010 0101
calendar.temperature=DS3231_ReadReg(DS3231_TEMPERATUREH);
}
/*计算公历天数得出星期*/
void GregorianDay(_calendar_obj * tm)
{
int leapsToDate;
int lastYear;
int day;
int MonthOffset[] = { 0,31,59,90,120,151,181,212,243,273,304,334 };
lastYear=tm->year-1;
/*计算从公元元年到计数的前一年之中一共经历了多少个闰年*/
leapsToDate = lastYear/4 - lastYear/100 + lastYear/400;
/*如若计数的这一年为闰年,且计数的月份在2月之后,则日数加1,否则不加1*/
if((tm->year%4==0) &&
((tm->year%100!=0) || (tm->year%400==0)) &&
(tm->month>2)) {
/*
* We are past Feb. 29 in a leap year
*/
day=1;
} else {
day=0;
}
day += lastYear*365 + leapsToDate + MonthOffset[tm->month-1] + tm->date; /*计算从公元元年元旦到计数日期一共有多少天*/
tm->week=day%7; //算出星期
}
DS3231.h
#ifndef __I2C_DS3231_H
#define __I2C_DS3231_H
/* Includes ------------------------------------------------------------------*/
#include "stm32f10x.h"
#include "stdio.h"
/* Exported types ------------------------------------------------------------*/
typedef struct
{
int hour;
int min;
int sec;
u32 year;
int month;
int date;
int week;
int temperature;
}_calendar_obj;
extern _calendar_obj calendar; //日历结构体
/* DS3231 地址定义 */
#define DS3231_ADDRESS 0xD0
/*等待超时时间*/
#define I2CT_FLAG_TIMEOUT ((uint32_t)0x1000)
#define I2CT_LONG_TIMEOUT ((uint32_t)(10 * I2CT_FLAG_TIMEOUT))
/*信息输出*/
#define DS3231_DEBUG_ON 0
#define DS3231_INFO(fmt,arg...) printf("<<-DS3231-INFO->> "fmt"\n",##arg)
#define DS3231_ERROR(fmt,arg...) printf("<<-DS3231-ERROR->> "fmt"\n",##arg)
#define DS3231_DEBUG(fmt,arg...) do{\
if(DS3231_DEBUG_ON)\
printf("<<-DS3231-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
}while(0)
/* DS3231寄存器地址 */
#define DS3231_SECOND 0x00 //秒
#define DS3231_MINUTE 0x01 //分
#define DS3231_HOUR 0x02 //时
#define DS3231_WEEK 0x03 //星期
#define DS3231_DAY 0x04 //日
#define DS3231_MONTH 0x05 //月
#define DS3231_YEAR 0x06 //年
/* 闹铃1 */
#define DS3231_SALARM1ECOND 0x07 //秒
#define DS3231_ALARM1MINUTE 0x08 //分
#define DS3231_ALARM1HOUR 0x09 //时
#define DS3231_ALARM1WEEK 0x0A //星期/日
/* 闹铃2 */
#define DS3231_ALARM2MINUTE 0x0b //分
#define DS3231_ALARM2HOUR 0x0c //时
#define DS3231_ALARM2WEEK 0x0d //星期/日
#define DS3231_CONTROL 0x0e //控制寄存器
#define DS3231_STATUS 0x0f //状态寄存器
#define BSY 2 //忙
#define OSF 7 //振荡器停止标志
#define DS3231_XTAL 0x10 //晶体老化寄存器
#define DS3231_TEMPERATUREH 0x11 //温度寄存器高字节(8位)
#define DS3231_TEMPERATUREL 0x12 //温度寄存器低字节(高2位)
/* Exported functions ------------------------------------------------------- */
void DS3231_Init(void);
void DS3231_WriteReg(uint8_t RegAddress, uint8_t Data);
uint8_t DS3231_ReadReg(uint8_t RegAddress);
void I2C_WaitDs3231StandbyState(void);
uint8_t BCD_DEC(u8 val);
uint8_t DEC_BCD(u8 val);
void I2C_DS3231_SetTime(u8 yea,u8 mon,u8 da,u8 we,u8 hou,u8 min,u8 sec);
void I2C_DS3231_getTime(void);
void I2C_DS3231_getTemperature(void);
void GregorianDay(_calendar_obj * tm);
#endif /* __I2C_DS3231_H */
main.c
#include "stm32f10x.h"
#include "Delay.h"
#include "DS3231.h"
#include "Serial.h"
#include <string.h>
/* Private typedef -----------------------------------------------------------*/
uint8_t i=0;
extern _calendar_obj calendar; //日历结构体
int main(void)
{
Serial_Init();
DS3231_Init();
I2C_DS3231_SetTime(23, 11, 11, 6, 20, 54, 00);
//Serial_SendByte(calendar.temperature);
while(1)
{
// Delay_ms(20);
// if(i==1)
// {
// //I2C_DS3231_SetTime(calendar.year, calendar.month, calendar.date, calendar.week, calendar.hour, calendar.min, calendar.sec);
// I2C_DS3231_SetTime(23, 11, 11, 6, 20, 54, 00);
// i=0;
// }
I2C_DS3231_getTime(); //获取时间
I2C_DS3231_getTemperature(); //获取温度
// printf("%d年%d月%d日%d时%d分%d秒 星期%d 温度%d\n",calendar.year+2000,calendar.month,calendar.date,\
// calendar.hour,calendar.min,calendar.sec,calendar.week,calendar.temperature);//打印到串口屏不能有printf("\n");换行!!!
Delay_ms(5000);
Serial_SendByte(calendar.year);
Serial_SendByte(calendar.month);
Serial_SendByte(calendar.date);
Serial_SendByte(calendar.week);
Serial_SendByte( calendar.hour);
Serial_SendByte(calendar.min);
Serial_SendByte(calendar.sec);
Serial_SendByte(calendar.temperature);
}
}