Skip to content

Commit 098ecb1

Browse files
committed
Merge branch 'bugfix/uart_single_wire_mode_v5.4' into 'release/v5.4'
fix(uart): allow same pin for tx and rx in uart_set_pin; UART_SELECT_READ_NOTIF race conditon fix (v5.4) See merge request espressif/esp-idf!36250
2 parents 5bb41c4 + 441effd commit 098ecb1

File tree

5 files changed

+112
-39
lines changed

5 files changed

+112
-39
lines changed

components/esp_driver_uart/include/driver/uart.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -398,8 +398,10 @@ esp_err_t uart_enable_tx_intr(uart_port_t uart_num, int enable, int thresh);
398398
* RX pin binded to a GPIO through the GPIO matrix, whereas TX is binded
399399
* to its GPIO through the IOMUX.
400400
*
401-
* @note Internal signal can be output to multiple GPIO pads.
402-
* Only one GPIO pad can connect with input signal.
401+
* @note It is possible to configure TX and RX to share the same IO (single wire mode),
402+
* but please be aware of output conflict, which could damage the pad.
403+
* Apply open-drain and pull-up to the pad ahead of time as a protection,
404+
* or the upper layer protocol must guarantee no output from two ends at the same time.
403405
*
404406
* @param uart_num UART port number, the max port number is (UART_NUM_MAX -1).
405407
* @param tx_io_num UART TX pin GPIO number.

components/esp_driver_uart/src/uart.c

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -27,6 +27,7 @@
2727
#include "driver/uart_select.h"
2828
#include "esp_private/esp_clk_tree_common.h"
2929
#include "esp_private/gpio.h"
30+
#include "esp_private/esp_gpio_reserve.h"
3031
#include "esp_private/uart_share_hw_ctrl.h"
3132
#include "esp_clk_tree.h"
3233
#include "sdkconfig.h"
@@ -739,8 +740,19 @@ esp_err_t uart_set_pin(uart_port_t uart_num, int tx_io_num, int rx_io_num, int r
739740
}
740741
#endif
741742

