cancel
Showing results for 
Search instead for 
Did you mean: 

UART bridge on STM32F0: data corruption

Mark81
Associate III

I'm trying to link together two UARTs to create a sort of "bridge". Each byte received by one of them is sent to the other and vice-versa. I used this code for 7-8 years on small and slow MCUs like xmega (8-bit and 32 MHz), so I think there should no problem at all with such a beast like an STM32! Hence, I'm sure I did something wrong in my code.

The approach is quite simple: when an rx interrupt fires I store the incoming byte into a circular buffer. Then, inside a low-priority task I pop the byte from this buffer and put it into another, the tx buffer. Here, always under interrupts I send data on the other serial. It works fine (at least on AVR!). Because both serials share the same baud-rate it's almost impossible to fail.

Here the initialization, shown for one UART only, the other is the same:

huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart1) != HAL_OK) Error_Handler();
 
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);

Here the usage::

UART_data_t UART1_data;
UART_data_t UART2_data;
 
bool repeater = true;
 
void Init(void)
{
	UART_Init(&UART1_data, &huart1);
	UART_Init(&UART2_data, &huart2);
}
 
// called every 1 ms
void ReceiveData(void)
{
	while (UART_RxBufferData_Available(&UART1_data))
	{
		uint8_t ch = UART_RxBuffer_GetByte(&UART1_data);
		if (repeater) UART_TxBuffer_PutByte(&UART2_data, ch);
                else { // do something }
	}
 
	while (UART_RxBufferData_Available(&UART2_data))
	{
		uint8_t ch = UART_RxBuffer_GetByte(&UART2_data);
		if (repeater) UART_TxBuffer_PutByte(&UART1_data, ch);
                else { // do something }
	}
}
 
void USART1_IRQHandler(void)
{
	UART_IRQHandler(&UART1_data);
}
 
void USART2_IRQHandler(void)
{
	UART_IRQHandler(&UART2_data);
}
 
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	UART_data_t *UART_data;
	if (huart == UART1_data.huart) UART_data = &UART1_data;
	else if (huart == UART2_data.huart) UART_data = &UART2_data;
 
	UART_RxCallback(UART_data);
}
 
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
	UART_data_t *UART_data;
	if (huart == UART1_data.huart) UART_data = &UART1_data;
	else if (huart == UART2_data.huart) UART_data = &UART2_data;
 
	UART_TxCallback(UART_data);
}

Here the relevant functions of my circular buffer:

#define UART_RX_BUFFER_SIZE 64
#define UART_TX_BUFFER_SIZE 64
#define UART_RX_BUFFER_MASK (UART_RX_BUFFER_SIZE - 1)
#define UART_TX_BUFFER_MASK (UART_TX_BUFFER_SIZE - 1)
 
typedef struct UART_Buffer_t
{
	char ch;
	char RX[UART_RX_BUFFER_SIZE];
	char TX[UART_TX_BUFFER_SIZE];
	uint8_t RX_Head;
	uint8_t RX_Tail;
	uint8_t TX_Head;
	uint8_t TX_Tail;
} UART_Buffer_t;
 
typedef struct UART_data_t
{
	UART_HandleTypeDef *huart;
	UART_Buffer_t buffer;
} UART_data_t;
 
void UART_Init(UART_data_t *uart_data, UART_HandleTypeDef *huart)
{
	uart_data->huart = huart;
	uart_data->buffer.RX_Tail = 0;
	uart_data->buffer.RX_Head = 0;
	uart_data->buffer.TX_Tail = 0;
	uart_data->buffer.TX_Head = 0;
 
	if (HAL_UART_Receive_IT(uart_data->huart, (uint8_t *) &uart_data->buffer.ch, 1) != HAL_OK) Error_Handler();
 }
 
bool UART_TxBuffer_FreeSpace(UART_data_t *uart_data)
{
	uint8_t tempHead = (uart_data->buffer.TX_Head + 1) & UART_TX_BUFFER_MASK;
	uint8_t tempTail = uart_data->buffer.TX_Tail;
	return (tempHead != tempTail);
}
 
bool UART_TxBuffer_PutByte(UART_data_t *uart_data, char data)
{
	UART_Buffer_t *buffer = &uart_data->buffer;
	bool TxBuffer_FreeSpace = UART_TxBuffer_FreeSpace(uart_data);
	if (TxBuffer_FreeSpace)
	{
		uint8_t tempTX_Head = buffer->TX_Head;
		buffer->TX[tempTX_Head] = data;
		buffer->TX_Head = (tempTX_Head + 1) & UART_TX_BUFFER_MASK;
 
		if (uart_data->huart->gState == HAL_UART_STATE_READY) UART_TxCallback(uart_data);
	}
	return TxBuffer_FreeSpace;
}
 
