STM32 RTC时钟掉电日期不更新

        RTC(Real-Time Clock)实时时钟,是一个独立的定时器。拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。

        RTC 模块和时间配置系统(RCC_BDCR 寄存器)处于后备区域,即在系统复位或从待机模式唤醒后,RTC 的设置和时间维持不变。

        RTC 的时钟源可以从 LSE,LSI,HSE 中得到。一般使用的是 LSE,LSE是外部低速时钟,外接一个 32.768Khz 的晶振。为什么其晶振频率是 32.768Khz 呢?因为 2的15次方就是 32768。在分频15次后就是 1Hz,周期为 1s。同时这个参数也是工程师总结的,时钟最为准确。LSI是内部低速时钟,频率在 40khz,精度低,而且有温漂现象,所以不怎么使用。HSE是外部高速时钟,外接晶振频率在 4MHz 到 16MHz 之间,精度高,性能好,但在断电后就停止工作了,所以不适合。

        对于实现RTC掉电复位后更新日期现在使用比较广的是如下两种方式。

方式一:

        使用RTC时钟的BKP备份寄存器。HAL库在初始化的时候把时间写到计数寄存器中,日期保存在缓存区中,如果掉电日期信息就会丢失了。如果使用备份寄存器,把日期信息存储在寄存器中,因为RTC模块属于后备区域,所以寄存器在信息在复位后也是维持不变的。在下次上电后读取寄存器数据就可以知道日期了。但这个方式也有一种缺陷,日期虽然写在了备份寄存器中,由于没有上电,寄存器内的日期也没不能进行更新了。只要经过0点,显示的日期就出错了。

方式二:

        使用RTC时钟的可编程计数器,把当前时间初始化写入寄存器中。因为在断电后计数器也是在不断的计数的。在重新上电后,通过对计数进行解析就可以得到当前时间了。

代码实现,首先用stm32cubemx配置RTC时钟,然后生成keil工程文件。打开工程,

在rtc.c文件中编写RTC时钟设置函数RTC_Set,设置RTC时钟。

/*
************************************************************
*	函数名称:	void RTC_Set(uint16_t syear,uint8_t smon,uint8_t sday,uint8_t hour,uint8_t min,uint8_t sec)
*
*	函数功能:	设置RTC时钟
*
*	入口参数:	年,月,日,时,分,秒
*
*	返回参数:	无
*
*	说明:
************************************************************
*/
void RTC_Set(uint16_t syear,uint8_t smon,uint8_t sday,uint8_t hour,uint8_t min,uint8_t sec)
{
	uint16_t t;
	uint32_t seccount=0;
	
	if(syear<1970||syear>2099)	
		return; 
	
	for(t=1970;t<syear;t++)	//把所有年份的秒钟相加
	{
		if(Is_Leap_Year(t))
			seccount += 31622400;//闰年的秒钟数
		else 
			seccount += 31536000;			  //平年的秒钟数
	}
	
	smon-=1;
	
	for(t = 0; t<smon; t++)	   //把前面月份的秒钟数相加
	{
		seccount+=(uint32_t)mon_table[t]*86400;//月份秒钟数相加
		
		if(Is_Leap_Year(syear)&&t==1)
			seccount+=86400;//闰年2月份增加一天的秒钟数	   
	}
	
	seccount+=(uint32_t)(sday-1)*86400;//把前面日期的秒钟数相加 
	
	seccount+=(uint32_t)hour*3600;//小时秒钟数
	
  seccount+=(uint32_t)min*60;	 //分钟秒钟数
	
	seccount+=sec;//最后的秒钟加上去	
 
	//设置时钟
  RCC->APB1ENR|=1<<28;//使能电源时钟
	
  RCC->APB1ENR|=1<<27;//使能备份时钟
	
	PWR->CR|=1<<8;    //取消备份区写保护
	//上面三步是必须的!
	
	RTC->CRL|=1<<4;   //允许配置 
	
	RTC->CNTL=seccount&0xffff;
	
	RTC->CNTH=seccount>>16;
	
	RTC->CRL&=~(1<<4);//配置更新
	
	while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成 
//	HAL_Delay(1000);
	RTC_Get();//设置完之后更新一下数据 	   
}

时钟设置的方式是以s为单位,把设置时间装换为参数。然后使能时钟,把参数配置成计数寄存器的值,这样计数寄存器的值就是当前的时间了。

 RTC_Get函数就是通过获取计数寄存器的值,把计数寄存器的值以s为单位,转换成时间。

