cancel
Showing results for 
Search instead for 
Did you mean: 

Problems using UART with DMA

Raül G
Associate II

Hello,

I'm logging the speed of a motor and I want to transmit some amount of informatio to the computer using the UART. The conversion of the sprintf is good but I have problems only when I'm using more than one time the function HAL_UART_Transmit_DMA. Thanks for your support!

for (i=0; i<MaxLog; i++)

{

sprintf(cadena, "V: %i %.3fv \n\r", LogSpeed[i], (float) LogSpeed[i] *3.3 / 4095);

HAL_UART_Transmit_DMA(&huart4, cadena, strlen(cadena));

HAL_UART_IRQHandler(&huart4);

}

6 REPLIES 6
S.Ma
Principal

This does not look healthy to call IRQHandler directly in the loop.

If you want to push out all your log by USART, first get the maximum accurate baudrate you can achieve, then use just UART transmission by polling method. Please also specify which STM32 as for the same UART/USART there are different IP versions.

Conversion to float is time consuming.... you could show a integer value in mV instead.

Bob S
Principal

You also need to wait until the DMA transfer is done before you call HAL_UART_Transmit_DMA() again. Your code above will possibly skip sending some of the strings because if the DMA/UART is still sending data from a previous call to HAL_UART_Transmit_DMA(), it will reject any new data.

One way would be to change your code to check the return value from HAL_UART_Transmit_DMA() (you are not alone here, it seems code posted on this forum almost never checks return values).

for (i=0; i<MaxLog; i++)
{
   sprintf(cadena, "V: %i %.3fv \n\r", LogSpeed[i], (float) LogSpeed[i] *3.3 / 4095);
   while( HAL_UART_Transmit_DMA(&huart4, cadena, strlen(cadena)) != HAL_OK )
   {
      /* EMPTY LOOP, or so something else useful */
   }
}

Another thing to look out for - how big us your cadena buffer? Is there ANY chance that the sprintf() call could write past the end of that buffer? It is safer to use snprintf() to guarantee you will never write past the end of the buffer.

Raül G
Associate II

​Hello,

thanks a lot for your help. I thing I located the problem but not the solution: I cannot execute two times the function HAL_UART_Transmit_DMA(). This easy code don't works:

Tx_Buffer[0]='A';

Tx_Buffer[1]='B';

Tx_Buffer[2]='C';

Tx_Buffer[3]='D';

Tx_Buffer[4]='E';

Tx_Buffer[5]=10;

Tx_Buffer[6]=13;

Tx_Buffer[7]=0;

//HAL_Delay(1000);

while(HAL_UART_Transmit_DMA(&huart4, Tx_Buffer,7)!= HAL_OK ){}

Tx_Buffer[0]='1';

Tx_Buffer[1]='2';

Tx_Buffer[2]='3';

Tx_Buffer[3]='4';

Tx_Buffer[4]='5';

Tx_Buffer[5]=10;

Tx_Buffer[6]=13;

Tx_Buffer[7]=0;

while(HAL_UART_Transmit_DMA(&huart4, Tx_Buffer,7)!= HAL_OK ){}

while(1){}

When I execute for second time the function HAL_UART_Transmit_DMA() it always returns "Busy", it returns the number 32 even when I add a delay of 1 second before the second data transmission.

Any other idea?  Thanks for your help!

S.Ma
Principal

