Skip to main content
PCamp.2
Associate III
October 6, 2022
Question

HAL UART Receive short reads - corrupt data

  • October 6, 2022
  • 6 replies
  • 2091 views

I am trying to implement a UART Rx using a typical

start-byte, length, payload

pattern.

I spin read on 1 character until it is my start byte. Then I read 2 bytes, shift them into a 16 bit length and read the remainder of the payload.

The trouble is, the second read for 2 bytes gives me garbage. It looks like the data stream has rewound 2 bytes.

I created a fresh project, enabled UART2 and UART6 only, connected the repeating test stream on UART6 Rx and read 1 byte at a time, immediately writing the one byte out on UART2 (The STM vCOM port).

The data is corrupted. It replays the same block of garbage and never advances.

Reading 2 bytes results in much the same.

Reading 4 bytes and the stream starts to track, but has drop outs and corruption.

Reading 50 bytes at a time and the stream is fine.

I looked through many examples, but have not found one that reads such short buffers. Most read larger buffers of at least 8 chars.

Is there a limitation or buffer alignment "gotcha" with the HAL uart functions? Is there a minimum read length? Is this just an artifact of using the blocking calls and creating too much overhead?

MCU: STM32F411RE (Nucleo-64 board)

UART Transmittor: ESP8266 using hardware Serial 0.

UART: 8bit, no parity, 1 stop bit, 115200baud

UART stream has been verified on the wire by a logic analyser and is clean.

Test "packet"

~(0x2E)(0x00)TEST_TOPIC/+/#/TOPIC THIS IS A 25 BYTE PAYLOAD

Repeated once a second.

Any ideas?

This topic has been closed for replies.

6 replies

gbm
Super User
October 6, 2022

Basic idea: when you ask for help with your code, show the code in question. Most of us here are engineers, not witches. Well, me at least. ;)

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice
Tesla DeLorean
Guru
October 6, 2022

Given the demographics I think most would be classified as wizards, old ones..

Tips, Buy me a coffee, or three.. PayPal Venmo (See Profile) Up vote any posts that you find helpful, it shows what's working..
Tesla DeLorean
Guru
October 6, 2022

I'm not aware of a problem, the HAL functions are a bit unhelpful, and most STM32 don't have a FIFO on the U(S)ARTs and interrupt on each byte.

General problem perhaps with multi-byte HAL calls is synchronization with respect to the data stream. I tend to prefer to buffer the data using the interrupt, and deal with the content, size and processing as a secondary task.

Tips, Buy me a coffee, or three.. PayPal Venmo (See Profile) Up vote any posts that you find helpful, it shows what's working..
PCamp.2
PCamp.2Author
Associate III
October 6, 2022
 while (1) {
 HAL_UART_Receive(&huart6, buffer, 8, HAL_MAX_DELAY);
 HAL_UART_Transmit(&huart2, buffer, 8, HAL_MAX_DELAY);
 }

Produces a loop of garbage out huart2

 while (1) {
 HAL_UART_Receive(&huart6, buffer, 100, HAL_MAX_DELAY);
 HAL_UART_Transmit(&huart2, buffer, 100, HAL_MAX_DELAY);
 }

Produces no garbage and tracks the stream.

I reimplemented it using HAL_UART_Receive_IT

And I get a lot of HAL_UART_ERROR_ORE.

With these event handlers:

uint8_t buffer[128];
uint16_t size = 50;
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) {
 if(huart == &huart6) {
 if(huart->ErrorCode == HAL_UART_ERROR_ORE) {
	 HAL_UART_Transmit_IT(&huart2, "\r\n**Overrun**\r\n", 15);
 } else {
	 HAL_UART_Transmit_IT(&huart2, "\r\n**Unknown**\r\n", 15);
 }
 HAL_UART_Receive_IT(&huart6, buffer, size);
 }
 
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
 if(huart == &huart6) {
 HAL_UART_Transmit(&huart2, buffer, size, HAL_MAX_DELAY);
 HAL_UART_Receive_IT(&huart6, buffer, size);
 }
}
~.TEST_TOPIC/+/#/TOPIC THIS IS A 25 BYTE PAYLOAD
**Overrun**
~.TEST_TOPIC/+/#/TOPIC THIS IS A 25 BYTE PAYLOAD
~.TEST_TOPIC/+/#/TOPIC THIS IS A 25 BYTE PAYLOAD
**Overrun**
~.TEST_TOPIC/+/#/TOPIC THIS IS A 25 BYTE PAYLOAD
~.TEST_TOPIC/+/#/TOPIC THIS IS A 25 BYTE PAYLOAD

