2022-10-06 04:58 AM
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?
2022-10-06 06:15 AM
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. ;)
2022-10-06 06:54 AM
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.
2022-10-06 06:56 AM
Given the demographics I think most would be classified as wizards, old ones..
2022-10-06 08:17 AM
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?
2022-10-06 11:03 AM
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.
2022-10-06 11:09 AM
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.