每個 MCU 開發工程師一定都了解寄存器這個東西,以 STM32 為例,其擁有非常多的外設模塊,如串口、SPI、IIC 等等,如果要使用這些外設,使其按照我們的要求工作,就需要配置這些外設的寄存器,往這些寄存器中寫入對應的配置數據,從而使其工作在我們所需要的模式中。
上述寄存器都是工程師日常編程會操作的寄存器,可以說和工程師的關系非常緊密。但有這么一組寄存器,與大多數工程師的關系就很疏遠,甚至一些初學者完全不知道。
這,就是內核寄存器組。
在大多數比較基礎的日常編程中你完全無需關注內核寄存器,但實際上你寫的每一行代碼,最終都是去操作內核寄存器,編譯器會負責完成這中間的轉換。并且,如果你要進行一些高級編程,如操作系統的編寫,或者做一些非常底層的優化,你就不得不去人為操作這些寄存器。
下面我們就基于 STM32F103 這款芯片來探一探其內核寄存器的究竟。
STM32F103 屬于 Cortex-M3 內核,其內核寄存器組中擁有 16 個寄存器,其中 13 個為 32 位通用寄存器,另外 3 個則有特殊用途:
R0 ~ R12
寄存器 R0?~ R12 為通用寄存器,其中前 8 個 (R0 ~ R7)也被稱為低寄存器。由于指令中的可用空間有限,許多 16 位的指令只能訪問低寄存器。
高寄存器 R8 ~ R12 則可以用于 32 位指令和幾個 16 位指令,如 MOV。
R0 ~ R12 的初始值是未定義的。
R13 棧指針 SP
R13 為棧指針,可以通過 PUSH 和 POP 指令實現棧的訪問。實際在物理上存在兩個棧指針:主棧指針 MSP(Main Stack Pointer)和進程棧指針 PSP(Process Stack Pointer)。
主棧指針(MSP)為默認的棧指針,在復位后或處理器處于特權模式(如進中斷)時會被使用。通常用于芯片初始化代碼和中斷服務函數中。
進程棧指針(PSP)只能用于線程模式。在使用支持任務或線程切換的實時操作系統(RTOS)中,PSP 用于管理用戶級任務的堆棧。這允許操作系統進行有效而穩定的任務調度,因為每個任務可以有自己的堆棧,在切換任務時只需要切換 PSP 的值即可。
大多數情況下,如果你是裸機編程,即沒有用到嵌入式操作系統,那么 PSP 也沒有必要使用。許多簡單的應用可以完全依賴于 MSP,一般在操作系統中才會使用到 PSP,因其各個任務(或線程)的棧是要求相互獨立的。
PSP 的初始值未定義,而 MSP 的初始值則會在復位流程中從存儲器的第一個字中取出。
R14?鏈接寄存器 LR
R14 也被稱作鏈接寄存器 LR,用于函數或子程序調用時返回地址的保存。
在函數或子程序被調用時,返回地址(即調用指令后面那條指令的地址)被保存到 LR 寄存器中。這樣,在函數或子程序結束時,處理器可以通過 LR 寄存器中的值返回到正確的地址繼續執行。
當執行了函數或子程序的調用后,LR 的數值會自動更新。若該函數或子程序內還要調用其他函數或子程序(如 A 調用 B,B 中還調用了 C),則此時需要將 LR 的數值保存在棧中,否則,一旦執行了 C 調用,LR 就會更新為 B 函數中調用 C 這條函數的下一條指令的地址,原本 A 調 B 的下一條指令地址就會被覆蓋丟失,從而無法再恢復到 A 函數中繼續運行。
在異常處理期間,LR 也會被自動更新為特殊的 EXC_RETURN(異常返回)數值,之后該數值會在異常處理結束時觸發異常返回,從而實現從異常處理返回到正確的用戶程序中 。
需要注意的是,盡管 Cortex-M 處理器中的返回地址總是偶數(由于指令會對齊到半字地址上,因此,最低位即第 0 位為 0),LR 的第 0 位是可讀可寫的,有些跳轉/調用操作需要將 LR(或正在使用的任何寄存器)的第 0 位置 1 來表示當前處理器處于 Thumb 狀態。
R15 程序計數器 PC
R15 為程序計數器 PC,可讀可寫。讀操作返回當前指令地址加 4(這是由于流水線特性及同 ARM7TDMI 處理器兼容的需要),而對 PC 的寫操作(例如使用數據傳輸/處理指令)則會引起程序的跳轉(不會更新 LR 寄存器)。
由于指令必須要對齊到半字或字地址,PC 的最低位(LSB)為 0.不過,在使用一些跳轉或讀存儲器指令更新 PC 時,需要將新 PC 值的 LSB 置 1 來表示 Thumb 狀態,否則就會由于試圖使用不支持的 ARM 指令(如 ARM7TDMI 中的 32 位 ARM 指令)而觸發錯誤異常。對于高級編程語言(包括 C 和 C++),編譯器會自動將跳轉目標的 LSB 置位。
多數情況下,跳轉和調用由專門的指令實現,利用數據處理指令更新 PC 的情況較為少見。不過,在訪問位于程序存儲器的字符數據時,PC的數值非常有用,因此,會經常發現存儲器的讀操作將 PC 作為基地址寄存器,而地址偏移則由指令中的立即數生成。
總結
以上就是 Cortex-M3 內核寄存器組中所有的寄存器。雖然在日常編程過程中我們很少或者可以說幾乎不會直接接觸并操作這些寄存器,一旦程序出現問題崩潰,或者運行過程中出現異常輸出或邏輯,此時這些內核寄存器就能夠為我們提供非常重要且直觀的調試分析依據,以此快速定位問題并解決。如果你不理解這些內核寄存器組,那你的問題分析過程將會變得異常艱辛,甚至最終從調試到放棄。