# STM32 學習筆記 Week1 Author: KT LIU reference: Mastering_STM32-Second_Edtion ## Cortex and Cortex-M-Base Processors ### ARM Cortex ARM Cortex 處理器分為 Cortex-A、Cortex-R、Cortex-M 三大系列,它們各自有不同的應用領域和特性。以下是簡要介紹: #### Cortex-A 系列: 應用領域:主要用於高性能應用處理器,例如智慧型手機、平板電腦和部分嵌入式系統。 特性:支援高效能和多任務操作,通常具備強大的處理能力與先進的指令集,適合複雜的操作系統(如 Linux 或 Android)。 架構:支援 ARMv7-A 和 ARMv8-A 架構,並包含更多的記憶體管理和安全功能。 #### Cortex-R 系列: 應用領域:設計用於實時(real-time)應用,例如車輛電子控制單元(ECU)、工業控制和醫療設備。 特性:強調低延遲與高可靠性,支援硬體的錯誤檢測和容錯功能,適合時間敏感的任務。 架構:支援 ARMv7-R 和 ARMv8-R 架構,並具備高效能的內部記憶體系統,以確保穩定的實時性能。 #### Cortex-M 系列: 應用領域:廣泛應用於微控制器領域,如物聯網(IoT)、小型嵌入式設備和低功耗系統。 特性:低功耗設計,簡單的架構和指令集,適合嵌入式系統開發,易於使用。 架構:通常使用 ARMv6-M、ARMv #### 舉例: **Arduino** Arduino 使用的處理器一般屬於 Cortex-M 系列,主要原因是其架構適合用於微控制器、低功耗且成本較低,非常適合 Arduino 開發板的應用範疇。 **樹莓派和 Jetson Nano** 樹莓派和 Jetson Nano 都採用了 ARM Cortex-A 系列的 CPU,特別是 Cortex-A53、Cortex-A57 和 Cortex-A72 等。 ### Cortex-M 進一部探討(STM32為此架構) #### Core Rigister(暫存器) 在嵌入式系統和微處理器的領域中,「暫存器」(register)是處理器內部的一種高速存儲單元,用於臨時存儲數據和指令。 ![image](https://hackmd.io/_uploads/HykJjMmf1e.png) 在Cortex-M處理器中,常見的核心暫存器包括: **通用暫存器:** R0-R12:這些是通用用途的暫存器,可以用於數據操作和存儲中間結果。 Caller-saved: R0~R3, R12 可被自由使用的。 Callee_saved: 其他則需要先儲存再調用。 **特定用途暫存器:** R13(SP,堆疊指針):指向當前堆疊的頂部,用於函數調用和中斷處理。 R14(LR,鏈接寄存器):存儲函數返回地址。 R15(PC,程序計數器):存儲當前執行指令的地址。 **狀態寄存器:** xPSR(程序狀態寄存器):包含處理器的狀態信息,包括運行模式和條件標誌。 #### Memory Map(記憶體架構) 此為STM32的Memory Layout ![image](https://hackmd.io/_uploads/rJYXpG7GJx.png) **Code Area (0x00000000 - 0x1FFFFFFF):** 這是程式碼區域,包含了Flash記憶體和可能的別名區域。這些記憶體區域在系統啟動時會被用來存放程式碼和靜態資料。 0x00000000 - 0x1FFFFFFF:這些地址區域被分配給系統啟動時的映像(例如,Flash,系統記憶體或SRAM),具體內容取決於啟動配置。 **SRAM (0x20000000 - 0x3FFFFFFF):** 這是靜態隨機存取記憶體(SRAM),用於存放在執行期間動態分配的資料,例如堆疊和堆區。 0x20000000 - 0x3FFFFFFF:這個地址範圍專門用於內部SRAM。STM32的SRAM一般分佈在這些地址範圍內。 **Peripheral (0x40000000 - 0x5FFFFFFF):** 這是外設區域,用於存取各種外設寄存器,例如GPIO、定時器、UART等。 0x40000000 - 0x5FFFFFFF:外設記憶體映射範圍,用於各種外設的控制和狀態寄存器。 **External RAM (0x60000000 - 0x9FFFFFFF):** 這是外部RAM的地址範圍,用於連接外部RAM設備,如SDRAM或SRAM模組。 0x60000000 - 0x9FFFFFFF:此範圍通常用於外部RAM設備,允許系統擴展其記憶體容量。 #### 進一步展開Code Area: **System memory (0x1FFF0000 - 0x1FFFFFFF):** 系統記憶體包含了引導加載程式和其他系統相關的功能。 0x1FFF0000 - 0x1FFFFFFF:這個範圍內的記憶體通常包含系統引導加載程式和其他內部功能。 **Option bytes (0x1FFFC000 - 0x1FFFFFFF):** 選項字節用於配置裝置的各種選項,如讀保護、寫保護和啟動模式。 0x1FFFC000 - 0x1FFFFFFF:這些地址範圍用於存儲選項字節,影響系統的配置和保護機制。 **Flash memory (0x08000000 - 0x080FFFFF):** 這是內部Flash記憶體的地址範圍,用於存儲程式碼和靜態資料。 0x08000000 - 0x080FFFFF:內部Flash的地址範圍,用於存放程式碼和常數數據。 **Reserved:** 保留區域是未分配或未使用的地址範圍,通常用於未來的擴展或內部用途。 0x1FFF0000 - 0x1FFF7FFF和其他特定區域:這些範圍通常保留供未來使用或特殊內部功能。 ### Bit-Banding(位帶區域) #### 位帶映射區域的定義 ARM Cortex-M3/M4 定義了兩個位帶映射區域,每個區域有 1 MB 的大小,分別對應到 SRAM 和外設記憶體: **SRAM 位帶區域:** 地址範圍為 0x2000 0000 - 0x200F FFFF,映射到 0x2200 0000 - 0x23FF FFFF。 **外設位帶區域:** 地址範圍為 0x4000 0000 - 0x400F FFFF,映射到 0x4200 0000 - 0x43FF FFFF。 這些位帶映射區域可用於快速位元操作。 以下為位帶與映射的情況: ![image](https://hackmd.io/_uploads/SyPZmXQM1l.png) 實例:操作 GPIO 位元 使用位帶技術修改 GPIOA 的輸出資料暫存器(GPIO->ODR)中的第 5 位。 也就是 GPIOA, PIN_5 已知GPIOA的ODR在 0x4002 0014。 要修改 GPIOA 的第 5 位(PIN5),使用位帶地址計算公式: ``` alias_region_base = 0x4200 0000 region_base_offset = 0x4002 0014 - 0x4000 0000 = 0x20014 bit_band_address = 0x4200 0000 + (0x20014 * 32) + (5 * 4) = 0x4240 0294 ``` **alias_region_base** 是別名區的基準地址。 **region_base_offset** 是目標地址相對於基準地址的偏移量。 **bit_number** 是想要操作的位元位置。 計算得到 0x4240 0294 即是位帶對應的位元地址,可以直接操作此位元。 以下為課本中的範例: ``` // Define base address of bit-band #define BITBAND_SRAM_BASE 0x20000000 // Define base address of alias band #define ALIAS_SRAM_BASE 0x22000000 // Convert SRAM address to alias region #define BITBAND_SRAM(a,b) ((ALIAS_SRAM_BASE + ((uint32_t)&(a)-BITBAND_SRAM_BASE)*32 + (b*4))) // Define base address of peripheral bit-band #define BITBAND_PERI_BASE 0x40000000 // Define base address of peripheral alias band #define ALIAS_PERI_BASE 0x42000000 // Convert PERI address to alias region #define BITBAND_PERI(a,b) ((ALIAS_PERI_BASE + ((uint32_t)a-BITBAND_PERI_BASE)*32 + (b*4))) #define GPIOA_PERH_ADDR 0x40020000 #define ODR_ADDR_OFF 0x14 uint32_t *GPIOA_ODR = GPIOA_PERH_ADDR + ODR_ADDR_OFF; uint32_t *GPIOA_PIN5 = BITBAND_PERI(GPIOA_ODR, 5); *GPIOA_PIN5 = 0x1; // Turns GPIO HIGH ``` #### 其他常見的位址(不一定如此) 在 STM32 微控制器的記憶體地圖中,每個 GPIO 埠(例如 GPIOA、GPIOB 等)都會分配到一個固定的基底地址,各暫存器基於這個基底地址進行偏移。以 STM32F4 系列為例: GPIOA 基底地址:0x4002 0000 GPIOB 基底地址:0x4002 0400 GPIOC 基底地址:0x4002 0800 其他 GPIO 埠依此類推,每個 GPIO 埠相隔 0x400 的地址範圍。 GPIOB 中的暫存器地址舉例 GPIOB 的基底地址是 0x4002 0400,因此 GPIOB 的各個暫存器地址如下(以 32 位元偏移): GPIOB 模式暫存器 (MODER): 0x4002 0400 GPIOB 輸出數據暫存器 (ODR): 0x4002 0414 GPIOB 輸入數據暫存器 (IDR): 0x4002 0410 GPIOB 輸出類型暫存器 (OTYPER): 0x4002 0404 GPIOB 位設定/清除暫存器 (BSRR): 0x4002 0418 ### Thumb-2 and Memory Alignment #### ARM 處理器的 32 位元指令集 **32 位元指令集的特點:** ARM 處理器最初使用的是 32 位元的指令集,提供了豐富的指令,能夠高效執行算術和記憶體操作。在 32 位元的指令集架構下,程式運行速度較快。 **缺點:** 這種指令集在記憶體佔用上比較大,導致需要更多的快閃記憶體來儲存韌體程式碼,這樣會增加 MCU 的生產成本和功耗。 #### Thumb 16 位元指令集 **介紹 Thumb 指令集:** 為了解決 32 位元指令佔用空間大的問題,ARM 推出了 Thumb 指令集。Thumb 指令是 32 位元指令集中常用指令的子集,每條指令只有 16 位元。 **指令轉換:** Thumb 指令會自動轉換為等效的 32 位元 ARM 指令,因此在效能上與 32 位元指令無差別,但能夠降低記憶體佔用。 **優點與限制:** Thumb 指令的程式碼大小約為原本 ARM 指令集的 65%,且在 16 位元記憶體系統上運行時效能可以達到原本 ARM 程式碼的 160%。但因為縮減了指令長度,Thumb 指令的功能也有限,例如只有跳躍指令能夠有條件執行,且能操作的暫存器數量較少。 #### Thumb-2 指令集 **介紹 Thumb-2:** ARM 後來推出了 Thumb-2 指令集,這是一種將 16 位元和 32 位元指令混合在一起的指令集,提供了比 Thumb 指令集更豐富的指令。 **優點:** Thumb-2 具有可變長度指令集,使程式碼密度更高,達到類似 ARM 的豐富功能,同時保持記憶體使用效率。 #### 支援非對齊記憶體訪問的特性 **非對齊記憶體訪問的問題:** 在傳統的 ARM 架構中,記憶體存取必須是「對齊」的。32 位元的資料需存放在 32 位元對齊的地址(例如 0x2000 0000、0x2000 0004 等),若要存取非對齊的地址(例如 0x2000 0002),會引發 UsageFault 錯誤。同樣地,16 位元資料需存放在 2 字節對齊的地址。 **RAM 分散問題:** 這種限制導致 RAM 中出現空白區塊(記憶體碎片),造成浪費。例如,如果我們需要存放一個 32 位元資料在 0x2000 0007,則傳統 ARM 架構中無法做到。 **下為非對齊記憶體和對齊記憶體的示意圖:** ![image](https://hackmd.io/_uploads/BkE2NNmGkl.png) #### Cortex-M3/4/7 支援非對齊訪問 **非對齊訪問解決方案:** Cortex-M3/4/7 核心解決了上述問題,支援非對齊記憶體存取。這樣就能更高效地排列變數,減少空白區塊。例如,在新架構中,我們可以將 32 位元變數從地址 0x2000 0007 開始存放,這樣節省了 4 個字節的 SRAM。 **限制:** 並非所有指令都支援非對齊訪問,僅限於以下幾種指令: LDR(載入暫存器)、LDRH(載入半字)、LDRSH(載入帶符號半字) 這些指令支援非對齊訪問,其他指令仍需要對齊地址。 ### Pipline #### 流水線的基本概念 在傳統的非流水線處理器中,指令是順序執行的,即一條指令必須等前一條指令執行完畢後才能開始執行。這種方式效率較低,因為處理器中的大部分資源在等待期間都處於空閒狀態。通過引入流水線技術,指令的執行過程被分為多個階段,如下所示: **取指(Fetch, F):** 從內存中取出指令。 **解碼(Decode, D):** 解碼指令,確定需要執行的操作。 **執行(Execute, E):** 執行指令所指定的操作。 #### 流水線執行過程: ![image](https://hackmd.io/_uploads/H1xOLSQfJl.png) 在正常的流水線執行過程中,各個指令按照順序進入流水線並被執行。在圖中,綠色的 ADD 指令依次進入並通過流水線的各個階段。 **分支指令的出現:** 當流水線遇到分支指令(如圖中的 BNE,即“Branch if Not Equal”),會產生兩種可能的執行路徑。根據分支條件,程序可能會跳轉到新的指令位置或繼續執行當前路徑上的指令。 **分支影響:** 在圖中,BNE 指令之後的 MOV 和 LOAD 指令已經進入流水線。如果分支條件成立(跳轉),這些指令會被丟棄,這段被丟棄的指令稱為“分支陰影”(Branch Shadow)。 **分支跳轉:** 如果 BNE 指令決定跳轉,程序將跳轉到新的指令位置繼續執行(圖中橙色的分支箭頭所示),例如另一條 ADD 指令。這會導致已經進入流水線但未被執行的指令被丟棄,並且重新從新的指令位置開始取指和執行。 #### 分支預測 為了減少分支對流水線的影響,現代處理器會採用分支預測技術。分支預測的基本原理是猜測分支指令的執行結果,提前加載可能會執行的指令,減少等待時間和流水線清空的成本。如果預測正確,流水線效率將大大提高;如果預測錯誤,需要丟棄錯誤路徑上的指令並重新加載正確的指令,這會有一定的性能損失。 ### Interrupts and Exceptions Handling 在ARM架構中,中斷(interrupts)是一種異常(exception),通常由片上外設(例如定時器)或外部輸入(例如連接到GPIO的觸摸開關)生成,有時也可以由軟件觸發。 #### 異常和中斷的標識 每個異常(以及中斷)都有一個唯一的標識號。這個標識號反映了異常處理例程在向量表中的位置,其中存儲了例程的實際地址。例如,位置15包含了SysTick中斷處理程序的內存地址,當SysTick定時器歸零時會生成中斷。 #### 優先級 除了前三個異常外,每個異常都可以分配一個優先級,這決定了在同時發生中斷時的處理順序:數字越小,優先級越高。例如,假設有兩個與外部輸入A和B相關的中斷例程。我們可以給輸入A分配一個更高的優先級(更低的數字)。如果處理器在處理來自輸入B的中斷時接收到A的中斷,則B的執行將被暫停,允許高優先級的中斷服務例程立即執行。 #### NVIC 異常和中斷由一個名為嵌套向量中斷控制器(Nested Vectored Interrupt Controller,NVIC)的專用單元處理。NVIC具有以下特點: **1. 靈活的異常和中斷管理:** NVIC能夠處理來自外設的中斷信號/請求和來自處理器核心的異常,允許我們在軟件中啟用/禁用它們(除非是不可屏蔽中斷NMI)。 **2, 支持嵌套的異常/中斷:** NVIC允許為異常和中斷(除前三種異常類型外)分配優先級,根據用戶需求對中斷進行分類。 **3. 向量異常/中斷入口:** NVIC自動定位與異常/中斷相關的異常處理程序的位置,無需額外的代碼。 **4. 中斷屏蔽:** 開發者可以自由地暫停所有異常處理程序的執行(除NMI外),或者根據優先級暫停其中一些,這得益於一組專用寄存器。這允許在不處理異步中斷的情況下安全地執行關鍵任務。 **5. 確定性的中斷延遲:** NVIC的一個有趣特性是中斷處理的確定性延遲,對於所有Cortex-M3/4核心為12個週期,對於Cortex-M0為15個週期,對於Cortex-M0+為16個週期,無論處理器當前狀態如何,中斷延遲時間應為可預期。 **6. 異常處理程序的重定位:** 異常處理程序可以被重定位到其他的閃存位置,甚至是完全不同的外部非只讀內存。這為高級應用提供了很大的靈活性。 #### 向量表 向量表是一個數組,其中每個條目對應一個異常或中斷,存儲的是異常處理程序的地址。這使得NVIC能夠快速找到並執行相應的異常處理程序,從而減少異常處理的延遲時間。