STM32F4開發板 外部中斷實驗
STM32的中斷分為核心中斷和外部中斷,“核心中斷”在《ARM CM3&CM4權威指南》中也被稱作“系統異常”,如下所示:
今天主要討論外部中斷,即 CMSIS-Core中列舉值為正的異常(見表7.3)。
首先看關鍵詞“中斷線”,即“EXTI線”。STM32F407有23個外部中斷,即23箇中斷線,從EXTI線0到EXTI線22。(此處書中似乎寫成了22箇中斷線,貌似有誤)
我們需要將I/O口與中斷線之間建立對映關係,由於I/O口數量多於23,因此這種對映是多對一的,多個I/O口對應一箇中斷線。以EXTI線0為例,STM32按照GPIOA.0,GPIOB.0,GPIOC.0,GPIOD.0,GPIOE.0,GPIOF.0,GPIOG.0,GPIOH.0,GPIOI.0對應EXTI線0(即PA0,PB0,PC0,PD0,PE0,PF0,PG0,PH0,PI0均可以對應EXTI線0)。實際用到某個I/O引腳時,再通過配置決定具體哪個引腳對應EXTI線0。
接下來看關鍵詞“步驟”,即配置外部中斷的步驟。可以參考STM32的韌體庫函式stm32f4xx_exti.c程式碼和《精通STM32F4(庫函式版)》。
程式碼中的註釋部分可以看到:
##### How to use this driver #####
==================================================
[..] In order to use an I/O pin as an external interrupt source, follow steps
below:
(#) Configure the I/O in input mode using GPIO_Init()
[..]
(@) SYSCFG APB clock must be enabled to get write access to SYSCFG_EXTICRx
registers using RCC_APB2PeriphClockCmd
書中的步驟也基本類似:
1.使能I/O口時鐘,初始化I/O口為輸入。—— GPIO_Init(),RCC_AHB1PeriphClockCmd
2.使能SYSCFG時鐘,設定I/O口與中斷線的對映關係。—— RCC_APB2PeriphClockCmd(),SYSCFG_EXTILineConfig()
3.初始化中斷線(哪條EXTI線,中斷模式是中斷還是事件,觸發方式是下降沿觸發、上升沿觸發還是任意電平觸發,是否使能中斷線)。—— EXTI_Init()
4.配置巢狀中斷向量控制器NVIC。—— NVIC_Init()
5.編寫中斷服務函式。—— EXTI0_IRQHandler(),EXTI1_IRQHandler(),EXTI2_IRQHandler(),EXTI3_IRQHandler(),EXTI4_IRQHandler(),EXTI9_5_IRQHandler(),EXTI15_10_IRQHandler()。這些函式名稱是在.s啟動檔案中定義的。
其實庫函式是個殼,實際操作的還是暫存器,庫函式只是相當於把對暫存器的操作“包起來”。如下所示:
EXTI_Init()操作的暫存器實際是下面這幾個:
這幾個暫存器。。。再說吧
stm32中斷初識與實踐(下)
這一部分我們將使用按鍵作為觸發源,在產生中斷時,實現控制LED燈的亮滅狀態切換。
在具體應用前,我們還需先認識認識EXTI。
EXTI
全稱為External interrupt/event controller,即外部中斷/事件控制器。其管理了20箇中斷/事件線,每條線都有對應的一個邊沿檢測器,用於輸入訊號上升沿和下降沿的檢測。如圖6-1為stm32參考手冊裡的EXTI框圖。
圖6-1
圖中有兩條走向的線路,藍色線路用於產生中斷,綠色線路產生事件。我們從右往左看圖。
查閱按鍵原理圖可知,按鍵按下時,電平狀態由低變高,會在輸入線呈現出一個上升沿訊號,這個訊號到達邊沿檢測電路後,會被上升沿觸發選擇暫存器(EXTI_RTSR)檢測並觸發,輸出有效訊號1給編號3電路,否則輸出無效訊號0。如果電平由高變低,則會被下降沿觸發選擇暫存器(EXTI_FTSR)檢測觸發。
觸發的訊號到達編號3電路,這是一個或閘電路,它的輸入訊號除了來源於邊沿檢測電路,還有來自軟體中斷事件暫存器(EXTI_SWIER)。無論是來自EXTI_SWIER或邊沿檢測電路的訊號,只要有一個是有效訊號1,那麼便可以輸出有效訊號1給編號3電路。編號3電路之後,分為兩條線,一條產生中斷,一條產生事件。
訊號沿著藍色線路產生中斷,編號3輸出訊號到編號4。編號4是一個與閘電路,它的另一個訊號來源是中斷遮蔽暫存器(EXTI_IMR)。眾所周知,與閘電路要求輸入訊號都為1才能輸出1,換言之,如果EXTI_IMR置為0,那麼編號4電路輸出的訊號都為0,只有EXTI_IMR置1時,編號4輸出的訊號才由編號3決定。這樣一來我們可以通過EXTI_IMR來控制是否產生中斷。隨後,編號4電路輸出的訊號會被儲存到掛起暫存器(EXTI_PR)內。最後將EXTI_PR裡的值輸出到NVIC,實現中斷控制。
訊號沿著綠色線路產生事件,最終會輸出一個脈衝訊號。編號3輸出訊號到編號5,編號5也是一個與閘電路,訊號來源於編號3電路和事件遮蔽暫存器(EXTI_EMR)。和編號4的與閘電路一樣,我們可以通過EXTI_EMR來控制是否產生事件。當編號5輸出有效訊號1時會在脈衝發生器(Pulse generator)輸出一個脈衝訊號(無效訊號不會輸出脈衝)。這個脈衝訊號可以給其他外設電路使用,如TIM、ADC等等,一般用來觸發TIM或ADC開始轉換。
EXTI的中斷/事件線
EXTI有20條中斷/事件線,其中有16條用於GPIO線上的外部中斷/事件,佔用EXTI0~EXTI15,其他4條用於特定外設的外部中斷/事件。如圖6-2。
圖6-2
可以通過操作AFIO的四個外部中斷配置暫存器(AFIO_EXTICR1~AFIO_EXTICR4)的EXTIx[3:0]位選擇配置PAx、PBx、PCx...PGx等引腳。如圖6-3為AFIO_EXTICR1暫存器描述。
圖6-3
EXTI_InitTypeDef
EXTI_InitTypeDef是EXTI初始化結構體,其定義在stm32f10x.h檔案中,如圖6-4所示。
圖6-4
有四個結構體成員,
- EXTI_Line:中斷/事件線,可選擇EXTI0~EXTI19,如圖6-2所示;
- EXTI_Mode:EXTI模式,可設定產生中斷(EXTI_Mode_Interrupt)或產生事件(EXTI_Mode_Event);
- EXTI_Trigger:EXTI邊沿觸發,可選擇上升沿觸發(EXTI_Trigger_Rising)、下降沿觸發(EXTI_Trigger_Falling)或者上升沿下降沿都觸發(EXTI_Trigger_Rising_Falling);
- EXTI_LineCmd:使能(ENABLE)/失能(DISABLE)EXTI線。
開始實驗
按鍵按下時,產生電平變化,EXTI檢測到上升沿訊號,觸發中斷,執行中斷服務函式,實現LED燈的亮滅切換。
簡要分析下程式設計要點:
- 初始化產生中斷的外設(GPIO);
- 配置NVIC;
- 初始化EXTI;
- 中斷服務函式;
- main函式
NVIC配置
我們先對NVIC進行配置,將其封裝為函式NVIC_Configuration(),供後續呼叫。
/**
* @brief NVIC配置
* @param 無
* @retval 無
*/
static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); // 配置優先順序分組
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; // 配置按鍵中斷源
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 搶佔優先順序為1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 子優先順序為1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中斷暫存器
NVIC_Init(&NVIC_InitStructure);
}
NVIC配置部分,需要配置優先順序分組、中斷源、搶佔優先順序、子優先順序以及使能中斷暫存器等。關於優先順序分組配置以及NVIC_InitTypeDef結構體分析,已在上篇文章裡詳細說明,讀者可點選進入閱讀。
EXTI中斷配置
/**
* @brief EXTI按鍵中斷配置
* @param 無
* @retval 無
*/
void EXTI_Key_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
/* 呼叫函式配置NVIC */
NVIC_Configuration();
/* 初始化GPIO */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空輸入
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* 初始化EXTI */
// 配置中斷線的輸入源
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
EXTI_InitStructure.EXTI_Line = EXTI_Line0; // 配置中斷線為EXTI0
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 配置為中斷模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;// 上升沿觸發中斷
EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 使能中斷
EXTI_Init(&EXTI_InitStructure);
}
這個配置函式裡用到了GPIO和EXTI兩個初始化結構體,對其分別進行初始化配置,同時呼叫NVIC_Configuration()函式配置NVIC。
其中,
- GPIO_EXTILineConfig()韌體函式是對AFIO_EXTICR1的操作,所以我們需要開啟AFIO時鐘;
- 需要把GPIO配置為輸入模式(浮空輸入),由外部電路決定引腳狀態;
- 通過查閱按鍵原理圖可得,按鍵引腳為PA0,可得GPIO埠源和引腳源,並將其中斷線配置為EXTI0;
- 由按鍵原理圖可得,按鍵按下時為高電平,所以使用上升沿觸發中斷。
中斷服務函式
上篇文章裡已經說明中斷服務函式名應在啟動檔案裡找到,並統一寫在stm32f10x_it.c檔案中。
/**
* @brief EXTI0線中斷服務函式
* @param 無
* @retval 無
*/
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0) != RESET) // 確保產生了EXTI0線中斷
{
LED_TOGGLE; // LED燈狀態切換
EXTI_ClearITPendingBit(EXTI_Line0);// 清除中斷標誌位
}
}
需要先確保是否產生了中斷,這一步我們直接呼叫stm32f10x_exti.c檔案裡的庫函式EXTI_GetITStatus(),通過其返回值判斷。EXTI_GetITStatus()函式操作的是中斷遮蔽暫存器(EXTI_IMR)和掛起暫存器(EXTI_PR),通過兩個暫存器的值判斷是否產生中斷,。由圖6-1功能框圖可得,如果相應線的EXTI_IMR和EXTI_PR都置1,則返回“SET”,即產生中斷。具體的原始碼實現可檢視3.5版本的stm32f10x韌體庫。
LED_TOGGLE是一個巨集,在巨集裡實現LED狀態切換。具體的實現在專欄(stm32):GPIO輸入——按鍵檢測文章裡已經有過說明,讀者可移步閱讀。
中斷服務實現後,需要清除該中斷線的中斷標誌位,以免下次程式判斷失誤。
main函式
int main(void)
{
LED_GPIO_Config(); // LED埠初始化
EXTI_Key_Config(); // EXTI按鍵中斷配置
while(1){} // 等待中斷產生
}
當按鍵按下時,即進入中斷,執行中斷服務函式,完成實驗。
至此,stm32的中斷總結完成了,文字有點多,耐心看完,肯定會有所收穫。