ARM單片機中斷處理過程解析
前言
中斷,在單片機開發(fā)中再常見不過了。當然對于中斷的原理和執(zhí)行流程都了然于胸,那么對于ARM單片機中斷的具體處理行為,你真的搞清楚了嗎?
今天來簡單聊一聊,ARM單片機中斷處理過程中的具體行為是什么樣的,搞清楚了這些,讓你徹底理解中斷是如何執(zhí)行的。
掌握了這些內(nèi)容后,以后在開發(fā)過程中遇到中斷問題,可以做到游刃有余。
本篇文章主要梳理一下 Cortex-M3 內(nèi)核的單片機在處理中斷事件的具體行為,以及不同的中斷是如何處理的。
中斷響應(yīng)
Cortex-M3 單片機在開始響應(yīng)一個中斷時,會進行以下三個操作:
寄存器入棧,將寄存器的值壓入棧
取向量:從向量表中找出對應(yīng)的服務(wù)程序入口地址
選擇堆棧指針 MSP/PSP,更新堆棧指針SP,更新連接寄存器LR,更新程序計數(shù)器PC
響應(yīng)中斷的第一個動作,就是自動保存現(xiàn)場的必要部分:依次把 xPSR, PC, LR, R12 以及 R3-R0 由硬件自動壓入適當?shù)亩褩V小?/p>
當響應(yīng)異常時,當前的代碼正在使用 PSP,則壓入 PSP,也就是使用進程堆棧;否則就壓入 MSP,使用主堆棧。一旦進入了服務(wù)例程,就將一直使用主堆棧。
入棧順序以及入棧后堆棧中的內(nèi)容,如下圖所示。在自動入棧的過程中,把寄存器寫入堆棧內(nèi)存的時間順序,并不是與寫入的空間順序相對應(yīng)的。但是機器會保證:正確的寄存器將被保存到正確的位置 。


先把PC與 xPSR 的值保存,就可以更早地啟動服務(wù)例程指令的預(yù)取——因為這需要修改PC;同時,也做到了在早期就可以更新 xPSR 中 IPSR 位段的值。
取出中斷服務(wù)例程地址,從中斷向量表中找出正確的異常向量,然后在服務(wù)程序的入口處預(yù)取指。這部分由指令總線(I-Code總線)完成。
在入棧和取向量操作完成之后,執(zhí)行中斷服務(wù)例程之前,還要更新一系列的寄存器:
SP:在入棧后會把堆棧指針(PSP 或 MSP)更新到新的位置。在執(zhí)行服務(wù)例程時,將由 MSP 負責(zé)對堆棧的訪問。
PSR:更新 IPSR 位段(PSR的最低部分)的值為新響應(yīng)的異常編號。
PC:在取向量完成后, PC將指向服務(wù)例程的入口地址。
LR:在出入 ISR 的時候, LR 的值將重新詮釋為 “EXC_RETURN”。在異常進入時由系統(tǒng)計算并賦給 LR,并在異常返回時使用它。(后面會講解 EXC_RETURN)
以上是在響應(yīng)異常時通用寄存器的變化。另一方面,在 NVIC 中,也會更新若干個相關(guān)有寄存器。
在完成以上工作之后,系統(tǒng)開始執(zhí)行中斷服務(wù)程序里的指令。當指令執(zhí)行完畢,進入中斷返回處理階段。
中斷返回
當異常服務(wù)例程執(zhí)行完畢后,需要做一個“異常返回”動作,從而恢復(fù)先前的系統(tǒng)狀態(tài),才能使被中斷的程序得以繼續(xù)執(zhí)行 。觸發(fā)中斷返回的指令:

有些處理器會使用特殊的返回指令來標示中斷返回,例如 8051 就使用 reti。但是在 CM3 中,是通過向 PC 中寫入 EXC_RETURN
來識別返回動作的。
在進行中斷返回操作后,會進行下面的處理:
出棧:先前壓入棧中的寄存器在這里恢復(fù)。內(nèi)部的出棧順序與入棧時的相對應(yīng),堆棧指針的值也改回先前的值。
更新 NVIC 寄存器:伴隨著異常的返回,它的活動位也被硬件清除。對于外部中斷,倘若中斷輸入再次被置為有效,懸起位也將再次置位,新一次的中斷響應(yīng)序列也可隨之再次開始。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)? ?


中斷返回值
前面已經(jīng)講到,在進入異常服務(wù)程序后,將自動更新 LR 的值為特殊的 EXC_RETURN
。這是一個高 28 位全為1的值,只有[3:0] 的值有特殊含義:

當中斷服務(wù)例程把這個值送往 PC 時,就會啟動處理器的中斷返回操作。因為 LR 的值是由 CM3 自動設(shè)置的,所以只要沒有特殊需求,就不要改動它。
總結(jié)一下上表,可以得到,合法的 EXC_RETURN 值共3個:

