在上一篇文章中分享了 Cortex-M3 內核寄存器組的相關知識,實際上除了內核寄存器組外,CM3 處理器中還存在多個特殊寄存器,它們分別為?程序狀態寄存器,中斷/異常屏蔽寄存器?和?控制寄存器。
需要注意的是,特殊寄存器未經過存儲器映射,即沒有對應的存儲器地址,也只能使用專門的 MSR 和 MRS 等特殊寄存器訪問指令來進行訪問:
MRS <reg> <special_reg> ;將特殊寄存器讀入寄存器
MSR <special_reg> <reg> ;寫入特殊寄存器
CMSIS-Core 也提供了幾個用于訪問特殊寄存器的 C 函數,其本質也是以上兩個指令的封裝。如在 gcc 環境下(cmsis_gcc.h),對 CONTROL 寄存器的操作如下:
__STATIC_FORCEINLINE?uint32_t?__get_CONTROL(void)
{uint32_t?result;__ASM?volatile?("MRS?%0,?control"?:?"=r"?(result)?);return(result);
}__STATIC_FORCEINLINE?void?__set_CONTROL(uint32_t?control)
{__ASM?volatile?("MSR?control,?%0"?:?:?"r"?(control)?:?"memory");
}
接下來我們看一下這些特殊寄存器的具體含義。
程序狀態寄存器(PSRs 或 xPSR)
程序狀態寄存器包含以下三個狀態寄存器:
-
應用 PSR(APSR)
-
執行 PSR(EPSR)
-
中斷 PSR(IPSR)
通過上面提到的 MRS 和 MSR 指令,這三個 PSRs 可以單獨訪問:
MRS?r0,?APSR??;將應用狀態讀入?R0
MRS?r0,?IPSR??;將中斷/異常狀態讀入?R0
MSR?APSR,?R0??;寫應用狀態
也可以組合訪問(兩個組合或三個組合都可以)。當使用三合一方式訪問時,應使用?“xPSR” 或 “PSR” 這兩個名字。
MRS?r0,?PSR??;讀組合程序狀態字
MSR?PSR,?r0??;寫組合程序狀態字
需要注意的是,軟件代碼無法直接使用 MRS (讀出為 0)或 MSR 直接訪問 EPSR。同時 IPSR 為只讀,可以從組合 PSR 中讀出。
這三個寄存器的位域結構如下:
組合形式:
其中每個位域字段的含義如下:
-
N:負標志。
-
Z:零標志。
-
C:進位(或非借位)標志。
-
V:溢出標志。
-
Q:飽和標志(ARMv6-M 中不存在)。
-
ICI/IT:中斷繼續指令狀態位(ICI),用于條件執行的 IF-THEN 指令狀態位(ARMv6-M 中不存在)。
-
T:Thumb 狀態,總是 1,嘗試清除此位會引起錯誤異常。
-
Exception Number:表示處理器正在處理的異常對應的編號。
PRIMASK,?FAULTMASK 和 BASEPRO
PRIMASK、FAULTMASK 和 BASEMASK 寄存器都用于異常或中斷的屏蔽,每個異常(包括中斷)都有一個優先等級,數值越小優先級越高,反之數值越大優先級越低。以上三個特殊寄存器可以基于優先級屏蔽異常,只有在特權訪問等級才能對它們進行操作(非特權狀態下的寫操作會被忽略,而讀取則會返回 0)。它們的默認值都為 0,即不屏蔽任何異常或中斷。這些寄存器的編程模型如下圖所示:
PRIMASK 寄存器是位寬為 1 的中斷屏蔽寄存器。在置位時,它會阻止不可屏蔽中斷(NMI)和 HardFault 異常之外的所有異常(包括中斷)。實際上,它的原理是將當前異常優先級提升為 0,這也是可編程異常/中斷的最高優先級。PRIMASK?最常見的用途是在一些時間要求很嚴格的進程中禁止所有中斷,在該進程完成后,需要將 PRIMASK 清除以重新使能中斷。
FAULTMASK 和 PRIMASK 非常相似,不過它還能屏蔽 HardFault 異常,它實際上是將異常優先級提升到了 -1。錯誤處理代碼可以使用 FAULTMASK 以免在錯誤處理期間再次觸發其他錯誤(只有幾種)。例如, FAULTMASK 可用于旁路 MPU 或屏蔽總線錯誤(這些都是可配置的),這樣,錯誤處理代碼執行修復措施也就更容易了。與 PRIMASK 不同, FAULTMASK 在異常返回時會被自動清除。
BASEPRI 會根據優先級屏蔽異常或中斷。BASEPRI 的寬度取決于設計中實際實現的優先級數量,這通常是由微控制器供應商決定的。大多數 Cortex-M3 或 Cortex-M4 微控制器都有 8 個或 16 個可編程的異常優先級,此時 BASEPRI 的寬度就相應地為 3 位或者 4 位。BASEPRI 為 0 時不會起作用,當被設置為非 0 數值時,他就會屏蔽具有相同或更低優先級的異常(包括中斷),而更高優先級的則仍然會被處理器接受。
CMSIS-Core 提供了多個 C 函數用于訪問 PRIMASK、FAULTMASK、及 BASEPRI 寄存器。(這些寄存器只能在特權等級下訪問)
x?=?__get_BASEPRI();?//?讀?BASEPRI?寄存器
x?=?__get_PRIMARK();?//?讀?PRIMASK?寄存器
x?=?__get_FAULTMASK();?//?讀?FAULTMASK?寄存器
__set_BASEPRI(x);?//?設置?BASEPRI
__set_PRIMASK(x);?//?設置?PRIMASK
__set_FAULTMASK(x);?//?設置?FAULTMASK
__disable_irq();?//?設置?PRIMASK,?禁用?IRQ
__enable_irq();?//?清除?PRIMASK,?使能?IRQ
同時也可以使用匯編代碼訪問這些寄存器:
MRS r0, BASEPRI ; 將 BASEPRI 寄存器的值讀入 R0
MRS r0, PRIMASK ; 將 PRIMASK 寄存器的值讀入 R0
MRS r0, FAULTMASK ; 將 FAULTMASK 寄存器的值讀入 R0
MSR BASEPRI, r0 ; 將 R0 寄存器的值寫入 BASEPRI
MSR PRIMASK, r0 ; 將 R0 寄存器的值寫入 PRIMASK
MSR FAULTMASK, r0 ; 將 R0 寄存器的值寫入 FAULTMASK
此外,利用修改處理器狀態(CPS)指令,可以非常方便地設置或清除 PRIMASK 和 FAULTMASK 的值:
CPSIE i ; 使能 interrupt (清除 PRIMASK)
CPSID i ; 禁用 interrupt (設置 PRIMASK)
CPSIE f ; 使能 interrupt (清除 FAULTMASK)
CPSID f ; 禁用 interrupt (設置 FAULTMASK)
CONTROL 寄存器
CONTROL 寄存器中包含了如下兩個主要信息:
-
棧指針的選擇(主棧指針 MSP 和 進程棧指針 PSP)。
-
線程模式的訪問等級(特權級和非特權級)。
其編程模型如下:
具體的位域描述為:
位? ? ? | 描述 |
CONTROL[1] (SPSEL) | 定義棧指針的選擇? 0=選擇主棧指針 MSP(復位后缺省值)? 1=選擇進程棧指針 PSP? 在線程或基礎級(沒有在響應異常),可以使用 PSP。在 handler 模式下, 只允許使用 MSP,所以此時不得往該位寫 1。 |
CONTROL[0] (nPRIV) | 定義線程模式中的特權等級 0=特權級的線程模式? 1=用戶級的線程模式? Handler 模式永遠都是特權級的。 |
復位后,CONTROL 寄存器默認為 0,這意味著處理器此時處于線程模式,具有特權訪問權限并且使用主棧指針。通過寫 CONTROL 寄存器,特權線程模式的程序可以切換棧指針的選擇或進入非特權訪問等級,如下圖所示:
不過,nPRIV(CONTROL[0])置位后,運行在線程模式的程序就不能訪問 CONTROL 寄存器了。
運行在非特權等級的程序一般情況下無法再切換回特權訪問等級,這樣就提供了一個基本安全的模型。例如,嵌入式系統中可能會具有運行在非特權等級且不受信任的應用,這些應用的訪問權限就需要受到限制,以免不可靠的程序引起系統的崩潰。
若需要在線程模式切換回特權訪問等級,則需要借助于異常機制。在異常處理期間,處理程序可以清除 nPRIV 位。在返回到線程模式后,處理器就會進入特權訪問等級。
若使用嵌入式 OS,每次上下文切換時都可以重新編程 CONTROL 寄存器,以滿足應用間不同特權訪問等級的需要。
nPRIV 和 SPSEL 的設置有 4 種組合方式,其中 3 種在實際應用中較為常見:
nPRIV | SPSEL | 應用場景 |
0 | 0 | 簡單應用,整個應用運行在特權訪問等級,主程序和中斷處理只會使用一個棧,即主棧 MSP |
0 | 1 | 具有嵌入式 OS 的應用,當前執行的任務運行在特權級線程模式,當前任務選擇使用進程棧指針 PSP,而 MSP 則用于 OS 內核以及異常處理 |
1 | 1 | 具有嵌入式 OS 的應用,當前執行的任務運行在非特權級線程模式,當前任務選擇使用進程棧指針 PSP,而 MSP 則用于 OS 內核以及異常處理 |
1 | 0 | 線程模式運行在非特權訪問等級,且使用 MSP,在 Handler 模式下可以觀察到,而在用戶任務中一般不會使用,這是因為在多數嵌入式 OS 中,應用任務的棧和 OS 內核以及異常處理使用的棧是相互獨立的 |
對于未使用嵌入式 OS 的多數簡單應用,無須修改 CONTROL 寄存器的數值,整個應用可以運行在特權訪問等級并且只使用 MSP:
要利用 C 語言訪問 CONTROL 寄存器,可以使用符合 CMSIS 的設備驅動庫提供的以下函數:
x?=?__get_CONTROL();?//?讀取當前?CONTROL?寄存器的值
__set_CONTROL(x);?//?設置?CONTROL?寄存器的值為?x
如果使用匯編,則可以借助于 MRS 和 MSR 指令:
MRS r0, CONTROL ;將 CONTROL 寄存器的值讀到 r0
MSR CONTROL, r0 ;將 r0 中的值寫到 CONTROL 寄存器
最后,你可以通過檢查 CONTROL 和 IPSR 的數值來確定當前是否為特權等級:
int?in_privileged(void)?{if?(__get_IPSR()?!=?0)return?1;??//?Trueelse?if?((__get_CONTROL()?&?0x1)?==?0)return?1;??//?Trueelsereturn?0;??//?False
}