espressif / esp-idf

Espressif IoT Development Framework. Official development framework for Espressif SoCs.
Apache License 2.0
13.71k stars 7.3k forks source link

ESP-IDF UART interrupt issue (IDFGH-6942) #8562

Open Huckies opened 2 years ago

Huckies commented 2 years ago

Environment

Problem Description

I intend to receive UART data transmitted in very short internal, so I'm trying a interrupt way, here is the implementation:

#define INTER_CONNECT_UART_RX_BUFFER_SIZE (512)
#define INTER_CONNECT_UART_TX_BUFFER_SIZE (512)

static uart_config_t uart_config = {
    .baud_rate = 1500000,
    .data_bits = UART_DATA_8_BITS,
    .parity = UART_PARITY_DISABLE,
    .stop_bits = UART_STOP_BITS_1,
    // Currently we don't need flow control
    .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
    .source_clk = UART_SCLK_APB,
};

static uart_intr_config_t uart_intr_config_param = {
    .intr_enable_mask = UART_RXFIFO_TOUT_INT_RAW_M,
    .rx_timeout_thresh = 3,
    .rxfifo_full_thresh = UART_FIFO_LEN,
};

void IRAM_ATTR uart1_irq_handler(void *params)
{
    portENTER_CRITICAL_ISR(&HuckAL_uart1_spinlock);

    volatile uint32_t length = 0;

    ESP_ERROR_CHECK(uart_get_buffered_data_len(INTER_CONNECT_UART_NUM_1, &length));
    length = uart_read_bytes(INTER_CONNECT_UART_NUM_1, data, length, 0);
    total_length += length;
    // uart_flush(INTER_CONNECT_UART_NUM_1);
    uart_clear_intr_status(INTER_CONNECT_UART_NUM_1, UART_RXFIFO_TOUT_INT_CLR_M);

    portEXIT_CRITICAL_ISR(&HuckAL_uart1_spinlock);
}

vPortCPUInitializeMutex(&HuckAL_uart1_spinlock);
vPortCPUInitializeMutex(&HuckAL_uart2_spinlock);
ESP_ERROR_CHECK(uart_param_config(INTER_CONNECT_UART_NUM_1, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(INTER_CONNECT_UART_NUM_1, INTER_CONNECT_UART1_TXD, INTER_CONNECT_UART1_RXD, INTER_CONNECT_UART1_RTS, INTER_CONNECT_UART1_CTS));
ESP_ERROR_CHECK(uart_driver_install(INTER_CONNECT_UART_NUM_1, INTER_CONNECT_UART_RX_BUFFER_SIZE, INTER_CONNECT_UART_TX_BUFFER_SIZE, 0, NULL, 0));
ESP_ERROR_CHECK(uart_isr_free(INTER_CONNECT_UART_NUM_1)); 
ESP_ERROR_CHECK(uart_isr_register(INTER_CONNECT_UART_NUM_1, uart1_irq_handler, NULL, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM, &HuckAL_uart1_isr_handle));
ESP_ERROR_CHECK(uart_intr_config(INTER_CONNECT_UART_NUM_1, &uart_intr_config_param));
ESP_ERROR_CHECK(uart_enable_rx_intr(INTER_CONNECT_UART_NUM_1));

Actual Behavior

If I don't execute uart_flush(INTER_CONNECT_UART_NUM_1); the interrupt watchdog can get triggered in very short period.

Question

The uart documentation make a very fuzzy description on uart_get_buffered_data_len() uart_read_bytes() as well as uart_flush(). Which buffer do they operate on earth? If all of them works on the FIFO buffer(refer to the documentation), why would I retrieve a constant zero when using uart_get_buffered_data_len() in the ISR handler? https://docs.espressif.com/projects/esp-idf/en/v4.4/esp32s3/api-reference/peripherals/uart.html

Further, is there any way that can detect the short interval and move data out of the FIFO quickly and then flush it so I can parse them without blocking the next uart sentence?

ginkgm commented 2 years ago

Hi @Huckies , Part of the driver rely on the default ISR we provided, so I strongly recommend to try to fix this issue without rewriting the ISR (don't call uart_isr_register).

There are two factors affecting the interrupt frequency:

  1. rx timeout rx_timeout_thresh. When the sender doesn't pull down the line for N bytes of time after the last byte, the interrupt will be triggered.
  2. RX fifo full threshold rxfifo_full_thresh. If the sender keeps sending without timeout, the driver will be interrupted when rxfifo_full_thresh bytes are received.

Please try reconfigure these parameter. If it still don't meet your expectations, please come up with your configurations and maybe a signal waveform by logic analyzer so that we can see if the driver works properly.

Huckies commented 2 years ago

Hi @Huckies , Part of the driver rely on the default ISR we provided, so I strongly recommend to try to fix this issue without rewriting the ISR (don't call uart_isr_register).

There are two factors affecting the interrupt frequency:

  1. rx timeout rx_timeout_thresh. When the sender doesn't pull down the line for N bytes of time after the last byte, the interrupt will be triggered.
  2. RX fifo full threshold rxfifo_full_thresh. If the sender keeps sending without timeout, the driver will be interrupted when rxfifo_full_thresh bytes are received.

Please try reconfigure these parameter. If it still don't meet your expectations, please come up with your configurations and maybe a signal waveform by logic analyzer so that we can see if the driver works properly.

Thank you for the instruction, I've turn to SPI interface since it's quite latency sensitive.

Would you please tell me the target of uart_get_buffered_data_len() uart_read_bytes() and uart_flush() ? Is it the hardware FIFO register or the ring buffer in memory? Thanks.

ginkgm commented 2 years ago

Except from read_bytes, I think the other two are kind of internal/lower level APIs.. So I don't suggest to use them.

uart_get_buffered_data_len() and uart_read_bytes() are to read from SW ring buffer, while uart_flush will make sure both SW ring buffer and the HW fifo will be flushed.

ScumCoder commented 9 months ago

Except from read_bytes, I think the other two are kind of internal/lower level APIs.. So I don't suggest to use them.

That definitely doesn't seem to be correct. For one, both are mentioned in the official man: uart_get_buffered_data_len uart_flush

Moreover, uart_get_buffered_data_len is a well-known go-to method of low-latency Rx.

uart_flush will make sure both SW ring buffer and the HW fifo will be flushed

"flushed" is an ambiguous term and in a lot of contexts means "wait until all Tx data is sent", which is the polar opposite of what uart_flush does (destroying all data in Rx buffers).