2018-05-24 04:41 AM
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).
Problem:
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
However I cannot find that point written up anywhere.
The RM0351 Reference manual (DocID 024597 Rev 5, page 335, section 4.1) says this about DMA transactions:
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
David
#dma-problem2018-05-24 06:50 AM
The short answer is 'no' to the second question and 'there's anything to be expected, so the result is not surprising' on the first question, and the guideline is 'don't'.
The thing is, that the described 'request/acknowledge' mechanism in STM32 DMA is not implemented as one would expect, i.e. an RS flip-flop, set by the request (trigger) from peripheral, and reset by acknowledgement signal when the transfer is done; not in the case when the request comes from UART or SPI (
https://community.st.com/0D50X00009XkaAtSAJ
). In UART/SPI, the DMA transfer is governed *directly* by the RXNE/TXE signal, i.e. the 'acknowledge' here is indirect, by clearing the RXNE/TXE bit when the data register in UART/SPI is read/written by the DMA.This allowed ST to spare down at least eight transistors and maybe two or three square micrometres of silicon, per such DMA channel. Oh, yes, this solution works, if used in the simplistic way.
The most straighforward consequence - and one you can try out simply - is, that if the DMA does not target the given data register, once the given flag gets active, it will transfer forever.
Then there are more convolved consequences - the one you've experienced, being lucky to come across it early enough. I
https://community.st.com/0D50X00009XkW5oSAF
, where suspending the request signal resulted in 'funny' states (as there's no latch, the internal state machine of DMA aborts the transfer halfway through). I've also tried to use two channels from the same trigger, on 'F4, and it results in one of the channels being stuck in such a way that only complete DMA reset (or chip reset) recovers.JW