????????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>© 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>© 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****/
|