8、EXTI之按鍵中斷實驗
EXTI (External interrupt) 就是指外部中斷,通過GPIO檢測輸入脈沖,引起中斷事件,打斷原來的代碼執(zhí)行流程,進(jìn)入到中斷服務(wù)函數(shù)中進(jìn)行處理,處理完后,再返回到中斷之前的代碼中執(zhí)行。
前面提到,STM32的所有GPIO都可以用作外部中斷源的輸入端,利用這個特性,我們可以把按鍵輪詢檢測 改為由中斷 來處理,大大提高軟件執(zhí)行的效率。
8.1 STM32的中斷和異常
Cortex內(nèi)核具有強(qiáng)大的異常響應(yīng)系統(tǒng),它把能夠打斷當(dāng)前代碼執(zhí)行流程的事件分為異常(exception)和中斷(interrupt),并把它們用一個表管理起來,編號為0~15的稱為內(nèi)核異常,而16以上的則稱為外部中斷(外,相對內(nèi)核而言),這個表就稱為中斷向量表。
而STM32對這個表重新進(jìn)行了編排,把編號從-3至6的中斷向量定義為系統(tǒng)異常,編號為負(fù) 的內(nèi)核異常不能被設(shè)置優(yōu)先級,如復(fù)位(Reset)、不可屏蔽中斷 (NMI)、硬錯誤(Hardfault)。從編號7開始的為外部中斷,這些中斷的優(yōu)先級都是可以自行設(shè)置的。詳細(xì)的STM32中斷向量表見圖 81,STM32中斷向量表。
圖 81中斷向量表
這個表可以從《STM32中文參考手冊》找到,但野火一般是從啟動文件startup_stm32f10x_hd.s中查找的,因為不同型號的STM32芯片,中斷向量表稍微有點區(qū)別,在啟動文件中,已經(jīng)有相應(yīng)芯片可用的全部中斷向量。而且在編寫中斷服務(wù)函數(shù)時,需要從啟動文件中定義的中斷向量表查找中斷服務(wù)函數(shù)名。
8.2 NVIC中斷控制器
STM32的中斷如此之多,配置起來并不容易,因此,我們需要一個強(qiáng)大而方便的中斷控制器NVIC (Nested Vectored Interrupt Controller)。NVIC是屬于Cortex內(nèi)核的器件,不可屏蔽中斷 (NMI)和外部中斷都由它來處理,而SYSTICK不是由NVIC來控制的。
圖82 NVIC在內(nèi)核中的位置
8.2.1 NVIC結(jié)構(gòu)體成員
當(dāng)我們要使用NVIC來配置中斷時,自然想到ST庫肯定也已經(jīng)把它封裝成庫函數(shù)了。查找?guī)鞄椭臋n,發(fā)現(xiàn)在Modules->STM32F10x_StdPeriph_Driver->misc 查找到一個NVIC_Init() 函數(shù),對NVIC初始化,首先要定義并填充一個NVIC_InitTypeDef 類型的結(jié)構(gòu)體。
這個結(jié)構(gòu)體有四個成員
前面兩個結(jié)構(gòu)體成員都很好理解,首先要用NVIC_IRQChannel參數(shù)來選擇將要配置的中斷向量,用NVIC_IRQChannelCmd參數(shù)來進(jìn)行使能(ENABLE)或關(guān)閉(DISABLE)該中斷。在NVIC_IRQChannelPreemptionPriority成員要配置中斷向量的搶占優(yōu)先級,在NVIC_IRQChannelSubPriority需要配置中斷向量的響應(yīng)優(yōu)先級。對于中斷的配置,最重要的便是配置其優(yōu)先級,但STM32的同一個中斷向量為什么需要設(shè)置兩種優(yōu)先級?這兩種優(yōu)先級有什么區(qū)別?
8.2.2 搶占優(yōu)先級和響應(yīng)優(yōu)先級
STM32的中斷向量具有兩個屬性,一個為搶占屬性,另一個為響應(yīng)屬性,其屬性編號越小,表明它的優(yōu)先級別越高。
搶占,是指打斷其它中斷的屬性,即因為具有這個屬性,會出現(xiàn)嵌套中斷(在執(zhí)行中斷服務(wù)函數(shù)A的過程中被中斷B打斷,執(zhí)行完中斷服務(wù)函數(shù)B再繼續(xù)執(zhí)行中斷服務(wù)函數(shù)A),搶占屬性由NVIC_IRQChannelPreemptionPriority的參數(shù)配置。
而響應(yīng)屬性則應(yīng)用在搶占屬性相同的情況下,當(dāng)兩個中斷向量的搶占優(yōu)先級相同時,如果兩個中斷同時到達(dá),則先處理響應(yīng)優(yōu)先級高的中斷,響應(yīng)屬性由NVIC_IRQChannelSubPriority的參數(shù)配置。
例如,現(xiàn)在有三個中斷向量:
若內(nèi)核正在執(zhí)行C的中斷服務(wù)函數(shù),則它能被搶占優(yōu)先級更高的中斷A打斷,由于B和C的搶占優(yōu)先級相同,所以C不能被B打斷。但如果B和C中斷是同時到達(dá)的,內(nèi)核就會首先響應(yīng)響應(yīng)優(yōu)先級別更高的B中斷。
8.2.3 NVIC的優(yōu)先級組
在配置優(yōu)先級的時候,還要注意一個很重要的問題,中斷種類的數(shù)量。NVIC只可以配置16種 中斷向量的優(yōu)先級,也就是說,搶占優(yōu)先級和響應(yīng)優(yōu)先級的數(shù)量由一個4位的數(shù)字來決定,把這個4位數(shù)字的位數(shù) 分配成搶占優(yōu)先級部分和響應(yīng)優(yōu)先級部分。有5組分配方式:
第0組: 所有4位用來配置搶占優(yōu)先級,即NVIC配置的24 =16種中斷向量都是只有搶占屬性,沒有響應(yīng)屬性。
第1組:最高1位用來配置搶占優(yōu)先級,低3位用來配置響應(yīng)優(yōu)先級。表示有21=2種級別的搶占優(yōu)先級(0級,1級),有23=8種響應(yīng)優(yōu)先級,即在16種中斷向量之中,有8種中斷,其搶占優(yōu)先級都為0級,而它們的響應(yīng)優(yōu)先級分別為0~7,其余8種中斷向量的搶占優(yōu)先級則都為1級,響應(yīng)優(yōu)先級別分別為0~7。
第2組:2位用來配置搶占優(yōu)先級,2位用來配置響應(yīng)優(yōu)先級。即22=4種搶占優(yōu)先級,22=4種響應(yīng)優(yōu)先級。
第3組:高3位用來配置搶占優(yōu)先級,最低1位用來配置響應(yīng)優(yōu)先級。即有8種搶占優(yōu)先級,2種響應(yīng)2優(yōu)先級。
第4組:所有4位用來配置響應(yīng)優(yōu)先級。即16種中斷向量具有都不相同的響應(yīng)優(yōu)先級。
要配置這些優(yōu)先級組,可以采用庫函數(shù)NVIC_PriorityGroupConfig(),可輸入的參數(shù)為NVIC_PriorityGroup_0 ~ NVIC_PriorityGroup_4,分別為以上介紹的5種分配組。
于是,有讀者覺得疑惑了,如此強(qiáng)大的STM32,所有GPIO都能夠配置成外部中斷,USART、ADC等外設(shè)也有中斷,而NVIC只能配置16種中斷向量,那在某個工程中使用了超過16個的中斷怎么辦呢?注意NVIC能配置的是16種 中斷向量,而不是16個,當(dāng)工程之中有超過16個中斷向量時,必然有2個以上的中斷向量是使用相同的中斷種類,而具有相同中斷種類的中斷向量不能互相嵌套。
STM2 單片機(jī)的所有I/O端口都可以配置為EXTI中斷模式,用來捕捉外部信號,可以配置為下降沿中斷,上升沿中斷和上升下降沿中斷這三種模式。它們以下圖的方式連接到16個外部中斷/事件線上
8.3 EXTI外部中斷
STM32的所有GPIO都引入到EXTI外部中斷線上,使得所有的GPIO都能作為外部中斷的輸入源。GPIO與EXTI的連接方式見圖 03
圖 03 EXTI與GPIO連接圖
觀察這個圖知道,PA0~PG0 連接到EXTI0 、PA1~PG1連接到EXTI1、 ……、 PA15~PG15連接到EXTI15。這里大家要注意的是:PAx~PGx端口的中斷事件都連接到了EXTIx,即同一時刻EXTx只能相應(yīng)一個端口的事件觸發(fā),不能夠同一時間響應(yīng)所有GPIO端口的事件,但可以分時復(fù)用。它可以配置為上升沿觸發(fā),下降沿觸發(fā)或雙邊沿觸發(fā)。EXTI 最普通的應(yīng)用就是接上一個按鍵,設(shè)置為下降沿觸發(fā),用中斷來檢測按鍵。
8.4 中斷檢測按鍵實驗分析
8.4.1實驗描述及工程文件清單
8.4.2 配置工程環(huán)境
本中斷檢測按鍵實驗照例使用了GPIO和RCC片上外設(shè),由于還使用到了中斷,所以比上一個按鍵實驗要多使用兩個庫文件,分別為FWlib/stm32f10x_exti.c和FWlib/misc.c,必須把這兩個文件也添加到工程之中。其中stm32f10x_exti.c文件包含了支持exti配置和操作的相關(guān)庫函數(shù);而misc.c文件則包含了NVIC的配置函數(shù)。本實驗中我們還會在stm32f10x_it.c文件中編寫中斷服務(wù)函數(shù)。
添加了所需要的庫文件、用戶文件之后,要在stm32f10x_conf.h文件中配置使用到的頭文件。
/**
**********************************************************
* @file Project/STM32F10x_StdPeriph_Template/stm32f10x_conf.h
* @author MCD Application Team
* @version V3.5.0
* @date 08-April-2011
* @brief Library configuration file.
*************************************************/
#include "stm32f10x_exti.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "misc.h" /
8.4.5 main文件
我們從main函數(shù)開始分析:
/*
* 函數(shù)名:main
* 描述 :主函數(shù)
* 輸入 :無
* 輸出 :無
*/
int main(void)
{
/* config the led */
LED_GPIO_Config();
LED1( ON );
/* exti line config */
EXTI_PE5_Config();
/* wait interrupt */
while(1)
{
}
}
使用LED_GPIO_Config() 配置好LED用到的I/O后,調(diào)用LED1()點亮一盞LED燈。這兩個函數(shù)的具體講解可參考前面的教程。
8.4.6 配置外部中斷
現(xiàn)在我們重點分析下EXTI_PE5_Config() 這個函數(shù),這是一個在用戶文件exti.c中實現(xiàn)的函數(shù),它完成了一般配置一個I/O為EXTI中斷的步驟,主要為功能:
1、使能EXTIx線的時鐘和第二功能AFIO時鐘
2、配置EXTIx線的中斷優(yōu)先級
3、配置EXTI 中斷線I/O
4、選定要配置為EXTI的I/O口線和I/O口的工作模式
5、EXTI 中斷線工作模式配置
EXTI_PE5_Config()代碼:
/*
* 函數(shù)名:EXTI_PE5_Config
* 描述 :配置 PE5 為線中斷口,并設(shè)置中斷優(yōu)先級
* 輸入 :無
* 輸出 :無
* 調(diào)用 :外部調(diào)用
*/
void EXTI_PE5_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
/* config the extiline(PE5) clock and AFIO clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE | RCC_APB2Periph_AFIO,ENABLE);
/* config the NVIC(PE5) */
NVIC_Configuration();
/* EXTI line gpio config(PE5) */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; // 上拉輸入
GPIO_Init(GPIOE, &GPIO_InitStructure);
/* EXTI line(PE5) mode config */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource5);
EXTI_InitStructure.EXTI_Line = EXTI_Line5;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿中斷
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
}
8.4.7 AFIO時鐘
EXTI_PE5_Config()代碼的第14行,調(diào)用RCC_APB2PeriphClockCmd() 時還輸入了參數(shù)RCC_APB2Periph_AFIO,表示開啟AFIO的時鐘。見圖 04。
AFIO (alternate-function I/O),指GPIO端口的復(fù)用功能,GPIO除了用作普通的輸入輸出(主功能),還可以作為片上外設(shè)的復(fù)用輸入輸出,如串口,ADC,這些就是復(fù)用功能。大多數(shù)GPIO都有一個默認(rèn)復(fù)用功能,有的GPIO還有重映射功能, 重映射功能是指把原來屬于A引腳的默認(rèn)復(fù)用功能,轉(zhuǎn)移到了B引腳進(jìn)行使用,前提是B引腳具有這個重映射功能
當(dāng)把GPIO用作EXTI外部中斷 或使用重映射功能的時候,必須開啟AFIO時鐘,而在使用默認(rèn)復(fù)用功能的時候,就不必開啟AFIO時鐘了。
圖 04 GPIO引腳功能說明
8.4.8 NVIC初始化配置
在EXTI_PE5_Config()代碼的第17行調(diào)用了NVIC_Configuration(),這是用戶編寫的用來配置NVIC控制器的函數(shù)。其實現(xiàn)如下:
/*
* 函數(shù)名:NVIC_Configuration
* 描述 :配置嵌套向量中斷控制器NVIC
* 輸入 :無
* 輸出 :無
* 調(diào)用 :內(nèi)部調(diào)用
*/
static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Configure one bit for preemption priority */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/* 配置P[A|B|C|D|E]5為中斷源 */
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
本代碼的第13行調(diào)用了 NVIC_PriorityGroupConfig()庫函數(shù),把NVIC中斷優(yōu)先級分組設(shè)置為第1組。接下來開始向NVIC初始化結(jié)構(gòu)體寫入?yún)?shù) .NVIC_IRQChannel = EXTI9_5_IRQn,表示要配置的為EXTI第5~9線的中斷向量。因為按鍵PE5對應(yīng)的EXTI線為EXTI5,而從EXTI5~EXTI9線,由于它們是使用同一個中斷向量的,所以只能寫入EXTI9_5_IRQn這個參數(shù)。這些可寫入的參數(shù)可以在stm32f10x.h文件的IRQn類型定義中查找到。
然后配置搶占優(yōu)先級和響應(yīng)優(yōu)先級,因為這個工程簡單,就直接把它設(shè)置為最高級中斷。填充完結(jié)構(gòu)體,別忘記最后要調(diào)用NVIC_Init() 函數(shù)來向寄存器寫入?yún)?shù)。
8.4.9 EXTI初始化配置
回到EXTI_PE5_Config()代碼中,配置好NVIC后,還要對GPIOE進(jìn)行初始化,這部分和按鍵輪詢的設(shè)置類似。
接下來,調(diào)用GPIO_EXTILineConfig()函數(shù)把GPIOE,Pin5設(shè)置為EXTI輸入線。
圖 05 EXTI中斷源配置函數(shù)
選擇好了GPIO,開始填寫EXTI的初始化結(jié)構(gòu)體。從這些參數(shù)的名字,讀者就已經(jīng)可以知道野火是如何把它應(yīng)用到按鍵檢測中了吧?
.EXTI_Line = EXTI_Line5;
給EXTI_Line成員賦值。選擇EXTI_Line5線進(jìn)行配置,因為按鍵的PE5連接到了EXTI_Line5。
.EXTI_Mode = EXTI_Mode_Interrupt;
給EXTI_Mode成員賦值。把EXTI_Line5的模式設(shè)置為為中斷模式EXTI_Mode_Interrupt。這個結(jié)構(gòu)體成員也可以賦值為事件模式EXTI_Mode_Event ,這個模式不會立刻觸發(fā)中斷,而只是在寄存器上把相應(yīng)的事件標(biāo)置位置1,應(yīng)用這個模式要不停地查詢相應(yīng)的寄存器。
.EXTI_Trigger = EXTI_Trigger_Falling;
給EXTI_Trigger成員賦值。把觸發(fā)方式(EXTI_Trigger)設(shè)置為下降沿觸發(fā)(EXTI_Trigger_Falling)。
.EXTI_LineCmd = ENABLE;
給EXTI_LineCmd成員賦值。把EXTI_LineCmd設(shè)置為使能。
最后調(diào)用EXTI_Init()把EXTI初始化結(jié)構(gòu)體的參數(shù)寫入寄存器。
8.4.10 編寫中斷服務(wù)函數(shù)
在這個EXTI設(shè)置中我們把PE5連接到內(nèi)部的EXTI5,GPIO配置為上拉輸入,工作在下降沿中斷。在外圍電路上我們將PE5接到了key1上。當(dāng)按鍵沒有按下時,PE5始終為高,當(dāng)按鍵按下時PE5變?yōu)榈?,從?span style="COLOR: #008080">PE5上產(chǎn)生一個下降沿跳變,EXTI5會捕捉到這一跳變,并產(chǎn)生相應(yīng)的中斷,中斷服務(wù)程序在stm32f10x_it.c中實現(xiàn)。
stm32f10x_it.c文件是專門用來存放中斷服務(wù)函數(shù)的。文件中默認(rèn)只有幾個關(guān)于系統(tǒng)異常的中斷服務(wù)函數(shù),而且都是空函數(shù),在需要的時候自已進(jìn)行編寫。那么中斷服務(wù)函數(shù)名是不是可以自己定義呢?不可以。中斷服務(wù)函數(shù)的名字必須要跟啟動文件startup_stm32f10x_hd.s中的中斷向量表定義一致。以下為啟動文件中定義的部分向量表:
DCD EXTI0_IRQHandler ; EXTI Line 0
DCD EXTI1_IRQHandler ; EXTI Line 1
DCD EXTI2_IRQHandler ; EXTI Line 2
DCD EXTI3_IRQHandler ; EXTI Line 3
DCD EXTI4_IRQHandler ; EXTI Line 4
DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1
DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2
DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3
DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4
DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5
DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6
DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7
DCD ADC1_2_IRQHandler ; ADC1 & ADC2
DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX
DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0
DCD CAN1_RX1_IRQHandler ; CAN1 RX1
DCD CAN1_SCE_IRQHandler ; CAN1 SCE
DCD EXTI9_5_IRQHandler ; EXTI Line 9..5
DCD TIM1_BRK_IRQHandler ; TIM1 Break
DCD TIM1_UP_IRQHandler ; TIM1 Update
DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation
DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare
DCD TIM2_IRQHandler ; TIM2
DCD TIM3_IRQHandler ; TIM3
DCD TIM4_IRQHandler ; TIM4
DCD I2C1_EV_IRQHandler ; I2C1 Event
DCD I2C1_ER_IRQHandler ; I2C1 Error
DCD I2C2_EV_IRQHandler ; I2C2 Event
DCD I2C2_ER_IRQHandler ; I2C2 Error
DCD SPI1_IRQHandler ; SPI1
DCD SPI2_IRQHandler ; SPI2
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD USART3_IRQHandler ; USART3
DCD EXTI15_10_IRQHandler ; EXTI Line 15..10
第18行,為EXTI9_5IRQHandler,表示為EXTI9~EXTI5中斷向量的服務(wù)函數(shù)名。
于是,我們就可以在stm32f10x_it.c文件中加入名為EXTI9_5_IRQHandler()的函數(shù):
/* I/O線中斷,中斷線為PE5 */
void EXTI9_5_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line5) != RESET) //確保是否產(chǎn)生了EXTI Line中斷
{
// LED1 取反
GPIO_WriteBit(GPIOC, GPIO_Pin_3,
(BitAction)((1-GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_3))));
EXTI_ClearITPendingBit(EXTI_Line5); //清除中斷標(biāo)志位
}
}
其內(nèi)容比較容易理解,進(jìn)入中斷后,調(diào)用庫函數(shù)EXTI_GetITStatus() 來重新檢查是否產(chǎn)生了EXTI_Line中斷,接下來把LED取反,操作完畢后,調(diào)用EXTI_ClearITPendingBit() 清除中斷標(biāo)置位再退出中斷服務(wù)函數(shù)。這兩個函數(shù)的解釋見圖 06及圖 07。
圖 06EXTI狀態(tài)檢查函數(shù)
圖 07 EXTI清除標(biāo)志位函數(shù)
這兩種函數(shù)在ST庫函數(shù)非常見,當(dāng)我們要讀取某外設(shè)的狀態(tài)時,可調(diào)用該外設(shè)的XXX_GetFlagStatus()函數(shù)來獲取該狀態(tài)。一般也有XXX_ClearFlag()庫函數(shù)可供調(diào)用,進(jìn)行相應(yīng)的標(biāo)志位清除。
中斷服務(wù)程序比較簡單,很容易讀懂,但我們在寫中斷函數(shù)入口的時候要注意函數(shù)名的寫法,函數(shù)名只有兩種命名方法:
1-> EXTI0_IRQHandler ; EXTI Line 0
EXTI1_IRQHandler ; EXTI Line 1
EXTI2_IRQHandler ; EXTI Line 2
EXTI3_IRQHandler ; EXTI Line 3
EXTI4_IRQHandler ; EXTI Line 4
2-> EXTI9_5_IRQHandler ; EXTI Line 9..5
EXTI15_10_IRQHandler ; EXTI Line 15..10
只要是中斷線在5之后的就不能像0~4那樣單獨一個函數(shù)名,都必須寫成EXTI9_5_IRQHandler和EXTI15_10_IRQHandler。假如寫成EXTI5_IRQHandler、EXTI6_IRQHandler……EXTI15_IRQHandler這樣子的話編譯器是不會報錯的,只是中斷服務(wù)程序不能工作罷了。如果你不知道的話,會讓你搞半天也不知問題出現(xiàn)在哪。
8.4.11實驗現(xiàn)象
將野火STM32開發(fā)板供電(DC5V),插上JLINK,將編譯好的程序下載到開發(fā)板,LED1亮,按下按鍵時LED1滅,再按下按鍵時LED1亮,如此循環(huán)。