1. 程式人生 > >cortex-M3 異常和中斷

cortex-M3 異常和中斷

問題:

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
堆疊指標的最低兩位永遠是 0,這意味著堆疊總是 4 位元組對齊的。==>u32 的 0x2000 00Fc=......1111 1100b,即最低兩位總是0,能夠被4整除,不會出現0x2000 00FE這種地址於SP中。
ARM中異常:
凡是打斷程式順序執行的事件,都被稱為異常(exception)。除了外部中斷外,當有指令執行了“非法操作”,或者訪問被禁的記憶體區間,因各種錯誤產生的fault,以及不可遮蔽中斷髮生時,都會打斷程式的執行,這些情況統稱為異常。在不嚴格的上下文中,異常與中斷也可以混用。另外,程式程式碼也可以主動請求進入異常狀態的(常用於系統呼叫)。

R14:連線暫存器

當呼叫一個子程式時,由 R14 儲存返回地址。
不像大多數其它處理器,ARM為了減少訪問記憶體的次數(訪問記憶體的操作往往要3 個以上指令週期,帶MMU和cache的就更加不確定了),把返回地址直接儲存在暫存器中。這樣足以使很多隻有1級子程式呼叫的程式碼無需訪問記憶體(堆疊記憶體),從而提高了子程式呼叫的效率。如果多於1級,則需要把前一級的R14值壓到堆疊裡。
在ARM上程式設計時,應儘量只使用暫存器儲存中間結果,迫不得以時才訪問記憶體。在RISC處理器中,為了強調訪內操作越過了處理器的界線,並且帶來了對效能的不利影響,給它取了一個專業的術語:濺出。

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) 
通過 MRS/MSR 指令,這 3 個 PSRs即可以單獨訪問,也可以組合訪問(2 個組合,3 個組合都可以)。當使用三合一的方式訪問時,應使用名字“xPSR”或者“PSR”。


關於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);
}


以上...