OK, was told that to use the HAL properly you must read the header files of the UART to be able to code (because it's not intuitive when it tries to work with all possible configurations)

When using a peripheral with DMA, the callbacks should come from the peripheral, not the DMA.

Look into USARTxxxx_DMA functions instead.

However, remember the DMA in this case will slow down the implementation and has no benefits.

Take a step back and imagine the timing of running the code.

If even pushing without DMA bytes directly to the usart without interrupt system should be good enough. once you write a byte to USART DR, it will take some time before can write the next one (depending on the baud rate). If running a printf which build the log screeen char by char and push it straight whenever possible to the usart, you won't have to build a buffer and you will be doing string building while RS232 is actively sending out data. Save memory, save time. If you use DMA or use a Buffer to move the data around, between sequential tasks (printf then send) this may be slower.

For beginners, UART LL with TX by polling and RX with interrupt should be the first thing to get working. If next is to implement a console type to talk to the MCU, you'll have to process the incoming message once you receive the "/n" which makes the incoming message size unguessable for DMA transfer. I've got a shared code with SW FIFO which enables packing bytes to send between peripherals or main loop or tasks. It helps move bytes around taking account the irregular flows of bytes and control the peripheral interrupts enable when the FIFO is no longer empty and disable interrupt when fifo becomes empty to minimize the core bandwidth. Have a look.

One application of these FIFO is to transit USART into SPI to other MCU and finally to it's own USART. The FIFO gets filled up, sliced by up to 8 bytes in SPI and moved around at SPI regular intervals. USB is similar, the VCP is endpoint packet with fixed size message and implementing a COM port.... nothing fancy.

Bob S
Principal

First - sorry, my example code won't work as intended. It still ends up over-writing the buffer while the first buffer is being sent. More on that below.

Second - You say that the second call to HAL_UART_Transmit_DMA() always returns the decimal (base 10) value of 32? That shouldn't ever be possible. You don't say which CPU you are using, but in the STM32L4xxx HAL library, the HAL_StatusTypeDef only has values from 0 to 3, with 0 = HAL_OK, 1 = HAL_ERROR and 2 = HAL_BUSY, which are the only values that can be returned.

If you look in the HAL examples for your CPU (or a CPU close to yours) you should see a "UART Two Boards DMA" example. This uses the HAL_UART_TxCpltCallback() to set a flag when the DMA Tx is complete.

So you code would look something like this:

volatile uint8_t uartTxDone;  // This is a global variable
 
void HAL_UART_TxCpltCallback( UART_HandleTypeDef *UartHandle )
{
   uartTxDone = 1;  /// Signal main code that TX is complete
}
 
void YourFunctionToSendData()
{
  HAL_StatusTypeDef sts;
 
   // Fill the buffer here
   Tx_Buffer[0] = ????;
   etc.
 
   // Send the buffer
   uartTxDone = 0;
   sts = HAL_UART_Transmit_DMA(&huart4,Tx_Buffer,
   if ( sts != HAL_OK ) {
      // Use whatever error reporting system you have to report the error
      ReportTheErrorSomehow( sts );
   }
   else {
      // Wait for data to be sent
      while ( uartTxDone == 0 ) { /*EMPTY LOOP*/ }
   }  
}

HABOT
Associate III

Hi everyone, I've been struggling for some days now with a problem regarding UART DMA transmission and reception as well in TrueSTUDIO. I'm working with the STM32F427VITx LQFP100 Microcontroller on a proprietary board that drives 8 UART ports in full-duplex mode and DMA enabled only for transmission with the default settings (normal mode, byte and MEMINC). It runs at 168 MHz over a proprietary kernel architecture based on a model I call "pseudo-RTOS", since it has a task scheduler based on the systick timer, message dispatcher and some other fancy software patterns. 

I've created a protocol stack over UART in order to dynamically discover the length of the payload. It is composed of a fixed length header, checksum for header integrity validation, variable length payload and whole packet checksum. With this approach, I can dynamically change the IT based reception in order to adjust it to the transmitted packet length, so I don't rely on the IDLE-LINE-DETECTION interrupt. Here is a summary of the approach:

//Payload + 5 bytes for Header + 1 byte for Checksum

phportsm->_rxbufferpayloadsize = (((U16)phportsm->_rxbuffer[0x03]) << 0x08) | (((U16)phportsm->_rxbuffer[0x04]) << 0x00);

//Header checksum validation ...

U8 plchecksum = GetRawChecksum(&(phportsm->_rxbuffer[0x02]), 0x00, 0x03);

//If recalculated checksum equals the contained checksum, DMA/IT buffer counter + iterator are adjusted 

//according to the protocol structure ...

phportsm->_pusart->RxXferCount = (phportsm->_rxbufferpayloadsize + 0x01);

phportsm->_pusart->RxXferSize = (phportsm->_rxbufferpayloadsize + 0x05 + 0x02);

Once the whole packet has arrived, it is marked for processing within the main loop (not within the ISR in order to recover the stack). The 

UART port changes its state to "HAL_UART_STATE_READY". The state is stored in "gState" which is defined as "volatile", so no optimization is applied.

The received buffer is processed quickly and the system attempts to post back to the sender immediately, in order to confirm reception by calling "HAL_UART_Transmit_DMA" and then calling "HAL_UART_Receive_IT" within the "HAL_UART_TxCpltCallback" callback to start over. Both functions have some possible return values, HAL_OK, HAL_ERROR, HAL_BUSY. It is possible to block the call in a while loop by allowing it to pass only when HAL_OK is returned (I also tried this approach).

I've noticed that since the previous process is really fast, the call to "HAL_UART_Transmit_DMA" is not sending any data after receiving. As a workaround, the only way I've achieved a successfull operation of both calls, is by applying a scheduled "delay" of 50 milliseconds among calls (no HAL_Delay used. The call is delayed by using timers). It means that after receiving the decoded packet, I needed to delay the call to "HAL_UART_Transmit_DMA" in order to make it work. Could anyone please help with a logical explanation of this misbehavior?

How can I call both methods as fast as possible with a deterministic approach instead of using calculated delays??

Please notice that if I change "HAL_UART_Transmit_DMA" with "HAL_UART_Transmit_IT" it works almost the same and I get the same misbehavior.

Thanks