點選上方“ 果果小師弟 ”,選擇“ 置頂/星标公衆号 ”幹貨福利,第一時間送達!
STM32F4 的每個 IO 都可以作為外部中斷的中斷輸入口,這點也是 STM32F4 的強大之處。STM32F429 的中斷控制器支援 22個外部中斷/事件請求。每個中斷設有狀态位,每個中斷/事件都有獨立的觸發和屏蔽設定。 STM32F429有22個外部中斷,我們這裡隻看IO口的16個外部中斷:EXTI 線 0~15:對應外部 IO 口的輸入中斷。
STM32F4 供 IO 口使用的中斷線隻有 16 個,但是 STM32F4 的 IO 口卻遠遠不止 16 個,那麼 STM32F4 是怎麼把 16 個中斷線和 IO 口一一對應起來的呢?于是 STM32就這樣設計,GPIO 的引腳 GPIOx.0-GPIOx.15(x=A,B,C,D,E,F,G,H,I)分别對應中斷線 0~15。這樣每個中斷線對應了最多 9 個 IO 口,以線 0 為例:它對應了
GPIOA.0,GPIOB.0,GPIOC.0,GPIOD.0,GPIOE.0,GPIOF.0,GPIOG.0,GPIOH.0,GPIOI.0。而中斷線每次隻能連接配接到 1 個 IO口上,這樣就需要通過配置來決定對應的中斷線配置到哪個 GPIO 上了。
我舉一個例子:我們一個學校(對應一個單片機)有16個老師(對應16根中斷線)。但是我們現在有9個班級(GPIOA.0-GPIOI)。每個班級有16個同學(GPIOA_0…..GPIOA_15)。如何讓這16位老師負責9個班級一共9X16=144個學生呢?現在的方法就是:讓第1個老師負責每個班級的第1位同學。讓第2個老師負責每個班級的第2位同學………….,讓第16個老師負責每個班級的第16位同學這樣就可以了對吧。
下面我們看看 GPIO跟中斷線的映射關系圖就容易了解多了:
中斷線的映射關系圖 哈哈哈 是不是通俗易懂!!!
接下來就是寫程式了。
程式配置:
1.第一步當然是初始化你的IO口了對吧。比如我們開始寫按鍵的時候是這樣寫的。
void KEY_Init(void){
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能PA端口時鐘
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉輸入
GPIO_Init(GPIOC, &GPIO_InitStructure); //根據設定參數初始化GPIOC
}
2.初始化了IO口,接下來我們要幹嘛呢?你不是要讓按鍵按下了之後去幹别的事嗎?那就打開IO口的複用功能:使能EXTI外設對應的時鐘。注意:當使用EXTI外設時,使能的是AFIO時鐘,而不是EXTI外設時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
3.現在就是要把中斷線和對應的IO口的關系給連接配接上!
打開stm32f4xx_gpio.h。就看到下面這每個班的16個學生了,整整齊齊的排在這裡。因為每一個GPIO都有16個管腳,是以這裡最大是從 GPIO_PinSource0到GPIO_PinSource15。
利用GPIO_EXTILineConfig()将EXTI線0連接配接到端口GPIOA的第0個針腳上
具體代碼:
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);
注意:如果配置的針腳是4号,那麼參數必須是GPIO_PinSource4 如果配置的針腳是3号,那麼參數必須是GPIO_PinSource3
4.接下來就是初始化EXTI了:打開stm32f42x_exti.h。就看到這16個老師高興的站在那裡,等待着他的學生(還有幾個老師也站在那裡但是不是負責GPIO的我們先不管他們)。我們要做的就是讓第1個老師去負責第一個班級(GPIOA)的第一個同學(GPIOA_0)。因為你是第一個老師要負責第1個班級自然就是負責班級1的第1個同學啦。
typedef enum
{
EXTI_Mode_Interrupt = 0x00, //中斷觸發
EXTI_Mode_Event = 0x04 //事件觸發
}EXTIMode_TypeDef;typedef enum
{
EXTI_Trigger_Rising = 0x08, //上升沿觸發
EXTI_Trigger_Falling = 0x0C, //下降沿觸發
EXTI_Trigger_Rising_Falling = 0x10 //高低電平觸發
}EXTITrigger_TypeDef;#define EXTI_Line0 ((uint32_t)0x00001) /*!#define EXTI_Line1 ((uint32_t)0x00002) /*!#define EXTI_Line2 ((uint32_t)0x00004) /*!#define EXTI_Line3 ((uint32_t)0x00008) /*!#define EXTI_Line4 ((uint32_t)0x00010) /*!#define EXTI_Line5 ((uint32_t)0x00020) /*!#define EXTI_Line6 ((uint32_t)0x00040) /*!#define EXTI_Line7 ((uint32_t)0x00080) /*!#define EXTI_Line8 ((uint32_t)0x00100) /*!#define EXTI_Line9 ((uint32_t)0x00200) /*!#define EXTI_Line10 ((uint32_t)0x00400) /*!#define EXTI_Line11 ((uint32_t)0x00800) /*!#define EXTI_Line12 ((uint32_t)0x01000) /*!#define EXTI_Line13 ((uint32_t)0x02000) /*!#define EXTI_Line14 ((uint32_t)0x04000) /*!#define EXTI_Line15 ((uint32_t)0x08000) /*!#define EXTI_Line16 ((uint32_t)0x10000) /*!#define EXTI_Line17 ((uint32_t)0x20000) /*!#define EXTI_Line18 ((uint32_t)0x40000) /*! #define EXTI_Line19 ((uint32_t)0x80000) /*!
那具體的代碼就是下面這樣的:
void exti_Init(void){
EXTI_InitTypeDef EXTI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //外部中斷,需要使能AFIO時鐘
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);//将EXTI線連接配接到對應的IO端口上
EXTI_InitStructure.EXTI_Line = EXTI_Line0; //常用的就是EXTI_Line0-EXTI_Line015負責gpio管腳的那幾個
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿觸發
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //中斷線使能
EXTI_Init(&EXTI_InitStructure); //初始化中斷
}
到此為止外部中斷就寫好了。當然這些函數你可以放在一起,就是下面這樣:
void key_exti_init(void){
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //外部中斷,需要使能AFIO時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能PA端口時鐘
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉輸入
GPIO_Init(GPIOA, &GPIO_InitStructure); //根據設定參數初始化GPIOA //注意:如果配置的針腳是0号,那麼參數必須是GPIO_PinSource0 如果配置的針腳是3号,那麼參數必須是GPIO_PinSource3
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);//将EXTI線連接配接到對應的IO端口上//注意:如果配置的0号針腳,那麼EXTI_Line0是必須的 如果配置的針腳是3号,那麼參數必須是EXTI_Line3
EXTI_InitStructure.EXTI_Line = EXTI_Line0; //常用的就是EXTI_Line0-EXTI_Line015負責gpio管腳的那幾個
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿觸發
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure); //初始化中斷
}
你以為到次就結束了嗎?當然沒有。既然有了中斷就要有中斷優先級。同一時間你爸爸叫你吃飯,正在這時候你女朋友給你打電話。你是先去吃飯了還是先接電話?當然是先接電話對吧。但是單片機可是不知道要先去幹嘛。你就必須給他配一下中斷優先級。是以就引出了NVIC中斷優先級。你要問我NVIC幹嘛的,我就告訴你NVIC就是你在同一時間有兩個事件來了先幹哪一個。這回就有人說了假如我現在有100個事件同時來了先幹哪一個呢?不要着急待我細細道來!!!
優先級的定義
在 NVIC 有一個專門的寄存器:中斷優先級寄存器 NVIC_IPRx(在 F429 中,x=0…90)用來配置外部中斷的優先級,IPR寬度為 8bit,原則上每個外部中斷可配置的優先級為0~255,數值越小,優先級越高。但是絕大多數 CM4晶片都會精簡設計,以緻實際上支援的優先級數減少,在 F429 中,隻使用了高 4bit,就是每個外部中斷可配置的優先級為0-15。如下所示:
用于表達優先級的這 4bit,又被分組成 搶占優先級 和 子優先級 。 如果有多個中斷同時響應,搶占優先級高的就會 搶占搶占優先級低的優先得到執行,如果搶占優先級相同,就比較子優先級。 如果搶占優先級和子優先級都相同的話,就比較他們的硬體中斷編号,編号越小,優先級越高。
優先級的分組
這裡我們用階級來表示搶占優先級,用階層來表示子優先級。通常我們把響應優先級也叫作子優先級。
1.如果我們按照NVIC_PriorityGroup_4這樣分組的話,系統就配置設定了4位搶占優先級。0位響應優先級。就分了16個階級(2^4=16),0個階層。
比如我來了一個中斷叫做外部中斷1(EXTI1_IRQn)。他的搶占優先級就可以設定為0-15.響應優先級就隻能設定為0,假如在這個時候又來了一個中斷叫做外部中斷2(EXTI2_IRQn)。他的搶占優先級就可以設定為0-15.響應優先級就隻能設定為0。t他們的優先級可以設定為一樣的,也可以設定為不一樣的。如果假如都設定搶占優先級為4,那麼系統就看哪一個中斷先發生,先發生就先執行。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); //設定NVIC中斷分組4:4位搶占優先級,0位響應優先級
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;//使能外部中斷通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 4; //搶占優先級4 因為為分組為4 這裡可以設定為0-15
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //響應優先級0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中斷通道
NVIC_Init(&NVIC_InitStructure); //根據NVIC_InitStruct中指定的參數初始化外設NVIC寄存器
2.如果我們按照NVIC_PriorityGroup_2這樣分組的話,系統就配置設定了2位搶占優先級。2位響應優先級。就分了4個階級(2^2=4),4個階層。
比如我來了一個中斷叫做外部中斷1(EXTI1_IRQn)。他的搶占優先級就不能設定為0-15,範圍應該是0-3,響應優先級範圍設定為0-3,假如在這個時候又來了一個中斷叫做外部中斷2(EXTI2_IRQn)。他的搶占優先級就可以設定為0-3.響應優先級範圍也是0-3。同樣的他們優先級可以設定為一樣的,也可以設定為不一樣的。如果外部中斷1的搶占優先級為2,子優先級為1,外部中斷2的搶占優先級為2,子優先級為0。那麼當兩個中斷同時發生的時候就會首先響應外部中斷2,因為外部中斷2子優先級高于外部中斷1的子優先級.數值越小優先級越高。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //設定NVIC中斷分組2:2位搶占優先級,2位響應優先級
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;//使能外部中斷通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //搶占優先級2 因為為分組為2 這裡可以設定為0-3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //響應優先級0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中斷通道
NVIC_Init(&NVIC_InitStructure); //根據NVIC_InitStruct中指定的參數初始化外設NVIC寄存器
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; //使能外部中斷通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//搶占優先級2 因為為分組為4 這裡可以設定為0-3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //響應優先級0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中斷通道
NVIC_Init(&NVIC_InitStructure); //根據NVIC_InitStruct中指定的參數初始化外設NVIC寄存器
一般情況下,系統代碼執行過程中,隻設定一次中斷優先級分組,比如分組2,設定好分組之後一般不會再改變分組。随意改變分組會導緻中斷管理混亂,程式出現意想不到的執行結果。換句話說NVIC_PriorityGroupConfig();這個函數在你的整個程式中隻能設定一次,這個要切記!!!
3.上面我們說到來了一個外部中斷1(EXTI1_IRQn)和外部中斷2(EXTI2_IRQn)。根據我在最前面講的一個故事也就是說,用到的管腳與中斷線的序号是要一一對應的,不管A--H用的哪一組的管腳,PIN1就要對應EXTI1--PIN15就要對應EXTI15.同時也要對應相應的中斷函數,在庫函數中EXTI0_IRQn,EXTI1_IRQn,EXTI2_IRQn,EXTI3_IRQn,EXTI4_IRQn,EXTI9_5_IRQn(EXTI5-EXTI9都對應這個中斷),EXTI15_10_IRQn(EXITI10-EXTI15都對應這個中斷函數)。這些中斷通道全都在stm32f4xx.h中用了一個枚舉結構體包起來了。想用哪一個找到對應的通道寫上就可以了。
typedef enum IRQn
{/****** Cortex-M4處理器異常數 ****************************************************************/
NonMaskableInt_IRQn = -14, /*!
MemoryManagement_IRQn = -12, /*!
BusFault_IRQn = -11, /*!
UsageFault_IRQn = -10, /*!
SVCall_IRQn = -5, /*!
DebugMonitor_IRQn = -4, /*!
PendSV_IRQn = -2, /*!
SysTick_IRQn = -1, /*!/****** STM32 特定的中斷數量 **********************************************************************/
WWDG_IRQn = 0, /*!
PVD_IRQn = 1, /*!
TAMP_STAMP_IRQn = 2, /*!
RTC_WKUP_IRQn = 3, /*!
FLASH_IRQn = 4, /*!
RCC_IRQn = 5, /*!
EXTI0_IRQn = 6, /*!
EXTI1_IRQn = 7, /*!
EXTI2_IRQn = 8, /*!
EXTI3_IRQn = 9, /*!
EXTI4_IRQn = 10, /*!
DMA1_Stream0_IRQn = 11, /*!
DMA1_Stream1_IRQn = 12, /*!
DMA1_Stream2_IRQn = 13, /*!
DMA1_Stream3_IRQn = 14, /*!
DMA1_Stream4_IRQn = 15, /*!
DMA1_Stream5_IRQn = 16, /*!
DMA1_Stream6_IRQn = 17, /*!
ADC_IRQn = 18, /*!
#if defined(STM32F429_439xx)
CAN1_TX_IRQn = 19, /*!
CAN1_RX0_IRQn = 20, /*!
CAN1_RX1_IRQn = 21, /*!
CAN1_SCE_IRQn = 22, /*!
EXTI9_5_IRQn = 23, /*!
TIM1_BRK_TIM9_IRQn = 24, /*!
TIM1_UP_TIM10_IRQn = 25, /*!
TIM1_TRG_COM_TIM11_IRQn = 26, /*!
TIM1_CC_IRQn = 27, /*!
TIM2_IRQn = 28, /*!
TIM3_IRQn = 29, /*!
TIM4_IRQn = 30, /*!
I2C1_EV_IRQn = 31, /*!
I2C1_ER_IRQn = 32, /*!
I2C2_EV_IRQn = 33, /*!
I2C2_ER_IRQn = 34, /*!
SPI1_IRQn = 35, /*!
SPI2_IRQn = 36, /*!
USART1_IRQn = 37, /*!
USART2_IRQn = 38, /*!
USART3_IRQn = 39, /*!
EXTI15_10_IRQn = 40, /*!
RTC_Alarm_IRQn = 41, /*!
OTG_FS_WKUP_IRQn = 42, /*!
TIM8_BRK_TIM12_IRQn = 43, /*!
TIM8_UP_TIM13_IRQn = 44, /*!
TIM8_TRG_COM_TIM14_IRQn = 45, /*!
TIM8_CC_IRQn = 46, /*!
DMA1_Stream7_IRQn = 47, /*!
FMC_IRQn = 48, /*!
SDIO_IRQn = 49, /*!
TIM5_IRQn = 50, /*!
SPI3_IRQn = 51, /*!
UART4_IRQn = 52, /*!
UART5_IRQn = 53, /*!
TIM6_DAC_IRQn = 54, /*!
TIM7_IRQn = 55, /*!
DMA2_Stream0_IRQn = 56, /*!
DMA2_Stream1_IRQn = 57, /*!
DMA2_Stream2_IRQn = 58, /*!
DMA2_Stream3_IRQn = 59, /*!
DMA2_Stream4_IRQn = 60, /*!
ETH_IRQn = 61, /*!
ETH_WKUP_IRQn = 62, /*!
CAN2_TX_IRQn = 63, /*!
CAN2_RX0_IRQn = 64, /*!
CAN2_RX1_IRQn = 65, /*!
CAN2_SCE_IRQn = 66, /*!
OTG_FS_IRQn = 67, /*!
DMA2_Stream5_IRQn = 68, /*!
DMA2_Stream6_IRQn = 69, /*!
DMA2_Stream7_IRQn = 70, /*!
USART6_IRQn = 71, /*!
I2C3_EV_IRQn = 72, /*!
I2C3_ER_IRQn = 73, /*!
OTG_HS_EP1_OUT_IRQn = 74, /*!
OTG_HS_EP1_IN_IRQn = 75, /*!
OTG_HS_WKUP_IRQn = 76, /*!
OTG_HS_IRQn = 77, /*!
DCMI_IRQn = 78, /*!
CRYP_IRQn = 79, /*!
HASH_RNG_IRQn = 80, /*!
FPU_IRQn = 81, /*!
UART7_IRQn = 82, /*!
UART8_IRQn = 83, /*!
SPI4_IRQn = 84, /*!
SPI5_IRQn = 85, /*!
SPI6_IRQn = 86, /*!
SAI1_IRQn = 87, /*!
LTDC_IRQn = 88, /*!
LTDC_ER_IRQn = 89, /*!
DMA2D_IRQn = 90 /*!
#endif /* STM32F429_439xx */
} IRQn_Type;
是以我們結合EXTI和NVIC的代碼,就可以整理為
void key_exti_init(void){
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //外部中斷,需要使能AFIO時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能PA端口時鐘
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉輸入
GPIO_Init(GPIOA, &GPIO_InitStructure); //根據設定參數初始化GPIOA //注意:如果配置的針腳是0号,那麼參數必須是GPIO_PinSource0 如果配置的針腳是3号,那麼參數必須是GPIO_PinSource3
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);//将EXTI線連接配接到對應的IO端口上//注意:如果配置的0号針腳,那麼EXTI_Line0是必須的 如果配置的針腳是3号,那麼參數必須是EXTI_Line3
EXTI_InitStructure.EXTI_Line = EXTI_Line0; //常用的就是EXTI_Line0-EXTI_Line015負責gpio管腳的那幾個
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿觸發
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure); //初始化中斷
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //設定NVIC中斷分組2:2位搶占優先級,2位響應優先級
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;//使能外部中斷通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //搶占優先級2 因為為分組為2 這裡可以設定為0-3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //響應優先級0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中斷通道
NVIC_Init(&NVIC_InitStructure); //根據NVIC_InitStruct中指定的參數初始化外設NVIC寄存器
}
當然為了代碼的美觀,你可以把關于NVIC的代碼放到一起便于管理。就像這樣,就可以清楚的看到響應的順序
以上就是所有關于中斷和中斷管理知識了。這方面不是很難了解,在遇到問題時對看看對應晶片的中文參考手冊就可以了。
END往期精彩回顧 一、STM32第一章-寄存器你懂嗎 二、STM32第二章-啟動過程詳解 三、STM32第三章-系統時鐘配置 如果覺得文章對你有幫助,歡迎轉發、分享給你的朋友,感謝您的支援!如需轉載請聯系我!