/*
************************************************************
*	函数名称:	void RTC_Get(void)
*
*	函数功能:	获取RTC时钟
*
*	入口参数:	无
*
*	返回参数:	无
*
*	说明:
************************************************************
*/
void RTC_Get(void)
{
	static uint16_t daycnt=0;
	uint32_t timecount=0; 
	uint32_t temp=0;
	uint16_t temp1=0;	  
	
 	timecount=RTC->CNTH;//得到计数器中的值(秒钟数)
	
	timecount<<=16;
	
	timecount+=RTC->CNTL;			 
 
 	temp=timecount/86400;   //得到天数(秒钟数对应的)
	
	if(daycnt!=temp)//超过一天了
	{	  
		daycnt=temp;
		
		temp1=1970;	//从1970年开始
		
		while(temp>=365)
		{				 
			if(Is_Leap_Year(temp1))//是闰年
			{
				if(temp>=366)temp-=366;//闰年的秒钟数
				else break;  
			}
			else temp-=365;	  //平年 
			temp1++;  
		} 
		
		calendar.w_year=temp1;//得到年份
		
		temp1=0;
		
		while(temp>=28)//超过了一个月
		{
			if(Is_Leap_Year(calendar.w_year)&&temp1==1)//当年是不是闰年/2月份
			{
				if(temp>=29)temp-=29;//闰年的秒钟数
				else break; 
			}
			else 
			{
				if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年
				else break;
			}
			temp1++;  
		}
		
		calendar.w_month=temp1+1;	//得到月份
		
		calendar.w_date=temp+1;  	//得到日期 
	}
	temp=timecount%86400;     		//得到秒钟数   	
	
	calendar.hour=temp/3600;     	//小时
	
	calendar.min=(temp%3600)/60; 	//分钟	
	
	calendar.sec=(temp%3600)%60; 	//秒钟
	
	calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);//获取星期   
}	

完整代码rtc.h如下:

/**
  ******************************************************************************
  * @file    rtc.h
  * @brief   This file contains all the function prototypes for
  *          the rtc.c file
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __RTC_H__
#define __RTC_H__

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

extern RTC_HandleTypeDef hrtc;

/* USER CODE BEGIN Private defines */

#pragma pack(1)
typedef struct 
{
	 uint8_t hour;
	 uint8_t min;
   uint8_t sec;			
	//公历日月年周
	 uint16_t w_year;
	 uint8_t  w_month;
	 uint8_t  w_date;
	 uint8_t  week;	
}_calendar_obj;	
#pragma pack()

extern _calendar_obj calendar;				//日历结构体

/* USER CODE END Private defines */

void MX_RTC_Init(void);

/* USER CODE BEGIN Prototypes */

void rtc_init_user(void);

void RTC_Set(uint16_t syear,uint8_t smon,uint8_t sday,uint8_t hour,uint8_t min,uint8_t sec);

void RTC_Get(void);

uint8_t Is_Leap_Year(uint16_t year);

uint8_t RTC_Get_Week(uint16_t year,uint8_t month,uint8_t day);

/* USER CODE END Prototypes */

#ifdef __cplusplus
}
#endif

#endif /* __RTC_H__ */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

完整代码rtc.c如下:

/**
  ******************************************************************************
  * @file    rtc.c
  * @brief   This file provides code for the configuration
  *          of the RTC instances.
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include "rtc.h"


/* USER CODE BEGIN 0 */

_calendar_obj calendar;//时钟结构体 
//月份数据表											 
const uint8_t table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表	 
//平年的月份日期表
const uint8_t mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};

/* USER CODE END 0 */

RTC_HandleTypeDef hrtc;

/* RTC init function */
//void MX_RTC_Init(void)
//{
//  /* USER CODE BEGIN RTC_Init 0 */

//  /* USER CODE END RTC_Init 0 */

//  RTC_TimeTypeDef sTime = {0};
//  RTC_DateTypeDef DateToUpdate = {0};

//  /* USER CODE BEGIN RTC_Init 1 */

//  /* USER CODE END RTC_Init 1 */
//  /** Initialize RTC Only
//  */	
	
    /**Initialize RTC Only 
    */
//  hrtc.Instance = RTC;
//  hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
//  hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
//  if (HAL_RTC_Init(&hrtc) != HAL_OK)
//  {
//    _Error_Handler(__FILE__, __LINE__);
//  }

    /**Initialize RTC and set the Time and Date 
    */
//	if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1) != 0x5051)
//	{
//		sTime.Hours = 0x16;
//		sTime.Minutes = 0x50;
//		sTime.Seconds = 0x0;

//		if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
//		{
//	//    _Error_Handler(__FILE__, __LINE__);
//		}