bool UART_RxBufferData_Available(UART_data_t *uart_data)
{
	uint8_t tempHead = uart_data->buffer.RX_Head;
	uint8_t tempTail = uart_data->buffer.RX_Tail;
	return (tempHead != tempTail);
}
 
uint8_t UART_RxBuffer_GetByte(UART_data_t *uart_data)
{
	UART_Buffer_t *buffer = &uart_data->buffer;
	char ch = buffer->RX[buffer->RX_Tail];
	buffer->RX_Tail = (buffer->RX_Tail + 1) & UART_RX_BUFFER_MASK;
	return ch;
}
 
void UART_IRQHandler(UART_data_t *uart_data)
{
	HAL_UART_IRQHandler(uart_data->huart);
}
 
char UART_RxCallback(UART_data_t *uart_data)
{
	UART_Buffer_t *buffer = &uart_data->buffer;
	uint8_t tempRX_Head = (buffer->RX_Head + 1) & UART_RX_BUFFER_MASK;
	uint8_t tempRX_Tail = buffer->RX_Tail;
	char data = buffer->ch;
 
	if (tempRX_Head != tempRX_Tail)
	{
		uart_data->buffer.RX[uart_data->buffer.RX_Head] = data;
		uart_data->buffer.RX_Head = tempRX_Head;
	}
 
	HAL_StatusTypeDef res = HAL_UART_Receive_IT(uart_data->huart, (uint8_t *) &uart_data->buffer.ch, 1);
	if (res != HAL_OK) Error_Handler();
	return data;
}
 
void UART_TxCallback(UART_data_t *uart_data)
{
	UART_Buffer_t *buffer = &uart_data->buffer;
	uint8_t tempTX_Tail = uart_data->buffer.TX_Tail;
	if (buffer->TX_Head != tempTX_Tail)
	{
		char data = buffer->TX[tempTX_Tail];
		HAL_StatusTypeDef res = HAL_UART_Transmit_IT(uart_data->huart, (uint8_t *) &data, 1);
		if (res != HAL_OK) Error_Handler();
		buffer->TX_Tail = (tempTX_Tail + 1) & UART_TX_BUFFER_MASK;
	}
}

If I type anything on a serial terminal I get the same content on the other one. So the whole code is "correct".

The problems rise when I send a bunch of bytes, even 7-8 without any inter-char delay.

In this case I got corrupted data in the rx buffer. It means, it loses some HAL_UART_RxCpltCallback interrupts.

It's hard to believe it cannot execute the few lines of code needed to put the byte inside the buffer, before a new byte arrives. It has about 10 us, plenty of time!

So, would you please help me to understand my mistake?

7 REPLIES 7
Mark81
Associate III

So the short answer is:you can't (with HAL, at least). Great.

I'm going to give it a try with LL. I don't find any stm32f0xx_ll_*.h file on my system. I need a separate package after STM32CubeIDE?

I can't see which F0 derivate exactly you use, but this family is quite "matured".

You can still download SPL based code (board firmware packages and generic SPL libs), which is much less constricting and far easier to comprehend.

And which doesn't have this problem.

KnarfB
Principal III

In the STM32CubeIDE Device Configuration Tool (.ioc file editor) also known as STM32CubeMX: Project Manager tab, Advanced Settings you can choose between HAL and LL for each peripheral. LL is really close to register level. Don't know if its worth it.

IMHO, DMA is the correct approach because RX should always be listening. The DMA buffer is a replacement for a RX FIFO which is not present in the smaller MCUs.

> The DMA buffer is a replacement for a RX FIFO which is not present in the smaller MCUs.

This statement is, AFAIK, not quite correct. Only some families like the newer L devices use FIFOs for UART/USARTs. I haven't seen them on F1, F3, F4 and F7 devices I know.

ST is heavily relying on DMA in this regard.

Which I find a bit unfortunate, especially in use cases which are asynchhronous and burst-based in nature (like most serial protocols).

One corrupted character can put your DMA-based approach in serious trouble.

> IMHO, DMA is the correct approach

Is there a reliable example to avoid common mistakes?

A very detailed discussion and a lot of LL code can be found here: https://stm32f4-discovery.net/2017/07/stm32-tutorial-efficiently-receive-uart-data-using-dma/. Haven't tested it for reliability though.