關于STM32F407一次事件觸發兩次中斷
- 問題描述
- 一、bug的提出
- 二、bug的分析
- 三、bug的解決
- 總結
問題描述
之前一直有想寫博客的想法,因沒有貧訓而遲遲沒有行動起來,但這次本人在給自己做的F4小板子做測驗時遇到一個奇葩的bug耽誤了我好幾個小時,我之所以稱之為“奇葩的bug”,是因為一次按鍵事件竟然觸發了單片機兩次中斷,
一、bug的提出
別急!問題一出來我知道你肯定很多想法,比方按鍵事件沒有做好消抖處理,或者中斷請求標志位沒有被及時清零等,順便說下,對于STM32芯片而言,如果中斷請求標志沒有被清零程式會卡死在中斷服務程式里,諸如以上的疑點,我拿出以下幾點實際情況來做解釋,
- 按鍵有硬體消抖,并聯了一個0.1uf的電容 ,實際用示波器測得波形比較好,并沒有抖動產生誤操作;
(圖1)示波器時間單位為50ms/格,按下后松開時間大概是170ms
(圖2)按鍵按下3次,產生3次下降沿觸發條件實體
2. 相應代碼展示:
下面展示按鍵配置代碼 按鍵GPIO和NVIC配置,
void EXTI_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_Configuration();/* 配置 NVIC */
RCC_AHB1PeriphClockCmd(KEY1_INT_GPIO_CLK|KEY2_INT_GPIO_CLK,ENABLE);/*開啟按鍵GPIO口的時鐘*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); /* 使能 SYSCFG 時鐘 ,使用GPIO外部中斷時必須使能SYSCFG時鐘*/
/*******************KEY1 - PD3***********************/
GPIO_InitStructure.GPIO_Pin = KEY1_INT_GPIO_PIN;/* 選擇按鍵1的引腳 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;/* 設定引腳為輸入模式 */
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;/* 設定引腳上拉 */
GPIO_Init(KEY1_INT_GPIO_PORT , &GPIO_InitStructure); /* 使用上面的結構體初始化按鍵 */
SYSCFG_EXTILineConfig(KEY1_INT_EXTI_PORTSOURCE,KEY1_INT_EXTI_PINSOURCE);/* 連接 EXTI 中斷源 到KEY1引腳 */
EXTI_InitStructure.EXTI_Line = KEY1_INT_EXTI_LINE;/* 選擇 EXTI 中斷源 */
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;/* 中斷模式 */
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; /* 下降沿觸發 */
EXTI_InitStructure.EXTI_LineCmd = ENABLE;/* 使能中斷/事件線 */
EXTI_Init(&EXTI_InitStructure);
/*******************KEY2 - PD5***********************/
GPIO_InitStructure.GPIO_Pin = KEY2_INT_GPIO_PIN; /* 選擇KEY2的引腳 */
GPIO_Init(KEY2_INT_GPIO_PORT, &GPIO_InitStructure); /* 其他配置與上面相同 */
SYSCFG_EXTILineConfig(KEY2_INT_EXTI_PORTSOURCE,KEY2_INT_EXTI_PINSOURCE);/* 連接 EXTI 中斷源 到KEY2 引腳 */
EXTI_InitStructure.EXTI_Line = KEY2_INT_EXTI_LINE; /* 選擇 EXTI 中斷源 */
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; /* 下降沿觸發 */
EXTI_InitStructure.EXTI_LineCmd = ENABLE;/* 使能中斷/事件線 */
EXTI_Init(&EXTI_InitStructure);
}
- 中斷函式代碼展示:
void KEY1_IRQHandler()
{
Key_Conter++;
EXTI_ClearFlag(KEY1_INT_EXTI_LINE);
}
void KEY2_IRQHandler()
{
Key_Conter--;
EXTI_ClearFlag(KEY2_INT_EXTI_LINE);
}
- 最后是主函式呼叫液晶顯示Key_Conter:
uint8_t Key_Conter=1;
int main(void)
{
LED_GPIO_Config();
EXTI_Config();
LED_RED;
OLED_Init();//初始化OLED
OLED_ShowString(0,3, "conter:");
while (1)
{
switch(Key_Conter)
{
case 1: LED_RED; break;
case 2: LED_GREEN; break;
case 3: LED_BLUE; break;
case 4: LED_YELLOW; break;//黃(紅+綠)
case 5: LED_PURPLE; break;//紫(紅+藍)
case 6: LED_CYAN; break;//青(綠+藍)
case 7: LED_WHITE; break;//白(紅+綠+藍)
case 8: LED_RGBOFF; break;//滅
default: Key_Conter=1;LED_D1(0);break;
}
sprintf(buffer, "%d",Key_Conter);
OLED_ShowString(57,3,buffer);//顯示按鍵按下次數
}
}
- 這個程式下載到單片機后的現象就如我開頭寫的每次當按鍵K1和K2按下后液晶上顯示的Key_Conter會以加2和減2,相應的led燈變化也是如此,
二、bug的分析
由于在博客中上傳視頻演示太過于麻煩,因此,為了用圖片直觀的反應硬體現象,我在中斷代碼中加了延時函式然后用示波器來觀察中斷所占用的時間,以此判斷到底是否執行了兩次中斷,(圖3是測得延時函式的運行時間,圖4是單片機中斷時間)
(圖3)延時函式時間:58ms

