目前STM32芯片都是基于各種ARM Cortex-M內核的芯片,支持可編程中斷優先級。支持中斷搶占的同時,還中斷響應的晚到和咬尾機制。中斷搶占不難理解,就是某中斷在運行時產生了另外的更高優先級的中斷事件,低優先級的中斷服務程序被暫停而去執行優先級更高的中斷服務程序【后面中斷服務程序用ISR表示】。
所謂晚到機制,就是中斷響應還在壓棧階段但沒有正式進入ISR時又來了更高優先級的中斷請求,等壓棧操作完成后,則立刻執行高優先級的ISR,此時高優先級中斷省去壓棧操作,它執行完后再返回執行剛才申請壓棧的低優先級ISR。而咬尾機制則是指某ISR正在執行過程中產生了新的不高于當前中斷優先級的中斷事件,一直等到當前中斷ISR執行完畢,在退棧之前立即響應處于等待執行的ISR,此時該中斷也無須再做壓棧。不難理解,不論晚到機制還是咬尾機制都是為了提高中斷響應速度,提高芯片性能。
下面是三幅分別代表中斷搶占、中斷晚到、中斷咬尾的示意圖。【注:從時間上講,雖然發生中斷晚到、中斷咬尾可以顯著節省時間,但也不等于從中斷申請到進入ISR完全不要時間,下面圖中沒有體現出來。】
對于這幾個中斷響應的概念,我們是否可以比較直觀地感受下呢?
或許有人想過一些諸如時間記錄、波形輸出等方法來感受之。這里,我想從中斷響應先后順序加上棧幀內容變化來體會中斷搶占、中斷晚到和中斷咬尾。
對于Cortex M系列芯片【這里暫時將M33除外,我不知這個地方加上Security是否有很大差異。印象中只是有兩套而已】,發生中斷需要壓棧的話,壓棧內容根據是否啟用浮點存儲來進行。
上圖表述了兩種壓棧格式。這里我們只關注右邊的壓棧格式,后面試驗不使用浮點存儲。
我這里借助Cortex-M4內核的STM32G4芯片的TIM1/TIM2的更新中斷來完成試驗。二者建立主從同步啟動關系,TIM1的搶占優先級高于TIM2的,均工作在單脈沖模式,即每次啟動后都能且只能產生1次更新中斷。通過給二者設置相應的溢出時間參數,來模擬實現上面的三種情形。我們從中關注棧幀和個別特定寄存器的內容。【注:本試驗過程中僅開啟了TIM1/TIM2更新事件中斷,再無其它。】
對于發生搶占情形,我們通過在代碼里設置斷點,一方面可以看到2個中斷的響應先后,另一方面可以看到因搶占動作導致棧幀內容的變動。
對于晚到情形和晚到情形,同樣也會通過在代碼里設置斷點查看中斷執行先后順序。
對于晚到情形,申請壓棧的是低優先級中斷事件,先得到執行的則是高優先級中斷,同時會發現雖然響應了2次中斷,卻只看到1次壓棧,兩次ISR運行時維持同一棧內容。對于咬尾情形在棧幀內容變化上跟晚到情形類似,但實現機理不同,它申請壓棧的是高優先級或同級中斷事件,做咬尾操作的是低優先級或晚發生中斷請求的同級中斷。
先介紹兩個跟中斷返回有關的寄存器LR(R14)和EXC_RETURN。糾正下,EXC_RETURN不是寄存器,是微處理器動態生成的跟中斷返回有關的一個值。這個值有點神秘,也很重要。神秘的就是這個值怎么產生的、放在哪里的,似乎在ARM相關手冊找不到具體說明。另外,這個值本身很特別,大大區別于通用程序運行地址。如果程序里不啟用浮點存儲,它的值可能是下面三個。
這里我們重點關注圖中的前2個,后面試驗過程中會見到這兩個值。結合圖中信息,如果該值等于0xfffffff1,ISR執行完畢后要返回Handle Mode和Main Stack,極大可能地發生了中斷嵌套;如果該值等于0xfffffff9,ISR執行完畢后要返回Thread Mode和Main Stack,意味著當前中斷是在線程中產生的,不用OS的話,即Main程序被打斷。
它很重要,中斷返回得仰仗它。沒有它,中斷返回可能就亂套了。
每當中斷壓棧申請完成后,這個EXC_RETURN值就被硬件根據中斷發生時CPU運行狀態、運行模式、所用棧幀模式給生成好了,并在開始運行ISR之前將該值主動賦給LR寄存器。硬件在ISR執行完畢即將退棧返回時又自動將LR的內容提供給PC寄存器。當PC寄存器發現這個特殊的值后會不會一臉懵逼,啥玩意?地址不像地址。我們可以把這個值理解成中斷返回告知書,并非程序地址。EXC_RETURN值通過LR寄存器做中間人傳達給PC,主要傳達下面幾個信息:
1、恭喜我們完美地處理了剛才的突發事件,要歸隊返回了;
2、我們清楚剛才處理事情時的狀態和待遇,但更要清楚返回后的狀態、模式,不得以剛才的模式或狀態來套返回后的模式或狀態,不能因出了趟差就不知回家后的姿態和責任;
3、記住上面提到的,具體返回路線會專人提供【即之前壓棧的PC值經出棧提供】;
戲說下,知道大意即可,更多細節可以查看相關手冊。退一步講,個中細節我們旁人也真的難以知曉。
鋪墊性的話題就聊到這里。下面具體看看針對中斷搶占、中斷晚到、中斷咬尾的試驗。
先看中斷搶占的情形。下面截圖是有關TIM1/TIM2時基參數的配置。
在前面提到的固定配置前提下,我將TIM2溢出周期比TIM1少21個脈沖【這個地方不是固定的,14~24應該都可以,具體自行驗證】,二者同步啟動。這樣配置的目的就是確保TIM2一定是先進中斷但又不至于它執行完畢了TIM1中斷還沒來,否則就沒法看到搶占情形了。下圖是TIM2首先進入中斷時的情形:
從上圖可以看出,TIM2首先進入中斷,棧幀有新內容放入。LR寄存器為0xfffffff9,表示當前中斷ISR是從線程模式下發生的,這里就是main程序被打斷了。
下圖是TIM1中斷搶占TIM2中斷的情形。
從上圖中,明顯看到棧幀內容再次被添加了8個字的內容,內容變多。我們還可以從LR寄存器的內容看出,結尾是F1,說明當前中斷是搶占了其它低優先級中斷,即發生了中斷嵌套,它執行完后返回的還是handle模式,這跟它搶占了TIM2 ISR相吻合。
下圖是TIM1 ISR執行完畢CPU再回來執行剛才被打斷的TIM2ISR情形。
從上圖可以看出,TIM1中斷搶占TIM2中斷并完成ISR后,在返回TIM2 ISR之前還做了出棧操作。在當前TIM2 ISR里可以看到棧幀恢復到TIM2中斷剛被響應時的情形,內容變少了。同樣,我們可以發現LR寄存器內容也恢復到剛被響應時的值。
顯然,發生搶占時除了看到ISR執行的順序外,明顯地看到棧幀內容的變化。
接著看看中斷晚到的情形。先看TIM1/TIM2基本時基配置。
這樣配置的目的,就是讓優先級低的TIM2提前一點點發生中斷,讓它在申請壓棧完成附近發生TIM1中斷,TIM2 ISR并不能立即執行反而是TIM1搶先【不是搶占】執行ISR,之后再來運行TIM2 ISR。整個過程,只發生1次壓棧、出棧操作。TIM1中斷事件雖然晚發生,由于其高優先級和卡著點發生而搶先執行其ISR。
開始運行程序后,TIM1 ISR首先得到響應。【參加圖中備注說明】
下圖是TIM2 ISR得到執行的情形:
TIM1中斷執行完畢后,回頭來繼續執行TIM2 ISR時,棧幀內容無變化。兩次中斷得到執行,只看到1次壓棧操作。執行順序靠TIMER時間參數保證TIM2的中斷事件先發生并由其申請壓棧, TIM1事件雖晚到卻因高優先級而被優先執行其ISR。
最后來看看中斷咬尾的情形。TIM1/TIM2時基參數配置如下:
二者設置的時基參數一樣,上面TIM1的溢出周期減個1不是必須的,這里主要是為了確保TIM1中斷事件不要晚于TIM2的即可,因為TIM1優先級高。二者同時申請中斷,自然先響應TIM1的。
下圖是TIM1 中斷首先得到響應的情形:
下圖是TIM1 ISR執行完后運行TIM2 ISR的情形:
TIM2 ISR基于TIM1申請壓棧并完成ISR后接著執行,也省去了壓棧過程,完成2次中斷只見1次壓棧。從棧幀內容結果上看,中斷晚到和中斷咬尾很類似。不過,中斷晚到情形下,申請壓棧的是低優先級的中斷事件,而咬尾中斷情形下,申請壓棧的是高優先級或者是先申請壓棧的同級中斷事件。比方以現在討論的中斷咬尾情形為例,如果把TIM1/TIM2的搶占優先級設置一樣,TIM2溢出時間參數稍微調短點,這時玩咬尾動作的就是TIM1中斷了,因為二者優先級一樣,TIM2先產生溢出中斷自然先響應它的,TIM1的中斷則等它執行完ISR基于咬尾機制而得以執行。
不難看出,基于晚到機制和咬尾機制而得以執行中斷的行為不屬于中斷搶占。順便提醒下,如果通過上面方式體驗中斷響應的話,測試代碼盡量簡單,尤其中斷服務程序,否則若棧幀里壓入太多其它信息,觀察分析起來可能就不太方便了。
編輯:黃飛
?
評論
查看更多