diff --git a/components/driver/include/driver/uart.h b/components/driver/include/driver/uart.h index b410d2917e..f36ddcc166 100644 --- a/components/driver/include/driver/uart.h +++ b/components/driver/include/driver/uart.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -381,8 +381,10 @@ esp_err_t uart_enable_tx_intr(uart_port_t uart_num, int enable, int thresh); * RX pin binded to a GPIO through the GPIO matrix, whereas TX is binded * to its GPIO through the IOMUX. * - * @note Internal signal can be output to multiple GPIO pads. - * Only one GPIO pad can connect with input signal. + * @note It is possible to configure TX and RX to share the same IO (single wire mode), + * but please be aware of output conflict, which could damage the pad. + * Apply open-drain and pull-up to the pad ahead of time as a protection, + * or the upper layer protocol must guarantee no output from two ends at the same time. * * @param uart_num UART port number, the max port number is (UART_NUM_MAX -1). * @param tx_io_num UART TX pin GPIO number. diff --git a/components/driver/test/test_uart.c b/components/driver/test/test_uart.c index da040e57e2..1b16e3d315 100644 --- a/components/driver/test/test_uart.c +++ b/components/driver/test/test_uart.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -227,9 +227,7 @@ static void uart_write_task(void *param) { int uart_num = (int)param; uint8_t *tx_buf = (uint8_t *)malloc(1024); - if(tx_buf == NULL) { - TEST_FAIL_MESSAGE("tx buffer malloc fail"); - } + TEST_ASSERT_NOT_NULL(tx_buf); for(int i = 1; i < 1023; i++) { tx_buf[i] = (i & 0xff); } @@ -257,9 +255,7 @@ TEST_CASE("uart read write test", "[uart]") { const int uart_num = UART_NUM1; uint8_t *rd_data = (uint8_t *)malloc(1024); - if(rd_data == NULL) { - TEST_FAIL_MESSAGE("rx buffer malloc fail"); - } + TEST_ASSERT_NOT_NULL(rd_data); uart_config_t uart_config = { .baud_rate = 2000000, .data_bits = UART_DATA_8_BITS, @@ -324,10 +320,9 @@ TEST_CASE("uart tx with ringbuffer test", "[uart]") { const int uart_num = UART_NUM1; uint8_t *rd_data = (uint8_t *)malloc(1024); + TEST_ASSERT_NOT_NULL(rd_data); uint8_t *wr_data = (uint8_t *)malloc(1024); - if(rd_data == NULL || wr_data == NULL) { - TEST_FAIL_MESSAGE("buffer malloc fail"); - } + TEST_ASSERT_NOT_NULL(wr_data); uart_config_t uart_config = { .baud_rate = 2000000, .data_bits = UART_DATA_8_BITS, @@ -439,3 +434,42 @@ TEST_CASE("uart int state restored after flush", "[uart]") TEST_ESP_OK(uart_driver_delete(uart_echo)); free(data); } + +TEST_CASE("uart in one-wire mode", "[uart]") +{ + const uart_port_t uart_num = UART_NUM_1; + // let tx and rx use the same pin + const int uart_tx = UART1_RX_PIN; + const int uart_rx = UART1_RX_PIN; + uart_config_t uart_config = { + .baud_rate = 115200, + .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, + }; + + TEST_ESP_OK(uart_driver_install(uart_num, BUF_SIZE * 2, 0, 20, NULL, 0)); + TEST_ESP_OK(uart_param_config(uart_num, &uart_config)); + TEST_ESP_OK(uart_set_pin(uart_num, uart_tx, uart_rx, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + + // If configured successfully in one-wire mode + TEST_ESP_OK(uart_wait_tx_done(uart_num, portMAX_DELAY)); + vTaskDelay(pdMS_TO_TICKS(20)); // make sure last byte has flushed from TX FIFO + TEST_ESP_OK(uart_flush_input(uart_num)); + + const char *wr_data = "ECHO!"; + const int len = strlen(wr_data); + uint8_t *rd_data = (uint8_t *)calloc(1, 1024); + TEST_ASSERT_NOT_NULL(rd_data); + + uart_write_bytes(uart_num, wr_data, len); + int bytes_received = uart_read_bytes(uart_num, rd_data, BUF_SIZE, pdMS_TO_TICKS(20)); + TEST_ASSERT_EQUAL(len, bytes_received); + TEST_ASSERT_EQUAL_STRING_LEN(wr_data, rd_data, bytes_received); + + free(rd_data); + + TEST_ESP_OK(uart_driver_delete(uart_num)); +} diff --git a/components/driver/uart.c b/components/driver/uart.c index 8f5e678b73..1003debc05 100644 --- a/components/driver/uart.c +++ b/components/driver/uart.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -647,18 +647,21 @@ esp_err_t uart_set_pin(uart_port_t uart_num, int tx_io_num, int rx_io_num, int r ESP_RETURN_ON_FALSE((rts_io_num < 0 || (GPIO_IS_VALID_OUTPUT_GPIO(rts_io_num))), ESP_FAIL, UART_TAG, "rts_io_num error"); ESP_RETURN_ON_FALSE((cts_io_num < 0 || (GPIO_IS_VALID_GPIO(cts_io_num))), ESP_FAIL, UART_TAG, "cts_io_num error"); + // Since an IO cannot route peripheral signals via IOMUX and GPIO matrix at the same time, + // if tx and rx share the same IO, both signals need to be route to IOs through GPIO matrix + bool tx_rx_same_io = (tx_io_num == rx_io_num); + /* In the following statements, if the io_num is negative, no need to configure anything. */ - if (tx_io_num >= 0 && !uart_try_set_iomux_pin(uart_num, tx_io_num, SOC_UART_TX_PIN_IDX)) { + if (tx_io_num >= 0 && (tx_rx_same_io || !uart_try_set_iomux_pin(uart_num, tx_io_num, SOC_UART_TX_PIN_IDX))) { gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[tx_io_num], PIN_FUNC_GPIO); esp_rom_gpio_connect_out_signal(tx_io_num, UART_PERIPH_SIGNAL(uart_num, SOC_UART_TX_PIN_IDX), 0, 0); // output enable is set inside esp_rom_gpio_connect_out_signal func after the signal is connected // (output enabled too early may cause unnecessary level change at the pad) } - if (rx_io_num >= 0 && !uart_try_set_iomux_pin(uart_num, rx_io_num, SOC_UART_RX_PIN_IDX)) { + if (rx_io_num >= 0 && (tx_rx_same_io || !uart_try_set_iomux_pin(uart_num, rx_io_num, SOC_UART_RX_PIN_IDX))) { gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[rx_io_num], PIN_FUNC_GPIO); - gpio_set_pull_mode(rx_io_num, GPIO_PULLUP_ONLY); // This does not consider that RX signal can be read inverted by configuring the hardware (i.e. idle is at low level). However, it is only a weak pullup, the TX at the other end can always drive the line. - gpio_set_direction(rx_io_num, GPIO_MODE_INPUT); + gpio_ll_input_enable(&GPIO, rx_io_num); esp_rom_gpio_connect_in_signal(rx_io_num, UART_PERIPH_SIGNAL(uart_num, SOC_UART_RX_PIN_IDX), 0); } @@ -936,11 +939,6 @@ static void UART_ISR_ATTR uart_rx_intr_handler_default(void *param) uart_event.type = UART_DATA; uart_event.size = rx_fifo_len; uart_event.timeout_flag = (uart_intr_status & UART_INTR_RXFIFO_TOUT) ? true : false; - UART_ENTER_CRITICAL_ISR(&uart_selectlock); - if (p_uart->uart_select_notif_callback) { - p_uart->uart_select_notif_callback(uart_num, UART_SELECT_READ_NOTIF, &HPTaskAwoken); - } - UART_EXIT_CRITICAL_ISR(&uart_selectlock); } p_uart->rx_stash_len = rx_fifo_len; //If we fail to push data to ring buffer, we will have to stash the data, and send next time. @@ -985,6 +983,14 @@ static void UART_ISR_ATTR uart_rx_intr_handler_default(void *param) p_uart->rx_buffered_len += p_uart->rx_stash_len; UART_EXIT_CRITICAL_ISR(&(uart_context[uart_num].spinlock)); } + + if (uart_event.type == UART_DATA) { + UART_ENTER_CRITICAL_ISR(&uart_selectlock); + if (p_uart->uart_select_notif_callback) { + p_uart->uart_select_notif_callback(uart_num, UART_SELECT_READ_NOTIF, &HPTaskAwoken); + } + UART_EXIT_CRITICAL_ISR(&uart_selectlock); + } } else { UART_ENTER_CRITICAL_ISR(&(uart_context[uart_num].spinlock)); uart_hal_disable_intr_mask(&(uart_context[uart_num].hal), UART_INTR_RXFIFO_FULL | UART_INTR_RXFIFO_TOUT);