The discussion around the STM32F2xx and STM32F4xx DMA controllers is becoming a bit too large for a single posting, so here we are with Part 2!

Clearing a DMA Interrupt

In theory this is very simple – in practice not so much. The documentation for the “DMA low interrupt flag clear register” (DMA_LIFCR) and the “DMA high interrupt flag clear register” (DMA_HIFCR) shows bits, such as CTCIFx: Stream x clear transfer complete interrupt flag (x = 3..0). The documentation says to write a “1″ to the bit to clear the interrupt flag. That is correct.

However, I was finding that in certain circumstances the interrupt kept on reocurring, immediately, even though I was clearing out the interrupt.

DMA Interrupt Re-entering Reason #1

There is a well-known reason for this. The Cortex-M3 / M4 is a pipelined processor. If you clear the interrupt at the very bottom of your interrupt service routine code, immediately before you exit your interrupt routine, the clearing of the interrupt might not have propagated through the processor before your interrupt routine exited, meaning that the interrupt will be immediately triggered again.

It’s important to ensure the interrupt clear has actually happened before you exit your interrupt code. The easy way to do this is to clear the interrupt at the very top of your interrupt handler, before you do anything else. Then the time taken by executing your interrupt handler code allows lots of time for the interrupt clear to propagate. If this isn’t possible, or if your interrupt handler is extremely short, write to the interrupt clear register, then do a while() loop on the interrupt flag register to wait until the clear happens. Note that if you do this, make sure you put a timeout of some description in your while() loop to prevent a hangup. The interrupt clear should normally take effect very quickly.

DMA Interrupt Re-entering Reason #2

My interrupt re-entering was not caused by #1 above, and I spent a frustrating day hunting down the reason, finally discovering reason #2.

My interrupt handler code looked generally like this:

void DMA2_Stream1_IRQHandler (void)
{
  DMA2->LIFCR = (uint32_t)0x00000F40;      // clear DMA IRQ flags
 
  ... do a bunch of stuff ....
 
  // setup the DMA for a new transfer - DMA currently on so turn it off first
  DMA2->S1CR = (uint32_t)0x02025410;      // need to turn off DMA first to change settings
  while (DMA2->S1CR & DMA_CR_EN);         // wait until DMA is actually off
 
  DMA2->S1M0AR = ...     
  DMA2->S1NDTR = ...     
  ...
  DMA2->S1CR = (uint32_t)0x02065510;      // everything setup but not enabled     
  DMA2->LIFCR = (uint32_t)0x00000F40;     // clear any pending (old) DMA2 Stream 1 interrupts
  DMA2->S1CR = (uint32_t)0x02065511;      // everything setup and enabled   
}

You can see two interrupt clears taking place. One at the top of the interrupt handler (as it should be), and a second clear shortly before I re-enable the DMA controller, to ensure nothing is pending when the DMA is started up again.

Remember too, that the STM32F2xx / STM32F4xx DMA won’t even start if it has a pending interrupt (see STM32F2xx DMA Controllers Part 1) so clearing the interrupt flags is not optional – it must be done (even if you’re not using interrupts).

This second clear is not necessarily essential – it’s just for my own peace of mind, to be sure the DMA will start up correctly.

The reason the interrupt immediately re-enters is here:

// setup the DMA for a new transfer – DMA currently on so turn it off first
DMA2->S1CR = (uint32_t)0×02025410; // need to turn off DMA first to change settings

In this example the DMA is currently enabled, due to it being in a double-buffer / circular mode. I turn off the DMA. This results in the DMA TCIF flag being set. Again, so to speak – the TCIF was set earlier which triggered this interrupt, I cleared the flag at the top of the interrupt handler, and now that I’m turning off the DMA the TCIF flag has become set a second time. In the DMA configuration register bit 4, “TCIE: Transfer complete interrupt enable” is set, meaning that when the TCIF flag becomes set, it’s passed on to the NVIC (the Cortex-M3 / M4 Nested Vectored Interrupt Controller).

Hence at this point, even though the STM32F2xx / STM32F4xx DMA has been turned off, the simple act of turning it off has caused a new DMA interrupt to be pending in the NVIC. Because this particular code is in an interrupt service routine, this pending interrupt hasn’t executed yet, but it will the moment we exit our interrupt handler. This is the source of the endlessly reoccuring DMA interrupt.

There are a few possible solutions to this problem. Here’s one:

void DMA2_Stream1_IRQHandler (void)
{
  DMA2->LIFCR = (uint32_t)0x00000F40;      // clear DMA IRQ flags
 
  ... do a bunch of stuff ....
 
  // setup the DMA for a new transfer - DMA currently on so turn it off first
  DMA2->S1CR = (uint32_t)0x02025410;      // need to turn off DMA first to change settings
  while (DMA2->S1CR & DMA_CR_EN);         // wait until DMA is actually off
 
  NVIC_ClearPendingIRQ (DMA2_Stream1_IRQn);    // clear pending DMA IRQ from the NVIC
 
  DMA2->S1M0AR = ...     
  DMA2->S1NDTR = ...     
  ...
  DMA2->S1CR = (uint32_t)0x02065510;      // everything setup but not enabled     
  DMA2->LIFCR = (uint32_t)0x00000F40;     // clear any pending (old) DMA2 Stream 1 interrupts
  DMA2->S1CR = (uint32_t)0x02065511;      // everything setup and enabled   
}

Using the NVIC_ClearPendingIRQ() function works in this example because we’re in an interrupt service routine, so the new interrupt is pending but hasn’t had the opportunity to execute yet. Using NVIC_ClearPendingIRQ() in non-interrupt code would not work, because the interrupt will trigger the moment the DMA is turned off. In non-interrupt code (ie your regular main code) you need to prevent the interrupt from reaching the NVIC to begin with. Which would mean making sure bit 4 (TCIE: Transfer complete interrupt enable) in the DMA configuration register is clear (zero) before turning off the DMA. Or disabling the DMA interrupt in the NVIC before turning off the DMA. For example:

NVIC_DisableIRQ (DMA2_Stream1_IRQn);

(I personally haven’t tried this one though, because NVIC_ClearPendingIRQ() worked in my case.)

STM32F DMA interrupts can be a source of great confusion – hopefully this post has clarified things a little.