STM32
直播中

一瞅一个准

13年用户 835经验值
私信 关注
[问答]

STM32F10xxx是怎样进入低功耗模式的呢

STM32低功耗的工作原理是什么?
STM32F10xxx有哪几种低功耗模式?

STM32F10xxx是怎样进入低功耗模式的呢?有哪几种方法?

回帖(2)

李琳

2021-11-29 14:22:47
10.1低功耗工作原理

STM32的工作电压(VDD)为2.0~3.6V。通过内置的电压调节器提供所需的1.8V电源。当主电源VDD掉电后,通过VBAT脚为实时时钟(RTC)和备份寄存器提供电源。





  图1电源管理 在系统或电源复位以后,微控制器处于运行状态。当CPU不需继续运行时,可以利用多种低功耗模式来节省功耗,例如等待某个外部事件时。用户需要根据最低电源消耗、最快速启动时间和可用的唤醒源等条件,选定一个最佳的低功耗模式。
STM32F10xxx有三种低功耗模式
● 睡眠模式(Cortex™-M3内核停止,所有外设包括Cortex-M3核心的外设,如NVIC、系统时钟(SysTick)等仍在运行)
● 停止模式(所有的时钟都已停止)
● 待机模式(1.8V电源关闭)
此外,在运行模式下,可以通过以下方式中的一种降低功耗:
● 降低系统时钟
● 关闭APB和AHB总线上未被使用的外设时钟。
  表1底功耗模式




从表中可以看到,这三种低功耗模式层层递进,运行的时钟或芯片功能越来越少,因而功耗越来越低。
睡眠模式中,仅关闭了 CPU 时钟,CPU 停止运行,但其片上外设,CM3 核心外设全都还照常运行。有两种方式进入睡眠模式,它的进入方式决定了从睡眠唤醒的方式,分别是 WFI(wait for interrupt)和 WFE(wait for event),即由等待“中断”唤醒和由“事件”唤醒。
停机模式中,进一步关闭了其它所有的时钟,于是所有的外设都停止了工作,但由于其 1.8V 区域的电源没有关闭,还保留了 CPU 的寄存器、内存的信息,所以从停机模式唤醒,并重新开启时钟后,还可以从上次停机处继续执行代码。停止模式可以由任意一个外部中断(EXTI)唤醒。在停止模式中可以选择电压调节器为开模式或低功耗模式,若选择低功耗模式,在唤醒时会加上电压调节器的唤醒延迟。
待机模式,这与我们平时印象中的手机关机模式相似,它除了关闭所有的时钟,还把1.8V 区域的电源也关闭了,也就是说,从待机模式唤醒后,由于没有之前代码的运行记录,只能对芯片复位,重新检测 boot 条件,从头开始执行程序。它有四种唤醒方式,分别是 WKUP(PA0)引脚的上升沿,RTC 闹钟事件,NRST 引脚的复位和 IWDG(窗口看门狗)复位。
在运行模式下,任何时候都可以通过停止为外设和内存提供时钟(HCLK和PCLKx)来减少功耗。为了在睡眠模式下更多地减少功耗,可在执行WFI或WFE指令前关闭所有外设的时钟。
通过设置AHB外设时钟使能寄存器(RCC_AHBENR)、APB2外设时钟使能寄存器(RCC_APB2ENR)和APB1外设时钟使能寄存器(RCC_APB1ENR)来开关各个外设模块的时钟。

10.1.1睡眠模式

 进入睡眠模式
通过执行WFI或WFE指令进入睡眠状态。根据Cortex™-M3系统控制寄存器中的SLEEPONEXIT位的值,有两种选项可用于选择睡眠模式进入机制:

  ● SLEEP-NOW:如果SLEEPONEXIT位被清除,当WRI或WFE被执行时,微控制器立即进入睡眠模式。
