cancel
Showing results for 
Search instead for 
Did you mean: 

HAL UART Receive short reads - corrupt data

PCamp.2
Associate III

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?

6 REPLIES 6
gbm
Lead III

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. ;)

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
Up vote any posts that you find helpful, it shows what's working..

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

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
PCamp.2
Associate III
 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
Associate III

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
Associate III

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.