Skip to main content
Krautermann
Senior
May 25, 2023
Solved

HAL_UART_Transmit_DMA with unwanted behaviour

  • May 25, 2023
  • 13 replies
  • 4533 views

I am working with the STM32H745XIH6 disco board to use HAL_UART_Transmit_DMA and send 3 different strings. But what I receive on the terminal are corrupted data. At least on the 1st send of every string, the string are not shown correctly.

Let's say the strings are as follows:

uint8_t TX_String[] = "Hello World";

uint8_t TX_String1[] = "WholeCallback";

uint8_t TX_String2[] = "Half";

What I see on the 1st send on the terminal is: a bunch of NULL characters when sending the 1st string. Then if I send the 1st string again, it appears correctly. On sending the 2nd string I get a mixed of 1st and 2nd string. Same happen for 3rd string. It seems that the buffer responsible for DMA transmit is not storing all the characters from my global variable on the 1st go.

This is what is seen on the terminal:

\0\0\0\0\0\0\0orld
Hello wllbackÿÿÿ
Whol

How can I fix that?

#include <gui/model/Model.hpp>
#include <gui/model/ModelListener.hpp>
 
#ifndef SIMULATOR
#include "main.h"
#include "string.h"
extern "C"
{
	extern UART_HandleTypeDef huart1;
	extern UART_HandleTypeDef huart3;
	extern uint8_t RX_String[512];
}
#endif
 
Model::Model() : modelListener(0)
{
 
}
 
void Model::tick()
{
#ifndef SIMULATOR
	strncpy(RXData, (char*)RX_String, sizeof(RXData));
	modelListener->RX_Data(RXData);
#endif
}
 
void Model::SendData2MCU(int UART, int DMA, char* data)
{
#ifndef SIMULATOR
	if(UART==0)
	{
		if(DMA==0)
		{
			HAL_UART_Transmit(&huart3, (uint8_t*) data, strlen(data), 100);
		}
		else if(DMA==1)
		{
			HAL_UART_Transmit_DMA(&huart3, (uint8_t*) data, strlen(data));
		}
	}
	if(UART==1)
	{
		if(DMA==0)
		{
			HAL_UART_Transmit(&huart1, (uint8_t*) data, strlen(data), 100);
		}
		else if(DMA==1)
		{
			HAL_UART_Transmit_DMA(&huart1, (uint8_t*) data, strlen(data));
		}
	}
#endif
}

This topic has been closed for replies.
Best answer by Guenael Cadier

Hi @Krautermann​ 

Again, just after exiting the first call of HAL_UART_Transmit_DMA(), depending of transmitted buffer size and baudrate, I think the transfer is not complete, so DMA_complete is still 0 => you do not enter the if() statements.

Instead, if you want to wait the first transfer is completed, before requesting the second one, you need rather to have a while. Something like :

// 1st buffer
DMA_complete = 0;
HAL_UART_Transmit_DMA();
while (DMA_complete == 0) {};
 
// 2nd buffer
DMA_complete = 0;
HAL_UART_Transmit_DMA();
while (DMA_complete == 0) {};
 
// 3rd buffer
DMA_complete = 0;
HAL_UART_Transmit_DMA();
while (DMA_complete == 0) {};

 Regarding the second question, on how to check result of HAL call, you could try :

if (HAL_OK != HAL_UART_Transmit_DMA(...))
{
 // something wrong happens
}

13 replies

waclawek.jan
Super User
May 25, 2023

> Let's say the strings are as follows:

> uint8_t TX_String[] = "Hello World";

> uint8_t TX_String1[] = "WholeCallback";

> uint8_t TX_String2[] = "Half";

What's that, global variables or local variables or what?

How exactly do you start transmission and how do you wait until transmission of one string ends?

JW

Krautermann
Senior
May 31, 2023

Hi @Community member​ , thank you for your reply.

They are global variables because I declared them before int main() and they remain unchanged throughout the whole process.

I have written the following in int main():

HAL_UART_Transmit_DMA(&huart3, TX_String, sizeof(TX_String));
 HAL_UART_Transmit_DMA(&huart3, TX_String1, sizeof(TX_String1));
 HAL_UART_Transmit_DMA(&huart3, TX_String2, sizeof(TX_String2));

And what I see on the terminal is only the 1st string, why don't I see the 2nd and 3rd String? when using regular HAL_UART_Transmit function, I can see all 3 strings in the right order instantly.

To your question: How exactly do you start transmission and how do you wait until transmission of one string ends?

Pressing reset button on the disco board starts the transmission. As for waiting till transmission ends, I don't know how to do that. Please help!

I don't know what to write inside void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) to make DMA work properly.

Guenael Cadier
ST Employee
May 25, 2023

Just an idea : could it be that the data present in the buffer you use for transmission, is updated during the transmission ?

The buffers you are using to send data with HAL_UART_Transmit() or HAL_UART_Transmit_DMA() have to contain the data to be transmitted till the transmission is complete.