(圖4)中斷運行時間:124ms

測驗代碼如下:
void KEY2_IRQHandler()
{
LED_D2(0); // 亮
Delay(0xeefff);
LED_D2(1); //滅
Key_Conter--;
EXTI_ClearFlag(KEY2_INT_EXTI_LINE);
}
看到這在中斷里放這么長時間的延時可能有些dalao忍不住就想噴了,我先說明在中斷里放延時函式只是為了測驗而已,最后我也是測驗過加不加這延時函式都不會影響我們接下來bug的分析和解決,言歸正傳,大家可以看到圖4波形中低電平延續了124ms,而前面我們測的Delay(0xeefff)為58ms,并且在整個低電平的程序中還出現了一個高電平毛刺,以及我的OLED上顯示的Key_Conter減少了2,這些足以說明期間進入了兩次中斷,
那這bug到底是怎么回事呢,前面也說明了按鍵觸發事件沒有抖動,觸發方式也沒有問題,在這按鍵按下的100多個ms里,不管我有沒有在中斷里加延時偏偏每次按鍵按下后就進入了兩次中斷而不是3次,4次?
三、bug的解決
剛開始我懷疑是STM32F407本身的bug,于是百度了一下,在stm32論壇上找到了一篇關于同一次事件觸發兩次中斷的博客,鏈接: 一次事件會觸發兩次中斷?.
在這篇博客中,博主提到 :“原因就在于那行清除中斷請求位的代碼放在最后,在第一次退出中斷服務程式時該請求位尚未完成被清零的狀態,程式指令執行速度越快,這種可能性就越高,既然該中斷請求位依然保持置1的有效狀態,經硬體觸發再次進入中斷服務程式就順理成章了,有人會問,我在退出中斷服務程式之前不是已經做了中斷請求位的清零操作嗎?怎么沒有立即生效呢?再怎么“立即”也是需要時間的,程式指令的執行完畢和指令執行后的狀態改變并不一定同步,比方你到包子鋪去跟老板說買3個饅頭,老板滿口應諾后,你不能立即扭頭就走啊,他還需要點時間來處理,不然一輩子都買不到3個饅頭,具體結合到stm32芯片,程式執行是基于哈佛結構的流水線形式,前面代碼執行時依然可以執行后序的指令代碼,”
于是我順著這個思路,把清除中斷標志位的陳述句放在了倒數第二條,bug竟然就這樣解決了! 不得不佩服大佬的才華,
改動后的代碼:
void KEY2_IRQHandler()
{
LED_D2(0); // 亮
Delay(0xeefff);
LED_D2(1); //滅
EXTI_ClearFlag(KEY2_INT_EXTI_LINE);
Key_Conter--;
}
就只需把最末行的中斷標志位清除陳述句放在倒數第二行,用最后一行的陳述句等待標志位清零,一次事件中斷便只會觸發一次,bug由此解決,至此我們似憾訓忽略了一個問題,那就是末尾的那一條陳述句能不能延時到退出中斷前把標志位清零,延時的時間到底是過長還太短,最后為了避免代碼的盲目性,用嚴謹的編程態度來打消我們以后使用按鍵中斷的后顧之憂,我們可以在中斷里使用對標志位的輪詢方式,將代碼稍加改動變成下面的樣子:
void KEY2_IRQHandler()
{
LED_D2(0); // 亮
Delay(0xeefff);
LED_D2(1); //滅
Key_Conter--;
EXTI_ClearFlag(KEY2_INT_EXTI_LINE); //清除中斷標志位
while(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET);//等待標志位成功清除
}
寫到這里,bug已經完美解決,
總結
這是本人第一次發表博客,感謝大家耐心看完,本篇介紹的bug可能對一些大佬而言會不屑一顧,若有不妥之處還希望您能斧正本文的錯誤,不寧賜教,如果這篇文章對您有所幫助的話,那是再好不過的事了,最后附上我在本文中測驗的硬體,自己設計的STM32F407VET6迷你版實物圖(第一次做的32),


轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/244353.html
標籤:其他
下一篇:HaaS物聯網專家訓練營
