STM32低功耗模式中RTC唤醒与PA0唤醒协同工作的实用指南

STM32 低功耗模式不同唤醒源的配合使用   by  矜辰所致

前言

关于 STM32 如何实现低功耗模式,我之前写过一篇文章:

STM32 使用 STM32CubeMX HAL库实现低功耗模式

各种休眠模式如何实现文中已经讲得很清楚了,但是作为教学文章,文中每次的休眠都是单一的唤醒源,但是在实际应用中,有时候需要根据不同的场合,需要不同的唤醒源配合使用,所以 本文主要内容就是说明一下多种唤醒源如何配合使用。

我是矜辰所致,全网同名,尽量用心写好每一系列文章,不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开!

目录

  • 前言
  • 一、 需求说明
  • 二、 设计实现
  • 2.1 确定唤醒源
  • 2.2 选择不同唤醒源
  • 2.3 RTC 相关问题
  • 三、 最终示例
  • 结语
  • 一、 需求说明

    本文使用一个示例来说明,需求如下:

    我们要实现的功能是要做一个低功耗设备,检测外部信号,我们把外部信号连接至 STM32 的 PA0 。

    如果检测到外部信号,执行某些工作(大概为200ms),但是呢,由于外部信号不是一次性的,而是持续一段时间的电平波动,所以在检测到一次外部信号以后,我们需要屏蔽PA0 一段时间,再重新开启 PA0 唤醒,为了保持低功耗,所以还是需要进入睡眠,除了PA0 唤醒后执行操作的 200ms 时间,其他时候都是在低功耗模式。

    我们根据上面的需求,整理一下思路,同时介绍一下我们的实现平台
    .
    硬件平台: STM32L010F4
    .
    工作模式: Standby 模式
    .
    具体需求:
    1、设备初次上电,直接进入 Standby 模式,设置只能通过 PA0 唤醒。
    2、PA0 唤醒后,延时200ms 用来替换唤醒后需要处理的工作,延时完成后进入 Standby 模式,屏蔽 PA0 唤醒源,开启 RTC 唤醒,RTC 唤醒时间设定为 5s 。
    3、5s 后 RTC 唤醒后,直接进入 Standby 模式,屏蔽 RTC 唤醒,开启 PA0 唤醒,设备只能通过 PA0 唤醒。
    4、检测到 PA0 唤醒,重复步骤 2:PA0 唤醒后,延时200ms 用来替换唤醒后需要处理的工作,延时完成后进入 Standby 模式,屏蔽 PA0 唤醒源,开启 RTC 唤醒,RTC 唤醒时间设定为 5s 。
    依次循环。

    二、 设计实现

    需求我们已经知道,接下来我们就来看看如何实现,首先第一点就是 如何判断芯片是从 RTC 唤醒还是通过 PA0 唤醒呢,在上一篇讲低功耗的文章中,我们通过 PWR_FLAG_SB 标志位来判断设备是不是从 Standby 模式唤醒,具体实现代码如下:

      if(__HAL_PWR_GET_FLAG(PWR_FLAG_SB) == SET){
        __HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
        if(HAL_RTC_GetTime(&hrtc,&sTime,RTC_FORMAT_BIN) == HAL_OK){
          printf("RTC current time: %02d:%02d:%02d\r\n",sTime.Hours,sTime.Minutes,sTime.Seconds);
        }
        printf("standby reset\r\n");
      }
      else{
        printf("normal reset!!!\r\n");
      }
    

    2.1 确定唤醒源

    那我们现在要判断 RTC 还是 PA0 ,我们可以检查 RTC_ISR 寄存器中的 WUTF 标志位( WUTF 名为 Wake Up Timer Flag)。

    如果芯片是从 RTC 唤醒,WUTF 标志位会被置位。

    我们可以使用下面语句判断:

    if (RTC->ISR & RTC_ISR_WUTF) {
        // RTC 唤醒触发
    }
    

    当然我们也可以使用 HAL 库:

    if (__HAL_RTC_WAKEUPTIMER_GET_FLAG(&hrtc, RTC_FLAG_WUTF)){
          printf("RTC reset\r\n");
       }
    

    那即便我们知道了 可以通过 WUTF 标志位判断是否芯片是从RTC 唤醒,那我们如何加入上面的判断呢? 我们回头看一下我们以前是通过 PWR_FLAG_SB 标志位来判断设备是不是从 Standby 模式唤醒,那么肯定是在判断完 PWR_FLAG_SB 标志位以后再进行 RTC 和 PA0 唤醒的判断。

    这里我就直接给出一个示例,后面再接着说明注意事项:

      if(__HAL_PWR_GET_FLAG(PWR_FLAG_SB) == SET){
    
        // 判断具体唤醒源
        if (__HAL_RTC_WAKEUPTIMER_GET_FLAG(&hrtc, RTC_FLAG_WUTF)){
          __HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&hrtc, RTC_FLAG_WUTF);
          __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
          printf("RTC reset\r\n");
          my_source = WAKEUP_SRC_RTC; 
        }
        else{
          // PA0唤醒:执行首次操作
          __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
          printf("PA0 reset\r\n");
          my_source = WAKEUP_SRC_PA0;  
        } 
        __HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
      }
      else{
        printf("normal reset\r\n");
        my_source = WAKEUP_SRC_PA0;  // 下一次由PA0唤醒
      }
    

    上面我们先通过 PWR_FLAG_SB 标志位判断设备是否从 Standby 模式唤醒,然后再通过判断 RTC_FLAG_WUTF 判断是否由RTC 唤醒,这里就有一点一定要注意一下!!!一定是先判断 RTC_FLAG_WUTF ,看是不是 RTC 唤醒,再确定是不是 PA0 唤醒,因为 PA0 唤醒没有标志位。

    我们在上一篇低功耗文章中有说到过:

    所以不管是 RTC 还是 PA0 唤醒 ,PWR_FLAG_WU 标志位都会置位,所以都需要清除一下。那因为我们知道是在 standby 模式下,只有通过唤醒引脚(PA0)上升沿、RTC闹钟中断,或者复位唤醒;所以,在我们确定是被唤醒源唤醒,而且不是 RTC 唤醒的情况下,一定是通过 PA0 唤醒。

    2.2 选择不同唤醒源

    完成唤醒源的判断,我们就可以按照我们的需求来进行接下来的设计,这里我也直接给出测试代码:

    while (1)
      {
        /*
        模拟工作状态,时间放长一点方便低功耗下的烧录
        */
        printf("working ...\r\n");
        HAL_Delay(2000);
        if (my_source == WAKEUP_SRC_PA0) {  
          HAL_RTC_MspInit(&hrtc);
          HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 4, RTC_WAKEUPCLOCK_CK_SPRE_16BITS);
          HAL_PWR_DisableWakeUpPin(PWR_WAKEUP_PIN1);
          printf("open RTC ,stop PA0!!!\r\n");
          
        } else {
          // 关闭 RTC 唤醒
          HAL_RTC_MspDeInit(&hrtc);
          HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
          printf("open PA0 ,stop RTC!!!\r\n");
        }
        
        // 关闭所有外设(根据需求调整)
        set_use_io_analog();
    
        // 进入Standby模式
        HAL_PWR_EnterSTANDBYMode();
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
      }
    

    这里 RTC 的屏蔽开启使用了 HAL_RTC_MspInit ,逻辑上看着是没有什么问题,但是实际测试起来还是有点问题:

    1. RTC 唤醒的时间,本意是 5s 后唤醒,但是实际上不到 5s 就被唤醒了。
    2. 会意外的唤醒,应该是标志位的问题

    我们虽然曾经说过 ,在实际使用中,进入 stop 或者 standby 模式之前,都得记得清除一下 PWR_FLAG_WU 标志位!!! 但是在确定唤醒源的时候,我们已经做了__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU); 操作,想着开头都清除了,在 while 循环中就没有加上这句话。

    所以在实际使用中,还是得加上这么一句,所以上面的程序部分改成如下:

     if (my_source == WAKEUP_SRC_PA0) {  
          HAL_RTC_MspInit(&hrtc);
          __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
          HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 4, RTC_WAKEUPCLOCK_CK_SPRE_16BITS);
          HAL_PWR_DisableWakeUpPin(PWR_WAKEUP_PIN1);
          
          printf("open RTC ,stop PA0!!!\r\n");
          
        } else {
          HAL_RTC_MspDeInit(&hrtc);
          __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
          HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
          
          printf("open PA0 ,stop RTC!!!\r\n");
        }
    

    所以还是得再次强调一遍,进入 stop 或者 standby 模式之前,务必清除一下 PWR_FLAG_WU 标志位!!!

    2.3 RTC 相关问题

    我们还要解决一个问题,就是上面 RTC 唤醒时间会早一点。

    在此之前,我们还需要讲一个问题,在上面示例代码中,我是通过HAL_RTC_MspInit(&hrtc);HAL_RTC_MspDeInit(&hrtc); 来开启和屏蔽 RTC 唤醒,虽然这种做法可行,但是其实不是很建议,因因为这种做法是停掉 RTC 所有的资源,包括时钟、NVIC ,每次重新初始化都得重新打开 NVIC, 而且频繁的 Init / DeInit 可能会导致功耗增高。

    官方有更加推荐的方式,使用HAL_RTCEx_DeactivateWakeUpTimer函数,这个函数功能明确:关闭 RTC 唤醒定时器功能(WakeUp Timer)。

    所以我们如果屏蔽 RTC 唤醒,直接使用上面函数:

    } else {
          // 关闭 RTC 唤醒
          HAL_RTCEx_DeactivateWakeUpTimer(&hrtc); 
          __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
          HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
          
          printf("open PA0 ,stop RTC!!!\r\n");
        }
    

    那接下来尝试解决一下为什么时间会提早的问题,我首先想到 STM32 的 Wakeup Timer 是一种 递减计数器,如果开启了的话他会一直计数,当我们调用 HAL_RTCEx_SetWakeUpTimer_IT() 时,它会立即开始计数,如果我们没有先关闭定时器,有没有可能旧的定时器的值没有被清除,RTC 正在计数又写入新的值产生异常? 这些是我们的推测,我们来把代码修改一下:

        if (my_source == WAKEUP_SRC_PA0) {
          HAL_PWR_DisableWakeUpPin(PWR_WAKEUP_PIN1);      // 禁用PA0唤醒  
          HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);         // 关闭 RTC
          __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);              // 清除上次唤醒标志
          HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 4, RTC_WAKEUPCLOCK_CK_SPRE_16BITS); // 设置4+1=5s
          //HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 2048 * 5, RTC_WAKEUPCLOCK_RTCCLK_DIV16);
          printf("open RTC ,stop PA0!!!\r\n");
          
        } else {
          // 关闭 RTC 唤醒
          ...
        }
    

    但是发现,即便这样还是不行,结果为大概 4s 左右:

    在上一篇文章我们知道,RTC 的唤醒时间为 HAL_RTCEx_SetWakeUpTimer_IT 的第二个参数的值+1,这点也可以在STM32 官方文档在 RM0376: Reference Manual for STM32L0x1 的 RTC 章节里查到:

    那这里肯定是有哪里没有注意到,但是呢,对于我们这个需求而言,如果实际效果和设置的第二个参数基本一致,那么也不是不能就这么用,于是乎,我测试了不同参数值:

    发现虽然与预期的设定不符合,但是是稳定的,这个地方确实不知道是哪里疏忽了,这里暂时就这么用着吧(程序设计,能用就行…… = =!如果大家知道是哪里的问题,还望留言告知,在这里提前感谢了!)

    三、 最终示例

    那么经过上文的测试,我们基本上可以实现我们需求的主要功能了,再完善一下一些细节问题就好了,那么针对我们文章开头的需求,最终的程序主题部分如下:

    int main(void)
    {
      HAL_Init();
      SystemClock_Config();
      MX_GPIO_Init();
      MX_LPUART1_UART_Init();
      MX_RTC_Init();
      /* USER CODE BEGIN 2 */
      if(__HAL_PWR_GET_FLAG(PWR_FLAG_SB) == SET){
        // 判断具体唤醒源
        if (__HAL_RTC_WAKEUPTIMER_GET_FLAG(&hrtc, RTC_FLAG_WUTF)){
          __HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&hrtc, RTC_FLAG_WUTF);
          __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
          printf("RTC reset\r\n"); 
          my_source = WAKEUP_SRC_RTC; 
        }
        else{
          // PA0唤醒:执行首次操作
          __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
          printf("PA0 reset\r\n");
          my_source = WAKEUP_SRC_PA0;  
        } 
        __HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
      }
      else{
        printf("normal reset\r\n");
        my_source = WAKEUP_SRC_PA0;  // 下一次由PA0唤醒
      }
      /* USER CODE END 2 */
    
      /* USER CODE BEGIN WHILE */
      while (1)
      {
        if (my_source == WAKEUP_SRC_PA0) {
          HAL_Delay(200);  //执行唤醒操作
          printf("working ...\r\n");
          HAL_PWR_DisableWakeUpPin(PWR_WAKEUP_PIN1);      // 禁用PA0唤醒  
          HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);         // 关闭 RTC
          __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);              // 清除上次唤醒标志
          HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 5, RTC_WAKEUPCLOCK_CK_SPRE_16BITS); // 
          //HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 2048 * 5, RTC_WAKEUPCLOCK_RTCCLK_DIV16);
          printf("open RTC ,stop PA0!!!\r\n");
          
        } else {
          // 关闭 RTC 唤醒
          HAL_RTCEx_DeactivateWakeUpTimer(&hrtc); 
          __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
          HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
          
          printf("open PA0 ,stop RTC!!!\r\n");
        } 
        // 关闭所有外设(根据需求调整)
        set_use_io_analog();
        // 进入Standby模式
        HAL_PWR_EnterSTANDBYMode();
      }
    
    }
    

    结语

    本文我们通过 RTC唤醒 和 PA0唤醒 配合使用完成了一个简单的示例,整体看来呢其实是很简单的,但是也得搞清楚不同的标志位的用途,然后再进入休眠模式之前务必清除 PWR_FLAG_WU 标志位。

    当然,文章依然有一个 RTC 唤醒时间的疑问,再次希望有知道原因的朋友留言告知!

    好了,本文就到这里,谢谢大家!

    作者:矜辰所致

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32低功耗模式中RTC唤醒与PA0唤醒协同工作的实用指南

    发表回复