Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6ebe14c
fix(uart): terminates uart driver whenever rx and tx are detached
SuGlider Nov 30, 2025
5f25e44
fix(uart): detaching pin check points
SuGlider Nov 30, 2025
85d92e9
fix(uart): extended lod messages
SuGlider Nov 30, 2025
f41795c
fix(uart): fixes setPins to keep driver working
SuGlider Dec 1, 2025
febad6d
fix(uart): peripheral manager CI test adjusting
SuGlider Dec 2, 2025
51a912c
Merge branch 'master' into fix/uart_dettach_end
SuGlider Dec 2, 2025
c329742
fix(uart): Refactor UART test to detach only one pin
SuGlider Dec 2, 2025
e681a94
fix(uart_test): do not detach both UART pins
SuGlider Dec 2, 2025
3663af9
fix(uart_test): formatting
SuGlider Dec 2, 2025
520cd35
fix(uart_doc): Document RX and TX pin detachment behavior
SuGlider Dec 2, 2025
9513917
Merge branch 'master' into fix/uart_dettach_end
SuGlider Dec 2, 2025
6f72075
fix(uart): code formatting
SuGlider Dec 2, 2025
7d3c421
fix(uart): code formatting
SuGlider Dec 2, 2025
8d2557a
fix(uart): code formatting
SuGlider Dec 2, 2025
6f614a0
fix(uart): code formatting
SuGlider Dec 2, 2025
66c43f7
feat(uart): C wrapper to end related Serial object
SuGlider Dec 3, 2025
0a87a50
fix(uart): fixes bad case value
SuGlider Dec 3, 2025
f2a23cc
fix(uart): C wrapper to end related Serial object
SuGlider Dec 3, 2025
2024abe
fix(docs): adding comma
SuGlider Dec 3, 2025
20eb9be
Merge branch 'master' into fix/uart_dettach_end
SuGlider Dec 3, 2025
1e90775
ci(pre-commit): Apply automatic fixes
pre-commit-ci-lite[bot] Dec 3, 2025
fec4a82
fix(uart_ci): typo in UART driver comment
SuGlider Dec 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions cores/esp32/HardwareSerial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,33 @@ HardwareSerial Serial5(5);
extern void HWCDCSerialEvent(void) __attribute__((weak));
#endif

// C-callable helper used by HAL when pins are detached and the high-level
// HardwareSerial instance must be finalized.
extern "C" void hal_uart_notify_pins_detached(int uart_num) {
log_d("hal_uart_notify_pins_detached: Notifying HardwareSerial for UART%d", uart_num);
switch (uart_num) {
case 0: Serial0.end(); break;
#if SOC_UART_NUM > 1
case 1: Serial1.end(); break;
#endif
#if SOC_UART_NUM > 2
case 2: Serial2.end(); break;
#endif
#if SOC_UART_NUM > 3
case 3: Serial3.end(); break;
#endif
#if SOC_UART_NUM > 4
case 4: Serial4.end(); break;
#endif
#if SOC_UART_NUM > 5
case 5: Serial5.end(); break;
#endif
default:
log_e("hal_uart_notify_pins_detached: UART%d not handled!", uart_num);
break;
}
}

