上一篇實做 STM32筆記(17):按鍵控制-使用 GPIO 讀取 使用掃描 GPIO 的狀態來偵測按鍵是否被按下?本篇將繼續進行按鍵的控制,不同的是使用外部中斷來偵測按鍵,如發生中斷則觸發執行另一段服務程式。我以按鍵次數為例,每按一下觸發一個中斷,將按下的次數呈現在 OLED 顯示器上,以下就來看一下 STM32 如何撰寫中斷的控制程式。
所謂的中斷(Interrupt)就是 CPU 正常執行程式時,遇到外部/內部的緊急事件需要處理,CPU 就暫停正在執行的程式,改執行另一段程式,稱做中斷服務流程(Interrupt Service Routine (ISR)),待處理完成,再回到原先被打斷的程序處繼續往下執行(下圖左)。如果在執行中斷服務程式時,又發生優先等級較高的中斷,則再次進入中斷程序,這時會執行完最後發生的中斷程序,再回到前一個中斷程序,等結束後才會回到主程序(下圖右)。
ARM Crotex M3/M4 內核支援 256 個中斷,其中包含 16 個內核中斷和 240 個外部中斷。但 STM32 並沒有使用全部的中斷,以 STM32F103x 的晶片來說只有定義 60 個中斷。參考手冊(9.1.2 中斷和異常向量)中列出所有的中斷值,分成內部中斷(如下表):
以及外部中斷(如下表):
STM32 可產生多種中斷事件,例如外部事件、系統計時器、記憶存取錯誤,或內部設備的 USART、I2C、DMA等。要使用 STM32 外部中斷,最重要的有兩個控制器要設定:
以下分別說明這兩個控制器相關的設定。
下圖是外部中斷/事件控制的方塊圖,圖中編號 ① 將 EXTI_PR 暫存器內容輸出到 NVIC ,而實現系統中斷事件控制。編號 ② 輸出一個脈衝信號,可以給其他外設電路使用,如定時器 TIM 、模擬數字轉換器 ADC 等。各階段有控制的暫存器,如:下降沿觸發(Falling Trigger)選擇寄存器(EXTI_FTSR)、上升沿觸發(Rising Trigger )選擇寄存器(EXTI_RTSR) 等,待日後再深入探討相關的暫存器控制。
有關觸發中斷的數量,如果每個 GPIO 都可以設定中斷,以 STM32F103 為例,GPIOx(x可以是 A..G),每群 GPIO 有 16 個 I/O,總計 102 個,通常不會同時發生這麼多數量的中斷。因此 ARM 設計一個方法,稱做 AFIO(Alternate function I/O and debug configuration),將不同群 GPIOx 的同一個 I/O 編號,指定使用同一個中斷編號,以下圖來說,PA0, PB0...PG0 使用同一個 AFIO,稱為 EXTI0,PA1, PB1...PG1 使用同一個 AFIO,稱為 EXTI1,EXTIx 的 x 編號從 0 到 15,這樣就只要 16 個中斷即可。 除了 I/O 口對應的外部輸入 EXTI 0~15 的 16 個中斷,還包含以下 4 個中斷,總計 20 個外部中斷/事件請求:
✈ GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
設定 EXIT 外部中斷/事件的 GPIO 中斷來源,傳入的引數是 GPIO 和對應的 I/O 端口。
- 引數 GPIO_PortSource 是對應的 GPIOx 來源,範圍是 GPIO_PortSourceA ~ GPIO_PortSourceG。
- 引數 GPIO_PinSource 是 GPIOx 對應的端口,範圍是 GPIO_PinSource0 ~ GPIO_PinSource15。
以下範例:設定 GPIOB Port 2 當作外部中斷:
✈ EXTI_Init(EXTI_InitTypeDef *EXTI_InitStructure);
對 EXTI 進行初始化,需先定義並填入一個 EXTI_InitTypeDef 類型的結構體。在 stm32f10x_exti.h 裡定義了EXTI_InitTypeDef 結構體變數。
執行 EXTI_Init()函式進行初始化:
這兩個優先等級,以數值表示,數字越小代表優先權越高,當中斷發生時,可能發生以下幾種情況:
STM32 中斷優先等級暫存器(Interrupt Priority Registers),使用 4 個位元來定義「主要優先」和「次要優先」,例如:「主要優先」不佔用位元,「次要優先」佔用 4 位元、第二種狀況是「主要優先」佔用 1 位元,「次要優先」佔用 3 位元,總共可分成以下五種狀況,分別給予一個名稱代碼,再使用NVIC_PriorityGroupConfig() 函式設定兩者優先的個數即可。
瞭解上述的優先順序後,要使用標準函式庫設定 NVIC,主要有以下三個步驟:
(1) 系統執行後先設定中斷優先順序分組,呼叫 NVIC_PriorityGroupConfig() 函式,整個系統執行過程中,只要設定一次中斷分組即可。
(2) 使用 NVIC_Init() 函式來對每個中斷設定主要和次要優先順序。
(3) 如果需檢視中斷當前的狀態,可分別呼叫相關函式即可。
上述的兩個函式的用法及引數設定,說明如下:
✈ NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
設定中斷優先順序分組。NVIC 相關的處理位於 misc.h 中。
- 引數 NVIC_PriorityGroup 可以是上方表格的「優先群組名稱」,設定範圍:NVIC_PriorityGroup_0 ~ NVIC_PriorityGroup_4。這個函數定義在 misc.c 及 misc.h 文件中。
✈ NVIC_Init(NVIC_InitTypeDef* NVIC_InitStructure);
對 NVIC 進行初始化,定義在標準函式庫的 misc 文件中,需先定義並填入一個 NVIC_InitTypeDef 類型的結構體。
有關 NVIC 使用暫存器控制的文件,可參考:STM32F10x Cortex-M3 programming manual。
所謂的中斷(Interrupt)就是 CPU 正常執行程式時,遇到外部/內部的緊急事件需要處理,CPU 就暫停正在執行的程式,改執行另一段程式,稱做中斷服務流程(Interrupt Service Routine (ISR)),待處理完成,再回到原先被打斷的程序處繼續往下執行(下圖左)。如果在執行中斷服務程式時,又發生優先等級較高的中斷,則再次進入中斷程序,這時會執行完最後發生的中斷程序,再回到前一個中斷程序,等結束後才會回到主程序(下圖右)。
ARM Crotex M3/M4 內核支援 256 個中斷,其中包含 16 個內核中斷和 240 個外部中斷。但 STM32 並沒有使用全部的中斷,以 STM32F103x 的晶片來說只有定義 60 個中斷。參考手冊(9.1.2 中斷和異常向量)中列出所有的中斷值,分成內部中斷(如下表):
以及外部中斷(如下表):
STM32 可產生多種中斷事件,例如外部事件、系統計時器、記憶存取錯誤,或內部設備的 USART、I2C、DMA等。要使用 STM32 外部中斷,最重要的有兩個控制器要設定:
- EXTI(External interrupt):外部中斷,在 APB2 總線上設定外部中斷和事件。
- NVIC(Nested Vectored Interrupt Controller):中文譯為「嵌套向量中斷控制器」,在內部設定中斷優先等級並啟用中斷。
以下分別說明這兩個控制器相關的設定。
[外部中斷EXTI]
STM32 的所有 GPIO 都引到 EXTI(External interrupt) 外部中斷線上,使得所有的 GPIO 都能作為外部中斷的輸入源。EXTI 是指外部中斷,藉由 GPIO 輸入脈衝來引發中斷事件,打斷正在執行的程式,進入中斷服務函數處理,處理結束後,再返回到中斷前的程式繼續執行。EXTI 的中斷可分成兩類:一是產生中斷,另一個是產生事件。中斷會向 CPU 產生請求,由 CPU 去回應中斷程序,而事件則是對芯片內其他模組發出脈衝觸發信號,引發事件。下圖是外部中斷/事件控制的方塊圖,圖中編號 ① 將 EXTI_PR 暫存器內容輸出到 NVIC ,而實現系統中斷事件控制。編號 ② 輸出一個脈衝信號,可以給其他外設電路使用,如定時器 TIM 、模擬數字轉換器 ADC 等。各階段有控制的暫存器,如:下降沿觸發(Falling Trigger)選擇寄存器(EXTI_FTSR)、上升沿觸發(Rising Trigger )選擇寄存器(EXTI_RTSR) 等,待日後再深入探討相關的暫存器控制。
有關觸發中斷的數量,如果每個 GPIO 都可以設定中斷,以 STM32F103 為例,GPIOx(x可以是 A..G),每群 GPIO 有 16 個 I/O,總計 102 個,通常不會同時發生這麼多數量的中斷。因此 ARM 設計一個方法,稱做 AFIO(Alternate function I/O and debug configuration),將不同群 GPIOx 的同一個 I/O 編號,指定使用同一個中斷編號,以下圖來說,PA0, PB0...PG0 使用同一個 AFIO,稱為 EXTI0,PA1, PB1...PG1 使用同一個 AFIO,稱為 EXTI1,EXTIx 的 x 編號從 0 到 15,這樣就只要 16 個中斷即可。 除了 I/O 口對應的外部輸入 EXTI 0~15 的 16 個中斷,還包含以下 4 個中斷,總計 20 個外部中斷/事件請求:
- EXTI 16:連接到 PVD 輸出。
- EXTI 17:連接到 RTC 鬧鐘事件。
- EXTI 18:連接到 USB 喚醒事件。
- EXTI 19:連接到 Ethernet 喚醒事件(需支援網路功能)。
✈ GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
設定 EXIT 外部中斷/事件的 GPIO 中斷來源,傳入的引數是 GPIO 和對應的 I/O 端口。
- 引數 GPIO_PortSource 是對應的 GPIOx 來源,範圍是 GPIO_PortSourceA ~ GPIO_PortSourceG。
- 引數 GPIO_PinSource 是 GPIOx 對應的端口,範圍是 GPIO_PinSource0 ~ GPIO_PinSource15。
以下範例:設定 GPIOB Port 2 當作外部中斷:
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource2); //設定 GPIOB 為中斷來源
✈ EXTI_Init(EXTI_InitTypeDef *EXTI_InitStructure);
對 EXTI 進行初始化,需先定義並填入一個 EXTI_InitTypeDef 類型的結構體。在 stm32f10x_exti.h 裡定義了EXTI_InitTypeDef 結構體變數。
EXTI_InitTypeDef EXTI_InitStructure;EXTI_InitStructure 這個結構體有以下 4 個引數:
引數名稱 | 說明 |
---|---|
EXTI_InitStructure.EXTI_Line = EXTI_Line##; | 選擇輸入線 EXTI_Line,## 範圍從 0-15 ,代表 GPIOx 的 PIN 0~15。 |
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_xxx; | EXTI 模式選擇,可選擇: ▸中斷模式(EXTI_Mode_Interrupt) ▸事件模式(EXTI_Mode_Event)。 |
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_xxx; | 選擇是上升沿還是下降沿觸發中斷。觸發方式有三種: ▸EXTI_Trigger_Rising 上升沿觸發 ▸EXTI_Trigger_Falling 下降沿觸發 ▸EXTI_Trigger_Rising_Falling 上升沿和下降沿都觸發 |
EXTI_InitStructure.EXTI_LineCmd = xxx; | 控制 EXTI 線,xxx為 Enable 為啟用, Disable為停用。 |
執行 EXTI_Init()函式進行初始化:
EXTI_Init(&EXTI_InitStructure);
[NVIC控制器]
NVIC 是 ARM Cortex-M 處理器的一部分,它屬於內核的一個外設(Peripheral),負責處理例外和中斷相關程序,包括:中斷優先級的分組與配置、讀取與清除中斷請求標誌等、啟用或停止中斷等功能。NVIC 其中一個比較重要的功能是設定中斷優先級的分組。ARM 將每個中斷分配了兩個優先等級:- group priority/preempt priority:主要優先,或稱做「搶佔優先」。
- subpriority:次要優先,或稱做「響應優先或副優先」。
這兩個優先等級,以數值表示,數字越小代表優先權越高,當中斷發生時,可能發生以下幾種情況:
- 當一個中斷正在執行,核心收到一個數值較小的「主優先」中斷時,原先正在執行的中斷,也會再次被新的中斷插隊而停止。
- 如果新的中斷其「主優先」數值與原先的中斷相同,則只能等待當時正在執行的中斷完成後,才會再執行新的中斷。
- 如果有兩個優先中斷擁有同樣的數值發生中斷時,「次要優先」數字較小的,其中斷會優先被執行。
STM32 中斷優先等級暫存器(Interrupt Priority Registers),使用 4 個位元來定義「主要優先」和「次要優先」,例如:「主要優先」不佔用位元,「次要優先」佔用 4 位元、第二種狀況是「主要優先」佔用 1 位元,「次要優先」佔用 3 位元,總共可分成以下五種狀況,分別給予一個名稱代碼,再使用NVIC_PriorityGroupConfig() 函式設定兩者優先的個數即可。
主要優先 | 次要優先 | |||
---|---|---|---|---|
優先群組名稱 | 位元數 | 可設定數(範圍) | 位元數 | 可設定數(範圍) |
NVIC_PriorityGroup_0 | 0 | 2^0 = 1(0) | 4 | 2^4 = 16(0~15) |
NVIC_PriorityGroup_1 | 1 | 2^1 = 2(0~1) | 3 | 2^3 = 8(0~7) |
NVIC_PriorityGroup_2 | 2 | 2^2 = 4(0~3) | 2 | 2^2 = 4(0~3) |
NVIC_PriorityGroup_3 | 3 | 2^3 = 8(0~7) | 1 | 2^1 = 2(0~1) |
NVIC_PriorityGroup_4 | 4 | 2^4 = 16(0~15) | 0 | 2^0 = 1(0) |
瞭解上述的優先順序後,要使用標準函式庫設定 NVIC,主要有以下三個步驟:
(1) 系統執行後先設定中斷優先順序分組,呼叫 NVIC_PriorityGroupConfig() 函式,整個系統執行過程中,只要設定一次中斷分組即可。
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
(2) 使用 NVIC_Init() 函式來對每個中斷設定主要和次要優先順序。
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
(3) 如果需檢視中斷當前的狀態,可分別呼叫相關函式即可。
上述的兩個函式的用法及引數設定,說明如下:
✈ NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
設定中斷優先順序分組。NVIC 相關的處理位於 misc.h 中。
- 引數 NVIC_PriorityGroup 可以是上方表格的「優先群組名稱」,設定範圍:NVIC_PriorityGroup_0 ~ NVIC_PriorityGroup_4。這個函數定義在 misc.c 及 misc.h 文件中。
✈ NVIC_Init(NVIC_InitTypeDef* NVIC_InitStructure);
對 NVIC 進行初始化,定義在標準函式庫的 misc 文件中,需先定義並填入一個 NVIC_InitTypeDef 類型的結構體。
NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure 這個結構體有以下 4 個引數:
引數名稱 | 說明 |
---|---|
NVIC_InitStructure.NVIC_IRQChannel = xxx; | 定義初始化的是哪一個中斷,xxx 可以在 stm32f10x.h 檔案中 查到每個中斷對應的名字,如 USART1_IRQn; |
NNVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = n; | n 為定義「主要優先」的等級,參考上表可設定數(範圍)。 |
NVIC_InitStructure.NVIC_IRQChannelSubPriority = n; | n 為定義「次要優先」的等級,參考上表可設定數(範圍)。 |
NVIC_InitStructure.NVIC_IRQChannelCmd = xxx; | 設定是否啟用/停用中斷,xxx 為 Enable/Disable。 |
有關 NVIC 使用暫存器控制的文件,可參考:STM32F10x Cortex-M3 programming manual。
張貼留言