单片机RTC的中断剖析

实时时钟是一个独立的定时器。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 */

}

声明:本内容为作者独立观点,不代表电子星球立场。未经允许不得转载。授权事宜与稿件投诉,请联系:editor@netbroad.com
觉得内容不错的朋友,别忘了一键三连哦!
赞 1
收藏 2
关注 192
成为作者 赚取收益
全部留言
0/200
成为第一个和作者交流的人吧