Raspberry Pi UART Speed Test

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.

Setup

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.

Benchmark 1

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

Your email address will not be published. Required fields are marked *