● SLEEP-ON-EXIT:如果SLEEPONEXIT位被置位,系统从最低优先级的中断处理程序中退出时,微控制器就立即进入睡眠模式。
在睡眠模式下,所有的I/O引脚都保持它们在运行模式时的状态。
 退出睡眠模式
如果执行WFI指令进入睡眠模式,任意一个被嵌套向量中断控制器响应的外设中断都能将系统从睡眠模式唤醒。
如果执行WFE指令进入睡眠模式,则一旦发生唤醒事件时,微处理器都将从睡眠模式退出。唤醒事件可以通过下述方式产生:

  ● 在外设控制寄存器中使能一个中断,而不是在NVIC(嵌套向量中断控制器)中使能,并且在Cortex-M3系统控制寄存器中使能SEVONPEND位。当MCU从WFE中唤醒后,外设的中断挂起位和外设的NVIC中断通道挂起位(在NVIC中断清除挂起寄存器中)必须被清除。
● 配置一个外部或内部的EXIT线为事件模式。当MCU从WFE中唤醒后,因为与事件线对应的挂起位未被设置,不必清除外设的中断挂起位或外设的NVIC中断通道挂起位。
该模式唤醒所需的时间最短,因为没有时间损失在中断的进入或退出上。
  表2 SLEEP-NOW模式 [tr]SLEEP-NOW模式说明[/tr]
进入在以下条件下执行WFI(等待中断)或WFE(等待事件)指令:– SLEEPDEEP = 0 和– SLEEPONEXIT = 0参考Cortex-M3系统控制寄存器。
退出如果执行WFI进入睡眠模式:中断。如果执行WFE进入睡眠模式:唤醒事件。
唤醒延时
表3 SLEEP-ON-EXIT模式 [tr]SLEEP-ON_EXIT模式说明[/tr]
进入在以下条件下执行WFI指令:–SLEEPDEEP = 0和–SLEEPONEXIT = 1参考Cortex™-M3系统控制寄存器
退出中断:参考中断向量表
唤醒延时

10.1.2停止模式

停止模式是在Cortex™-M3的深睡眠模式基础上结合了外设的时钟控制机制,在停止模式下电压调节器可运行在正常或低功耗模式。此时在1.8V供电区域的的所有时钟都被停止, PLL、 HSI和HSE RC振荡器的功能被禁止, SRAM和寄存器内容被保留下来。
在停止模式下,所有的I/O引脚都保持它们在运行模式时的状态。
 进入停止模式
在停止模式下,通过设置电源控制寄存器(PWR_CR)的LPDS位使内部调节器进入低功耗模式,能够降低更多的功耗。
如果正在进行闪存编程,直到对内存访问完成,系统才进入停止模式。
如果正在进行对APB的访问,直到对APB访问完成,系统才进入停止模式。可以通过对独立的控制位进行编程,可选择以下功能。

  ● 独立看门狗(IWDG):可通过写入看门狗的键寄存器或硬件选择来启动IWDG。一旦启动了独立看门狗,除了系统复位,它不能再被停止。
● 实时时钟(RTC):通过备份域控制寄存器 (RCC_BDCR)的RTCEN位来设置。
● 内部RC振荡器(LSI RC):通过控制/状态寄存器 (RCC_CSR)的LSION位来设置。
● 外部32.768kHz振荡器(LSE):通过备份域控制寄存器 (RCC_BDCR)的LSEON位设置。
在停止模式下,如果在进入该模式前ADC和DAC没有被关闭,那么这些外设仍然消耗电流。通过设置寄存器ADC_CR2的ADON位和寄存器DAC_CR的ENx位为0可关闭这2个外设。
 退出停止模式
当一个中断或唤醒事件导致退出停止模式时, HSI RC振荡器被选为系统时钟。
当电压调节器处于低功耗模式下,当系统从停止模式退出时,将会有一段额外的启动延时。如果在停止模式期间保持内部调节器开启,则退出启动时间会缩短,但相应的功耗会增加。
  表4 停止模式 [tr]停止模式说明[/tr]
