MCU_關於STM32Fxxx中斷EXTI產生時多次(兩次)進入中斷的原因

調試新的芯片Stm32F407時,發現和以前的不一樣。

相同的代碼,EXTI中斷總是會進入兩次,爲了驗證,我手動在中斷中進行了清除,

void EXTI0_IRQHandler(void)
{
    /* USER CODE BEGIN EXTI0_IRQn 0 */

    /* USER CODE END EXTI0_IRQn 0 */
    HAL_NVIC_ClearPendingIRQ(EXTI0_IRQn);    // HAL_GPIO_EXTI_IRQHandler 會做同樣的工作
    __HAL_GPIO_EXTI_CLEAR_FLAG(GPIO_PIN_0);  // 等價於 __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin)
    //HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);  // 其中調用了 __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin); 
    /* USER CODE BEGIN EXTI0_IRQn 1 */
    //if(GPIO_PIN_RESET == HAL_GPIO_ReadPin(GPIO_INPUT_EXTI_PORT, GPIO_INPUT_EXTI_P0))
    Exti_Interrupt(0); //自己寫的中斷功能函數
    /* USER CODE END EXTI0_IRQn 1 */
}

即使使用了ClearPendingIRQ,也同樣會在中斷產生時進入兩次。以前使用的STM32F1xx芯片沒有這個現象,不清楚不同批次之間,會不會有類似的問題。

當然,從程序的角度,對於按鍵類的操作,一般要求是在Exti_Interrupt中進行debouncing(去抖動)操作,這樣是最穩妥的辦法。

不過這裏糾結的是,爲什麼會兩次進入中斷呢?

因爲沒法弄明白芯片設計,只能猜,大約是硬件在設計時的考量取捨有一定難度,所以導致有的STM32芯片會多次進入,有的不會。

經過搜索,在這裏發現了一個比較合理的解釋
http://www.keil.com/support/docs/3928.htm

在概意思就是和硬件有關。後面還給出了一個解決辦法(和去抖動類似),弄一個延時操作在前面。

我把全部原文貼在下面,

ARM: Cortex-M3/M4 Interrupts Happening Twice?


Information in this knowledgebase article applies to:

  • Cortex-M3 and Cortex-M4 Devices

SYMPTOM

Cortex-M3 and Cortex-M4 interrupts appear to be triggering twice.

CAUSE

This may happen with devices:

  • That add an external, system-level write buffer in their Cortex-M3 or Cortex-M4 design, AND
  • The ISR code exits immediately after a write to clear the interrupt.

In this situation, the write to clear the interrupt may not complete before interrupt triggers a second time.

REASON

For the Cortex-M3 and Cortex-M4 cores, writes (STR, STMIA or PUSH) to memory are internally buffered. The Harvard architecture allows the MCU to fetch and execute instructions without waiting for data writes to memory to complete. The Cortex-M cores are aware of the internal buffer and prevent subsequent interrupts until the internal buffer empties and the write completes.

Sometimes vendors incorporate an additional external, system-level write buffer in their Cortex-M3 and Cortex-M4 designs for better performance. But unfortunately, the core is not aware of this external write buffer and cannot access it's status. For these externally-buffered devices, if an ISR exits immediately after clearing the interrupt register, a second interrupt could trigger again before the write to clear the interrupt completes.

For example, this ISR exits immediately after clearing the timer interrupt. Without the external buffer implementation, this code would work as expected. However, on a device with an external, system-level write buffer, this code could cause this "double IRQ" condition:

void Timer_IRQHandler (void) {
  Timeout_counter++;                /* Increment timeout counter */
  PortD->PTOR |= 1<<0;              /* Toggle output on port D0  */
  Timer->MSR |= TIMER_MASK;         /* Clear timer interrupt     */
}

RESOLUTION

Using the DSB instruction or __dsb(0) intrinsic before exiting will force a wait for the internal write buffer to empty, but that instruction cannot test the status of an optional system-level write buffer if there happens to be one. To make sure the peripheral interrupt register gets set properly, just perform another memory write before exiting the ISR.

Given the example above, one way to do this is by incrementing the timeout counter AFTER clearing the interrupt:

void Timer_IRQHandler (void) {
  PortD->PTOR |= 1<<0;              /* Toggle output on port D0         */
  Timer->MSR |= TIMER_MASK;         /* Clear timer interrupt            */
  Timeout_counter++;                /* Count timeout & insure IRQ clear */
}

But any type of memory write will accomplish this. The Timeout_counter++ works because it performs a read-modify-write to memory. If you can't move an instruction like in the above example, just add harmless code that performs a read-write like this:

void Timer_IRQHandler (void) {
  Timeout_counter++;                /* Increment timeout counter */
  PortD->PTOR |= 1<<0;              /* Toggle output on port D0  */
  Timer->MSR |= TIMER_MASK;         /* Clear timer interrupt     */
  PortD->PTOR = PortD->PTOR;        /* Insure IRQ clear          */
}

Last Reviewed: Wednesday, June 14, 2017

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章