聊聊一个STM32中断处理问题

2023-08-15  

先看一段代码:


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次运行相关函数。我们来一起找找原因。为了便于查看代码,我把中断服务程序和主程序代码截图放在一起。

348cf5ca-c844-11ed-bfe3-dac502259ad0.png

34a6f79a-c844-11ed-bfe3-dac502259ad0.png

在中断服务程序里就是清除中断请求标志,关闭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)函数在循环体内,它就可以扭转刚才在中断服务程序里关闭外部中断响应的局面,即把它扳回来。这样的话功能上至少能正常运转了。

原因基本就大致这么回事。基于现有代码写法,如何破除这个连写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;

。。。。。。

}

}


文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。