After having some trouble with Raspberry Pi pyserial
module, I started to think about some benchmarks to see how UART performs with a high load.
This setup requires 1 x ESP32 and 1 x Raspberry Pi. I created different programs for ESP32. The experiments run on Raspberry Pi 4B and ESP32-WROOM-32. The cables are regular jumper cables and the baud rate is 115200.
Benchmark 1: Counter
ESP32 send counter at a maximum rate to the UART and Raspberry Pi tries to find any sequence where UART missed a number.
Here Pi receives data and checks for the sequence in case we missed something. At the same time there are programs open on Pi which benchmarks CPU and Disk IO. The setup is designed to ensure that UART works expectedly under high CPU and IO load.
While these programs are running, reading some text file with cat text_file.txt
takes around 10 seconds.
Results:
Running the benchmark code(and the load at the same time) produced not even a single data point loss during 8m44.325s real time. The communication is fully robust.
However, the time between reads has a higher variance under high load, but the time difference is negligible for most of the operations.
Program Output:
Total received data in bytes: 1189766, counter: 190000, recv_int: 189999, time elapsed: 13.88819432258606
# Here the load starts – above this point it always start with 13.8881
Total received data in bytes: 1349766, counter: 200000, recv_int: 199999, time elapsed: 14.149656295776367
Total received data in bytes: 1509766, counter: 210000, recv_int: 209999, time elapsed: 13.626783609390259
Total received data in bytes: 1669766, counter: 220000, recv_int: 219999, time elapsed: 13.888224363327026
Total received data in bytes: 1829766, counter: 230000, recv_int: 229999, time elapsed: 14.5758957862854
Total received data in bytes: 1989766, counter: 240000, recv_int: 239999, time elapsed: 13.200477600097656
# After this point, it gets stable again when IO load finishes
Benchmark 2: Compute Intensive Threading
This benchmark is basically the same program as in benchmark 1, there are 2 threads in the Raspberry Pi Python program. This block the communication, and data arrives very late due to scheduling. However, this is not a UART test; so I skip the results. Consequently, just keep in mind that threading library can be blocking the communication due to scarce computations.
Test Codes
Benchmark 1
Raspberry Pi Code
import serial
import traceback
import time
ser = serial.Serial ("/dev/ttyS0", 115200)
output_file = open('output_file.txt', 'w')
counter = 0
total_chars_recv = 0
start_time = time.time()
while True:
try:
received_data = ser.read_until(b'\n')
total_chars_recv = total_chars_recv + len(received_data)
received_data = received_data[1:-1] # for some reason the first character is weird...
try:
recv_int = int(received_data.decode().lstrip())
output_file.write(str(recv_int) + '\n')
if recv_int != counter:
print(f'Received integer is {recv_int} whereas the counter is {counter}')
counter = recv_int
counter = counter + 1
if counter % 1000 == 0:
print(f'Total received data in bytes: {total_chars_recv}, counter: {counter}, recv_int: {recv_int}, time elapsed: {time.time() - start_time}')
start_time = time.time()
except UnicodeDecodeError as e:
print('Error is ' + str(e))
except Exception as e:
print(f'Unexpected error {e} with traceback {traceback.print_exc()}')
ESP32 Code
Please check Espressif for more details. https://github.com/espressif/esp-idf/tree/master/examples
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include "sdkconfig.h"
#include "esp_log.h"
#define ECHO_TEST_TXD (17)
#define ECHO_TEST_RXD (16)
#define ECHO_TEST_RTS (UART_PIN_NO_CHANGE)
#define ECHO_TEST_CTS (UART_PIN_NO_CHANGE)
#define ECHO_UART_PORT_NUM (CONFIG_EXAMPLE_UART_PORT_NUM)
#define ECHO_UART_BAUD_RATE (CONFIG_EXAMPLE_UART_BAUD_RATE)
#define ECHO_TASK_STACK_SIZE (CONFIG_EXAMPLE_TASK_STACK_SIZE)
#define BUF_SIZE (1024)
static void echo_task(void *arg)
{
/* Configure parameters of an UART driver,
* communication pins and install the driver */
uart_config_t uart_config = {
.baud_rate = ECHO_UART_BAUD_RATE,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_DEFAULT,
};
int intr_alloc_flags = 0;
#if CONFIG_UART_ISR_IN_IRAM
intr_alloc_flags = ESP_INTR_FLAG_IRAM;
#endif
ESP_ERROR_CHECK(uart_driver_install(ECHO_UART_PORT_NUM, BUF_SIZE * 2, 0, 0, NULL, intr_alloc_flags));
ESP_ERROR_CHECK(uart_param_config(ECHO_UART_PORT_NUM, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(ECHO_UART_PORT_NUM, ECHO_TEST_TXD, ECHO_TEST_RXD, ECHO_TEST_RTS, ECHO_TEST_CTS));
int counter = 0;
while (1) {
counter ++;
if(counter > (1<<20)) counter = 0;
char counter_str[16] = " ";
sprintf(counter_str, "%d\n", counter);
uart_write_bytes(ECHO_UART_PORT_NUM, (const char *) counter_str, 16);
}
}
void app_main(void)
{
xTaskCreate(echo_task, "uart_echo_task", ECHO_TASK_STACK_SIZE, NULL, 10, NULL);
}
Conclusion
UART is pretty robust against OS loads including IO and CPU. However, I had other problems with the serial such as freezing behavior and corrupt data. Even single file operation mixed with UART operation can cause issues. Therefore, Pi UART is not directly usable in large scale apps where software needs to both handle files and serial. This is most probably becuase of pyserial
library, but more tests are needed to make sure. I will go over those issues in the next posts.
Leave a Reply