进入在以下条件下执行WFI(等待中断)或WFE(等待事件)指令:– 设置Cortex-M3系统控制寄存器中的SLEEPDEEP位,– 清除电源控制寄存器(PWR_CR)中的PDDS位 , – 通过设置PWR_CR中LPDS位选择电压调节器的模式注:为了进入停止模式,所有的外部中断的请求位(挂起寄存器(EXTI_PR))和RTC的闹钟标志都必须被清除,否则停止模式的进入流程将会被跳过,程序继续运行。
退出如果执行WFI进入停止模式:设置任一外部中断线为中断模式(在NVIC中必须使能相应的外部中断向量)。参见中断向量。如果执行WFE进入停止模式:设置任一外部中断线为事件模式。参见唤醒事件管理。
唤醒延时HSI RC唤醒时间 + 电压调节器从低功耗唤醒的时间。
10.1.3待机模式

待机模式可实现系统的最低功耗。该模式是在Cortex-M3深睡眠模式时关闭电压调节器。整个1.8V供电区域被断电。 PLL、 HSI和HSE振荡器也被断电。 SRAM和寄存器内容丢失。只有备份的寄存器和待机电路维持供电。
 进入待机模式
可以通过设置独立的控制位,选择以下待机模式的功能:

  ● 独立看门狗(IWDG):可通过写入看门狗的键寄存器或硬件选择来启动IWDG。一旦启动了独立看门狗,除了系统复位,它不能再被停止。详见《STM32F10xxx参考手册》17.3节。
● 实时时钟(RTC):通过备用区域控制寄存器(RCC_BDCR)的RTCEN位来设置。
● 内部RC振荡器(LSI RC):通过控制/状态寄存器(RCC_CSR)的LSION位来设置。
● 外部32.768kHz振荡器(LSE):通过备用区域控制寄存器(RCC_BDCR)的LSEON位设置。
 退出待机模式
当一个外部复位(NRST引脚)、 IWDG复位、 WKUP引脚上的上升沿或RTC闹钟事件的上升沿发生时,微控制器从待机模式退出。从待机唤醒后,除了电源控制/状态寄存器(PWR_CSR),所有寄存器被复位。
从待机模式唤醒后的代码执行等同于复位后的执行(采样启动模式引脚、读取复位向量等)。 电源控制/状态寄存器(PWR_CSR)将会指示内核由待机状态退出。
待机模式可实现系统的最低功耗。该模式是在Cortex-M3深睡眠模式时关闭电压调节器。整个1.8V供电区域被断电。PLL、HSI和HSE振荡器也被断电。SRAM和寄存器内容丢失。只有备份的寄存器和待机电路维持供电。
  表5待机模式 [tr]待机模式说明[/tr]
进入在以下条件下执行WFI(等待中断)或WFE(等待事件)指令:– 设置Cortex™-M3系统控制寄存器中的SLEEPDEEP位;– 设置电源控制寄存器(PWR_CR)中的PDDS位;– 清除电源控制/状态寄存器(PWR_CSR)中的WUF位
退出WKUP引脚的上升沿、RTC闹钟事件的上升沿、NRST引脚上外部复位、IWDG复位。
唤醒延时复位阶段时电压调节器的启动
待机模式下的输入/输出端口状态在待机模式下,所有的I/O引脚处于高阻态,除了以下的引脚:

  ● 复位引脚(始终有效)
● 当被设置为防侵入或校准输出时的TAMPER引脚
● 被使能的唤醒引脚

10.2 STM32Cube生成工程

笔者这里使用的是按键唤醒,其电路如下:





  图2按键电路 我们在串口的例子的基础上进行配置。根据图1所示的电路,KEY1的引脚是PA0,我们将PA0的GPIO设置为上升沿触发的外部中断模式,添加3个LED的GPIO配置。参考博文如下。