Produces that output.

If I remove the blocking Transmit it does not hit that error handler.

I'm still a bit confused, because those buffer overruns are suspiciously aligned with "dead space" on the line.

If I can HAL_UART_Receive_IT for 4 bytes and there is no data on the wire for 950ms, will that produce an error?

Should I be polling the UART ready state before calling Receive?

PCamp.2
PCamp.2Author
Associate III
October 6, 2022

So, I'm assuming my approach was just not going to work, there isn't enough FIFO room to do anything blocking with one UART while another is runnng. It has to be async/IT or DMA.

I got the protocol working, I was always going to go to IT and may still go to DMA, I just for some reason expected the polling method to work.

Here is the protocol async 2-sided state machine:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
 if(huart == &message_uart) {
 switch(protocol_state) {
	case INIT:
	 break; // No op
	case START_BYTE_WAIT:
	 if(buffer[0] == START_BYTE) {
	 protocol_state=START_BYTE_READ_OK;
	 } else {
	 HAL_UART_Receive_IT(&message_uart, buffer, 1);
	 }
	 break;
	case START_BYTE_READ_OK:
	 break;//noop
	case LENGTH_READ_WAIT:
	 protocol_state = LENGTH_READ_OK; // we think
	 break;
	case LENGTH_READ_OK:
	 break;//noop
	case PAYLOAD_READ_WAIT:
	 protocol_state = PAYLOAD_READ_OK;
	 break;
	case PAYLOAD_READ_OK:
	 break; //noop
	default:
	 debugPrint("PROTOCOL ERROR 2");
	 break;
 }
 }
}
/**
 * @brief The application entry point.
 * @retval int
 */
int main(void)
{
 HAL_Init();
 SystemClock_Config();
 MX_GPIO_Init();
 MX_USART6_UART_Init();
 MX_USART2_UART_Init();
 
 while (1)
 {
 switch (protocol_state){
	case INIT:
	 protocol_state = START_BYTE_WAIT;
	 HAL_UART_Receive_IT(&message_uart, buffer, 1);
	 break;
 
	case START_BYTE_WAIT:
	 break; // no op
 
	case START_BYTE_READ_OK:
	 protocol_state = LENGTH_READ_WAIT;
	 HAL_UART_Receive_IT(&message_uart, buffer, 2);
	 break;
 
	case LENGTH_READ_WAIT:
	 break; //no op
 
	case LENGTH_READ_OK:
	 protocol_state = PAYLOAD_READ_WAIT;
	 uint16_t pktLength = (buffer[0] ) | (buffer[1]<<8);
	 HAL_UART_Receive_IT(&message_uart, buffer, pktLength);
	 break;
 
	case PAYLOAD_READ_WAIT:
	 break; // no op
 
	case PAYLOAD_READ_OK:
	 debugPrintB(buffer, pktLength);
	 protocol_state = INIT;
	 break;
	default:
	 debugPrint("PROTOCOL ERROR 1");
	 break;
 }
 }
}

Now for testing with real data. Then redrawing the screen without upsetting the UARTS.

PCamp.2
PCamp.2Author
Associate III
October 6, 2022

I still have an issue with the debug_uart Transmit_IT.

If you want to send two things immediately after the other, you get HAL_BUSY.

I thought it was wise to do

while( HAL_Transmit_IT(....) == HAL_BUSY );

but that turns out to lock it up. I believe the reason is that a transmit fails for some buffer under/over run condition, which might be fine, but it doesn't return the UART to READY ever again. I'm trying to work out which error handler it hits and how I can recover the UART state back to READY.

EDIT: Disregard. I'm dumb. I'll leave it here for others... however the mistake here is waiting on a UART status flag to change while hot looping in the ISR! I can assume the ISRs are non-renetrant on a single task system, so.. .instant deadlock.