cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F030 UART transmit DMA circular mode won't stop transmitting

RonHreha
Associate II

Hello

I'm using the latest HAL library for STM32F030.  I've set up a 512 byte transmit buffer for holding bytes that are to be transmitted out the UART using DMA in circular mode.  The goal is to treat this 512 byte buffer as a circular transmit queue using queue head and queue tail variables as indexes into the buffer.  The qhead index controls where characters to be transmitted are to be inserted into the 512 byte buffer by my firmware.  The qtail index is used to control the start address for where the DMA is supposed to transmitting from.  As bytes are added into the transmit buffer, my firmware manages wrapping the data from the end of the buffer back to the beginning of the buffer.  Then, using the UART transmit DMA in DMA_CIRCULAR mode, the goal would be to have the DMA perform a similar process - automatically wrapping from end of buffer back to the beginning of the buffer to finish the transmission when necessary.  For the very first transmit, when I select DMA_NORMAL mode, everything works as expected - 21 bytes are transmitted as expected.  If I change one line of code to run the DMA in DMA_CIRCULAR mode, those same 21 bytes are repeatedly transmitted by the DMA.  Any Ideas as to what I can try to make the DMA stop after those 21 bytes are transmitted - so the next block of bytes can be transmitted?  Should the UART transmit DMA running in DMA_CIRCULAR mode work as I described above?  If not, please explain why.  I'm calling HAL_UART_Transmit_DMA() to start the transmission process

Note that I already have this working for the UART receive DMA operating in DMA_CIRCULAR mode.  For reception, I'm using HAL_UARTEx_ReceiveToIdle_DMA()

9 REPLIES 9
RonHreha
Associate II

Additionally, I'm using the HAL_UART_TxCpltCallback callback function to inspect the qhead and tail to see if any more bytes need to be transmitted, and potentially restart the UART transmit DMA from within the callback function.  All of that is working in DMA_NORMAL mode.  Obviously, wrapping from the end of the 512 byte buffer back to the beginning of the buffer is a problem though

Karl Yamashita
Lead III

You're using the wrong tool for what you're trying to achieve. The DMA in circular mode will transmit each byte in the buffer you point to non-stop or until you call for it to stop/pause.

Instead, created a queue buffer to add your packets of data to and use the DMA in normal mode.

See this https://github.com/karlyamashita/Nucleo-G431RB_Three_UART/wiki

 

Tips and Tricks with TimerCallback https://www.youtube.com/@eebykarl
If you find my solution useful, please click the Accept as Solution so others see the solution.
RonHreha
Associate II

It appears you have experience with this so I guess I believe you.  I didn't see where the STM32 reference manual describe the UART transmit DMA working that way.  I also don't see a valid use for the UART transmit DMA to operate in the fashion you describe.  Can you provide some insight?

The most effective way is to dispatch the active portion of the buffer, don't let the DMA wrap the buffer, but be ready at the call-back to dispatch the next active portion.

When you don't have any data to transmit, stop submitting DMA buffers.

Alternatively you can create a linked list / chain, or scatter-gather list and have the call-back work through it.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
RonHreha
Associate II

Thanks guys for the suggestions.  I got this working yesterday using DMA_NORMAL mode - bascially as Tesla Delorean suggested - determine when the data in the circular buffer wraps, then use the DMA to send the data up to the end of the buffer, and send the rest of the data at the beginning of the buffer in the transmit complete callback.  What I was hoping is that there is some currently unknown setting for the DMA running in circular mode where the DMA simply stops after the specified amount of data bytes has been transmitted like it does in normal mode.  This is the logical use of the DMA in all mode.  I can't even think of a logical use for the DMA to repeatedly transmit the same message forever - especially for a UART.  Basically, I was hoping someone from ST Micro would comment and tell me that we are all fighting the same bug.

The DMA in circular mode, the DMA doesn't know how many bytes that you've added. All you did was point to the buffer and size.