743+
// Potential IO reserved mask
744+
uint64_t io_reserve_mask = 0;
745+
io_reserve_mask |= (tx_io_num > 0 ? BIT64(tx_io_num) : 0);
746+
io_reserve_mask |= (rx_io_num > 0 ? BIT64(rx_io_num) : 0);
747+
io_reserve_mask |= (rts_io_num > 0 ? BIT64(rts_io_num) : 0);
748+
io_reserve_mask |= (cts_io_num > 0 ? BIT64(cts_io_num) : 0);
749+
750+
// Since an IO cannot route peripheral signals via IOMUX and GPIO matrix at the same time,
751+
// if tx and rx share the same IO, both signals need to be route to IOs through GPIO matrix
752+
bool tx_rx_same_io = (tx_io_num == rx_io_num);
753+
742754
/* In the following statements, if the io_num is negative, no need to configure anything. */
743-
if (tx_io_num >= 0 && !uart_try_set_iomux_pin(uart_num, tx_io_num, SOC_UART_TX_PIN_IDX)) {
755+
if (tx_io_num >= 0 && (tx_rx_same_io || !uart_try_set_iomux_pin(uart_num, tx_io_num, SOC_UART_TX_PIN_IDX))) {
744756
if (uart_num < SOC_UART_HP_NUM) {
745757
gpio_func_sel(tx_io_num, PIN_FUNC_GPIO);
746758
esp_rom_gpio_connect_out_signal(tx_io_num, UART_PERIPH_SIGNAL(uart_num, SOC_UART_TX_PIN_IDX), 0, 0);
@@ -749,27 +761,27 @@ esp_err_t uart_set_pin(uart_port_t uart_num, int tx_io_num, int rx_io_num, int r
749761
}
750762
#if SOC_LP_GPIO_MATRIX_SUPPORTED
751763
else {
752-
rtc_gpio_init(tx_io_num);
753-
rtc_gpio_iomux_func_sel(tx_io_num, RTCIO_LL_PIN_FUNC);
754-
764+
rtc_gpio_init(tx_io_num); // set as a LP_GPIO pin
755765
lp_gpio_connect_out_signal(tx_io_num, UART_PERIPH_SIGNAL(uart_num, SOC_UART_TX_PIN_IDX), 0, 0);
756766
// output enable is set inside lp_gpio_connect_out_signal func after the signal is connected
757767
}
758768
#endif
759769
}
760770

761-
if (rx_io_num >= 0 && !uart_try_set_iomux_pin(uart_num, rx_io_num, SOC_UART_RX_PIN_IDX)) {
771+
if (rx_io_num >= 0 && (tx_rx_same_io || !uart_try_set_iomux_pin(uart_num, rx_io_num, SOC_UART_RX_PIN_IDX))) {
772+
io_reserve_mask &= ~BIT64(rx_io_num); // input IO via GPIO matrix does not need to be reserved
762773
if (uart_num < SOC_UART_HP_NUM) {
763774
gpio_func_sel(rx_io_num, PIN_FUNC_GPIO);
764-
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.
765-
gpio_set_direction(rx_io_num, GPIO_MODE_INPUT);
775+
gpio_input_enable(rx_io_num);
766776
esp_rom_gpio_connect_in_signal(rx_io_num, UART_PERIPH_SIGNAL(uart_num, SOC_UART_RX_PIN_IDX), 0);
767777
}
768778
#if SOC_LP_GPIO_MATRIX_SUPPORTED
769779
else {
770-
rtc_gpio_set_direction(rx_io_num, RTC_GPIO_MODE_INPUT_ONLY);
771-
rtc_gpio_init(rx_io_num);
772-
rtc_gpio_iomux_func_sel(rx_io_num, RTCIO_LL_PIN_FUNC);
780+
rtc_gpio_mode_t mode = (tx_rx_same_io ? RTC_GPIO_MODE_INPUT_OUTPUT : RTC_GPIO_MODE_INPUT_ONLY);
781+
rtc_gpio_set_direction(rx_io_num, mode);
782+
if (!tx_rx_same_io) { // set the same pin again as a LP_GPIO will overwrite connected out_signal, not desired, so skip
783+
rtc_gpio_init(rx_io_num); // set as a LP_GPIO pin
784+
}
773785

774786
lp_gpio_connect_in_signal(rx_io_num, UART_PERIPH_SIGNAL(uart_num, SOC_UART_RX_PIN_IDX), 0);
775787
}
@@ -784,31 +796,39 @@ esp_err_t uart_set_pin(uart_port_t uart_num, int tx_io_num, int rx_io_num, int r
784796
}
785797
#if SOC_LP_GPIO_MATRIX_SUPPORTED
786798
else {
787-
rtc_gpio_init(rts_io_num);
788-
rtc_gpio_iomux_func_sel(rts_io_num, RTCIO_LL_PIN_FUNC);
799+
rtc_gpio_init(rts_io_num); // set as a LP_GPIO pin
789800
lp_gpio_connect_out_signal(rts_io_num, UART_PERIPH_SIGNAL(uart_num, SOC_UART_RTS_PIN_IDX), 0, 0);
790801
// output enable is set inside lp_gpio_connect_out_signal func after the signal is connected
791802
}
792803
#endif
793804
}
794805

795806
if (cts_io_num >= 0 && !uart_try_set_iomux_pin(uart_num, cts_io_num, SOC_UART_CTS_PIN_IDX)) {
807+
io_reserve_mask &= ~BIT64(cts_io_num); // input IO via GPIO matrix does not need to be reserved
796808
if (uart_num < SOC_UART_HP_NUM) {
797809
gpio_func_sel(cts_io_num, PIN_FUNC_GPIO);
798-
gpio_set_pull_mode(cts_io_num, GPIO_PULLUP_ONLY);
799-
gpio_set_direction(cts_io_num, GPIO_MODE_INPUT);
810+
gpio_pullup_en(cts_io_num);
811+
gpio_input_enable(cts_io_num);
800812
esp_rom_gpio_connect_in_signal(cts_io_num, UART_PERIPH_SIGNAL(uart_num, SOC_UART_CTS_PIN_IDX), 0);
801813
}
802814
#if SOC_LP_GPIO_MATRIX_SUPPORTED
803815
else {
804816
rtc_gpio_set_direction(cts_io_num, RTC_GPIO_MODE_INPUT_ONLY);
805-
rtc_gpio_init(cts_io_num);
806-
rtc_gpio_iomux_func_sel(cts_io_num, RTCIO_LL_PIN_FUNC);
807-
817+
rtc_gpio_init(cts_io_num); // set as a LP_GPIO pin
808818
lp_gpio_connect_in_signal(cts_io_num, UART_PERIPH_SIGNAL(uart_num, SOC_UART_CTS_PIN_IDX), 0);
809819
}
810820
#endif
811821
}
822+
823+
// IO reserve
824+
uint64_t old_busy_mask = esp_gpio_reserve(io_reserve_mask);
825+
uint64_t conflict_mask = old_busy_mask & io_reserve_mask;
826+
while (conflict_mask > 0) {
827+
uint8_t pos = __builtin_ctzll(conflict_mask);
828+
conflict_mask &= ~(1ULL << pos);
829+
ESP_LOGW(UART_TAG, "GPIO %d is not usable, maybe used by others", pos);
830+
}
831+
812832
return ESP_OK;
813833
}
814834

@@ -1133,12 +1153,6 @@ static void UART_ISR_ATTR uart_rx_intr_handler_default(void *param)
11331153
uart_event.type = UART_DATA;
11341154
uart_event.size = rx_fifo_len;
11351155
uart_event.timeout_flag = (uart_intr_status & UART_INTR_RXFIFO_TOUT) ? true : false;
1136-
UART_ENTER_CRITICAL_ISR(&uart_selectlock);
1137-
if (p_uart->uart_select_notif_callback) {
1138-
p_uart->uart_select_notif_callback(uart_num, UART_SELECT_READ_NOTIF, &HPTaskAwoken);
1139-
need_yield |= (HPTaskAwoken == pdTRUE);
1140-
}
1141-
UART_EXIT_CRITICAL_ISR(&uart_selectlock);
11421156
}
11431157
p_uart->rx_stash_len = rx_fifo_len;
11441158
//If we fail to push data to ring buffer, we will have to stash the data, and send next time.
@@ -1187,6 +1201,15 @@ static void UART_ISR_ATTR uart_rx_intr_handler_default(void *param)
11871201
p_uart->rx_buffered_len += p_uart->rx_stash_len;
11881202
UART_EXIT_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
11891203
}
1204+
1205+
if (uart_event.type == UART_DATA) {
1206+
UART_ENTER_CRITICAL_ISR(&uart_selectlock);
1207+
if (p_uart->uart_select_notif_callback) {
1208+
p_uart->uart_select_notif_callback(uart_num, UART_SELECT_READ_NOTIF, &HPTaskAwoken);
1209+
need_yield |= (HPTaskAwoken == pdTRUE);
1210+
}
1211+
UART_EXIT_CRITICAL_ISR(&uart_selectlock);
1212+
}
11901213
} else {
11911214
UART_ENTER_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
11921215
uart_hal_disable_intr_mask(&(uart_context[uart_num].hal), UART_INTR_RXFIFO_FULL | UART_INTR_RXFIFO_TOUT);

components/esp_driver_uart/test_apps/uart/main/test_uart.c

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -306,9 +306,7 @@ static void uart_write_task(void *param)
306306
{
307307
uart_port_t uart_num = (uart_port_t)param;
308308
uint8_t *tx_buf = (uint8_t *)malloc(1024);
309-
if (tx_buf == NULL) {
310-
TEST_FAIL_MESSAGE("tx buffer malloc fail");
311-
}
309+
TEST_ASSERT_NOT_NULL(tx_buf);
312310
for (int i = 1; i < 1023; i++) {
313311
tx_buf[i] = (i & 0xff);
314312
}
@@ -330,9 +328,7 @@ TEST_CASE("uart read write test", "[uart]")
330328

331329
uart_port_t uart_num = port_param.port_num;
332330
uint8_t *rd_data = (uint8_t *)malloc(1024);
333-
if (rd_data == NULL) {
334-
TEST_FAIL_MESSAGE("rx buffer malloc fail");
335-
}
331+
TEST_ASSERT_NOT_NULL(rd_data);
336332
uart_config_t uart_config = {
337333
.baud_rate = 2000000,
338334
.data_bits = UART_DATA_8_BITS,
@@ -399,10 +395,9 @@ TEST_CASE("uart tx with ringbuffer test", "[uart]")
399395

400396
uart_port_t uart_num = port_param.port_num;
401397
uint8_t *rd_data = (uint8_t *)malloc(1024);
398+
TEST_ASSERT_NOT_NULL(rd_data);
402399
uint8_t *wr_data = (uint8_t *)malloc(1024);
403-
if (rd_data == NULL || wr_data == NULL) {
404-
TEST_FAIL_MESSAGE("buffer malloc fail");
405-
}
400+
TEST_ASSERT_NOT_NULL(wr_data);
406401
uart_config_t uart_config = {
407402
.baud_rate = 2000000,
408403
.data_bits = UART_DATA_8_BITS,
@@ -536,3 +531,56 @@ TEST_CASE("uart int state restored after flush", "[uart]")
536531
TEST_ESP_OK(uart_driver_delete(uart_num));
537532
free(data);
538533
}
534+
535+
TEST_CASE("uart in one-wire mode", "[uart]")
536+
{
537+
uart_port_param_t port_param = {};
538+
TEST_ASSERT(port_select(&port_param));
539+
port_param.tx_pin_num = port_param.rx_pin_num; // let tx and rx use the same pin
540+
541+
uart_port_t uart_num = port_param.port_num;
542+
uart_config_t uart_config = {
543+
.baud_rate = 115200,
544+
.data_bits = UART_DATA_8_BITS,
545+
.parity = UART_PARITY_DISABLE,
546+
.stop_bits = UART_STOP_BITS_1,
547+
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
548+
.source_clk = port_param.default_src_clk,
549+
};
550+
551+
TEST_ESP_OK(uart_driver_install(uart_num, BUF_SIZE * 2, 0, 20, NULL, 0));
552+
TEST_ESP_OK(uart_param_config(uart_num, &uart_config));
553+
esp_err_t err = uart_set_pin(uart_num, port_param.tx_pin_num, port_param.rx_pin_num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
554+
if (uart_num < SOC_UART_HP_NUM) {
555+
TEST_ESP_OK(err);
556+
#if SOC_UART_LP_NUM > 0
557+
} else {
558+
#if !SOC_LP_GPIO_MATRIX_SUPPORTED
559+
TEST_ESP_ERR(ESP_FAIL, err); // For LP UART port, if no LP GPIO Matrix, unable to be used in one-wire mode
560+
#else
561+
TEST_ESP_OK(err);
562+
#endif
563+
#endif // SOC_UART_LP_NUM > 0
564+
}
565+
566+
// If configured successfully in one-wire mode
567+
if (err == ESP_OK) {
568+
TEST_ESP_OK(uart_wait_tx_done(uart_num, portMAX_DELAY));
569+
vTaskDelay(pdMS_TO_TICKS(20)); // make sure last byte has flushed from TX FIFO
570+
TEST_ESP_OK(uart_flush_input(uart_num));
571+
572+
const char *wr_data = "ECHO!";
573+
const int len = strlen(wr_data);
574+
uint8_t *rd_data = (uint8_t *)calloc(1, 1024);
575+
TEST_ASSERT_NOT_NULL(rd_data);
576+
577+
uart_write_bytes(uart_num, wr_data, len);
578+
int bytes_received = uart_read_bytes(uart_num, rd_data, BUF_SIZE, pdMS_TO_TICKS(20));
579+
TEST_ASSERT_EQUAL(len, bytes_received);
580+
TEST_ASSERT_EQUAL_STRING_LEN(wr_data, rd_data, bytes_received);
581+
582+
free(rd_data);
583+
}
584+
585+
TEST_ESP_OK(uart_driver_delete(uart_num));
586+
}

docs/en/api-reference/peripherals/gpio.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ In addition, if you would like to dump the configurations of all IOs, you can us
8181

8282
::
8383

84-
gpio_dump_all_io_configuration(stdout, SOC_GPIO_VALID_GPIO_MASK);
84+
gpio_dump_io_configuration(stdout, SOC_GPIO_VALID_GPIO_MASK);
8585

8686
If an IO pin is routed to a peripheral signal through the GPIO matrix, the signal ID printed in the dump information is defined in the :component_file:`soc/{IDF_TARGET_PATH_NAME}/include/soc/gpio_sig_map.h` header file. The word ``**RESERVED**`` indicates the IO is occupied by either SPI flash or PSRAM. It is strongly not recommended to reconfigure them for other application purposes.
8787

docs/zh_CN/api-reference/peripherals/gpio.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ GPIO 驱动提供了一个函数 :cpp:func:`gpio_dump_io_configuration` 用来
8181

8282
::
8383

84-
gpio_dump_all_io_configuration(stdout, SOC_GPIO_VALID_GPIO_MASK);
84+
gpio_dump_io_configuration(stdout, SOC_GPIO_VALID_GPIO_MASK);
8585

8686
如果 IO 管脚通过 GPIO 交换矩阵连接到内部外设信号,输出信息打印中的外设信号 ID 定义可以在 :component_file:`soc/{IDF_TARGET_PATH_NAME}/include/soc/gpio_sig_map.h` 头文件中查看。``**RESERVED**`` 字样则表示此 IO 用于连接 SPI flash 或 PSRAM,强烈建议不要重新配置这些管脚用于其他功能。
8787

0 commit comments

Comments
 (0)