先看一段代碼:
while(1)
{
if(EXTI_Sign==1)
{
HAL_Delay(Period);
HAL_NVIC_EnableIRQ(EXTI2_3_IRQn);
HAL_NVIC_EnableIRQ(EXTI2_3_IRQn);
EXTI_Sign=0;
。。。。。。
}
}
有人使用STM32G0系列的芯片開發產品,有段功能測試驗證代碼如上所示,相同的函數必須調用2次才能正常運行,調用2次倒也罷了,關鍵是必須!頗為納悶。
這里開啟了PA3的外部中斷功能,上下沿均可觸發。PA3接收外來報警信號,類似于煙感報警器。報警信號是一串脈沖信號,報警信號過來時存在多次抖動問題??蛻粝肓藗€方法消抖,只要報警端口有電平變化就觸發中斷然后把中斷Disable,并設置報警標志再回到主程序。
主程序里識別到報警有效標志后延時幾分鐘再Enable剛才Disable掉的外部中斷。但是,他發現再次使能外部中斷時需要連續兩次調用使能中斷的代碼才可以響應新的報警信號。【此處文字依據反饋者的文字描述組織而成】
下面MX_GPIO_Init(void)是經CubeMx配置后自動生成的,里面有EXTI相關NVIC配置。相關代碼如下:
static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); 。。。。。。 /*Configure GPIO pin : PA3 */ GPIO_InitStruct.Pin = GPIO_PIN_3; GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* EXTI interrupt init*/ HAL_NVIC_SetPriority(EXTI2_3_IRQn, 0, 0); HAL_NVIC_EnableIRQ(EXTI2_3_IRQn); } 基于上沿觸發的中斷服務程序如下[基于下沿觸發的此處省略】: EXTI ISR(): { __HAL_GPIO_EXTI_CLEAR_RISING_IT(GPIO_PIN_3);//清中斷申請標志; HAL_NVIC_DisableIRQ(EXTI2_3_IRQn);//關閉中斷響應 EXIT_Sign=1;//表示收到報警信號 } 主循環代碼像下面書寫才能讓程序正常運行:【略去了其它代碼】 while (1) { if(EXTI_Sign ==1) { HAL_Delay(Period); MX_GPIO_Init();//客戶無意中發現加這句有用 HAL_NVIC_EnableIRQ(EXTI2_3_IRQn); EXTI_Sign =0; 。。。。。。 } }
現在的疑問是在EXTI中斷服務程序運行HAL_NVIC_DisableIRQ(EXTI2_3_IRQn)后,到主循環代碼里再次使能外部中斷時,為何還要額外運行一次MX_GPIO_Init()函數才能讓程序正常運行。最終發現運行該函數的實質就是將HAL_NVIC_EnableIRQ(EXTI2_3_IRQn)多運行一次。
換句話說,上面的主循環代碼要改成下面樣子才可以讓程序正常運行:
while(1)
{
if(EXTI_Sign==1)
{//報警有效,即發生過報警時,代碼進到這里。
HAL_Delay(Period);
HAL_NVIC_EnableIRQ(EXTI2_3_IRQn);//1
HAL_NVIC_EnableIRQ(EXTI2_3_IRQn);//2
EXTI_Sign=0; //清除報警標志,準備監測新的警情
。。。。。。
}
}
說到底,問題就是主循環里為何要兩次重復運行HAL_NVIC_EnableIRQ(EXTI2_3_IRQn)函數后才能響應新的報警信號呢?
可以肯定,理論上講,開啟某個中斷響應無須2次運行相關函數。我們來一起找找原因。為了便于查看代碼,我把中斷服務程序和主程序代碼截圖放在一起。
在中斷服務程序里就是清除中斷請求標志,關閉PA3的外部中斷響應,并設置警情標志EXTI_Sign為1。
這里有沒有問題呢?
他使用的HAL_NVIC_EnableIRQ(EXTI2_3_IRQn)函數,關閉的是內核對該中斷請求的響應,盡管他剛才在進中斷時做外部中斷請求標志的清零,但并不能保證他這個清零操作之后不會再產生外部中斷請求。事實上,結合目前的使用場景,由于報警信號是一串跳變脈沖,即使一進中斷就先做了個中斷請求標志的清零,在中斷退出甚至還未完全退出時大概率還會產生新的中斷請求,但又由于他在中斷服務程序里把中斷響應關閉了,中斷不能得到及時響應,請求只能懸著【Pending】跟隨程序來到主循環。
主循環代碼首先檢查報警標志是否生效,生效則進入循環體,先靜靜地歇會兒【HAL_Delay(Period)】,讓剛才的報警信號完全消停下來,然后再調用第一個HAL_NVIC_EnableIRQ(EXTI2_3_IRQn)函數打開中斷響應。這下可好,剛才候著的中斷請求得到響應機會了,則馬上去執行中斷服務程序。這次在中斷服務程序里的操作跟上次完全一樣,即在中斷服務程序里,又調用中斷響應關閉函數,做了跟剛才主循環里第一個HAL_NVIC_EnableIRQ(EXTI2_3_IRQn)函數完全相反的功能。即到這個點的時候,中斷響應被關閉了。
如果中斷返回后沒有使用第2句HAL_NVIC_EnableIRQ(EXTI2_3_IRQn)函數打開中斷響應,而只是執行那句清零報警標志然后退出循環體。由于中斷響應已經關閉,不管外部怎么報警都不會得到響應,報警標志也就永遠不會被置1,這樣主循環體也進不了內循環來開啟中斷響應。
如果有了第2句HAL_NVIC_EnableIRQ(EXTI2_3_IRQn)函數在循環體內,它就可以扭轉剛才在中斷服務程序里關閉外部中斷響應的局面,即把它扳回來。這樣的話功能上至少能正常運轉了。
原因基本就大致這么回事?;诂F有代碼寫法,如何破除這個連寫2次的搞法呢。其實,我們只需要在主循環體內開啟外部中斷響應的函數前,延時等待函數之后加上對相關中斷請求標志位的清零即可解決當前困惑。
比如像下面這樣【其中DSB是個數據同步隔離指令,保障它前面的指令執行完畢后才執行它后面的】,在主循環內開啟中斷響應前,先做中斷請求標志的清零。
while(1)
{
if(EXTI_Sign==1)
{
HAL_Delay(Period);
__HAL_GPIO_EXTI_CLEAR_RISING_IT(GPIO_PIN_3); __HAL_GPIO_EXTI_CLEAR_FALLING_IT(GPIO_PIN_3);
__DSB();
HAL_NVIC_EnableIRQ(EXTI2_3_IRQn);
EXTI_Sign=0;
。。。。。。
}
}
OK,本話題就聊到這里,愿君有所獲。類似問題不論STM32新手還是老手都可能不期而遇,祝君好運!
-
STM32
+關注
關注
2270文章
10915瀏覽量
356754 -
中斷
+關注
關注
5文章
900瀏覽量
41590 -
程序
+關注
關注
117文章
3793瀏覽量
81220 -
函數
+關注
關注
3文章
4341瀏覽量
62806 -
代碼
+關注
關注
30文章
4808瀏覽量
68813
原文標題:聊聊一個STM32中斷處理問題
文章出處:【微信號:stmcu832,微信公眾號:茶話MCU】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論