STM32 gotchas
103. After being cleared in NVIC outside an ISR, interrupt reappears (if pending flag in NVIC is cleared too soon after removing the interrupt source)

Sometimes, we want to enable an interrupt in NVIC, source of which has been active during the time it has been disabled. It means, that the given interrupt is set as pending in NVIC (as can be observed in NVIC_ISPRx registers), and as soon as it is enabled, it will "fire" and run the associated ISR.

If we don't want this, rather, we want an interrupt to occur only when the interrupt source gets active in the future, we need to clear the "old", pending interrupt, Using the NVIC_ICPRx register is intended exactly for this purpose1.

However, we might not know, when was the interrupt source removed - it might've been just before we want to enable the interrupt. Or, it might've been our deliberate action which removed the interrupt source (e.g. clearing some status bit in a peripheral), just before enabling it. And, as we already know, it takes time until changes of interrupt source propagate to NVIC, so we might expect that clearing the pending bit too soon may not work.

It may seem that this is simple to work around: it should be enough to clear the NVIC pending bit in a loop until it reads as being cleared, right?

Unfortunately, this is not the case. After clearing the pending bit only a few cycles after removing the interrupt source, it reads zero for a couple more cycles, but then it gets set again. The following snippet has been run on an STM32F407 just after reset (i.e. with no clock tree setup):

int main(void) {
  RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; // enable TIM3 clock

  TIM3->DIER |= TIM_DIER_UIE;  // enable interrupt from update event

  testA[0] = NVIC_GetPendingIRQ(TIM3_IRQn);  // there's no pending IRQ at the moment
  testA[1] = TIM3->SR;

  TIM3->EGR = TIM_EGR_UG;      // generate an update event

  testA[2] = NVIC_GetPendingIRQ(TIM3_IRQn);  // pending IRQ in NVIC? not yet
  testA[3] = TIM3->SR;                       // but we should see status register being set
    __asm("nop");     // okay, it may take time until the interrupt flags "propagate" to NVIC
    __asm("nop");
  testA[4] = NVIC_GetPendingIRQ(TIM3_IRQn);  // there should already be a pending IRQ in the NVIC
  testA[5] = TIM3->SR;                       // and also status register of the timer

  TIM3->SR &= ~TIM_SR_UIF;  // this clears the status bit

  // the key to the problem is the number of cycles elapsed between these two events 
  // - more than 6 is needed to remove the problem in this snippet
    __asm("nop");
    __asm("nop");
    __asm("nop");
    __asm("nop");
    __asm("nop");
    __asm("nop");
  // __asm("nop");  // uncommenting this line makes delay long enough to remove the problem

  NVIC_ClearPendingIRQ(TIM3_IRQn);  // and this should clear the pending interrupt

  testA[6] = NVIC_GetPendingIRQ(TIM3_IRQn);  // there should be no pending IRQ in the NVIC by now
  testA[7] = TIM3->SR;                       //

    __asm("nop");
    __asm("nop");
    __asm("nop");
    __asm("nop");

  testA[8] = NVIC_GetPendingIRQ(TIM3_IRQn);  // and it should remain so
  testA[9] = TIM3->SR;                       //


  while(1) {
    __asm("nop");
  }
}

Running this code results in the following content of testA array:

(gdb) p /x testA
$26 = {0x0, 0x0,  0x0, 0x1,  0x1, 0x1,  0x0, 0x0,  0x1, 0x0} 
As witnessed by nonzero testA[8], the pending interrupt "reappeared" and would cause ISR to be entered immediately after enabling the interrupt in NVIC. Only after the 7th nop was uncommented before clearing the pending bit, it did not reappear again.

This is one more of those pesky timing problems, which are not documented at all, and even if experimentally determined, given we don't know what do they exactly depend upon (e.g. APB prescalers), they have the nasty potential to backfire if not handled with utmost caution.


Originally reported here.

1.In the "normal" case, after clearing the interrupt source within executing an ISR, the hardware automatically clears the associated pending bit in NVIC.