实时时钟是一个独立的定时器。RTC模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。RTC模块和时钟配置系统(RCC_BDCR寄存器)处于后备区域,即在系统复位或从待机模式唤醒后,RTC的设置和时间维持不变。系统复位后,对后备寄存器和RTC的访问被禁止,这是为了防止对后备区域(BKP)的意外写操作。执行以下操作将使能对后备寄存器和RTC的访问:
设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备接口时钟
设置寄存器PWR_CR的DBP位,使能对后备寄存器和RTC的访问。
其供电部分如图所示,当VDD断点之后,需要VBAT管脚为其供电,才能保证RTC的正常工作。
看一下中断函数,stm32不同系列的中断函数是不一样的
stm32F和L系列,比如低功耗这块
1.使用RTC闹钟功能:再进低功耗前先获取当前RTC的时间,在当前时间上加10分钟,算出唤醒时间,然后设置RTC闹钟唤醒时间,
设置函数:HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BCD)
RTC闹钟中断函数:void RTC_Alarm_IRQHandler(void)
2.使用RTC的WakeUp功能,最大计数值可以设置0x1FFFF,根据时钟频率可以任意调整延时唤醒时间,如果时1HZ的RTC计数频率,最大延时唤醒时间(0x1FFFF+1)*1/60/60=36小时
设置函数: HAL_RTCEx_SetWakeUpTimer_IT(&hrtc,600,RTC_WAKEUPCLOCK_CK_SPRE_16BITS);//RTC600秒后唤醒
RTC周期唤醒中断函数 : void RTC_WKUP_IRQHandler(void)
3.RTC全局中断函数 RTC_IRQHandler()
注意事项:
4.产生闹钟中断的前一瞬间,一定产生了秒中断,那么会先执行RTC_IRQHandler() 中断函数, 在RTC_IRQHandler() 执行的过程中,闹钟中断标志又被挂起,
由于RTC_IRQHandler()是全局中断函数,必须清除所有的中断标志,程序才能退出该函数, 假如RTC_IRQHandler() 和RTCAlarm_IRQHandler() 是同样的优先级,
要想让程序退出RTC_IRQHandler() 函数,那么你必须清除闹钟中断标志(如果不清除闹钟中断标志,程序会死在RTC_IRQHandler() ), 这样问题又出现了,清除闹钟中断标志后,程序就不会进入RTCAlarm_IRQHandler(),那么RTCAlarm_IRQHandler()函数永远也不会被执行。
5.STM32F10x有20条中断线,其中16条用于IO口中断使用,还有4条用于内部中断事件。EXTI17就是用于内部RTC闹钟唤醒中断事件时使用,所以初始化中除了打开RTC闹钟中断同时打开了EXTI17中断线。配置闹钟中断的话,也要开启EXTI17中断,特别注意。
6.STM32备份寄存器的配置与使用
嵌入式系统设计中,用来存储系统运行过程中的数据有很多种方式,而使用STM32的备份寄存器可以实现对少量数据的频繁存储。因为这种方式时将数据存储在RAM中,掉电则数据丢失,所以需要使用备份电源为芯片供电;也由于是在RAM中,理论上可以无限次存取。
代码如下
u8 RTC_Init()
{
u8 temp = 0;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP|RCC_APB1Periph_PWR,ENABLE);//电源时钟和背部时钟
PWR_BackupAccessCmd(ENABLE); //允许背部区域写
if (BKP_ReadBackupRegister(BKP_DR1) != 0xC0B4)
{
BKP_DeInit();
RCC_LSEConfig(RCC_LSE_ON);
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)
{
temp++;
delay_ms(10);
}
if(temp>=250)return 1;
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
RCC_RTCCLKCmd(ENABLE);
RTC_WaitForLastTask();
RTC_WaitForSynchro();
RTC_ITConfig(RTC_IT_SEC|RTC_IT_ALR, ENABLE); //打开RTC的秒中断和闹钟中断
RTC_WaitForLastTask();
RTC_EnterConfigMode(); //进入配置RTC模式
RTC_SetPrescaler(32767);
RTC_SetCounter(0); //初始值设定为0s
RTC_WaitForLastTask();
RTC_SetAlarm(40); //闹钟值设定为40s
RTC_WaitForLastTask(); //等待上述配置完成
RTC_ExitConfigMode(); //退出配置模式
BKP_WriteBackupRegister(BKP_DR1, 0XC0B4);
PWR_BackupAccessCmd(DISABLE); //不允许背部区域写操作
}
else
{
PWR_BackupAccessCmd(DISABLE);
RTC_WaitForSynchro();
RTC_ITConfig(RTC_IT_SEC|RTC_IT_ALR,ENABLE); //打开RTC的秒中断和闹钟中断
RTC_WaitForLastTask();
}
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn; //RTC全局中断的中断配置
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
return 0;
}
//此初始化函数在主函数中的用法
while(RTC_Init())
{
printf("INIT Programing is ERROR!!\r\n");
}
if (BKP_ReadBackupRegister(BKP_DR1) != 0xC0B4) 的意思就是让STM32上电后自检是不是第一次运行这个程序。BKP_ReadBackupRegister(BKP_DR1)代表读取BKP_DR1的值,如果第一次运行这个程序那这个值一定是0X0000,值和0XC0B4不相等就进入配置初始化程序。BKP_WriteBackupRegister(BKP_DR1, 0XC0B4)代表往BKP_DR1这个寄存器中写入0XC0B4,注意BKP_DR1这个值被写入之后就算复位他也不会被清除成0000。这样的话就算复位或者重新上电,初始程序也不会再执行一遍,所以RTC的值就不会再重新设置了。如果想要RTC的值重新从0开始计数,那就可以吧0XC0B4改成一个新的数字,重新下载一次程序就可以了。
最后在看STM32G系列的,H系列有喜欢的额可以尝试一下
只有这一个RTC中断函数,RTC_TAMP_IRQHandler
static void MX_RTC_Init(void)
{
/* USER CODE BEGIN RTC_Init 0 */
/* USER CODE END RTC_Init 0 */
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
RTC_AlarmTypeDef sAlarm = {0};
/* USER CODE BEGIN RTC_Init 1 */
/* USER CODE END RTC_Init 1 */
/** Initialize RTC Only
*/
hrtc.Instance = RTC;
hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
hrtc.Init.AsynchPrediv = 127;
hrtc.Init.SynchPrediv = 255;
hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;
hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
hrtc.Init.OutPutPullUp = RTC_OUTPUT_PULLUP_NONE;
if (HAL_RTC_Init(&hrtc) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN Check_RTC_BKUP */
// //重启后判断该寄存器是否有值,判定是不是第丿次初始化,是否要装载初始倿
// if(HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1) == 0xAA)
// {
// //已经初始化过了,直接跳出初始化函
// return;
// }
// //第一次初始化,将任意后备寄存器写任意值,做个标记,标记已经初始化过了,下次系统复位时不用初始匿
// HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0xAA);
/* USER CODE END Check_RTC_BKUP */
/** Initialize RTC and set the Time and Date
*/
sTime.Hours = 14;
sTime.Minutes = 50;
sTime.Seconds = 0;
sTime.SubSeconds = 0;
sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
sTime.StoreOperation = RTC_STOREOPERATION_RESET;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
{
Error_Handler();
}
sDate.WeekDay = RTC_WEEKDAY_THURSDAY;
sDate.Month = RTC_MONTH_JANUARY;
sDate.Date = 28;
sDate.Year = 21;
if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK)
{
Error_Handler();
}
/** Enable the Alarm A
*/
sAlarm.AlarmTime.Hours = 14;
sAlarm.AlarmTime.Minutes = 50;
sAlarm.AlarmTime.Seconds = 10; //设置 10s 后产生闹钟中断
sAlarm.AlarmTime.SubSeconds = 0;
sAlarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
sAlarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET;
sAlarm.AlarmMask = RTC_ALARMMASK_NONE;
sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_NONE;
sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
sAlarm.AlarmDateWeekDay = 28;
sAlarm.Alarm = RTC_ALARM_A;
if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN RTC_Init 2 */
/* USER CODE END RTC_Init 2 */
}