我们将PB0、PG6、PG7配置输出模式(高电平、低电平均可)、输出速率、上/下拉等,默认即可。LED配置如下:





  图3 LED配置 KEY1的引脚是PA0,KEY1的引脚是PA0,我们将PA0的GPIO设置为上升沿触发的外部中断模式。





  图4 KEY配置 当然还需要进行NVIC配置。





  图5 NVIC配置 最后生成工程文件即可。
10.3低功耗具体代码分析

通过以上介绍,我们了解了进入低功耗模式的三种方法,在者三种模式中待机模式功耗最低。
10.3.1睡眠模式

睡眠模式很简单,就是通过以下任意函数调用则进入睡眠:
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFE);  不同之处在于第二个参数不同,其定义如下:





我们来看看函数原型:

/**
  * @brief Enters Sleep mode.
  * @note  In Sleep mode, all I/O pins keep the same state as in Run mode.
  * @param Regulator: Regulator state as no effect in SLEEP mode -  allows to support portability from legacy software
  * @param SLEEPEntry: Specifies if SLEEP mode is entered with WFI or WFE instruction.
  *           When WFI entry is used, tick interrupt have to be disabled if not desired as
  *           the interrupt wake up source.
  *           This parameter can be one of the following values:
  *            @arg PWR_SLEEPENTRY_WFI: enter SLEEP mode with WFI instruction
  *            @arg PWR_SLEEPENTRY_WFE: enter SLEEP mode with WFE instruction
  * @retval None
  */
void HAL_PWR_EnterSLEEPMode(uint32_t Regulator, uint8_t SLEEPEntry)
{
  /* Check the parameters */
  /* No check on Regulator because parameter not used in SLEEP mode */
  /* Prevent unused argument(s) compilation warning */
  UNUSED(Regulator);


  assert_param(IS_PWR_SLEEP_ENTRY(SLEEPEntry));


  /* Clear SLEEPDEEP bit of Cortex System Control Register */
  CLEAR_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));


  /* Select SLEEP mode entry -------------------------------------------------*/
  if(SLEEPEntry == PWR_SLEEPENTRY_WFI)
  {
    /* Request Wait For Interrupt */
    __WFI();
  }
  else
  {
    /* Request Wait For Event */
    __SEV();
    __WFE();
    __WFE();
  }
}
以上不同的调用也就是不同的宏定义,也就对应两种不同的指令,那么以上两条指令又是啥意思呢?WFI(Wait for interrupt)和WFE(Wait for event)是两个让ARM核进入low-power standby模式的指令,由ARM architecture定义,由ARM core实现。以上就是把汇编指令都封装成了诸如__Commnad()的函数形式,并且预编译为二进制包。那么以上指令都能让ARM进入睡眠模式,又有啥区别呢?
WFI来说,执行WFI指令后,ARM core会立即进入low-power standby state,直到有WFI Wakeup events发生。
WFE则稍微不同,执行WFE指令后,根据Event Register(一个单bit的寄存器,每个PE一个)的状态,有两种情况:如果Event Register为1,该指令会把它清零,然后执行完成(不会standby);如果Event Register为0,和WFI类似,进入low-power standby state,直到有WFE Wakeup events发生。
总结一下,这两条指令的作用都是令MCU进入休眠/待机状态以便降低功耗,但是略有区别:
WFI: wait for Interrupt 等待中断,即下一次中断发生前都在此hold住不干活
WFE: wait for Events 等待事件,即下一次事件发生前都在此hold住不干活
因此我们要项唤醒MCU,最简单的就是通过中断唤醒。
睡眠模式时通过按键中断唤醒,代码如下:

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */


  /* USER CODE END 1 */


  /* MCU Configuration--------------------------------------------------------*/


  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();


  /* USER CODE BEGIN Init */


  /* USER CODE END Init */


  /* Configure the system clock */
  SystemClock_Config();


  /* USER CODE BEGIN SysInit */


  /* USER CODE END SysInit */


  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
        HAL_UART_Receive_IT(&huart1, (uint8_t *)&RxBuffer, 1);
  /* USER CODE END 2 */


  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
                HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
                HAL_Delay(500);
                HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);


                HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_6);
                HAL_Delay(500);
                HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_6);


                HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_7);
                HAL_Delay(500);
                HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_7);
               
                printf("进入睡眠模式n");
    /* 挂起滴答定时器增加以避免滴答定时器中断唤醒睡眠模式 */
    HAL_SuspendTick();
    /* 进入睡眠模式,等待中断唤醒  KEY1按键下降沿唤醒*/
                HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
                //HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFE);
    /* 退出睡眠模式之后,释放滴答定时器中断 */
    HAL_ResumeTick();
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}
值得注意的是,WFI和WFE两条指令让MCU进入睡眠模式,均可通过按键中断唤醒,但是他们的唤醒本质是有区别的





  图6中断与事件 如上图所示,图中的蓝色虚线箭头标出了中断信号的传输路径,而红色箭头标出了事件的传输路径。虽然中断和事件的产生源都是一样的,都是通过按键产生,但是路径却有不同,中断是需要CPU参与的,需要软件的中断服务函数才能完成中断后产生的结果;事件是靠脉冲发生器产生一个脉冲,进而由硬件自动完成这个事件产生的结果,当然相应的联动部件需要先设置好,比如引起DMA操作,AD转换等。
