AnsweredAssumed Answered

DMA+USART on STM32F407VG: TC Interrupt sometimes not triggered

Question asked by guertler.niklas on Jul 11, 2017
Latest reply on Jul 25, 2017 by guertler.niklas

Dear community,

I have run into a DMA-related issue while trying to implement an application for the STM32F407VG which receives data from a sensor via UART. The sensor sends a 162-byte data packet at 912600 baud every 10ms with pauses in between. Because the MCU is already quite busy, i want to use DMA. Since I have no way to stop/start the sensors' transmission, and the MCU may start up after the sensor and I want the whole system to be hot-pluggable, I have to find the beginning of each data packet. If I just enable the DMA for USART reception, configure it to 162 bytes and process the result in the transfer complete interrupt, I might end up receiving the end of a packet which is currently being transmitted and the beginning of the next packet as one data block.

To solve this, I use the USART's IDLE interrupt to recognize the pause between two packets and start the new DMA transfer. I use the DMA completion interrupt to determine that a packet has been completely received (and stored in memory). If the TC interrupt has not triggered before the next IDLE interrupt, I assume the data packet to be too short and discard it.

However, under certain conditions, sometimes (every few hundreds of packets), the DMA TC interrupt simply is not triggered. In the USART IDLE interrupt, I see that the DMA "NDTR" register is zero (indicating a complete transfer), while "LISR" is also zero (indicating that the TC interrupt is not pending). After resetting the DMA stream it works fine again for some time.

This behaviour seems to be influenced by the CPU load: I configured a timer to call an empty dummy ISR at a high frequency. The higher the frequency, the more TC interrupts go missing. This happens even though both the USART and the DMA interrupt have a higher priority than the timer interrupt.

Executing the code from RAM instead of flash results in many more missed interrupts too.

My suspicion is that this has something to do with a high (RAM) bus load, which would affect performance but should not lead to missing interrupts.

I have tried many variations but could not find a working constellation. I have uploaded an example code demonstrating the problem on GitHub. The interesting part is in the Src/main.c file (shortened):

static uint8_t rxBuffer [176] __attribute__ ((aligned (16)));
static DMA_Stream_TypeDef* const dmaStream = DMA1_Stream1;
static unsigned int state = 0;
static unsigned int printCounter = 0;

void USART3_IRQHandler (void) {
     if (USART3->SR & USART_SR_IDLE) {
          // Clear Interrupt via dummy read
          (void) USART3->DR;
          switch (state) {
               case 0:
                    // First IDLE detected. Do nothing special.
                    break;
               case 1:
                    // IDLE has been detected without a DMA interrupt. This should not happen.
                    printf ("Reception failed: NDTR = %lu, LISR = 0x%lx\n", dmaStream->NDTR, DMA1->LISR);
                    printCounter = 0;
                    break;
               case 2:
                    // DMA Completion and IDLE has happened. A packet has been properly received.
                    if (rxBuffer [0] == 0xFA && rxBuffer [160] == 0x27 && rxBuffer [161] == 0x10) {
                         if (printCounter == 99) {
                              puts ("Received 100 packets OK");
                              printCounter = 0;
                         } else {
                              ++printCounter;
                         }
                    } else
                         puts ("Packet received, but is invalid");
                    break;
          }
          state = 1;
          
          // Disable DMA stream properly
          dmaStream->CR = 0;
          while ((dmaStream->CR & DMA_SxCR_EN) != 0);
          
          // Clear Interrupt flags
          DMA1->LIFCR = DMA_LIFCR_CTCIF1 | DMA_LIFCR_CHTIF1 | DMA_LIFCR_CTEIF1 | DMA_LIFCR_CDMEIF1 | DMA_LIFCR_CFEIF1;
     
          // Make sure buffer is correctly aligned
          uint32_t mptr = (uint32_t) rxBuffer;
          assert_param (mptr % 16 == 0);
          
          // (Re-)Initialize DMA
          dmaStream->PAR = (uint32_t) (&USART3->DR);
          dmaStream->M0AR = mptr;
          dmaStream->NDTR = 162;
          dmaStream->FCR = DMA_SxFCR_DMDIS;
          dmaStream->CR = DMA_SxCR_CHSEL_2 | DMA_SxCR_PL_0 | DMA_SxCR_MSIZE_1 | DMA_SxCR_MINC | DMA_SxCR_EN | DMA_SxCR_TCIE;

          USART3->CR3 = USART_CR3_DMAR;
     }
}

void DMA1_Stream1_IRQHandler (void) {
     if (DMA1->LISR & DMA_LISR_TCIF1)
          state = 2;
}

// Dummy Timer ISR to simulate high workload
void TIM8_UP_TIM13_IRQHandler () {
     if (TIM13->SR & TIM_SR_UIF) {
          TIM13->SR = ~TIM_SR_UIF;
          __NOP ();
     }
}

int main(void) {
     // ... The usual initialization ...

     puts ("Application startup");
     // Configure interrupts
     HAL_NVIC_SetPriority (TIM8_UP_TIM13_IRQn, 1, 1);
     HAL_NVIC_EnableIRQ (TIM8_UP_TIM13_IRQn);

     HAL_NVIC_SetPriority (USART3_IRQn, 0, 1);
     HAL_NVIC_EnableIRQ (USART3_IRQn);

     HAL_NVIC_SetPriority (DMA1_Stream1_IRQn, 0, 0);
     HAL_NVIC_EnableIRQ (DMA1_Stream1_IRQn);

     // Enable peripheral clocks
     RCC->APB1ENR |= RCC_APB1ENR_TIM13EN;
     RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
     RCC->APB1ENR |= RCC_APB1ENR_USART3EN;

     // Initialize TIM13 to call the interrupt at 50kHz, which simulates some dummy load
     TIM13->PSC = 83;
     TIM13->DIER = TIM_DIER_UIE;
     TIM13->CR1 = 0;
     TIM13->SR = ~TIM_SR_UIF;

     TIM13->ARR = 19;
     TIM13->CR1 = TIM_CR1_URS;
     TIM13->EGR = TIM_EGR_UG;
     TIM13->CR1 = TIM_CR1_CEN;

     DBGMCU->APB1FZ |= DBGMCU_APB1_FZ_DBG_TIM13_STOP;
     // Initialize UsART3 for reception
     USART3->BRR = 46;     // 921600 Baud.
     USART3->CR1 = USART_CR1_UE | USART_CR1_RE | USART_CR1_IDLEIE; // Only enable IDLE interrupt
     while (1) {
          __WFI ();
     }
}

An example output is:

Application startup
Received 100 packets OK
Received 100 packets OK
Received 100 packets OK
Reception failed: NDTR = 0, LISR = 0x0
Received 100 packets OK
Received 100 packets OK
Received 100 packets OK
Reception failed: NDTR = 0, LISR = 0x0
Reception failed: NDTR = 0, LISR = 0x0
Received 100 packets OK
Received 100 packets OK

The output is different each time the code is run. The problem also occurs when i remove the (slow) printf statements. The whole thing seems to be quite erratic and elusive...

Does anyone have an idea as to what I am doing wrong or maybe a workaround that still allows robust operation when the sensor and MCU are randomy hotplugged?

Thank you very much in advance!

Outcomes