硬件I2C实战(DS3231)

 

 

一、模块简介


DS3231是一款高精度I2C实时时钟器件,具有集成的温度补偿晶体振荡器。该器件包含电池输入端,断开主电源时仍可保持精确计时。集成的晶体振荡器可提高器件的长期精确度。DS3231的寄存器能保存秒、分、时、星期、日期、月、年和闹钟设置等信息。少于31天的月份,可自动调整月末日期,包括闰年补偿。时钟的工作格式为24小时或带AM/PM指示的12小时格式。DS3231提供两个可编程日历闹钟和一路可编程方波输出。DS3231与单片机通过I2C双向串行总线传输地址与数据。

DS3231时钟芯片结构原理

引脚图
74cf7ca883aa44cf9da3e424fe215db2.png

680010f532cb4495b0513b60229d6b61.png

VCC为电源引脚;

INT/SQW为低电平有效中断或方波输出:是低电平有效复位引脚;

N.C.表示无连接,外部必须接地;

GND为地;

VBAT为备用电源输入;

SDA为串行数据输入输出;

SCL为串行时钟输入。

内部结构图

505e7e303b1f4c8c89058aecad8192f7.png


DS3231典型应用电路
下图为DS3231典型应用电路,图中可看出,DS3231几乎不需要外部元件。

5af1caade341483fb12f6507ce7d7b32.png

 

DS3231时钟寄存器介绍


如下图1所示,DS3231的主要组成部分有8个模块,划分为4个功能组:TCXO、电源控制、按钮复位和RTC。

1c63b9fb236b467ba904bc013a991137.png

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);
		
		
	}
}	

 

 

 

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值