//		DateToUpdate.WeekDay = RTC_WEEKDAY_MONDAY;
//		DateToUpdate.Month = RTC_MONTH_JANUARY;
//		DateToUpdate.Date = 0x28;
//		DateToUpdate.Year = 0x20;

//		if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BCD) != HAL_OK)
//		{
//	//    _Error_Handler(__FILE__, __LINE__);
//		}
//		
//		__HAL_RTC_SECOND_ENABLE_IT(&hrtc,RTC_IT_SEC);	     //
//		DateBuff = DateToUpdate;  //
//		HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0x5051);   //
//		HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, (uint16_t)DateBuff.Year);
//		HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR3, (uint16_t)DateBuff.Month);
//		HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR4, (uint16_t)DateBuff.Date);
//		HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR5, (uint16_t)DateBuff.WeekDay);
//	}
//	else
//	{
//		DateBuff.Year    = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2);
//		DateBuff.Month   = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR3);
//		DateBuff.Date    = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR4);
//		DateBuff.WeekDay = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR5);
//		DateToUpdate = DateBuff;
//		
//		if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN) != HAL_OK)
//		{
			_Error_Handler();
//		}
//		__HAL_RTC_SECOND_ENABLE_IT(&hrtc,RTC_IT_SEC);	 //	
//	}
	
//}

void HAL_RTC_MspInit(RTC_HandleTypeDef* rtcHandle)
{

  if(rtcHandle->Instance==RTC)
  {
  /* USER CODE BEGIN RTC_MspInit 0 */

  /* USER CODE END RTC_MspInit 0 */
    HAL_PWR_EnableBkUpAccess();
    /* Enable BKP CLK enable for backup registers */
    __HAL_RCC_BKP_CLK_ENABLE();
    /* RTC clock enable */
    __HAL_RCC_RTC_ENABLE();
  /* USER CODE BEGIN RTC_MspInit 1 */

  /* USER CODE END RTC_MspInit 1 */
  }
}

void HAL_RTC_MspDeInit(RTC_HandleTypeDef* rtcHandle)
{

  if(rtcHandle->Instance==RTC)
  {
  /* USER CODE BEGIN RTC_MspDeInit 0 */

  /* USER CODE END RTC_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_RTC_DISABLE();
  /* USER CODE BEGIN RTC_MspDeInit 1 */

  /* USER CODE END RTC_MspDeInit 1 */
  }
} 

/* USER CODE BEGIN 1 */

void MX_RTC_Init(void)
{
	hrtc.Instance = RTC;
  hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
  hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
  if (HAL_RTC_Init(&hrtc) != HAL_OK)
	{
		
	}
}
  
void rtc_init_user(void)
{	
//	HAL_RTCEx_SetSecond_IT(&hrtc);//秒中断使能,没有配置这个中断可以不加
	if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1)!=0x5050)//是否第一次配置
	{
		RTC_Set(2021,7,20,12,00,0); //设置日期和时间			
		
		HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0x5050);//标记已经初始化过了
	}
	RTC_Get();//更新时间
}


/*
************************************************************
*	函数名称:	void RTC_Set(uint16_t syear,uint8_t smon,uint8_t sday,uint8_t hour,uint8_t min,uint8_t sec)
*
*	函数功能:	设置RTC时钟
*
*	入口参数:	年,月,日,时,分,秒
*
*	返回参数:	无
*
*	说明:
************************************************************
*/
void RTC_Set(uint16_t syear,uint8_t smon,uint8_t sday,uint8_t hour,uint8_t min,uint8_t sec)
{
	uint16_t t;
	uint32_t seccount=0;
	
	if(syear<1970||syear>2099)	
		return; 
	
	for(t=1970;t<syear;t++)	//把所有年份的秒钟相加
	{
		if(Is_Leap_Year(t))
			seccount += 31622400;//闰年的秒钟数
		else 
			seccount += 31536000;			  //平年的秒钟数
	}
	
	smon-=1;
	
	for(t = 0; t<smon; t++)	   //把前面月份的秒钟数相加
	{
		seccount+=(uint32_t)mon_table[t]*86400;//月份秒钟数相加
		
		if(Is_Leap_Year(syear)&&t==1)
			seccount+=86400;//闰年2月份增加一天的秒钟数	   
	}
	
	seccount+=(uint32_t)(sday-1)*86400;//把前面日期的秒钟数相加 
	
	seccount+=(uint32_t)hour*3600;//小时秒钟数
	
  seccount+=(uint32_t)min*60;	 //分钟秒钟数
	
	seccount+=sec;//最后的秒钟加上去	
 
	//设置时钟
  RCC->APB1ENR|=1<<28;//使能电源时钟
	
  RCC->APB1ENR|=1<<27;//使能备份时钟
	
	PWR->CR|=1<<8;    //取消备份区写保护
	//上面三步是必须的!
	
	RTC->CRL|=1<<4;   //允许配置 
	
	RTC->CNTL=seccount&0xffff;
	
	RTC->CNTH=seccount>>16;
	
	RTC->CRL&=~(1<<4);//配置更新
	
	while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成 
//	HAL_Delay(1000);
	RTC_Get();//设置完之后更新一下数据 	   
}
 