举报

李波波

2021-11-29 14:22:54
10.3.2停止模式

进入停止模式之后,任何外部中断都可以唤醒低功耗,但是需要重新配置时钟,不然系统将以默认时钟(没有经过倍频)运行。笔者这里还是使用外部中断唤醒。我们先看看主函数:

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */


  /* USER CODE END 1 */


  /* MCU Configuration--------------------------------------------------------*/


  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();


  /* USER CODE BEGIN Init */


  /* USER CODE END Init */


  /* Configure the system clock */
  SystemClock_Config();


  /* USER CODE BEGIN SysInit */


  /* USER CODE END SysInit */


  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
        HAL_UART_Receive_IT(&huart1, (uint8_t *)&RxBuffer, 1);
       
        printf("n 进入停止模式 rn");
        /* 进入停止模式,设置电压调节器为低功耗模式,等待中断唤醒 KEY1按键下降沿唤醒*/
        HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON,PWR_STOPENTRY_WFI);       
  /* USER CODE END 2 */


  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
                HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
                HAL_Delay(500);
                HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);


                HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_6);
                HAL_Delay(500);
                HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_6);


                HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_7);
                HAL_Delay(500);
                HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_7);
               
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}
以上最重要的就一句:
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON,PWR_STOPENTRY_WFI);         
这是MCU进入低功耗模式的库函数,在stm32f10x_hal_pwr.c中实现,原型如下:

/**
  * @brief Enters Stop mode.
  * @note  In Stop mode, all I/O pins keep the same state as in Run mode.
  * @note  When exiting Stop mode by using an interrupt or a wakeup event,
  *        HSI RC oscillator is selected as system clock.
  * @note  When the voltage regulator operates in low power mode, an additional
  *         startup delay is incurred when waking up from Stop mode.
  *         By keeping the internal regulator ON during Stop mode, the consumption
  *         is higher although the startup time is reduced.   
  * @param Regulator: Specifies the regulator state in Stop mode.
  *          This parameter can be one of the following values:
  *            @arg PWR_MAINREGULATOR_ON: Stop mode with regulator ON
  *            @arg PWR_LOWPOWERREGULATOR_ON: Stop mode with low power regulator ON
  * @param STOPEntry: Specifies if Stop mode in entered with WFI or WFE instruction.
  *          This parameter can be one of the following values:
  *            @arg PWR_STOPENTRY_WFI: Enter Stop mode with WFI instruction
  *            @arg PWR_STOPENTRY_WFE: Enter Stop mode with WFE instruction   
  * @retval None
  */
