二、外部中斷EXTI
中斷:在主程序運行過程中,出現了特定的中斷觸發條件(中斷源),使得CPU暫停當前正在運行的程序,轉而去處理中斷程序,處理完成后又返回原來被暫停的位置繼續運行。
1、stm32中斷簡介
stm32F103有68個可屏蔽中斷通道,包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多個外設。使用NVIC(嵌套中斷向量控制器)統一管理中斷,每個中斷通道都擁有16個可編程的優先等級,可對優先級進行分組,進一步設置搶占優先級和響應優先級。
EXTI0~EXTI4、EXTI9_5和EXTI15_10:即為外部中斷,其中后面兩個為共用的中斷通道,需要在中斷函數中根據標志位判斷是哪個中斷源;
2、NVIC嵌套中斷向量控制器
CPU中用于控制分配中斷優先級,并根據優先級分配中斷的先后執行順序的模塊。分為搶占優先級?(英文前綴pre先占優先級)和響應優先級(英文前綴sub次占優先級)。
搶占優先級是可以產生中斷嵌套,在低搶占優先級的中斷執行中間先執行搶占優先級高的。
響應優先級是插隊,在低響應優先級中斷執行前插隊,但不能搶占。
NVIC中使用SCB_AIRCR寄存器的四位PRIGRUOP進行分組,分配有多少個搶占優先級,有多少個響應優先級。
然后通過NVIC_IPRx寄存器的7~4位決定每個中斷通道的優先級,高n位為搶占優先級,低4-n位為響應優先級。IPRx在庫函數中均已經直接按字節幫我們定義好了,直接使用NVIC->IP[通道號]即可。
NVIC的其他寄存器主要是控制中斷通道是否使能、是否有中斷掛起、設置和清除掛起位等,可以參考手冊和例程
3、EXTI外部中斷控制器
這部分屬于內核外設部分,對于互聯型產品,外部中斷/事件控制器由20個產生事件/中斷請求的邊沿檢測器組成,對于其它產品,則有19個能產生事件/中斷請求的邊沿檢測器。每個輸入線可以獨立地配置輸入類型(脈沖或掛起)和對應的觸發事件(上升沿或下降沿或者雙邊沿都觸發)。每個輸入線都可以獨立地被屏蔽。掛起寄存器保持著狀態線的中斷請求。
事件是指,引腳電平不發生中斷,信號不進入CPU,而是觸發DAC、DMA等外設,用于外設聯合工作。
GPIOA每個引腳需要通過AFIO選通,然后輸入到EXTI中;所以PA0、PB0。。。中只能有一個選通,并進入EXTI0中斷通道。
EXTI的結構
可見支持上升沿、下降沿、雙邊沿和軟件觸發方式。當一個中斷來了,會在請求掛起寄存器中對應位置1,然后根據中斷屏蔽寄存器來決定是否響應中斷。最上面就是外設接口和APB總線,我們可以通過總線訪問這些寄存器
4、使用
對射式紅外傳感器計次
EXTI庫
void EXTI_DeInit(void); // 清除EXTI的配置,恢復成上電默認的狀態
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct); // 調用這個函數根據結構體里的參數配置EXTI外設,使用方法和GPIO_Init一樣
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct); // 調用這個函數可以把參數傳遞的結構體變量復制一個默認值
// 前面三個函數,基本所有的外設都有,像是庫函數的模板函數一樣,基本每個外設都需要這些類型的函數
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);
/*軟件觸發外部中斷的,調用這個函數,參數給一個指定的中斷線,就能軟件觸發一次外部中斷 ,如果程序需要用到這個功能的話,可以使用這個函數,如果
只需要外部引腳出啊發中斷,那就不需要這個函數了*/
/*如果想在主程序里查看和清除標志位,就用下面兩個函數*/
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line); // 獲取指定的標志位是否被置1了
void EXTI_ClearFlag(uint32_t EXTI_Line); // 對置1的標志位進行清除
/*對于這些標志位,有的比較緊急,在置標志位后會觸發中斷,在中斷函數里,如果想查看標志位和清除標志位,就用下面兩個函數*/
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line); // 獲取中斷標志位是否被置1了
void EXTI_ClearITPendingBit(uint32_t EXTI_Line); // 清除中斷掛起標志位
/*上面4個函數也是庫函數的模板函數,很多模塊都有這四個函數,因為在外設運行過程中,會產生一些狀態標志位,比如外部中斷來了,
會有一個掛起寄存器置了一下標志位,對于其它外設,比如串口收到數據,會置標志位,定時器時間到,也會置標志位,這些標志位都是放在狀態寄存器的
,當程序想要看這些標志位時,就可以用到這四個函數*/
NVIC庫
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup); // 這個函數是用來中斷分組的,參數是中斷分組的方式
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct); // 根據結構體里面的指定參數初始化NVIC
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset); //設置中斷向量表
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState); //系統低功耗配置
代碼
uint16_t CountSensor_Count; //全局變量,用于計數/*** 函 數:計數傳感器初始化* 參 數:無* 返 回 值:無*/
void CountSensor_Init(void)
{/*開啟時鐘*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //開啟GPIOB的時鐘RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //開啟AFIO的時鐘,外部中斷必須開啟AFIO的時鐘/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure); //將PB14引腳初始化為上拉輸入/*AFIO選擇中斷引腳*/GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//將外部中斷的14號線映射到GPIOB,即選擇PB14為外部中斷引腳/*EXTI初始化*/EXTI_InitTypeDef EXTI_InitStructure; //定義結構體變量EXTI_InitStructure.EXTI_Line = EXTI_Line14; //選擇配置外部中斷的14號線EXTI_InitStructure.EXTI_LineCmd = ENABLE; //指定外部中斷線使能EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //指定外部中斷線為中斷模式EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //指定外部中斷線為下降沿觸發EXTI_Init(&EXTI_InitStructure); //將結構體變量交給EXTI_Init,配置EXTI外設/*NVIC中斷分組*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC為分組2//即搶占優先級范圍:0~3,響應優先級范圍:0~3//此分組配置在整個工程中僅需調用一次//若有多個中斷,可以把此代碼放在main函數內,while循環之前//若調用多次配置分組的代碼,則后執行的配置會覆蓋先執行的配置/*NVIC配置*///10~15共用一個中斷通道NVIC_InitTypeDef NVIC_InitStructure; //定義結構體變量NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //選擇配置NVIC的EXTI15_10線NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC線路使能NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC線路的搶占優先級為1NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC線路的響應優先級為1NVIC_Init(&NVIC_InitStructure); //將結構體變量交給NVIC_Init,配置NVIC外設
}/*** 函 數:獲取計數傳感器的計數值* 參 數:無* 返 回 值:計數值,范圍:0~65535*/
uint16_t CountSensor_Get(void)
{return CountSensor_Count;
}/*** 函 數:EXTI15_10外部中斷函數* 參 數:無* 返 回 值:無* 注意事項:此函數為中斷函數,無需調用,中斷觸發后自動執行* 函數名為預留的指定名稱,可以從啟動文件復制* 請確保函數名正確,不能有任何差異,否則中斷函數將不能進入*/
void EXTI15_10_IRQHandler(void)
{if (EXTI_GetITStatus(EXTI_Line14) == SET) //判斷是否是外部中斷14號線觸發的中斷{/*如果出現數據亂跳的現象,可再次判斷引腳電平,以避免抖動*/if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0){CountSensor_Count ++; //計數值自增一次}EXTI_ClearITPendingBit(EXTI_Line14); //清除外部中斷14號線的中斷標志位//中斷標志位必須清除//否則中斷將連續不斷地觸發,導致主程序卡死}
}int main(void)
{OLED_Init(); //OLED初始化CountSensor_Init(); //計數傳感器初始化/*顯示靜態字符串*/OLED_ShowString(1, 1, "Count:"); //1行1列顯示字符串Count:while (1){OLED_ShowNum(1, 7, CountSensor_Get(), 5); //OLED不斷刷新顯示CountSensor_Get的返回值}
}
OLED代碼使用GPIO開漏輸出模擬I2C總線,具體代碼見江協科技官網。后續學習I2C時再仔細研讀