UART read timeout looks not working

gibzwein
Posts: 11
Joined: Fri Jul 03, 2020 3:19 am

UART read timeout looks not working

Postby gibzwein » Sat Jan 09, 2021 12:35 pm

Hello,
I am connected to communication line. Device is sending two frames of bytes with gap between.
I use code:
  1. #include "freertos/FreeRTOS.h"
  2. #include "freertos/task.h"
  3. #include "esp_system.h"
  4. #include "esp_log.h"
  5. #include "driver/uart.h"
  6. #include "string.h"
  7. #include "driver/gpio.h"
  8.  
  9. static const int RX_BUF_SIZE = 128;
  10.  
  11. #define TXD_PIN (GPIO_NUM_4)
  12. #define RXD_PIN (GPIO_NUM_5)
  13. #define UART_TOUT_THRESH_DEFAULT (1)
  14.  
  15. void init(void)
  16. {
  17.     const uart_config_t uart_config = {
  18.         .baud_rate = 100,
  19.         .data_bits = UART_DATA_8_BITS,
  20.         .parity = UART_PARITY_DISABLE,
  21.         .stop_bits = UART_STOP_BITS_1,
  22.         .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
  23.         .source_clk = UART_SCLK_APB,
  24.     };
  25.     const uart_intr_config_t uart_intr = {
  26.         // .intr_enable_mask = UART_RXFIFO_TOUT_INT_ENA_M,
  27.         // .rxfifo_full_thresh = 1,        //UART_FULL_THRESH_DEFAULT,  //120 default!! aghh! need receive 120 chars before we see them
  28.         .rx_timeout_thresh = 1,       //UART_TOUT_THRESH_DEFAULT,  //10 works well for my short messages I need send/receive
  29.         // .txfifo_empty_intr_thresh = 10, //UART_EMPTY_THRESH_DEFAULT
  30.     };
  31.     uart_intr_config(UART_NUM_1, &uart_intr);
  32.     uart_set_rx_timeout(UART_NUM_1, 1);
  33.     uart_enable_rx_intr(UART_NUM_1);
  34.     uart_param_config(UART_NUM_1, &uart_config);
  35.     uart_set_pin(UART_NUM_1, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
  36.     uart_driver_install(UART_NUM_1, RX_BUF_SIZE * 2, 0, 0, NULL, 0);
  37. }
  38.  
  39. static void rx_task(void *arg)
  40. {
  41.     LOG(LL_INFO, ("Start receiving"));
  42.     uint8_t *data = (uint8_t *)malloc(RX_BUF_SIZE + 1);
  43.     while (1)
  44.     {
  45.         int rxBytes = 0;
  46.         rxBytes = uart_read_bytes(UART_NUM_1, data, RX_BUF_SIZE, 10);
  47.         if (rxBytes > 0)
  48.         {
  49.             data[rxBytes] = 0;
  50.             LOG(LL_INFO, ("Read %d bytes: '%s'", rxBytes, data));
  51.         }
  52.     }
  53.     free(data);
  54. }
  55.  
  56. enum mgos_app_init_result mgos_app_init(void)
  57. {
  58.     init();
  59.     xTaskCreate(rx_task, "uart_rx_task", 1024 * 2, NULL, configMAX_PRIORITIES, NULL);
  60.     return MGOS_APP_INIT_SUCCESS;
  61. }
When printing to console I get 26 bytes instead of 2 x 13 bytes.
I used:
#define UART_TOUT_THRESH_DEFAULT (1)
uart_intr.rx_timeout_thresh = 1
and
uart_enable_rx_intr(UART_NUM_1);
but all of that looks not working.
The gap between frames is c.a. 180 ms (1.5 size of byte).
What I am doing wrong? How to solve this?
Thanks in advance.

gibzwein
Posts: 11
Joined: Fri Jul 03, 2020 3:19 am

Re: UART read timeout looks not working

Postby gibzwein » Mon Jan 11, 2021 2:02 pm

Any help?

ESP_Minatel
Posts: 364
Joined: Mon Jan 04, 2021 2:06 pm

Re: UART read timeout looks not working

Postby ESP_Minatel » Mon Jan 11, 2021 3:45 pm

Hi,

Can you confirm the ESP-IDF version that you're using?

gibzwein
Posts: 11
Joined: Fri Jul 03, 2020 3:19 am

Re: UART read timeout looks not working

Postby gibzwein » Tue Jan 12, 2021 12:36 pm

Hi,
thank You for reply.
My version is ESP-IDF v4.1-dev-474-g2e6398aff-dirty
Best regards

ESP_Minatel
Posts: 364
Joined: Mon Jan 04, 2021 2:06 pm

Re: UART read timeout looks not working

Postby ESP_Minatel » Tue Jan 12, 2021 1:29 pm

Hi,


Take a look at this section on the ESP-IDF programming guide: uart_read_bytes - Receiving.

You should change your code to handle the the number of available bytes in the buffer.

Code: Select all

// Read data from UART.
const int uart_num = UART2;
uint8_t data[128];
int length = 0;
ESP_ERROR_CHECK(uart_get_buffered_data_len(uart_num, (size_t*)&length));
length = uart_read_bytes(uart_num, data, length, 100);
Waiting for your feedback!

gibzwein
Posts: 11
Joined: Fri Jul 03, 2020 3:19 am

Re: UART read timeout looks not working

Postby gibzwein » Tue Jan 12, 2021 7:38 pm

Hi
I added
  1. uart_get_buffered_data_len(UART_NUM_1, (size_t*)&rxBytes);
but get same results. Additionally, an error occurs E (32137) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
Best regards

ScumCoder
Posts: 4
Joined: Sat Feb 03, 2024 9:04 pm

Re: UART read timeout looks not working

Postby ScumCoder » Mon Feb 05, 2024 9:26 pm

If you need low-latency reaction to Rx, you must always call uart_read_bytes with length of 1.
See an ugly, but working, crutch in this post.
I tested it by connecting UART Rx to the same UART's Tx, and calling esp_timer_get_time both after uart_write_bytes and after the aforementioned crutch (when all data is already in my buffer). The time difference was about 140 us. Not great (considering that the UART was configured for 921600 bauds), but not terrible.

I should especially note that I did not use any interrupt tuning whatsoever, i.e. never called any of those uart_intr_config, uart_set_rx_timeout, uart_enable_rx_intr etc.

ScumCoder
Posts: 4
Joined: Sat Feb 03, 2024 9:04 pm

Re: UART read timeout looks not working

Postby ScumCoder » Tue Feb 06, 2024 1:55 pm

ScumCoder wrote:
Mon Feb 05, 2024 9:26 pm
I should especially note that I did not use any interrupt tuning whatsoever, i.e. never called any of those uart_intr_config, uart_set_rx_timeout, uart_enable_rx_intr etc.
Okay, I bit the bullet and made a dive into the confusing world of the aforementioned interrupt functions.
After reading the docs, reading several forum threads, and even reading the IDF source code, here is what I found.
  • The UART receiver has its own 128-byte hardware buffer.
    When the number of bytes in it exceeds a certain threshold (rxfifo_full_thresh), it (the receiver) raises the UART_RXFIFO_FULL_INT interrupt on the CPU.
    The default threshold value is 120 (UART_FULL_THRESH_DEFAULT).
  • Also, this receiver measures the time that has passed since the end of receiving of the previous byte.
    If this time (measured in "characters", i.e. the amount of time it takes to send 1 byte) exceeds a certain threshold (rx_timeout_thresh a.k.a. RX_TOUT_THRHD in ESP32 Technical Reference Manual), the receiver raises the UART_RXFIFO_TOUT_INT interrupt on the CPU.
    The default threshold value is 10 (UART_TOUT_THRESH_DEFAULT).
  • This means that in case of dense data transfer (when RXFIFO_TOUT is not working), the CPU will receive an Rx notification (and therefore read received data into its own software Rx buffer) only every 120 bytes.
  • In the docs, in the source code of the driver (components/driver/uart/uart.c), and in user API (components/soc/esp32/include/soc/uart_reg.h), three different notations are used to denote the interrupt enable bits:

    Code: Select all

    UART_RXFIFO_FULL_INT_ENA; UART_INTR_RXFIFO_FULL; UART_RXFIFO_FULL_INT_ENA_M
    UART_RXFIFO_TOUT_INT_ENA; UART_INTR_RXFIFO_TOUT; UART_RXFIFO_TOUT_INT_ENA_M
    ...although all three have the same value.
  • Both interrupts are enabled by default (see UART_INTR_CONFIG_FLAG) when installing the driver using uart_driver_install. Thus, calling uart_enable_rx_intr in this case is pointless.
  • For both, the value of the respective threshold can be set using uart_set_rx_full_threshold and uart_set_rx_timeout, respectively.
  • This means that calling uart_intr_config is also pointless (or at least not necessary).
TL;DR version: the only thing you need to react to Rx with absolute minimal achievable latency is to call

Code: Select all

ESP_ERROR_CHECK(uart_set_rx_full_threshold(uartPort, 1));
during UART initialization, and then use the crutch I mentioned in the comment above.

Nothing else is needed. uart_intr_config isn’t needed. uart_enable_rx_intr isn’t needed.

I tested this the same way as above, and the latency between “returned from uart_write_bytes” and “returned from uart_read_bytes” dropped to single digit microseconds (for a single byte transfer @ 921600 baud).

I hope this post will save some poor soul some time. I sure as hell wish someone explained all that to me two days ago.

gobftald
Posts: 1
Joined: Thu Nov 24, 2022 11:04 pm

Re: UART read timeout looks not working

Postby gobftald » Mon Aug 26, 2024 11:36 am

I am on the "bare metal side" of esp32c3 programming using Rust, programming this UART directly via its HW registers. In addition to the previous valuable post, for better understanding, I would like to highlight one of its findings. The timeout of this UART is not a classic timeout that a developer might think of at first hearing.
The emphasis here is that the timout counter measures the time elapsed between two incoming bytes/characters. Not the time elapsed from the start of the receiving, i.e. whether any character has been received within a given timeout from that point.

This implies several things, and therefore this function cannot really be used for the classic timeout measurement mentioned above.

One is that as long as there is a character in the rx fifo the timeout counter measures/counts. It measures/counts the time since the last character was received. Only when the rx fifo is completely empty, the timeout measurement stops. Therefore, after emptying the rx fifo, it is recommended/necessary to clear the possible timeout interrupt (UART_RXFIFO_TOUT_INT) received earlier. This interrupt clearing also resets the timeout counter.

The other is that after the rx fifo is empty, even if you "start waiting" for the next (first incoming) character, the timeout counter does not count/measure. The measurement/counting of the previously set timeout value starts only after the first character is received into the empty rx fifo.

That's why I mentioned that this is not the classic receive timeout measurement. Of great significance in the previous post is that this timeout only measures/counts the time between characters already incoming/receiving.

I know that for programmers in ESP-IDF, the above is not direct advice. Nor do I know how IDF C API functions handle this situation. But what I have outlined may help you to better understand the actual operation of this timeout function.

Who is online

Users browsing this forum: No registered users and 81 guests