void HAL_PWR_EnterSTOPMode(uint32_t Regulator, uint8_t STOPEntry)
{
  /* Check the parameters */
  assert_param(IS_PWR_REGULATOR(Regulator));
  assert_param(IS_PWR_STOP_ENTRY(STOPEntry));


  /* Clear PDDS bit in PWR register to specify entering in STOP mode when CPU enter in Deepsleep */
  CLEAR_BIT(PWR->CR,  PWR_CR_PDDS);


  /* Select the voltage regulator mode by setting LPDS bit in PWR register according to Regulator parameter value */
  MODIFY_REG(PWR->CR, PWR_CR_LPDS, Regulator);


  /* Set SLEEPDEEP bit of Cortex System Control Register */
  SET_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));


  /* Select Stop mode entry --------------------------------------------------*/
  if(STOPEntry == PWR_STOPENTRY_WFI)
  {
    /* Request Wait For Interrupt */
    __WFI();
  }
  else
  {
    /* Request Wait For Event */
    __SEV();
    PWR_OverloadWfe(); /* WFE redefine locally */
    PWR_OverloadWfe(); /* WFE redefine locally */
  }
  /* Reset SLEEPDEEP bit of Cortex System Control Register */
  CLEAR_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));
}
可以选择事件和中断唤醒两种方式,选择哪种方式是根据库函数的第二个参数决定的,笔者这里使用的中断唤醒。





停机模式下MCU唤醒之后,时钟和频率是没有经过倍频的,在F1上,低功耗唤醒之后,是8M频率运行,而正常运行是72M。所以,在唤醒停机模式之后,需要重新配置时钟。如下所示:

/**
  * @brief  停机唤醒后配置系统时钟: 使能 HSE, PLL并且选择PLL作为系统时钟.
  * @param None
  * @retval None
  */
void SYSCLKConfig_STOP(void)
{
  /* 使能 HSE */
  __HAL_RCC_HSE_CONFIG(RCC_HSE_ON);


  /* 等待 HSE 准备就绪 */
  while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET);
  
  /* 使能 PLL */
  __HAL_RCC_PLL_ENABLE();


  /* 等待 PLL 准备就绪 */
  while(__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) == RESET)
  {
  }


  /* 选择PLL作为系统时钟源 */
  __HAL_RCC_SYSCLK_CONFIG(RCC_SYSCLKSOURCE_PLLCLK);


  /* 等待PLL被选择为系统时钟源 */
  while(__HAL_RCC_GET_SYSCLK_SOURCE() != 0x08)
  {
  }
}
当然最简单就是直接HAL库函数:
SystemClock_Config(); 好了,我们最后再看看中断唤醒的函数:

/**
  * @brief  按键中断回调函数
  * @param  GPIO_Pin
  * @retval None
  */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
        if(GPIO_Pin==KEY_GPIO_PIN)
        {
                Delay(0xDFFFF);
               
                if(HAL_GPIO_ReadPin(KEY_GPIO,KEY_GPIO_PIN) == KEY_DOWN_LEVEL)
                {
                        /*  刚从停机唤醒,由于时钟未配置正确,
                        此printf语句的内容不能正常发送出去 */
                        printf("n 进入中断 n");               
                       
                        SystemClock_Config();
                        //SYSCLKConfig_STOP();                        //停机唤醒后需要启动HSE       


                        /*由于前面已经重新启动了HSE,
                        所以本printf语句能正常发出 */
                        printf("n 退出中断 n");       
                }
                __HAL_GPIO_EXTI_CLEAR_IT(KEY_GPIO_PIN);  //清除中断标志位
        }               
}
进入中断后主要是重启时钟。
10.3.3待机模式

