cancel
Showing results for 
Search instead for 
Did you mean: 

Sequential calls to HAL_UART_Transmit_DMA Unexpected Behavior.

Russeree
Associate II

Hey guys/gals, I have a UART TX and DMA issue, I can't call HAL_UART_Transmit_DMA sequentially in the same functions, the first call is successful but the UART gstate gets stuck in the TX_BUSY state so a second call fails to execute. 

Heres some details of my problem. 

Using a STM32F031F6P7TR - STM STUDIO AC6 - HAL DRIVERS

Upon the HAL_UART_Receive_DMA callback, I call a function to send over a list of menu options to the user in the UART Receive callback. Reception of data is working very well. 

Here is my problem, I am not able to send the HAL_UART_Transmit_DMA back to back within the scope of the same function. The first TX will transmit successfully, then the UART gState gets stuck in the busy state, and no data can be successfully sent until the function completes. 

I created a method that counts the remaining DMA transactions and once there are no more left, i manually force the UART to ready and unblock for the second transmission. Could anyone help me see what I am doing wrong. I would love to be able to insert a small delay between the two statements.

Please let me know if any more details are needed or explanations of my code 

code can be found at https://github.com/russeree/SushiRoll

9 REPLIES 9
Trevor Jones
Senior

its a bit messy,

I had to make a large buffer, [4096]

copied string to buffer, sent by DMA from that buffer.

during that DMA cycle, new strings are copied to the end of the last string.

I checked the DMA cycle has completed with a foreground flag every mSecond

if that cycle had completed then initiate a new DMA cycle from the end of the last DMA to the end of the current buffer of strings.

I used DMA_Tx in normal mode

the catch is that when you when you write the string, you have to check how close you are to the end of that large buffer,

if you are within say 500 bytes of the end, I shift everything that is not already being transmitted down to the zero point of the large buffer and set a flag to DMA from the start of buffer again.

messy but works seamlessly, on the proviso that strings are written from a single thread.

all functios can puts(string) at any time and it all works.

no printing from within interrupts or other tasks.

best to use 230400 baud or higher.

good luck, don't come here often...

Russeree
Associate II

Hey thanks Trevor, I see how that method would work to solve the issue. I will implement something similar if I can't solve this issue in a better way.

Why does the transfer complete callback only occur once my function completes... Example test i did... vs what I expected.

Example: Here is what I want if I were to call HAL_DMA_TX, DELAY, HAL_DMA_TX

  1. DMA TX START
  2. Delay Start
  3. DMA COMPLETE Interrupts Delay,
  4. Delay Continues
  5. Delay Complete
  6. DMA TX #2 START SECOND TX
  7. MCU does what ever else needs to be done in the mean time.
  8. DMA TX #2 COMPLETE INTERUPT FIRES
  9. Functions Returns

What I am getting

  1. DMA TX #1 START
  2. DELAY STARTS
  3. DELAY FINISHES
  4. DMA TX #2 Starts... But the function will not executes due to a check for TX busy
  5. Function Complete
  6. DMA TX #1 COMPLETE Interrupt fires.

Overall why does this function behave this way, I am using DMA in normal mode.

Ozone
Lead

I don't know the associated Cube code, and I don't want to look at it.

But DMA to peripherals depend on the speed of the peripheral unit. In your case, DMA only transfers another byte when the transmission of the previous byte is finished, and Tx becomes empty. This times depend on the baudrate. DMA does not magically speed up the UART,

In my case yes the DMA transfers all of the bytes at 115200, as long as I dont exit the function though the TX complete call back will never happen, Example I send "Hello" via DMA_TX, then my next instruction is a 90 second delay. Though the DMA has nothing left to send and the pending transfers count is 0, the callback or return to UART_READY wont execute until the 90 second delay has completed. If you clone my code or just run my .bin you can see that the TX becomes empty. But the callback and UART ready doesn't happen.

EDIT: Please see my response below, I didn't know that the admins disabled the ability to delete a post.

Trevor Jones
Senior

this is non-blocking code, so you can do do other stuff while its transmitting...

I check this in the foreground every mSecond:
 