For HAL_UART_Transmit(), buffer should not be reused or altered by application until you exit the function.

For HAL_UART_Transmit_DMA(), buffer should not be reused or altered by application until the TxComplete callback is executed.

Just for a try, if you declare your TX_String as const (global array) and try to directly use TX_String as parameter of HAL_UART_Transmit() or HAL_UART_Transmit_DMA(), what is the result ?

Krautermann
Senior
May 31, 2023

Hi @Guenael Cadier​ 

Thank you for your reply. I have been trying for the past few days to get it working but in vain. When I use HAL_UART_Transmit() with global variable, I can see all 3 strings correctly displayed on the terminal. But for HAL_UART_Transmit_DMA(), I only see the 1st string and not the 2nd and 3rd string.

I have a feeling that DMA is way slower than polling. How can I see all through strings via DMA if I write the following in my main?

 HAL_UART_Transmit_DMA(&huart3, TX_String, sizeof(TX_String));
 HAL_UART_Transmit_DMA(&huart3, TX_String1, sizeof(TX_String1));
 HAL_UART_Transmit_DMA(&huart3, TX_String2, sizeof(TX_String2));

I notice that I only see the 3 strings if I add a small delay HAL_Delay(1) in between each HAL_UART_Transmit_DMA(). In debug, the DMA works fine, but in run mode, it does not.

Guenael Cadier
ST Employee
May 31, 2023

Hi @Krautermann​ 

HAL_UART_Transmit_DMA() is a non blocking API : it means that when you exit the call of HAL_UART_Transmit_DMA(), transfer is not complete.

From what I understood from your answers, you need to wait for the execution of the TxComplete callback (end of the 1st transfer) before being able to call again the same API.

(this is also highlighted by the fact that inserting a delay is allowing to have correct result.

In your case, second and third call might be useless, as you will exit with HAL BUSY code (i.e. 1st transfer still ongoing).

To confirm this hypothesis, you could check the return code of each of your HAL_UART_Transmit_DMA() calls.

Regards

Tesla DeLorean
Guru
May 25, 2023

The routine returns immediately, not when the transmission has finished. It doesn't handle / manage concurrent operation, you have to do that.

Tips, Buy me a coffee, or three.. PayPal Venmo (See Profile) Up vote any posts that you find helpful, it shows what's working..
Karl Yamashita
Principal
May 31, 2023

You don't show how you're calling SendData2MCU? I'm guessing that you're using the same data array but copying the 3 TX_String to it first?

Also, you don't check HAL status which is very important.

If a reply has proven helpful, click on Accept as Solution so that it'll show at top of the post.CAN Jammer an open source CAN bus hacking toolCANableV3 Open Source
Krautermann
Senior
June 1, 2023

@Community member​ For SendData2MCU, I used a different array:

Basically the user enter a string on the LCD, that string is stored as unicode (in TX_TextAreaBuffer) and converted to ASCII (in TX_Buffer) and then this is stored in another buffer DataBuffer which is used in the HAL_UART_Transmit_DMA function.

I tried several times but I cannot see how the DataBuffer is getting modified in the middle of the DMA process. Whenever the user write another string on the LCD, DataBuffer is only modified when the user press on the Send Button.

void Screen2View::OKClicked()
{	//hide gui_keyboard and ok and exit button
	gui_keyboard.setVisible(false);
	gui_keyboard.invalidate();
	OKButton.setVisible(false);
	OKButton.invalidate();
	EXITButton.setVisible(false);
	EXITButton.invalidate();
	SendButton.setVisible(true);
	SendButton.invalidate();
	if (TX_mod)
	{
		Unicode::strncpy(TX_TextAreaBuffer, gui_keyboard.getBuffer(), TX_TEXTAREA_SIZE);
		TX_TextArea.invalidate();
	}
 
	gui_keyboard.clearBuffer();
}
 
void Screen2View::SendClicked()
{
	if(UARTToggleButton.getState()==0)
	{
		UART = 0;
		if(DMAToggleButton.getState()==0)
		{
			DMA = 0;
		}
		else
		{
			DMA = 1;
		}
	}
	else if(UARTToggleButton.getState()==1)
	{
		UART = 1;
		if(DMAToggleButton.getState()==0)
		{
			DMA = 0;
		}
		else
		{
			DMA = 1;
		}
	}
	int i=0;
	while(TX_TextAreaBuffer[i]!=0)
	{
		TX_Buffer[i] = (char)TX_TextAreaBuffer[i];
		i++;
	}
 
	sprintf((char*)DataBuffer, "%s", TX_Buffer);
	//memcpy(DataBuffer, TX_Buffer, sizeof(TX_Buffer));
 
	presenter->SendData2MCU(UART, DMA, DataBuffer);
 
	memcpy(TX_Buffer, "\0", TX_TEXTAREA_SIZE);
}