待机模式的功耗最低。待机模式的具体步骤如下:
1) 使能电源时钟
因为要配置电源控制寄存器,所以必须先使能电源时钟。在库函数中,使能电源时钟的方法是:
__HAL_RCC_PWR_CLK_ENABLE();  
2) 设置 WK_UP 引脚作为唤醒源
使能时钟之后后再设置 PWR_CSR 的 EWUP 位,使能 WK_UP 用于将 CPU 从待机模式唤醒。在库函数中,设置使能 WK_UP 用于唤醒 CPU 待机模式的函数是:
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1); //使能唤醒管脚功能
3) 设置 SLEEPDEEP 位, 设置 PDDS 位,执行 WFI 指令,进入待机模式。
进入待机模式, 首先要设置 SLEEPDEEP 位( 该位在系统控制寄存器( SCB_SCR)的第二位,详见《 CM3 权威指南》), 接着我们通过 PWR_CR 设置 PDDS 位,使得 CPU 进入深度睡眠时进入待机模式,最后执行 WFI 指令开始进入待机模式,并等待 WK_UP中断的到来。在库函数中,进行上面三个功能进入待机模式是在函数HAL_PWR_EnterSTANDBYMode中实现的:

void HAL_PWR_EnterSTANDBYMode (void);
HAL_PWR_EnterSTANDBYMode()函数原型如下所示:
/**
  * @brief Enters Standby mode.
  * @note  In Standby mode, all I/O pins are high impedance except for:
  *          - Reset pad (still available)
  *          - TAMPER pin if configured for tamper or calibration out.
  *          - WKUP pin (PA0) if enabled.
  * @retval None
  */
void HAL_PWR_EnterSTANDBYMode(void)
{
  /* Select Standby mode */
  SET_BIT(PWR->CR, PWR_CR_PDDS);


  /* Set SLEEPDEEP bit of Cortex System Control Register */
  SET_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));


  /* This option is used to ensure that store operations are completed */
#if defined ( __CC_ARM)
  __force_stores();
#endif
  /* Request Wait For Interrupt */
  __WFI();
}
该函数中先配置了 PDDS 寄存器位及 SLEEPDEEP 寄存器位,接着调用__force_stores函数确保存储操作完毕后再调用 WFI 指令,从而进入待机模式。这里值得注意的是,待机模式也可以使用 WFE 指令进入的。
在进入待机模式后,除了被使能了的用于唤醒的 I/O,其余 I/O 都进入高阻态,而待机模式唤醒后,相当于复位 STM32 芯片,程序重新从头开始执行。
4) 最后编写 WK_UP 中断函数
因为我们通过 WK_UP 中断( PA0 中断)来唤醒MCU,所以我们有必要设置一下该中断函数,同时我们也通过该函数里面进入待机模式。
通过以上几个步骤的设置,我们就可以使用 STM32 的待机模式了,并且可以通过 WK_UP来唤醒 MCU。
主函数如下:

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */


  /* USER CODE END 1 */


  /* MCU Configuration--------------------------------------------------------*/


  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();


  /* USER CODE BEGIN Init */


  /* USER CODE END Init */


  /* Configure the system clock */
  SystemClock_Config();


  /* USER CODE BEGIN SysInit */
        /* 电源管理时钟使能 */
  __HAL_RCC_PWR_CLK_ENABLE();  
       
  /* USER CODE END SysInit */


  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
        HAL_UART_Receive_IT(&huart1, (uint8_t *)&RxBuffer, 1);


        /* 检测系统是否是从待机模式启动的 */
  if (__HAL_PWR_GET_FLAG(PWR_FLAG_WU) == SET)
  {
    printf("rn 使能后检测,待机唤醒复位 rn");
                /* 清除待机标志位 */
    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
  }
  else
  {
    printf("rn 使能后检测,上电复位 rn");
               
  }
  /* USER CODE END 2 */


  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
                HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
                HAL_Delay(500);
                HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);


                HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_6);
                HAL_Delay(500);
                HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_6);


                HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_7);
                HAL_Delay(500);
                HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_7);
               
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}
在循环体外使用库函数 PWR_GetFlagStatus 检测 PWR_FLAG_WU 标志位,当这个标志位为SET 状态的时候,表示本次系统是从待机模式唤醒的复位,否则可能是上电复位。其中PWR_FLAG_WU的标志位在stm32f10x_hal_pwr.h中定义。