void CheckTxDMABufferProgress(void) {
    if (TxDMA1BufHasData) {
        char uartState = HAL_UART_GetState(&huart1);              // getting this now      HAL_UART_STATE_BUSY_TX  = 0x21
        if((uartState == HAL_UART_STATE_READY) || (uartState == HAL_UART_STATE_BUSY_RX) ||  (uartState == HAL_UART_STATE_BUSY_TX_RX)) 
        {
            uint32_t busy = huart1.Instance->ISR & USART_ISR_BUSY;
            if (!busy)
            {
                TxDMA1BufHasData = false;                     	// sending now      
                if(HAL_UART_Transmit_DMA(&huart1, (uint8_t *)Usart1TxDMABuffer + U1TxBufferPtrOUT, U1TxBufferPtrIN - U1TxBufferPtrOUT) == HAL_OK) {
                    U1TxBufferPtrOUT = U1TxBufferPtrIN;
                    Usart1TxDMABuffer[U1TxBufferPtrIN] = 0;                     // terminate current string @ptrIN to null
                }    
                
            }
        }
    }
}  
int  puts1(const char * ptr) {
    int length = 0;
    // check String has data
    if(ptr[0]) {
        // does the first element have a char ?
checkU1TxDMABufferLength();              //  DMA Transmission buffer house keeping
 
while(ptr[length] > 0) {
            // add this text too
                               // transfer to DMA buffer to nul terminator 0x00
       // if PTRin actually gets to the end of the buffer we are toast.
       Usart1TxDMABuffer[U1TxBufferPtrIN++] = ptr[length++];
            if (U1TxBufferPtrIN >= U1TxBufSize) U1TxBufferPtrIN	= 0;              		// should never get here
        }
        //adding text into the stream here successfully
        // U1TxBufferPtrIN += sprintf(Usart1TxDMABuffer + U1TxBufferPtrIN,"U1TxBufferPtrIN %04X, U1TxBufferPtrOUT %04X\n\r",U1TxBufferPtrIN,U1TxBufferPtrOUT);	// this does work
        TxDMA1BufHasData = true;
    }
    return length;
}
void checkU1TxDMABufferLength(void) {
    DMA1BufAlmostFull = false;
    if (U1TxBufferPtrIN > ((U1TxBufSize * 2) / 4))		// we are about to run out of room in DMA Buffer
        DMA1BufAlmostFull = true;
 
    if (DMA1BufAlmostFull) {
        // we have a DMA in progress, so best leave it alone.
            
        // possibly, we have some text already waiting for the DMA to clear.
        // we have this text to go on too.
            
        // 1. ptr in - prt out is the current length and it needs to be shifted back to 0 position
        // 2. then this incoming text too
        // 3. remove almostfull
            
 
        // 1. ptr in - prt out is the current length and it needs to be shifted back to 0 position
            // we could use a MtoM DMA for this, but easier and fast anyway to do by hand.
            
        int currentBufferLength, newBufferLength;
        currentBufferLength = U1TxBufferPtrIN - U1TxBufferPtrOUT;
            
        newBufferLength = 0;
            
        if (currentBufferLength) {
            if (currentBufferLength < U1TxBufSize - 2) {
                while (U1TxBufferPtrIN > U1TxBufferPtrOUT)
                    Usart1TxDMABuffer[newBufferLength++] = Usart1TxDMABuffer[U1TxBufferPtrOUT++];
 
                U1TxBufferPtrOUT = 0;
                U1TxBufferPtrIN = newBufferLength;
            }
            else {
                // this is a fatal error  // never happens
                // reset and clean up
                U1TxBufferPtrOUT = 0;
                U1TxBufferPtrIN = 0;
 
                char errorMessage[96];
                int msg_length = 0;
                sprintf(errorMessage, "\n\rcurrentBufferLength %d is too high, some of the string has been dumped\n\r", currentBufferLength);
                while (errorMessage[msg_length] > 0) {
                    // transfer to DMA buffer to nul terminator 0x00
                   // if U1TxBufferPtrIN actually gets to the end of the buffer we are toast.
                    Usart1TxDMABuffer[U1TxBufferPtrIN++] = errorMessage[msg_length++];
                    if (U1TxBufferPtrIN >= U1TxBufSize) 
                        U1TxBufferPtrIN	= 0;                		// should never get here
                }
            }
        }
        else {
            U1TxBufferPtrOUT = 0;
            U1TxBufferPtrIN = 0;
        }
        DMA1BufAlmostFull = false;
    }
}

Really 90 seconds ? That's ridiculous. Have you confirmed the delay routines as culprit with a debugger ?

Well, I have my reason for not using Cube/HAL, it is famous for such code/mental dropouts.

With the UART, I mostly used interrupts for sending. For low baud rates and unknown string length, there is not much won with DMA.

But for ADC, I almost always use DMA (without Cube). The TC interrupt tells you when a full cycle/transmission is done, and some housekeeping is necessary.

you must have a serious problem in your code the DMA is instantaneous,

With the code I have shown, you can print continuously while transmitting continuously without losing a byte and without using any delays or blocking code.

Overlapping buffers can be moved by optimized memmove() function:

https://en.cppreference.com/w/c/string/byte/memmove

But you don't have to move buffers around at all. Split the new data in two parts, put the first part at the end of Tx buffer, second part at the beginning and schedule DMA twice. Actually that's a generic and very popular FIFO ring buffer principle. =) Read more about it here:

https://ferrous-systems.com/blog/lock-free-ring-buffer/

https://www.snellman.net/blog/archive/2016-12-13-ring-buffers/

https://majerle.eu/index.php/projects/ring-buff