AnsweredAssumed Answered

STM32L496 - Strange behaviour using common / dual trigger for DMA

Question asked by David W on May 24, 2018
Latest reply on May 24, 2018 by waclawek.jan

I am using STM32L496 Cortex-M4 processor, which has two DMA controllers.

Serial communications are connected via USART1, and the DMA trigger 'USART1_RX' is available on two channels - DMA1 Channel 5 and DMA2 Channel 7.


I can successfully receive serial data via DMA into a circular buffer, using either of the USART1_RX triggers.

However I also want to timestamp the incoming serial data - without using interrupts, just using DMA.


As there are two DMA triggers for USART1_RX, I tried to use the 'alternate' trigger to capture timestamps (DMA from free running TIM6 to memory).



Although the receive data DMA continued to work without any problems, when I added the timestamp DMA its buffer would often contain two timestamp values for one received byte, sometimes just one timestamp, and - fatally- sometimes would not give a timestamp at all.


I expect this may be caused by bus arbitration / contention issues, as both DMA controllers can act as bus masters.


Although I'm not sure that code will help much with understanding the problem, since it will be the first question here it is. The DMA configuration code uses STM HAL library for initialisation but later writes to registers.


In "pseudo" form, goes something like this


// Serial Rx data DMA config:
dma_handle1.Instance = DMA2_Channel7;
dma_handle1.Init.Request = DMA_REQUEST_2; // DMA Channel selection bit field for regular UARTs - but not LPUART
dma_handle1.Init.Direction = DMA_PERIPH_TO_MEMORY;
dma_handle1.Init.PeriphInc = DMA_PINC_DISABLE;
dma_handle1.Init.MemInc = DMA_MINC_ENABLE;
dma_handle1.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
dma_handle1.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
dma_handle1.Init.Mode = DMA_CIRCULAR;
dma_handle1.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&dma_handle1) != HAL_OK)
_Error_Handler(__FILE__, __LINE__);
dma_handle1.Instance->CCR &= ~DMA_CCR_EN; // disable DMA channel to allow changes
dma_handle1.Instance->CPAR = (uint32_t) &USART1->RDR; // configure DMA for UART Rx
dma_handle1.Instance->CMAR = (uint32_t) pBuffer; // configure DMA memory destination --- pBuffer is the address of a uint8_t array
dma_handle1.Instance->CNDTR = buffer_size & DMA_CNDTR_NDT_Msk; // use entire buffer (in circular mode)
dma_handle1.Instance->CCR |= DMA_CCR_EN; // enable DMA channel

// Serial timestamp DMA config:
dma_handle2.Instance = DMA1_Channel5;
dma_handle2.Init.Request = DMA_REQUEST_2; // DMA Channel selection bit field for regular UARTs - but not LPUART
dma_handle2.Init.Direction = DMA_PERIPH_TO_MEMORY;
dma_handle2.Init.PeriphInc = DMA_PINC_DISABLE;
dma_handle2.Init.MemInc = DMA_MINC_ENABLE;
dma_handle2.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; // TIM6->CNT is 32-bit even though most of the timer is 16-bit
dma_handle2.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; // but we want to exclude bit 31
dma_handle2.Init.Mode = DMA_CIRCULAR;
dma_handle2.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&dma_handle2) != HAL_OK)
_Error_Handler(__FILE__, __LINE__);
dma_handle2.Instance->CCR &= ~DMA_CCR_EN; // disable DMA channel to allow changes
dma_handle2.Instance->CPAR = (uint32_t) &TIM6->CNT; // read timer counter (not USART1 peripheral)
dma_handle2.Instance->CMAR = (uint32_t) serial_Rx_timestamp_buffer; // configure DMA memory destination --- serial_Rx_timestamp_buffer is the address of a uint16_t array
dma_handle2.Instance->CNDTR = (sizeof(serial_Rx_timestamp_buffer) / sizeof(serial_Rx_timestamp_buffer[0])) & DMA_CNDTR_NDT_Msk; // use entire buffer (in circular mode)
dma_handle2.Instance->CCR |= DMA_CCR_EN; // enable DMA channel


Note that

  • TIM6 was set up as a free running counter
  • Altering the relative DMA priority between RxData and Timestamp DMAs seemed to make no difference (not surprising - they are on different DMA controllers)
  • Altering data alignment options did not affect the behaviour, nor did changing the type of 'serial_Rx_timestamp_buffer' to uint16_t or uint32_t (also not surprising)
  • Swapping the purpose of the DMA Channel triggers (receiving data or timestamping) did not remove the underlying behaviour of receive data (always correct) and timestamp (sometimes incorrect), however one of the configurations showed the errors rather more frequently than the other


So I suspect that using 'common' DMA triggering like this is not the intended purpose of the two USART1_RX trigger channels, and that they are meant to be used as 'exclusive or' options.

However I cannot find that point written up anywhere.


The RM0351 Reference manual (DocID 024597 Rev 5, page 335, section 11.4.1) says this about DMA transactions:

11.4.1 DMA transactions
After an event, the peripheral sends a request signal to the DMA Controller. The DMA controller serves the request depending on the channel priorities. As soon as the DMA Controller accesses the peripheral, an Acknowledge is sent to the peripheral by the DMA Controller. The peripheral releases its request as soon as it gets the Acknowledge from the DMA Controller. Once the request is de-asserted by the peripheral, the DMA Controller release the Acknowledge. If there are more requests, the peripheral can initiate the next transaction.


This does not explain what happens when two DMA transfers are being triggered by USART1_RX on two different DMA Controllers, either of which might be the first to 'Acknowledge' the peripheral.

If only a single DMA transfer is triggered then the ‘Acknowledge’ signals the USART that the Receive data register has been read, but with two DMA transfers - where one of them does not read from the same peripheral - what does the second 'Acknowledge' cause to happen?

Also there seems to be no way of knowing which DMA transfer will take place first, which may again confuse the operation of the two 'Acknowledge's.



Is the behaviour described above to be expected?

Are there any other solutions to timestamping serial data that use only DMA?


Thanks and regards