From 0ec8a3a52a9f9886b99cf9f8d809a9f623bae22f Mon Sep 17 00:00:00 2001 From: Kevin Foucault Date: Sun, 27 Sep 2026 23:07:59 +0200 Subject: [PATCH] Fix ESP32 UART reader thread spinning at 100% CPU pthread_recv_esp32() initialised its select() timeout once, before the for(;;) loop. On Linux, select() overwrites the timeval with the time remaining, so after the first 100 ms with no byte from the backpack the timeout is {0, 0} and every subsequent select() returns immediately. The thread then spins forever, burning a whole Cortex-A7 core whenever the ELRS backpack option is enabled (the backpack only talks on events, so the first timeout happens right after boot). Measured on HDZero Goggles 2 (Linux 4.9, idle main menu, elrs.enable=1): - reader thread: 98% of one core, 85% in kernel, 0 voluntary sleeps - whole app: 1.45 cores -> 0.54 core after the fix - cpufreq: pinned at 1104 MHz 97% of the time -> 816 MHz 93% of the time Re-arm the timeout on every iteration. esp32_handler_timeout() fires the delayed DVR start/stop (record_time). It only ran when select() timed out; the spinning loop hid that, as it ran the handler continuously. With the thread sleeping again, a backpack sending bytes less than 100 ms apart would hold the DVR command off, so call the handler on every wake-up. It only compares record_time with time(). --- src/driver/esp32.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/driver/esp32.c b/src/driver/esp32.c index aefe17212..8638baa8e 100644 --- a/src/driver/esp32.c +++ b/src/driver/esp32.c @@ -36,7 +36,7 @@ static void *pthread_recv_esp32(void *arg) { int i, len = 0; uint8_t buffer[128]; fd_set rd; - struct timeval tv = {0, 100000}; + struct timeval tv; // Init UART buffers uart3_rptr = 0; @@ -48,6 +48,11 @@ static void *pthread_recv_esp32(void *arg) { for (;;) { FD_ZERO(&rd); FD_SET(fd_esp32, &rd); + // On Linux, select() overwrites tv with the time left; once it has + // timed out it stays {0, 0} and select() returns immediately, so the + // timeout has to be re-armed on every iteration. + tv.tv_sec = 0; + tv.tv_usec = 100000; if (stopping) { LOGI("UART3:stopping, exiting thread."); return NULL; @@ -71,8 +76,10 @@ static void *pthread_recv_esp32(void *arg) { } if (len) esp32_rx(); - } else - esp32_handler_timeout(); + } + // Delayed DVR command: check it on every wake-up, not only on timeout, + // or a backpack talking more often than every 100 ms would hold it off. + esp32_handler_timeout(); } return NULL; }