中断函数如下:

/**
  * @brief  按键中断回调函数
  * @param  GPIO_Pin
  * @retval None
  */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
        if(GPIO_Pin==KEY_GPIO_PIN)
        {
                Delay(0xDFFFF);
               
                if(HAL_GPIO_ReadPin(KEY_GPIO,KEY_GPIO_PIN) == KEY_DOWN_LEVEL)
                {
                        if(PWR_Check_Standby())
                        {
                                printf("rn 进入待机模式rn");
                         /* The Following Wakeup sequence is highly recommended prior to each Standby mode entry
                                        mainly when using more than one wakeup source this is to not miss any wakeup event.
                                         - Disable all used wakeup sources,
                                         - Clear all related wakeup flags,
                                         - Re-enable all used wakeup sources,
                                         - Enter the Standby mode.
                                */
                                /* 禁用所有唤醒源: 唤醒引脚PA0 */
                                HAL_PWR_DisableWakeUpPin(PWR_WAKEUP_PIN1);


                                /* 清除所有唤醒标志位 */
                                __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
                               
                                /* 使能唤醒引脚:PA0做为系统唤醒输入 */
                                HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);


                                /* 进入待机模式 */
                                HAL_PWR_EnterSTANDBYMode();
                        }
                }
                        __HAL_GPIO_EXTI_CLEAR_IT(KEY_GPIO_PIN);  //清除中断标志位
        }               
}
PWR_Check_Standby()函数用于检测渐渐是否长按,当长按后就进入待机模式。

/**
  * @brief  用于检测按键是否被长时间按下
  * @param None
  * @retval None
  */
uint8_t PWR_Check_Standby(void)
{                       
        uint8_t downCnt =0;                //记录按下的次数
        uint8_t upCnt =0;        //记录松开的次数                       


        while(1)                        //死循环,由return结束
        {       
                HAL_Delay(20);                //延迟一段时间再检测


                if(HAL_GPIO_ReadPin(KEY_GPIO,KEY_GPIO_PIN)==KEY_DOWN_LEVEL)        //检测到按下按键
                {
                        //点亮所有LED灯
                        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
                        HAL_GPIO_WritePin(GPIOG, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_SET);
                       
                        downCnt++;                //记录按下次数
                        upCnt=0;        //清除按键释放记录
                        if(downCnt>=100)                //按下时间足够
                        {
                                HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
                                HAL_GPIO_WritePin(GPIOG, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_RESET);
                                return 1;         //检测到按键被时间长按下
                        }
                }
                else
                {
                        upCnt++;         //记录释放次数
                        if(upCnt>5)                //连续检测到释放超过5次
                        {
                                HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
                                HAL_GPIO_WritePin(GPIOG, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_RESET);
                                return 0;        //按下时间太短,不是按键长按操作
                        }
                }
        }
}
10.4低功耗实验现象

10.4.1睡眠模式

将程序编译好后下载到板子上,可以看到3个LED依次闪烁,然后熄灭。按下KEY1按键,3个LED再次依次闪烁。然后再次进入睡眠模式。





10.4.2停止模式

将程序编译好后下载到板子上。按下KEY1按键,3个LED依次闪烁。串口依次打印信息如下:





10.4.3待机模式

将程序编译好后下载到板子上。按下按键,3个LED依次闪烁。长按KEY1按键,MCU进入待机模式,LED熄灭,再次按下KEY1按键,3个LED会同时点亮,直到同时熄灭,松开KEY1按键,KEY1按下会使 PA0 引脚产生中断,从而唤醒系统。
系统唤醒后会进行复位,从头开始执行上述过程,与第一次上电时不同的是,这样的复位会使 PWR_FLAG_WU 标志位改为 SET 状态,3个LED再次依次闪烁。串口打印信息如下。






  
举报

更多回帖

发帖
×
20
完善资料,
赚取积分