All you need to do is wait for UART_DMATxHalfCplt or UART_DMATransmitCplt. You haven't mentioned the UART_DMATxHalfCplt so not sure if you're using the DMA correctly? When UART_DMATxHalfCplt is called, you know the first 256 bytes are sent. You then fill the 1st half of the array with 256 bytes of data while the second half is being sent. When UART_DMATransmitCplt is called, then you know the 2nd half has been sent so you fill the 2nd half with more data while the 1st half is being sent.

 


determine when the data in the circular buffer wraps, then use the DMA to send the data up to the end of the buffer, and send the rest of the data at the beginning of the buffer in the transmit complete callback.  

This doesn't make sense? In Normal mode, you have to initiate the DMA to start sending data each time. It always starts sending data at the first index of the buffer. Unless you have a circular buffer that holds your data as you add to it. Then from that 1st buffer you extract data and place into a 2nd buffer that the DMA uses?

Tips and Tricks with TimerCallback https://www.youtube.com/@eebykarl
If you find my solution useful, please click the Accept as Solution so others see the solution.
RonHreha
Associate II

I think the issue is my interpretation of what a circular DMA does.

In receive mode, the UART receive DMA running in DMA_CIRCULAR mode works almost exactly as I hoped it would.  I've implemented a 512 byte receive buffer.  I call HAL_UARTEx_ReceiveToIdle_DMA( &hUartMon, MonRxBuf, sizeof(MonRxBuf) ) only one time. Then, each time an idle line is detected, the callback function - HAL_UARTEx_RxEventCallback( UART_HandleTypeDef *huart, uint16_t Size ) - runs.  At this point, the DMA has already moved the data bytes into my receive buffer and the Size parameter is used as an index into this buffer (Size is not the number of bytes received).  The Size parameter has a little nuance in that it is '1' based and not '0' based so a little code has to be written to handle this.  I've implemented a receive Qhead and Qtail.  The DMA is always inserting received bytes starting at the Qhead and I read the bytes out of the buffer using Qtail until Qtail equals Qhead.  The DMA correctly handles wrapping this buffer in a circular fashion, as does my code when I read the bytes out of the buffer.  I was hoping the transmit DMA running in DMA_CIRCULAR mode would work in a similar fashion where I insert bytes starting at the Qhead of my transmit buffer (potentially wrapping from the end of the buffer back to the beginning).  Then use HAL_UART_Transmit_DMA( &hUartMon, (uint8_t *)&MonTxBuf[MonTxBufQTail], TxLen ) to have the DMA send TxLen bytes starting from Qtail, wrapping around if necessary.  Unfortunately, I guess the DMA running in CIRCULAR mode doesn't have an option to just stop transmitting after the specified amount of bytes go out.  The solution was to use DMA_NORMAL mode.  I still copy all of my data bytes into the buffer, wrapping if necessary.  When buffer wrapping occurred, I only transmitted the bytes from Qtail to the end of the buffer. The wrapped bytes will be transmitted when the callback function HAL_UART_TxCpltCallback( UART_HandleTypeDef *huart ) is called.  Obviously, I'm calling HAL_UART_Transmit_DMA( &hUartMon, (uint8_t *)&MonTxBuf[MonTxBufQTail], TxLen ) again in the transmit complete callback function to send the rest of the wrapped bytes

 

Got it. I though you just pointed to the buffer but I didn't know you also used a index pointer as well. So it makes sense now.

Pretty much the same as the github link I gave you but instead of byte pointer, I use a queue pointer. Visually easier to see what packet of data is in the queue(s) that are going to be sent than seeing large circular buffer with all the packets appended as one long data. At the end they both do their purpose.

Tips and Tricks with TimerCallback https://www.youtube.com/@eebykarl
If you find my solution useful, please click the Accept as Solution so others see the solution.

The HW uses the least complex possible implementation, it's not got the logic to be a FIFO buffer.

It has a looping/circular mode for audio type applications where you can keep filling the inactive half of the buffer at the HT (Half-Transfer) and TC (Transfer Complete) interrupt, but this is a continuous operation.

If you've got sufficiently large buffers, and are promptly filling them, the number of operations and interrupts will be relatively low.

The other method is to have a pool of output buffers, each gets retired to the queue once complete, you can have count, or bit mask, if the same data needs to be sent to multiple interfaces..

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..