关于STM32F407一次事件触发两次中断

关于STM32F407一次事件触发两次中断

  • 问题描述
    • 一、bug的提出
    • 二、bug的分析
    • 三、bug的解决
    • 总结

问题描述

之前一直有想写博客的想法,因没有契机而迟迟没有行动起来,但这次本人在给自己做的F4小板子做测试时遇到一个奇葩的bug耽误了我好几个小时,我之所以称之为“奇葩的bug”,是因为一次按键事件竟然触发了单片机两次中断。

一、bug的提出

别急!问题一出来我知道你肯定很多想法,比方按键事件没有做好消抖处理,或者中断请求标志位没有被及时清零等。顺便说下,对于STM32芯片而言,如果中断请求标志没有被清零程序会卡死在中断服务程序里。诸如以上的疑点,我拿出以下几点实际情况来做解释。

  1. 按键有硬件消抖,并联了一个0.1uf的电容 ,实际用示波器测得波形比较好,并没有抖动产生误操作;

(图1)示波器时间单位为50ms/格,按下后松开时间大概是170ms关于STM32F407一次事件触发两次中断_第1张图片
(图2)按键按下3次,产生3次下降沿触发条件实例关于STM32F407一次事件触发两次中断_第2张图片
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);
}
  1. 中断函数代码展示:
void KEY1_IRQHandler()
{
     
   Key_Conter++;
   EXTI_ClearFlag(KEY1_INT_EXTI_LINE);
}
void KEY2_IRQHandler()
{
     
   Key_Conter--;
   EXTI_ClearFlag(KEY2_INT_EXTI_LINE);
}
  1. 最后是主函数调用液晶显示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);//显示按键按下次数
   }
}
  1. 这个程序下载到单片机后的现象就如我开头写的每次当按键K1和K2按下后液晶上显示的Key_Conter会以加2和减2,相应的led灯变化也是如此

二、bug的分析

由于在博客中上传视频演示太过于麻烦,因此,为了用图片直观的反应硬件现象,我在中断代码中加了延时函数然后用示波器来观察中断所占用的时间,以此判断到底是否执行了两次中断。(图3是测得延时函数的运行时间,图4是单片机中断时间)

(图3)延时函数时间:58ms
关于STM32F407一次事件触发两次中断_第3张图片
(图4)中断运行时间:124ms
关于STM32F407一次事件触发两次中断_第4张图片
测试代码如下:

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)。关于STM32F407一次事件触发两次中断_第5张图片关于STM32F407一次事件触发两次中断_第6张图片
关于STM32F407一次事件触发两次中断_第7张图片

你可能感兴趣的:(stm32,单片机,嵌入式)