#if USB_SERIAL_IS_DEFINED == 1 // Native USB CDC Event
// Used by Hardware Serial for USB CDC events
extern void USBSerialEvent(void) __attribute__((weak));
Expand Down Expand Up @@ -483,9 +510,6 @@ void HardwareSerial::end() {
// including any tasks or debug message channel (log_x()) - but not for IDF log messages!
_onReceiveCB = NULL;
_onReceiveErrorCB = NULL;
if (uartGetDebug() == _uart_nr) {
uartSetDebug(0);
}
_rxFIFOFull = 0;
uartEnd(_uart_nr); // fully detach all pins and delete the UART driver
_destroyEventTask(); // when IDF uart driver is deleted, _eventTask must finish too
Expand Down
2 changes: 1 addition & 1 deletion cores/esp32/chip-debug-report.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ static void printPerimanInfo(void) {

void printBeforeSetupInfo(void) {
#if ARDUINO_USB_CDC_ON_BOOT
Serial.begin(0);
Serial.begin();
Serial.setDebugOutput(true);
uint8_t t = 0;
while (!Serial && (t++ < 200)) {
Expand Down
77 changes: 73 additions & 4 deletions cores/esp32/esp32-hal-uart.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
static int s_uart_debug_nr = 0; // UART number for debug output
#define REF_TICK_BAUDRATE_LIMIT 250000 // this is maximum UART badrate using REF_TICK as clock

/* C prototype for the notifier implemented in HardwareSerial.cpp */
extern void hal_uart_notify_pins_detached(int uart_num);

struct uart_struct_t {

#if !CONFIG_DISABLE_HAL_LOCKS
Expand Down Expand Up @@ -282,29 +285,67 @@ static bool _uartDetachPins(uint8_t uart_num, int8_t rxPin, int8_t txPin, int8_t
// Peripheral Manager detach callback for each specific UART PIN
static bool _uartDetachBus_RX(void *busptr) {
// sanity check - it should never happen
assert(busptr && "_uartDetachBus_RX bus NULL pointer.");
if (busptr == NULL) {
log_e("_uartDetachBus_RX: busptr is NULL");
return false;
}
uart_t *bus = (uart_t *)busptr;
if (bus->_rxPin < 0) {
log_d("_uartDetachBus_RX: RX pin already detached for UART%d", bus->num);
return true;
}
if (bus->_txPin < 0) { // both rx and tx pins are detached, terminate the uart driver
log_d("_uartDetachBus_RX: both RX and TX pins detached for UART%d, terminating driver", bus->num);
hal_uart_notify_pins_detached(bus->num);
return true;
}
return _uartDetachPins(bus->num, bus->_rxPin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
}

static bool _uartDetachBus_TX(void *busptr) {
// sanity check - it should never happen
assert(busptr && "_uartDetachBus_TX bus NULL pointer.");
if (busptr == NULL) {
log_e("_uartDetachBus_TX: busptr is NULL");
return false;
}
uart_t *bus = (uart_t *)busptr;
if (bus->_txPin < 0) {
log_d("_uartDetachBus_TX: TX pin already detached for UART%d", bus->num);
return true;
}
if (bus->_rxPin < 0) { // both rx and tx pins are detached, terminate the uart driver
log_d("_uartDetachBus_TX: both RX and TX pins detached for UART%d, terminating driver", bus->num);
hal_uart_notify_pins_detached(bus->num);
return true;
}
return _uartDetachPins(bus->num, UART_PIN_NO_CHANGE, bus->_txPin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
}

static bool _uartDetachBus_CTS(void *busptr) {
// sanity check - it should never happen
assert(busptr && "_uartDetachBus_CTS bus NULL pointer.");
if (busptr == NULL) {
log_e("_uartDetachBus_CTS: busptr is NULL");
return false;
}
uart_t *bus = (uart_t *)busptr;
if (bus->_ctsPin < 0) {
log_d("_uartDetachBus_CTS: CTS pin already detached for UART%d", bus->num);
return true;
}
return _uartDetachPins(bus->num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, bus->_ctsPin, UART_PIN_NO_CHANGE);
}

static bool _uartDetachBus_RTS(void *busptr) {
// sanity check - it should never happen
assert(busptr && "_uartDetachBus_RTS bus NULL pointer.");
if (busptr == NULL) {
log_e("_uartDetachBus_RTS: busptr is NULL");
return false;
}
uart_t *bus = (uart_t *)busptr;
if (bus->_rtsPin < 0) {
log_d("_uartDetachBus_RTS: RTS pin already detached for UART%d", bus->num);
return true;
}
return _uartDetachPins(bus->num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, bus->_rtsPin);
}

Expand Down Expand Up @@ -629,6 +670,16 @@ bool uartSetPins(uint8_t uart_num, int8_t rxPin, int8_t txPin, int8_t ctsPin, in
//log_v("setting UART%d pins: prev->new RX(%d->%d) TX(%d->%d) CTS(%d->%d) RTS(%d->%d)", uart_num,
// uart->_rxPin, rxPin, uart->_txPin, txPin, uart->_ctsPin, ctsPin, uart->_rtsPin, rtsPin); vTaskDelay(10);

// mute bus detaching callbacks to avoid terminating the UART driver when both RX and TX pins are detached
peripheral_bus_deinit_cb_t rxDeinit = perimanGetBusDeinit(ESP32_BUS_TYPE_UART_RX);
peripheral_bus_deinit_cb_t txDeinit = perimanGetBusDeinit(ESP32_BUS_TYPE_UART_TX);
peripheral_bus_deinit_cb_t ctsDeinit = perimanGetBusDeinit(ESP32_BUS_TYPE_UART_CTS);
peripheral_bus_deinit_cb_t rtsDeinit = perimanGetBusDeinit(ESP32_BUS_TYPE_UART_RTS);
perimanClearBusDeinit(ESP32_BUS_TYPE_UART_RX);
perimanClearBusDeinit(ESP32_BUS_TYPE_UART_TX);
perimanClearBusDeinit(ESP32_BUS_TYPE_UART_CTS);
perimanClearBusDeinit(ESP32_BUS_TYPE_UART_RTS);

// First step: detaches all previous UART pins
bool rxPinChanged = rxPin >= 0 && rxPin != uart->_rxPin;
if (rxPinChanged) {
Expand Down Expand Up @@ -660,6 +711,21 @@ bool uartSetPins(uint8_t uart_num, int8_t rxPin, int8_t txPin, int8_t ctsPin, in
if (rtsPinChanged) {
retCode &= _uartAttachPins(uart->num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, rtsPin);
}

// restore bus detaching callbacks
if (rxDeinit != NULL) {
perimanSetBusDeinit(ESP32_BUS_TYPE_UART_RX, rxDeinit);
}
if (txDeinit != NULL) {
perimanSetBusDeinit(ESP32_BUS_TYPE_UART_TX, txDeinit);
}
if (ctsDeinit != NULL) {
perimanSetBusDeinit(ESP32_BUS_TYPE_UART_CTS, ctsDeinit);
}
if (rtsDeinit != NULL) {
perimanSetBusDeinit(ESP32_BUS_TYPE_UART_RTS, rtsDeinit);
}

UART_MUTEX_UNLOCK();

if (!retCode) {
Expand Down Expand Up @@ -986,6 +1052,9 @@ void uartEnd(uint8_t uart_num) {
if (uart_is_driver_installed(uart_num)) {
uart_driver_delete(uart_num);
}
if (uartGetDebug() == uart_num) {
uartSetDebug(0);
}
UART_MUTEX_UNLOCK();
}

Expand Down
4 changes: 4 additions & 0 deletions docs/en/api/serial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ with additional features for advanced use cases.
* **Event callbacks**: Receive and error event callbacks
* **Configurable buffers**: Adjustable RX and TX buffer sizes

.. note::
In case that both pins RX and TX are detached from UART, the driver will be stopped.
Detaching may occur when, for instance, starting another peripheral using RX and TX pins, such as Wire.begin(RX0, TX0).

UART Availability
-----------------

Expand Down
16 changes: 11 additions & 5 deletions tests/validation/uart/uart.ino
Original file line number Diff line number Diff line change
Expand Up @@ -377,19 +377,23 @@ void change_pins_test(void) {
UARTTestConfig &config = *uart_test_configs[0];
// pinMode will force enabling the internal pullup resistor (IDF 5.3.2 Change)
pinMode(NEW_RX1, INPUT_PULLUP);
config.serial.setPins(NEW_RX1, NEW_TX1);
// Detaching both pins will result in stoping the UART driver
// Only detach one of the pins
config.serial.setPins(NEW_RX1, /*NEW_TX1*/ -1);
TEST_ASSERT_EQUAL(NEW_RX1, uart_get_RxPin(config.uart_num));
TEST_ASSERT_EQUAL(NEW_TX1, uart_get_TxPin(config.uart_num));
//TEST_ASSERT_EQUAL(NEW_TX1, uart_get_TxPin(config.uart_num));

uart_internal_loopback(config.uart_num, NEW_RX1);
config.transmit_and_check_msg("using new pins");
} else {
for (int i = 0; i < TEST_UART_NUM; i++) {
UARTTestConfig &config = *uart_test_configs[i];
UARTTestConfig &next_uart = *uart_test_configs[(i + 1) % TEST_UART_NUM];
config.serial.setPins(next_uart.default_rx_pin, next_uart.default_tx_pin);
// Detaching both pins will result in stoping the UART driver
// Only detach one of the pins
config.serial.setPins(next_uart.default_rx_pin, /*next_uart.default_tx_pin*/ -1);
TEST_ASSERT_EQUAL(uart_get_RxPin(config.uart_num), next_uart.default_rx_pin);
TEST_ASSERT_EQUAL(uart_get_TxPin(config.uart_num), next_uart.default_tx_pin);
//TEST_ASSERT_EQUAL(uart_get_TxPin(config.uart_num), next_uart.default_tx_pin);

uart_internal_loopback(config.uart_num, next_uart.default_rx_pin);
config.transmit_and_check_msg("using new pins");
Expand Down Expand Up @@ -450,7 +454,9 @@ void periman_test(void) {

for (auto *ref : uart_test_configs) {
UARTTestConfig &config = *ref;
Wire.begin(config.default_rx_pin, config.default_tx_pin);
// Detaching both pins will result in stoping the UART driver
// Only detach one of the pins
Wire.begin(config.default_rx_pin, /*config.default_tx_pin*/ -1);
config.recv_msg = "";

log_d("Trying to send message using UART%d with I2C enabled", config.uart_num);
Expand Down
Loading