如果主程序在線程模式下運行,并且在使用 MSP 時被中斷,則在服務(wù)例程中 LR=0xFFFF_FFF9(主程序被打斷前的 LR 已被自動入棧)。
如果主程序在線程模式下運行,并且在使用 PSP 時被中斷,則在服務(wù)例程中 LR=0xFFFF_FFFD(主程序被打斷前的 LR 已被自動入棧) 。
這樣描述可能比較抽象,不好理解。那就通過兩張圖來直觀感受一下。
主程序運行在線程模式,且使用主堆棧,進入中斷后,以及有中斷嵌套情況下,模式切換和 LR 的變化如下圖。

如果主程序在 Handler 模式下運行,則在服務(wù)例程中 LR = 0xFFFF_FFF1(主程序被打斷前的LR已被自動入棧)。這時的所謂“主程序”,其實更可能是被搶占的服務(wù)例程。事實上,在嵌套時,更深層 ISR 所看到的 LR 總是 0xFFFF_FFF1。
主程序運行在線程模式,且使用進程堆棧的情況下,LR 值的變化如下

通過這兩張圖,可以很好地理解異常返回值的變化情況。
中斷嵌套
Cortex-M3 內(nèi)核單片機支持中斷嵌套,即高優(yōu)先級中斷可以搶占低優(yōu)先級去執(zhí)行指令。我們要根據(jù)實際使用情況,為每個中斷建立適當?shù)膬?yōu)先級。
NVIC 和 CM3 處理器會根據(jù)優(yōu)先級的設(shè)置來控制搶占與嵌套行為。有了自動入棧和出棧,我們不用擔(dān)心在中斷發(fā)生嵌套時,會使寄存器的數(shù)據(jù)損毀。

我們知道,所有服務(wù)例程都只使用主堆棧(MSP)。所以當中斷嵌套加深時,對主堆棧的壓力會增大:每嵌套一級,就至少再需要8個字,即32字節(jié)的堆棧空間(這沒算上 ISR 對堆棧的額外需求),并且何時嵌套多少級也是不可預(yù)料的。
如果主堆棧的容量本來就已經(jīng)所剩無幾了,中斷嵌套又突然加深,則主堆棧有溢出的兇險。堆棧溢出是很致命的,新入棧數(shù)據(jù)會覆蓋掉主堆棧前面的數(shù)據(jù),數(shù)據(jù)遭到了破壞。
若在服務(wù)例程返回前混迭區(qū)的數(shù)據(jù)又被更改了,則在執(zhí)行中斷返回后,系統(tǒng)極可能功能紊亂,甚至出現(xiàn)程序跑飛的問題。
要注意的,相同的異常(中斷)是不允許重入的。因為每個異常都有自己的優(yōu)先級,并且在異常處理期間,同級或低優(yōu)先級的異常是要阻塞的。
因此對于同一個異常,只有在上次實例的服務(wù)例程執(zhí)行完畢后,方可繼續(xù)響應(yīng)新的請求。因此,在 SVC 服務(wù)例程中,就不得再使用SVC指令,否則將產(chǎn)生 fault 現(xiàn)象。
咬尾中斷
Cortex-M3 內(nèi)核為了縮短中斷延遲,新增了 “咬尾中斷” 機制。
當處理器在響應(yīng)某個中斷時,如果又發(fā)生低優(yōu)先級或者相同優(yōu)先級中斷,則被阻塞。在當前的中斷執(zhí)行返回后,系統(tǒng)處理懸起的中斷時,不再先POP,然后又把 POP 出來的內(nèi)容PUSH回去;而是繼續(xù)使用上一個中斷已經(jīng) PUSH 好的成果。
這么一來,看上去好像后一個中斷把前一個中斷的尾巴咬掉了,前前后后只執(zhí)行了一次 入棧/出棧 操作。于是,這兩個異常之間的“時間溝”變窄了很多。

晚到中斷
CM3 的中斷處理還有另一個機制,這就是“晚到的異常處理”。
當 CM3 對某異常的響應(yīng)序列還處在早期:入棧的階段,尚未執(zhí)行其服務(wù)例程時,如果此時收到了高優(yōu)先級異常的請求,則本次入棧就成了為高優(yōu)先級中斷所做的了。
入棧后,將執(zhí)行高優(yōu)先級異常的服務(wù)例程??梢?,它雖然來晚了,卻還是因優(yōu)先級高而優(yōu)先執(zhí)行。
比如,若在響應(yīng)某低優(yōu)先級 異常#1 的早期,檢測到了高優(yōu)先級 異常#2,則只要 #2 沒有太晚,就能以“晚到中斷”的方式處理:在入棧完畢后執(zhí)行ISR #2。
如果 異常#2 來得太晚,以至于已經(jīng)執(zhí)行了 ISR #1 的指令,則按普通的搶占處理,這會需要更多的處理器時間和額外的堆??臻g。
在 ISR #2 執(zhí)行完畢后,則以“咬尾中斷”方式,來啟動 ISR #1 的執(zhí)行。
原文作者:【一起學(xué)嵌入式】
