I've observed a wild behaviour from my application today. It's reading UART data to find complete messages and process them. When another device on the RS-485 bus went wild and sent tons of messages for a moment (just few seconds), my poor ESP32 got seriously confused. It read most of the messages first, then had several CRC errors in the read messages for a while, and then it turned strange.
It still did notice that new data was received regularly (every ~15 seconds, going on for minutes), but it was kind of like living in the past. uart_read_bytes() gave me data that was sent minutes ago. Apparently that data was still in some buffer but not reported or returned before. Also, each time only so much data was returned as was sent at the moment, but the data was old. I could see it by the logged message counters and the data wasn't aligned to the sent messages, there were always 3 or 4 bytes left of the following message (message size was around 11 bytes at the time).
So I'm asking, how can this happen? And how can I prevent that from happening again? If there's still more data to read, I want to have that immediately, not only when newer data is sent.
Here's parts of my code, please ask if you need to see more:
Code: Select all
#define BUF_SIZE (512)
#define STACK_SIZE (4096)
static QueueHandle_t uartQueue;
static uint8_t recvData[BUF_SIZE];
static QueueHandle_t recvQueue;
void init()
{
uart_config_t uart_config = {
.baud_rate = RS485_BAUDRATE,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
};
// Configure UART parameters
int rxBufferSize = BUF_SIZE * 2;
int txBufferSize = BUF_SIZE;
int queueSize = 20; // should I increase this?
int intr_alloc_flags = 0;
ESP_ERROR_CHECK(uart_driver_install(RS485_UART_NUM, rxBufferSize, txBufferSize, queueSize, &uartQueue, intr_alloc_flags));
ESP_ERROR_CHECK(uart_param_config(RS485_UART_NUM, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(RS485_UART_NUM, RS485TX_GPIO, RS485RX_GPIO, RS485DE_GPIO, UART_PIN_NO_CHANGE));
ESP_ERROR_CHECK(uart_set_mode(RS485_UART_NUM, UART_MODE_RS485_HALF_DUPLEX));
xTaskCreate(recvTaskCode, "uart-recv", STACK_SIZE, NULL, 2, &recvTaskHandle);
configASSERT(recvTaskHandle);
}
void recvTaskCode(void* unusedParameters)
{
uart_event_t event;
int usedData = 0;
uint16_t msgLength = MIN_MSG_LENGTH;
while (true)
{
// Read data from the UART to the end of the buffer
if (xQueueReceive(uartQueue, (void*)&event, (TickType_t)portMAX_DELAY))
{
if (event.type == UART_DATA)
{
uint32_t readLength = event.size;
uint32_t maxReadLength = BUF_SIZE - 1 - usedData;
if (readLength > maxReadLength)
readLength = maxReadLength;
int len = uart_read_bytes(RS485_UART_NUM, recvData + usedData, readLength, pdMS_TO_TICKS(50));
if (len == 0)
{
ESP_LOGW(TAG, "No bytes read although notified by event");
// didn't observe this ever
}
if (len > 0)
{
usedData += len;
ESP_LOGI(TAG, "Recv %d new bytes, total %d bytes", len, usedData);
while (usedData >= msgLength)
{
// Enough data to determine the message frame length, also every message is at least that long
msgLength = 6; // actually read from received data here
if (usedData >= msgLength)
{
// A complete message was received, check CRC
// (...)
if (recvCrc == compCrc)
{
// CRC matches, process the message
// log message incl. its counter so that I can see what's going on
// (...)
// Remove the message from the buffer and keep what comes after it
for (int j = msgLength; j < usedData; j++)
{
recvData[j - msgLength] = recvData[j];
}
usedData -= msgLength;
}
else
{
// CRC error, flush receive buffer and wait for idle
// (...)
}
// Reset expected number of bytes to process new messages
msgLength = MIN_MSG_LENGTH;
}
}
}
}
// I noticed I could also handle these events, would that help?
else if (event.type == UART_BUFFER_FULL ||
event.type == UART_FIFO_OVF ||
event.type == UART_FRAME_ERR ||
event.type == UART_PARITY_ERR)
{
// This code is new, I added it after observing the problem: would it help?
usedData = 0;
uart_flush_input(RS485_UART_NUM);
// Reset expected number of bytes to process new messages
msgLength = MIN_MSG_LENGTH;
}
}
}
}
I guess that waiting for the data event and only reading as much data as indicated is a problem. The data is already in the read buffer, but I'm not asking for it yet. Maybe some events got lost? Can I just skip the events and always read all the data that is there? I want uart_read_bytes() to give me all from the buffer, but not wait for more. Only if there is nothing in the buffer, I want it to wait a moment until something was received. Is that possible?
Also, I have two timeouts here: the xQueueReceive() and uart_read_bytes() calls both want a timeout. What should I pass here?
As described, I basically want all received bytes immediately but wait if nothing is available at the moment. Pretty simple.