cortex-M3 異常和中斷
阿新 • • 發佈:2019-01-08
問題:
1、如何開啟、關閉中斷
2、如何開啟、關閉異常
3、程式只跳轉一次時,把跳轉需要返回地址直接儲存在暫存器LR中。多次跳轉呢?
基礎概述:
操作模式:
Cortex‐M3 支援 2 個模式和兩個特權等級。handler模式和執行緒模式; 特權級和使用者級。
cortex-M3 暫存器組:
Cortex‐M3 處理器擁有 R0‐R15 的暫存器組。其中 R13 作為堆疊指標 SP。SP 有兩個,但在同一時刻只能有一個可以看到,這也就是所謂的“banked”暫存器。--影子暫存器
如下圖:
這裡,R13作為堆疊指標暫存器SP(Stack Pointer),R14作為連結暫存器LR(Link register),R15作為程式計數器PC(program counter);
R0-R12:通用暫存器
R0‐R12 都是 32 位通用暫存器,用於資料操作。但是注意:絕大多數 16 位 Thumb 指令只能訪問 R0‐R7,而 32 位 Thumb‐2 指令可以訪問所有暫存器。
Banked R13:兩個堆疊指標
Cortex‐M3 擁有兩個堆疊指標,然而它們是 banked,因此任一時刻只能使用其中的一個。
- 主堆疊指標(MSP):復位後預設使用的堆疊指標,用於作業系統核心以及異常處理例程(包括中斷服務例程)---master SP
- 程序堆疊指標(PSP):由使用者的應用程式程式碼使用。 ---process SP
ARM中異常:
R14:連線暫存器
當呼叫一個子程式時,由 R14 儲存返回地址。不像大多數其它處理器,ARM為了減少訪問記憶體的次數(訪問記憶體的操作往往要3 個以上指令週期,帶MMU和cache的就更加不確定了),把返回地址直接儲存在暫存器中。這樣足以使很多隻有1級子程式呼叫的程式碼無需訪問記憶體(堆疊記憶體),從而提高了子程式呼叫的效率。如果多於1級,則需要把前一級的R14值壓到堆疊裡。
R15:程式計數暫存器
指向當前的程式地址。如果修改它的值,就能改變程式的執行流。特殊功能暫存器
Cortex‐M3 還在核心水平上搭載了若干特殊功能暫存器,包括:- 程式狀態字暫存器組(PSRs)
- 中斷遮蔽暫存器組(PRIMASK, FAULTMASK, BASEPRI)
- 控制暫存器(CONTROL)
特殊功能暫存器只能被專用的 MSR 和 MRS指令訪問,而且它們也沒有儲存器地址。
MRS <gp_reg>, <special_reg> ;讀特殊功能暫存器的值到通用暫存器
MSR <special_reg>, <gp_reg> ;寫通用暫存器的值到特殊功能暫存器
程式狀態暫存器(PSRs 或曰 PSR)
程式狀態暫存器在其內部又被分為三個子狀態暫存器:- 應用程式 PSR(APSR)
- 中斷號 PSR(IPSR)
- 執行 PSR(EPSR)
關於ICI/IT、T:
ICI:在中斷延遲中有提到,與LDM/STM的處理機制有關。LDM/STM是一串LDR/STR的速度優化版。於是,為了加速中斷的響應,CM3支援LDM/STM指令的中止和繼續,就好像它們只是普通的一串LDR/STR一樣。為了實現“指令撕裂與粘合”的目的,需要記錄中斷時資料傳送的程序。為此,CM3在xPSR中開出若干個“ICI位”,記錄下一個即將傳送的暫存器是哪一個(LDM/STM在彙編時,都把暫存器號升序排序)。在服務例程返回後,xPSR被彈出,CM3再從ICI bits中獲取當時LDM/STM執行的進度,從而可以繼續傳送。
這個辦法聽起來是個好主意,只是在個別情況下還有一點限制:
IF‐THEN(IT)指令的執行也需要在xPSR中使用幾個位,可它需要的位剛好與ICI位重合(類似C中的union)——both ICI bits和IT條件都記錄在EPSR中。所以,如果在IF‐THEN中使用了LDM/STM,則不再記錄LDM/STM
的執行進度。但儘管如此,及時響應中斷依然是首要任務。此時只好把LDM/STM取消,待中斷返回後繼續執行。
詳見:cortex-M3權威指南
PRIMASK/FAULTMASK/BASEPRI
這三個暫存器用於控制異常的使能和除能。PRIMASK | 這是個只有1個位的暫存器。當它置1時, 就關掉所有可遮蔽的異常,只剩下NMI 和硬 fault可以響應。它的預設值是 0,表示沒有關中斷。 |
FAULTMASK | 這是個只有1個位的暫存器。當它置 1時,只有NMI 才能響應,所有其它的異常, 包括中斷和 fault,通通閉嘴。它的預設值也是0,表示沒有關異常。 |
BASEPRI | 這個暫存器最多有9 位(由表達優先順序的位數決定)。它定義了被遮蔽優先順序的閾 值。當它被設成某個值後,所有優先順序號大於等於此值的中斷都被關(優先順序號 越大,優先順序越低)。但若被設成0,則不關閉任何中斷,0 也是預設值。 |
對於時間‐關鍵任務而言,PRIMASK 和 BASEPRI 對於暫時關閉中斷是非常重要的。而FAULTMASK 則可以被 OS 用於暫時關閉 fault 處理機能,這種處理在某個任務崩潰時可能需要。因為在任務崩潰時,常常伴隨著一大堆 faults。在系統料理“後事”時,通常不再需要響應這些 fault——人死帳清。總之 FAULTMASK 就是專門留給 OS用的。
要訪問 PRIMASK, FAULTMASK 以及 BASEPRI,同樣要使用 MRS/MSR 指令。只有在特權級下,才允許訪問這 3個暫存器。
其實,為了快速地開關中斷,CM3還專門設定了一條 CPS指令,有4種用法 :
CPSID I ;PRIMASK=1, ;關中斷
CPSIE I ;PRIMASK=0, ;開中斷
CPSID F ;FAULTMASK=1, ;關異常
CPSIE F ;FAULTMASK=0 ;開異常
控制暫存器(CONTROL)
控制暫存器用於定義特權級別,還用於選擇當前使用哪個堆疊指標。CONTROL[1] :
在 Cortex‐M3的 handler 模式中,CONTROL[1]總是 0。線上程模式中則可以為 0 或 1。 僅當處於特權級的執行緒模式下,此位才可寫,其它場合下禁止寫此位。改變處理器的模式也有其它的方式:在異常返回時,通過修改 LR 的位 2,也能實現模式切換。--詳見權威指南
CONTROL[0]
僅當在特權級下操作時才允許寫該位。一旦進入了使用者級,唯一返回特權級的途徑,就是觸發一個(軟)中斷,再由服務例程改寫該位。
如前所述,特權等級和堆疊指標的選擇均由 CONTROL 負責。當 CONTROL[0]=0 時,在異常處理的始末,只發生了處理器模式的轉換,如下圖所示。
中斷前後的狀態轉換:
但若 CONTROL[0]=1(執行緒模式+使用者級),則在中斷響應的始末,both 處理器模式和特權等極都要發生變化,如下圖所示。
CONTROL[0]只有在特權級下才能訪問。使用者級的程式如想進入特權級,通常都是使用一條“系統服務呼叫指令(SVC)”來觸發“SVC 異常”,該異常的服務例程可以選擇修改CONTROL[0]。 當然,其他的異常也可。
一個指定PSP 進行更新的例子:
LDR R0, =0x20008000 MSR PSP, R0 BX LR ;如果是從異常返回到執行緒狀態,則使用新的PSP 的值作為棧頂指標SVC(supervisor call)和PendSV(可懸起系統呼叫),
異常型別
異常型別,優先順序以及位置。位置是指與向量表開始處的字偏移。在優先順序列中,數字越小表示優先順序越高。表中還顯示了異常型別的啟用方式,及是同步的還是非同步的標註。漸漸地認清自己的狀況,這麼多年了,菜鳥依舊伴著我。
開/關中斷
CM3專門設定的CPS指令,有四種用法:CPSID I ;PRIMASK=1, ;關中斷
CPSIE I ;PRIMASK=0, ;開中斷
CPSID F ;FAULTMASK=1, ;關異常
CPSIE F ;FAULTMASK=0 ;開異常
CMSIS-M3微控制器軟體介面標準中的core_cm3.h也給出了開關中斷或異常的函式:
/**
* @brief Set the Priority Mask value
*
* @param priMask PriMask
*
* Set the priority mask bit in the priority mask register
*/
static __INLINE void __set_PRIMASK(uint32_t priMask)
{
register uint32_t __regPriMask __ASM("primask");
__regPriMask = (priMask);
}
說明:使用__set_PRIMASK(1)關閉中斷;__setPRIMASK(0)開啟中斷。__INLINE是巨集定義,對應__inline,這是keil編譯器自定義關鍵字,表示這個函式是行內函數,但並不是強制性內聯,編譯器最終決定是否內聯。__ASM(“primask”): __ASM也是一個巨集,對應__asm,這是keil編譯器自定義關鍵字,關於這個關鍵字,有相當多的用法,可以在C中內嵌組合語言、內嵌彙編函式、指定彙編標號以及本程式碼中的宣告一個已命名暫存器變數。這裡,已命名的暫存器是("primask"),也就是說暫存器變數__regPriMask等同於編譯器已命名的primask。語法為:register type var-name __asm(reg);
開/關異常
/**
* @brief Set the Fault Mask value
*
* @param faultMask faultMask value
*
* Set the fault mask register
*/
static __INLINE void __set_FAULTMASK(uint32_t faultMask)
{
register uint32_t __regFaultMask __ASM("faultmask");
__regFaultMask = (faultMask & 1);
}
以上...