我们知道,通常来讲,当MCU基于WFI指令进入低功耗模式后,可以通过中断事件来唤醒。有人会想,如果在进入低功耗之前,将所有可配置中断响应关闭掉,中断事件是否还能唤醒沉睡的MCU呢?
具体做法就是通过调用编译器内建函数__disable_irq()将特殊寄存器PRIMASK进行写1来屏蔽CPU对各种可配置中断的响应。在此,我们不妨做些相关验证与探讨。这里我使用STM32G0B1开发板进行相关测试。使用STM32CubeMx进行初始化配置。使用一外部按键触发中断实现唤醒。
另外,使用UART2基于查询方式做些打印提示。
我在main()函数的适当位置通过WFI指令进入STOP模式,并在进入STOP模式前调用__disable_irq()来关闭CPU对各类可配置中断的响应,看看在发生外部中断时MCU能否被唤醒。
测试代码如下【为了代码简洁,多处用宏替换】:
int main(void){HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART2_UART_Init();MX_TIM3_Init();__HAL_RCC_PWR_CLK_ENABLE();while (1){PrintWorkString(); //打印四行working字符串PRINTENTERSTOP; //打印Stop mode Entered提示行STOPSYSTICK; //暂停SYSTICK,不再计数,也不会触发中断__disable_irq(); //屏蔽所有可配置中断,即置1 PRIMASK .ENTERSTOPMODE;//进入STOP via WFI指令,等待中断唤醒UsrDelay(); //非中断延时,唤醒后揉揉眼、打个呵欠定定神。可选。RESTARTSYSTICK; //恢复SYSTICK的工作__enable_irq(); //此处必须开启总中断。下面函数都用到SYSTICK中断,否则下面函数无法正常工作SystemClock_Config();HAL_Delay(500);}}
经测试,基于上面实现代码,此时的MCU是可以被外部中断唤醒的。下面是测试过程中的输出信息截图【每次停在等待唤醒状态】:
其中,字符串“Key Pressed ====> Wake UP~”是在按键中断程序里输出的。
刚才的测试是将WFI指令放在主流程进入低功耗模式,如果把WFI指令的执行放在某个中断服务程序执行,此时的唤醒效果会怎么样呢?
我这里另外启用一个TIMER更新中断,把执行进入低功耗模式的代码放进该中断服务程序。然后我在主循环体里的每次循环过程中,通过软件方式触发TIMER中断,令MCU进入低功耗模式。
我们依然利用按键中断作唤醒,看看结果怎么样。我将主循环代码稍作修改,并不再于主循环里做关总中断响应的操作。相关代码如下:
while (1){PrintWorkString(); //打印working字符串提示行PRINTENTERSTOP; //打印enter stop 字符串提示行STOPSYSTICK; //暂停SYSTICK,不再计数,也不会触发中断// __disable_irq(); //屏蔽所有可配置中断,即置1 PRIMASK// ENTERSTOPMODE;//进入STOP via WFI指令,等待中断唤醒GenerateTIM3int();//产生更新中断,将于该中断ISR进入低功耗模式UsrDelay(); //非中断延时,唤醒后揉揉眼、打个呵欠定定神。可选。RESTARTSYSTICK; //恢复SYSTICK的工作__enable_irq(); //此处必须开启总中断。下面函数都用到SYSTICK中断,否则下面函数无法正常工作SystemClock_Config();HAL_Delay(500);}
跟之前相比,主循环代码屏蔽了关总中断响应和进入低功耗模式的代码,增加了触发TIM3更新中断的代码。TIM3更新中断的代码也很简单,基于库代码基础,我只在刚进入该中断程序的开头让MCU进入低功耗。如果没有低功耗影响的话,每次进TIM3更新中断会输出一行提示字符串“TIMER ISR Response”。
代码这样设计的话,每次触发TIM3更新中断后马上会进入低功耗模式,若不是被唤醒是不会输出相应提示字符串的。经过测试发现,只要用于唤醒的EXTI中断优先级不高于TIM3更新中断优先级【注意,对于STM32G0,它是基于ARM Cortex-M0+内核的芯片,没有子优先级说法】,它就没法唤醒MCU。比方,当二者的中断优先级这样配置时,怎么摁按键都不能唤醒沉睡的芯片的。
在串口终端就一直显示停止在这里,等待被唤醒。
如果将EXTI的中断优先级配置得比TIM3的高时,按键操作就能可靠唤醒MCU。比方这样配置时【注意,数字大反而优先级低】:
我们可以看到MCU休眠前后、执行中断程时的相关提示信息,与预期相符。
刚才的测试,我们没有在TIM3的中断程序入口做关闭总中断操作,如果把这句关总中断响应的代码加上去会如何呢?即将TIM3中断程序改成这样:
其它代码不动的前提下,经测试发现,仍然是只要将按键EXTI的中断优先级配置得比TIM3中断优先级高时,按键操作就能唤醒MCU。下面是正常唤醒时的输出截图:
经测试验证发现,进入STOP模式之前不管是否关闭总中端的响应,只要用作唤醒事件的中断优先级高于进入STOP模式时所处代码原生中断优先级【这个词是我杜撰的,此刻没想出一个更合适的词】时就能唤醒MCU。
STM32G0是基于ARM cortex-M0+内核的芯片,属于ARMV6-M的架构,在相关手册里针对WFI唤醒事件有描述,我将部分原文拷贝如下:
ARMV6-M:When a processor issues a WFI instruction it can suspend execution and enter a low-power state. It can remain in that state until the processor detects one of the following WFI wake up events:
- A reset.An asynchronous exception at a priority that, if PRIMASK.PM was set to 0, would preempt any currently active exceptions.
Note:If PRIMASK.PM is set to 1, an asynchronous exception that has a higher group priority than any active exception results in a WFI instruction exit. If the group priority of the exception is less than or equal to the execution group priority, the exception is ignored.
- If debug is enabled, a debug event.An IMPLEMENTATION DEFINED WFI wakeup event.
我们重点关注黄色高亮内容,应该说测试结果跟描述是一致的。顺便提下,ARMV7-M的相关手册关于WFI事件的描述的意思跟这里的是一样的。
看到这里,有人或许会问,刚才把进入低功耗模式的代码放在TIM3中断入口,通过EXTI中断事件唤醒时,调用__disable_irq()和不调用__disable_irq(),程序的运行结果真的毫无差别吗?单从唤醒的角度看,只要EXTI的中断优先级高于TIM3的中断优先级就一定能唤醒,这点是没有差别的。
但从整个程序的运行流程及结果来看,还是存在差别的。【细心的人或许已经发现差别了】我将调用和不调用__disable_irq()的两种输出结果放在一起来比较下:
上图左右两部分对应两种应用情形。不妨看看,思考下哪边是没有在TIM3中断程序里关总中断的。我们只需比较红色框内输出信息的先后顺序,分别是在EXTI和TIM3中断程序里输出的。
不难看出,左边部分是没有在TIM3中断程序做关总中断响应【即调用__disable_irq()】的输出情形。为什么呢?按键中断事件唤醒MCU后,因为它的优先级高于TIM3的,CPU就从TIM3中断程序跳到EXTI中断去执行,之后才返回来继续执行TIM3的中断程序,这样就很自然地导致EXTI里的打印信息要早于TIM3的。
而右边呢?因为在进低功耗模式前做了关闭总中断响应的操作,按键中断事件可以唤醒MCU没问题。不过,尽管EXTI优先级比TIM3原生优先级高,但由于在TIM3中断里做了关总中断的操作,导致唤醒后该操作的后续程序代码执行优先级要高于或者至少不低于EXTI的优先级。
换言之,此时EXTI没法像之前一样抢占TIM3的中断执行。这样的话,TIM3中断服务程序就从唤醒处继续执行直至完毕,这就导致TIM3中断里的打印信息一定会早于EXTI里的打印信息。
其实,当在TIM3中断时调用了__disable_irq()后,如果不在别的地方调用__enable_irq()打开总中断响应的话,按键EXTI中断事件就只能行使唤醒任务,其中断服务程序是没有机会运行的,也就没法输出相关打印信息。
嗯?!现在怎么又看到按键EXTI的中断程序的执行并输出相关信息呢?那是因为我在主循环里,唤醒后调用了__enable_irq()代码。见下面主循环代码的唤醒后的部分代码。
既然这样,如果将这行代码屏蔽掉,是不是就看不到EXTI中断里的打印输出呢?是的。若屏蔽该行,当MCU被唤醒后,TIM3中断程序执行完毕,程序运行最后会卡在systemclock_config()里面,因为它内部还要基于systick中断做超时计数,此刻它也没法得到响应。不妨看看最后演示结果:
MCU进入低功耗后,等待按键唤醒。成功唤醒后,可以看到TIM3中断程序的顺畅运行,但见不到EXTI中断程序的执行,此时它一直处于中断挂起状态。可谓:但见舞者耀,不见举灯人。【注:如果有人对开篇的第一个测试结果有类似疑问的话,这里也算一并解答了】
稍微小结下:当MCU基于WFI指令进入低功耗模式后,只要是用作唤醒事件的中断优先级高于执行WFI指令所处程序代码的原生优先级时就能唤醒,跟是否开、关总中断响应无关。
但是,能否唤醒和程序怎么执行,尤其是中断程序如何响应又是另外一回事,要具体分析。如果希望MCU唤醒后从休眠处立即继续执行程序,可以在进入休眠前调用__disable_irq()临时关闭总中断,然后在适当的时候开启;如果说MCU被唤醒后,对于是否立即无耽搁地从休眠处接着执行程序不关心的话,就没必要做这个操作。
其实,__disable_irq()可以理解成一个具有提升当前执行程序优先级的函数,此处不多赘述,本人另外一篇公众号文章《常被误解的开、关总中断话题》可以阅读参考。就此打住,再聊~!
93