我們知道,通常來講,當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喚醒事件有描述,我將部分原文拷貝如下: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:- An asynchronous exception at a priority that, if PRIMASK.PM was set to 0, would preempt any currently active exceptions.
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()可以理解成一個具有提升當前執行程序優先級的函數,此處不多贅述,本人另外一篇公眾號文章《常被誤解的開、關總中斷話題》可以閱讀參考。就此打住,再聊~!1、一跟安全屬性相關的DMA方式UART發送異常話題3、為什么 HRTIM 的 TIMx 輸出總是無效 ?5、使用GPIO+DMA+TIM模擬SPI通信演示