/*
************************************************************
*	函数名称:	void RTC_Get(void)
*
*	函数功能:	获取RTC时钟
*
*	入口参数:	无
*
*	返回参数:	无
*
*	说明:
************************************************************
*/
void RTC_Get(void)
{
	static uint16_t daycnt=0;
	uint32_t timecount=0; 
	uint32_t temp=0;
	uint16_t temp1=0;	  
	
 	timecount=RTC->CNTH;//得到计数器中的值(秒钟数)
	
	timecount<<=16;
	
	timecount+=RTC->CNTL;			 
 
 	temp=timecount/86400;   //得到天数(秒钟数对应的)
	
	if(daycnt!=temp)//超过一天了
	{	  
		daycnt=temp;
		
		temp1=1970;	//从1970年开始
		
		while(temp>=365)
		{				 
			if(Is_Leap_Year(temp1))//是闰年
			{
				if(temp>=366)temp-=366;//闰年的秒钟数
				else break;  
			}
			else temp-=365;	  //平年 
			temp1++;  
		} 
		
		calendar.w_year=temp1;//得到年份
		
		temp1=0;
		
		while(temp>=28)//超过了一个月
		{
			if(Is_Leap_Year(calendar.w_year)&&temp1==1)//当年是不是闰年/2月份
			{
				if(temp>=29)temp-=29;//闰年的秒钟数
				else break; 
			}
			else 
			{
				if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年
				else break;
			}
			temp1++;  
		}
		
		calendar.w_month=temp1+1;	//得到月份
		
		calendar.w_date=temp+1;  	//得到日期 
	}
	temp=timecount%86400;     		//得到秒钟数   	
	
	calendar.hour=temp/3600;     	//小时
	
	calendar.min=(temp%3600)/60; 	//分钟	
	
	calendar.sec=(temp%3600)%60; 	//秒钟
	
	calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);//获取星期   
}	

/*
************************************************************
*	函数名称:	uint8_t Is_Leap_Year(uint16_t year)
*
*	函数功能:	判断是否是闰年
*
*	入口参数:	年
*
*	返回参数:	该年份是不是闰年。1表示是,0表示不是
*
*	说明:月份   1  2  3  4  5  6  7  8  9  10 11 12
*				闰年   31 29 31 30 31 30 31 31 30 31 30 31
*				非闰年 31 28 31 30 31 30 31 31 30 31 30 31
************************************************************
*/
uint8_t Is_Leap_Year(uint16_t year)
{			  
	if(year%4 == 0) //必须能被4整除
	{ 
		if(year%100 == 0) 
		{ 
			if(year%400 == 0)
				return 1;//如果以00结尾,还要能被400整除 	   
			else 
				return 0;   
		}
		else 
			return 1;   
	}
	else 
		return 0;	
}					

/*
************************************************************
*	函数名称:	uint8_t RTC_Get_Week(uint16_t year,uint8_t month,uint8_t day)
*
*	函数功能:	根据当前年月日,获得星期
*
*	入口参数:	年,月,日
*
*	返回参数:	星期号
*
*	说明:
************************************************************
*/
uint8_t RTC_Get_Week(uint16_t year,uint8_t month,uint8_t day)
{	
	uint16_t temp2;
	uint8_t yearH,yearL;
	
	yearH=year/100;	
	yearL=year%100; 
	
	// 如果为21世纪,年份数加100  
	if (yearH>19)
		yearL+=100;
	// 所过闰年数只算1900年之后的  
	
	temp2 = yearL+yearL/4;
	
	temp2 = temp2%7; 
	
	temp2=temp2+day+table_week[month-1];
	
	if (yearL%4 == 0&&month<3)
		temp2--;
	
	return(temp2%7);
}

/* USER CODE END 1 */

/**
  * @}
  */

/**
  * @}
  */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

版权声明:本文为CSDN博主「Kay Note」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_41836400/article/details/120941848

生成海报
点赞 0

Kay Note

我还没有学会写个人说明!

暂无评论

相关推荐