1、EXTI功能
外部中斷/事件控制器EXTI管理了STM32的20個中斷/事件線。
EXTI的功能框圖如下:
在功能框圖中,可以看到很多在信號線上打了一個斜杠並標注“20”的字樣,這是表示在STM32內部類似的信號線路有20個,也就是EXTI的20個中斷/事件線。
EXTI可以分為兩大部分功能:
產生中斷:如功能圖中的紅色線。
產生事件:如功能圖中的綠色線。
EXTI中斷功能說明:
電路1:
在功能框圖中,電路1是腳位輸入線,EXTI有19個中斷/事件輸入線,這些輸入線可以通過外部中斷配置寄存器AFIO_EXTI1~AFIO_EXTI2設置為任意一個GPIO,也可以是一些外設的事件。輸入線一般是電平變化的信號。
電路2:
電路2是邊沿檢測電路。該邊沿檢測電路是根據EXTI_RTSR上升沿觸發選擇寄存器和EXTI_FTSR下降沿選擇寄存器的設置來控制信號觸發。
如果檢測到與EXTI_RTSR和EXTI_FTSR寄存器設置相對應的邊沿跳變信號,就出輸出有效信號到電路3。通過EXTI_RTSR和EXTI_FTSR寄存器,可以設置觸發的信號為:上升沿觸發、下降沿觸發、電平變化觸發(即上升沿和下降沿都可以觸發)。
電路3:
電路3是一個或們電路,兩路輸入只要有一路輸入有效信號,則輸出有效信號。
從功能圖中可以看出,電路3的一路輸入來自電路2的輸出,另一路來自EXTI_SWIER軟件中斷事件寄存器。
EXTI_SWIER寄存器可以通過程序控制啟動中斷/事件線,即只要在程序中將EXTI_SWIER寄存器的對應位設置為1,那么電路3就會輸出有效信號,這樣就可以實現軟件模擬中斷或事件。
電路4:
電路4是一個門電路,只有當兩路都輸入有效信號時,電路4才會輸出有效信號。
電路4的輸入一路來自電路3的輸出,一路來自EXTI_IMR中斷屏蔽寄存器。只有當電路3輸出有效信號,而且EXTI_IMR寄存器的對應位使能,電路4才會輸出有效信號。
EXTI_IMR寄存器用來使能或屏蔽相應的中斷線。
電路5:
電路5是將EXTI_PR寄存器的內容輸出到NVIC中,從而實現系統中斷控制。
EXTI事件功能說明:
EXTI的事件產生線路,最終輸出一個脈沖信號。
產生事件的線路是在電路3之后才與產生中斷的線路有所不同,之前的電路都是共用的。
電路6:
電路3產生的有效信號會被輸入到電路6中。電路6是一個門控電路,電路3的輸出和EXTI_EMR事件屏蔽寄存器作為電路6的輸入。只有當電路3輸出有效信號且EXTI_EMR寄存器的相應位使能,電路6才會輸出有效信號。
EXTI_EMR寄存器用來使能或屏蔽相應的事件線。
電路7:
電路7是一個脈沖發生器電路,當電路6產生一個有效信號時將會輸出到電路7當中,這時電路7就會產生一個脈沖。
電路8:
電路8是一個脈沖信號,這個脈沖信號就是電路7產生的脈沖信號。這個脈沖信號可以給其它外設電路使用,比如TIM定時器、ADC模擬數字轉換器等等,這樣的脈沖信號一般用來觸發TIM或者開始ADC轉換。
產生中斷線路目的是運行相應的中斷服務函數,實現功能。
而產生事件的目的是傳輸一個脈沖信號給其它外設使用,而且產生事件的是電路級別的信號傳輸,屬於硬件級。
2、中斷/事件線
EXTI有20個中斷/事件線,每個GPIO都可以被設置為輸入線,占用EXTI0和EXTI15,還有4個用於特點的外設事件。它們如下:
-
- EXTI0中斷/事件線:輸入由PA0~PI0等組成。
- EXTI1中斷/事件線:輸入由PA1~PI1等組成。
- EXTI2中斷/事件線:輸入由PA2~PI2等組成。
- EXTI3中斷/事件線:輸入由PA3~PI3等組成。
- EXTI4中斷/事件線:輸入由PA4~PI4等組成。
- EXTI5中斷/事件線:輸入由PA5~PI5等組成。
- EXTI6中斷/事件線:輸入由PA6~PI6等組成。
- EXTI7中斷/事件線:輸入由PA7~PI7等組成。
- EXTI8中斷/事件線:輸入由PA8~PI8等組成。
- EXTI9中斷/事件線:輸入由PA9~PI9等組成。
- EXTI10中斷/事件線:輸入由PA10~PI10等組成。
- EXTI11中斷/事件線:輸入由PA11~PI11等組成。
- EXTI12中斷/事件線:輸入由PA12~PI12等組成。
- EXTI13中斷/事件線:輸入由PA13~PI13等組成。
- EXTI14中斷/事件線:輸入由PA14~PI15等組成。
- EXTI15中斷/事件線:輸入由PA15~PI15等組成。
- EXTI16中斷/事件線:PVD輸出。
- EXTI17中斷/事件線:RTC鬧鍾事件。
- EXTI18中斷/事件線:USB喚醒事件。
- EXTI19中斷/事件線:以太網喚醒事件(只適用互聯型)。
從上面可以看出,EXTI0到EXTI15都是用GPIO口做為中斷/事件線,需要通過AFIO_EXTICR1到AFIO_EXTICR4這幾個寄存器來配置具體使用哪一個位來作為中斷/事件線。
比如AFIO_EXTICR1寄存器的bit3~bit0位用來選擇EXTI0的中斷/事件線的輸入腳位,如下:
-
- Bit3~bit0 = 0000:則選擇PA0作為EXTI0的輸入腳位。
- Bit3~bit0 = 0001:則選擇PB0作為EXTI0的輸入腳位。
- Bit3~bit0 = 0010:則選擇PC0作為EXTI0的輸入腳位。
- Bit3~bit0 = 0011:則選擇PD0作為EXTI0的輸入腳位。
- Bit3~bit0 = 0100:則選擇PE0作為EXTI0的輸入腳位。
- Bit3~bit0 = 0101:則選擇PF0作為EXTI0的輸入腳位。
- Bit3~bit0 = 0110:則選擇PG0作為EXTI0的輸入腳位。
其它EXTI也是一樣的配置,不一樣的只是它們的配置位在AFIO_EXTICR1~AFIO_EXITCR4寄存器中的位置不一樣而已。
AFIO_EXTICR寄存器只有低16位有效,每4個bit配置一個EXTI,一個AFIO_EXTICR可以配置4個EXTI,4個AFIO_EXTICR就可以配置16個EXTI,也就是說AFIO_EXTICR1~AFIO_EXITCR4寄存器剛好可以配置EXTI0~EXTI15的輸入腳位。
需要特別注意的是,每一個EXIT只能設置一個IO作為輸入線,比如說,如果設置PA0作為EXTI0的輸入線之后,PB0和其它P0口就不能作為EXTI0的輸入線了。
3、EXTI的中斷服務函數
通過查詢IRQn_Type結構體中的中斷編號,會發現並不是每個外部中斷都有一個對應中斷編號。有一些外部中斷的中斷編號是組合在一起的,如下:
-
- EXTI0的中斷編號:EXTI0_IRQn。
- EXTI1的中斷編號:EXTI1_IRQn。
- EXTI2的中斷編號:EXTI2_IRQn。
- EXTI3的中斷編號:EXTI3_IRQn。
- EXTI4的中斷編號:EXTI4_IRQn。
- EXTI5~EXTI9的中斷編號:EXTI9_5_IRQn。
- EXTI10~EXTI5的中斷編號:EXTI15_10_IRQn。
從上面可以看到,EXTI5~EXTI9的中斷編號是同一個,也就是說EXTI5~EXIT9是共用一個中斷服務函數,如果要區分是哪一個EXTI產生中斷,可以在中斷服務函數內查詢EXTI_PR。EXTI10~EXTI5的中斷也是一樣的。
4、EXTI配置流程
初始化IO口:
使能要相應IO的時鍾。
將IO口配置上拉或下拉或浮空輸入。設置為浮空輸入的時候,腳位外部最好接上拉電阻或下拉電阻,防止IO口狀態不穩而定,而導致頻繁觸發中斷。
配置IO與中斷線的映射關系:
由於要配置AFIO_EXTICR1~AFIO_EXITCR4寄存器,所以需要開啟復用功能的時鍾。
根據IO配置AFIO_EXTICR寄存器的相應位,使IO作為EXTI功能。
開啟與該IO相對應的中斷/事件線並設置觸發條件:
通過EXTI_RTSR和EXTI_FTSR寄存器設置外部中斷的觸發條件,配置成上升沿觸發或下降沿觸發或電平變化觸發。還需要通過EXTI_IMR寄存器使能相應的外部中斷的中斷使能位。
需要注意的是,如果使用外部中單,並設置該中斷的EMR事件使能位的話,會引起軟件仿真不能跳到中斷,而硬件上可以。如果不是這ERM事件使能位的話,軟件仿真就可以進入中斷服務函數,並且硬件上也可以。
配置NVIC:
通過NVIC配置EXTI的中斷優先級,並使能中斷。
編寫中斷服務函數:
中斷服務函數是必不可少的,如果在代碼里面開啟了中斷,但沒有編寫中斷服務函數,就可以引起硬件錯誤,從而導致程序崩潰。
5、HAL庫初始化EXTI
以配置PA0為EXT0為例,代碼如下:
void EXIT_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); HAL_NVIC_SetPriority(EXTI0_IRQn,2,1); HAL_NVIC_EnableIRQ(EXTI0_IRQn); }
通過設置GPIO_InitStruct.Mode為GPIO_MODE_IT_FALLING,從而區分是設置IO狀態還是設置EXTI功能,這里將PA0配置為下降沿觸發的EXTI0。
在HAL_GPIO_Init函數中已經開啟了復用功能的時鍾。
通過HAL_NVIC_SetPriority函數設置EXTI0的搶占優先級和響應優先級。
通過HAL_NVIC_EnableIRQ函數使能EXIT0中斷。
中斷服務函數如下:
void EXTI0_IRQHandler(void ) { HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); } void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin) { /* EXTI line interrupt detected */ if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET) { __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin); HAL_GPIO_EXTI_Callback(GPIO_Pin); } }
在EXTI0中斷服務函數中調用HAL_GPIO_EXTI_IRQHandler函數,HAL_GPIO_EXTI_IRQHandler是HAL庫內定義的一個函數,該函數在進入的時候就通過宏清除了中斷標志位,然后通過調用HAL_GPIO_EXTI_Callback回調函數實現中斷服務函數的功能。
HAL_GPIO_EXTI_Callback回調函數是一個弱定義的函數,可以通過重新定義來覆蓋。
6、使用EXTI0產生軟件中斷
代碼如下:
void EXTI_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); EXTI->IMR |= 0x01; HAL_NVIC_SetPriority(EXTI0_IRQn,2,1); HAL_NVIC_EnableIRQ(EXTI0_IRQn); while (1) { if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) != 0) { HAL_Delay(20); if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) != 0) { EXTI->SWIER |= 0x01; while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) != 0) { } } } } }
首先將PA0口設為浮空輸入,外接下拉電阻。
然后通過EXTI_IMR寄存器設置EXIT0中斷使能。
通過HAL_NVIC_SetPriority函數設置EXTI0的搶占優先級和響應優先級。
通過HAL_NVIC_EnableIRQ函數使能EXIT0中斷。
最后在主循環中不斷檢測PA是否是高電平,如果是高電平,則給EXTI_SWIER寄存器置位bit0位,這樣就會觸發EXTI0中斷,使得程序跑到EXTI0的中斷服務函數中。