STM32 低功耗模式下 RTC唤醒 和 PA0唤醒 的配合使用

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

前言

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

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

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

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

目录

  • 前言
  • [一、 需求说明](#一、 需求说明)
  • [二、 设计实现](#二、 设计实现)
    • [2.1 确定唤醒源](#2.1 确定唤醒源)
    • [2.2 选择不同唤醒源](#2.2 选择不同唤醒源)
    • [2.3 RTC 相关问题](#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 模式唤醒,具体实现代码如下:

c 复制代码
  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 标志位会被置位。

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

c 复制代码
if (RTC->ISR & RTC_ISR_WUTF) {
    // RTC 唤醒触发
}

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

c 复制代码
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 唤醒的判断。

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

c 复制代码
  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 选择不同唤醒源

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

c 复制代码
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 循环中就没有加上这句话。

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

c 复制代码
 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 唤醒,直接使用上面函数:

c 复制代码
} 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 正在计数又写入新的值产生异常? 这些是我们的推测,我们来把代码修改一下:

c 复制代码
    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 章节里查到:

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

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

三、 最终示例

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

c 复制代码
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 唤醒时间的疑问,再次希望有知道原因的朋友留言告知!

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

相关推荐
十月的皮皮8 小时前
stm20260720-从新手 C 到量产 STM32 工程:程序设计推导指南
c语言·开发语言·stm32·stm32cubemx·hal库
开发笔记-阿牛9 小时前
解析一款有意思的产品||CK6159A语音芯片设计的儿童专注力训练机-舒尔特玩具
stm32·单片机·嵌入式硬件·音频
搞全栈小苏11 小时前
SIMetrix_simplis8.4安装教程——附安装包
stm32·单片机·嵌入式硬件·电力电子
qq_5739678212 小时前
STM32控制器
stm32·单片机·嵌入式硬件
某林21212 小时前
履带小车底盘stm32F103RCT6开发
人工智能·stm32·单片机·嵌入式硬件·架构·人机交互·ros2
做萤石二次开发的哈哈14 小时前
萤石 ERTC Windows SDK 包含哪些类型定义?
stm32·单片机·嵌入式硬件
亿道电子Emdoor14 小时前
【Aitium】PCB导出DXF文件出现eDuplicateRecordName报错
stm32·单片机·嵌入式硬件·altium
巴巴媛6661 天前
STM32学习笔记【38.DHT11实验】
笔记·stm32·学习
深圳市泰凌微电子1 天前
HK航顺 HK32F407RGT7替STM32F405RGT6 微控制器32位MCU
stm32·单片机·嵌入式硬件
小心亦新1 天前
STM32学习16--定时器输出比较2直流电机
stm32·嵌入式硬件·学习