Board
ESP32 Dev module
Device Description
Using a ESP32 development board, containing a ESP-WROOM-32
Hardware Configuration
Hardwired GPIO_12 to GPIO_25 and GPIO_15 to GPIO_26
Version
v3.3.6
Type
Bug
IDE Name
VSCOde
Operating System
Windows 11
Flash frequency
40MHz
PSRAM enabled
no
Upload speed
460800
Description
After PR #12201 (commit 155a986), UART communication is broken on ESP32-WROOM-32.
Working version: b929eb4
Broken version: 155a986
Issue: When using Serial1 and Serial2 with custom GPIO pins, no data is received.
Reproduction:
- Use the test case in this issue (two UARTs with cross-connected TX/RX)
- Expected: Messages pass through, success rate ~100%
- Actual: No data received, success rate 0%
The problem appears to be that uart_set_pin() is called separately for each pin
(RX, TX, CTS, RTS) instead of together as in the old _uartInternalSetPin() function.
Minimal test case: [Attach your test_with_diagnostics.cpp or main.cpp]
Sketch
++
// Diagnostic version of the UART test to help identify the root cause
// Replace src/main.cpp with this to get more detailed information
#include <Arduino.h>
#include <esp32-hal-uart.h>
#include "HWCDC.h"
#define SERIAL1_TX 12
#define SERIAL1_RX 15
#define SERIAL2_TX 25
#define SERIAL2_RX 26
#define TEST_INTERVAL 5000 // ms between tests
#define BAUD_RATE 115200
#define TEST_TIMEOUT 1000 // ms to wait for response
unsigned long serial1ToSerial2Success = 0;
unsigned long serial1ToSerial2Fail = 0;
unsigned long serial2ToSerial1Success = 0;
unsigned long serial2ToSerial1Fail = 0;
unsigned long lastTestTime = 0;
bool testSerial1ToSerial2 = true;
void printUARTStatus(const char* name, HardwareSerial &serial) {
Serial.printf("\n=== %s Status ===\n", name);
Serial.printf(" Bytes available to read: %d\n", serial.available());
}
void printStats() {
Serial.println("\n========== Serial Test Statistics ==========");
Serial.printf("Serial1 -> Serial2: %lu passed, %lu failed (%.1f%% success)\n",
serial1ToSerial2Success,
serial1ToSerial2Fail,
(serial1ToSerial2Success + serial1ToSerial2Fail) > 0
? (100.0 * serial1ToSerial2Success / (serial1ToSerial2Success + serial1ToSerial2Fail))
: 0.0);
Serial.printf("Serial2 -> Serial1: %lu passed, %lu failed (%.1f%% success)\n",
serial2ToSerial1Success,
serial2ToSerial1Fail,
(serial2ToSerial1Success + serial2ToSerial1Fail) > 0
? (100.0 * serial2ToSerial1Success / (serial2ToSerial1Success + serial2ToSerial1Fail))
: 0.0);
Serial.println("============================================\n");
}
bool testSerialPair(HardwareSerial &sender, HardwareSerial &receiver, const char* direction) {
static unsigned long testCounter = 0;
testCounter++;
String testMessage = String("TEST:") + testCounter + ":" + millis();
Serial.printf("[%s] Sending: %s\n", direction, testMessage.c_str());
// Clear receiver buffer
while (receiver.available()) {
receiver.read();
}
// Send test message
sender.println(testMessage);
sender.flush();
// Debug: Check UART status after sending
Serial.printf("[%s] After flush, checking receiver status...\n", direction);
if (strcmp(direction, "S1->S2") == 0) {
printUARTStatus("Serial2 (receiver)", receiver);
} else {
printUARTStatus("Serial1 (receiver)", receiver);
}
// Wait for response
unsigned long startTime = millis();
String received = "";
bool success = false;
int bytesRead = 0;
while (millis() - startTime < TEST_TIMEOUT) {
if (receiver.available()) {
char c = receiver.read();
bytesRead++;
if (c == '\n') {
break;
}
if (c != '\r') {
received += c;
}
}
delay(1);
}
// Verify received message
Serial.printf("[%s] Read %d bytes, received: '%s'\n", direction, bytesRead, received.c_str());
if (received == testMessage) {
Serial.printf("[%s] SUCCESS - Message verified\n", direction);
success = true;
} else {
Serial.printf("[%s] FAILED - Expected: %s, Got: %s\n",
direction, testMessage.c_str(),
received.length() > 0 ? received.c_str() : "(nothing)");
success = false;
}
return success;
}
void setup() {
delay(2000);
Serial.begin(115200);
Serial.println();
Serial.println();
Serial.println("========================================");
Serial.println(" ESP32 Serial Port Communication Test");
Serial.println(" (Diagnostic Version)");
Serial.println("========================================");
Serial.println();
Serial.println("Initializing Serial1...");
Serial1.begin(BAUD_RATE, SERIAL_8N1, SERIAL1_RX, SERIAL1_TX);
Serial.printf("Serial1 initialized: TX=GPIO%d, RX=GPIO%d, Baud=%d\n", SERIAL1_TX, SERIAL1_RX, BAUD_RATE);
printUARTStatus("Serial1", Serial1);
Serial.println("\nInitializing Serial2...");
Serial2.begin(BAUD_RATE, SERIAL_8N1, SERIAL2_RX, SERIAL2_TX);
Serial.printf("Serial2 initialized: TX=GPIO%d, RX=GPIO%d, Baud=%d\n", SERIAL2_TX, SERIAL2_RX, BAUD_RATE);
printUARTStatus("Serial2", Serial2);
Serial.println();
Serial.println("Connect the following pins together:");
Serial.printf(" Serial1 TX (GPIO%d) -> Serial2 RX (GPIO%d)\n", SERIAL1_TX, SERIAL2_RX);
Serial.printf(" Serial1 RX (GPIO%d) -> Serial2 TX (GPIO%d)\n", SERIAL1_RX, SERIAL2_TX);
Serial.println();
Serial.println("Starting tests in 3 seconds...");
Serial.println();
delay(3000);
}
void loop() {
unsigned long currentTime = millis();
if (currentTime - lastTestTime >= TEST_INTERVAL) {
lastTestTime = currentTime;
if (testSerial1ToSerial2) {
Serial.println("\n--- Testing Serial1 -> Serial2 ---");
bool success = testSerialPair(Serial1, Serial2, "S1->S2");
if (success) {
serial1ToSerial2Success++;
} else {
serial1ToSerial2Fail++;
}
} else {
Serial.println("\n--- Testing Serial2 -> Serial1 ---");
bool success = testSerialPair(Serial2, Serial1, "S2->S1");
if (success) {
serial2ToSerial1Success++;
} else {
serial2ToSerial1Fail++;
}
}
testSerial1ToSerial2 = !testSerial1ToSerial2;
if ((serial1ToSerial2Success + serial1ToSerial2Fail +
serial2ToSerial1Success + serial2ToSerial1Fail) % 2 == 0) {
printStats();
}
}
}
Debug Message
When running with b929eb4:
--- Terminal on COM3 | 115200 8-N-1
--- Available filters and text transformations: colorize, debug, default, direct, esp32_exception_decoder, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at https://bit.ly/pio-monitor-filters
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H
ets Jun 8 2016 00:22:57
rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0030,len:4744
load:0x40078000,len:15712
load:0x40080400,len:3152
entry 0x4008059c
========================================
ESP32 Serial Port Communication Test
(Diagnostic Version)
========================================
Initializing Serial1...
Serial1 initialized: TX=GPIO12, RX=GPIO15, Baud=115200
=== Serial1 Status ===
Bytes available to read: 0
Initializing Serial2...
Serial2 initialized: TX=GPIO25, RX=GPIO26, Baud=115200
=== Serial2 Status ===
Bytes available to read: 0
Connect the following pins together:
Serial1 TX (GPIO12) -> Serial2 RX (GPIO26)
Serial1 RX (GPIO15) -> Serial2 TX (GPIO25)
Starting tests in 3 seconds...
--- Testing Serial1 -> Serial2 ---
[S1->S2] Sending: TEST:1:5084
[S1->S2] After flush, checking receiver status...
=== Serial2 (receiver) Status ===
Bytes available to read: 13
[S1->S2] Read 13 bytes, received: 'TEST:1:5084'
[S1->S2] SUCCESS - Message verified
--- Testing Serial2 -> Serial1 ---
[S2->S1] Sending: TEST:2:10084
[S2->S1] After flush, checking receiver status...
=== Serial1 (receiver) Status ===
Bytes available to read: 14
[S2->S1] Read 14 bytes, received: 'TEST:2:10084'
[S2->S1] SUCCESS - Message verified
========== Serial Test Statistics ==========
Serial1 -> Serial2: 1 passed, 0 failed (100.0% success)
Serial2 -> Serial1: 1 passed, 0 failed (100.0% success)
============================================
When running with 155a986
--- Terminal on COM3 | 115200 8-N-1
--- Available filters and text transformations: colorize, debug, default, direct, esp32_exception_decoder, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at https://bit.ly/pio-monitor-filters
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H
ets Jun 8 2016 00:22:57
rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0030,len:4744
load:0x40078000,len:15712
load:0x40080400,len:3152
entry 0x4008059c
========================================
ESP32 Serial Port Communication Test
(Diagnostic Version)
========================================
Initializing Serial1...
Serial1 initialized: TX=GPIO12, RX=GPIO15, Baud=115200
=== Serial1 Status ===
Bytes available to read: 0
Initializing Serial2...
Serial2 initialized: TX=GPIO25, RX=GPIO26, Baud=115200
=== Serial2 Status ===
Bytes available to read: 0
Connect the following pins together:
Serial1 TX (GPIO12) -> Serial2 RX (GPIO26)
Serial1 RX (GPIO15) -> Serial2 TX (GPIO25)
Starting tests in 3 seconds...
--- Testing Serial1 -> Serial2 ---
[S1->S2] Sending: TEST:1:5086
[S1->S2] After flush, checking receiver status...
=== Serial2 (receiver) Status ===
Bytes available to read: 0
[S1->S2] Read 0 bytes, received: ''
[S1->S2] FAILED - Expected: TEST:1:5086, Got: (nothing)
--- Testing Serial2 -> Serial1 ---
[S2->S1] Sending: TEST:2:10085
[S2->S1] After flush, checking receiver status...
=== Serial1 (receiver) Status ===
Bytes available to read: 0
[S2->S1] Read 0 bytes, received: ''
[S2->S1] FAILED - Expected: TEST:2:10085, Got: (nothing)
========== Serial Test Statistics ==========
Serial1 -> Serial2: 0 passed, 1 failed (0.0% success)
Serial2 -> Serial1: 0 passed, 1 failed (0.0% success)
============================================
Other Steps to Reproduce
I let Claude Code analyze the problem and it came to the following conclusion:
# UART Communication Issue Analysis
## Problem Summary
- **Working Version**: `b929eb467dd88089d51638a7583c177655ec1f83`
- **Broken Version**: `155a9860c337b37ed18b6033957354617d32fd51`
- **Issue**: No data received on either Serial port after switching versions
- **Root Cause**: Commit removed custom `_uartInternalSetPin()` function and replaced it with IDF's `uart_set_pin()`
## The Critical Change
The commit "fix(uart): fixes setPins() when changing RTS and CTS (#12201)" made significant changes to `cores/esp32/esp32-hal-uart.c`:
**Removed (~147 lines):**
- `_uartInternalSetPin()` - Custom Arduino layer pin configuration function
- `_uartTrySetIomuxPin()` - Helper for IOMUX vs GPIO Matrix selection
**Replaced with:**
- Direct calls to IDF's `uart_set_pin()` function
## Key Differences in the Removed Code
The custom `_uartInternalSetPin()` included critical features:
1. **GPIO Sleep Configuration**
```c
#if CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND || CONFIG_PM_SLP_DISABLE_GPIO
gpio_sleep_sel_dis(tx_io_num); // Prevent TX isolation during sleep
gpio_sleep_sel_dis(rx_io_num); // Prevent RX isolation during sleep
#endif
` ``
2. **IOMUX vs GPIO Matrix Routing Logic**
- Attempted to use IOMUX for direct pad connections (faster)
- Fell back to GPIO Matrix if IOMUX not available
- Custom handling for when TX and RX share the same pin
3. **Conditional GPIO Configuration**
- Different paths for HP UART (Normal) vs LP UART (Low Power)
- Version-specific handling for ESP-IDF 5.4+ and 5.5+
I have checked existing issues, online documentation and the Troubleshooting Guide
Board
ESP32 Dev module
Device Description
Using a ESP32 development board, containing a ESP-WROOM-32
Hardware Configuration
Hardwired GPIO_12 to GPIO_25 and GPIO_15 to GPIO_26
Version
v3.3.6
Type
Bug
IDE Name
VSCOde
Operating System
Windows 11
Flash frequency
40MHz
PSRAM enabled
no
Upload speed
460800
Description
After PR #12201 (commit 155a986), UART communication is broken on ESP32-WROOM-32.
Working version: b929eb4
Broken version: 155a986
Issue: When using Serial1 and Serial2 with custom GPIO pins, no data is received.
Reproduction:
The problem appears to be that uart_set_pin() is called separately for each pin
(RX, TX, CTS, RTS) instead of together as in the old _uartInternalSetPin() function.
Minimal test case: [Attach your test_with_diagnostics.cpp or main.cpp]
Sketch
Debug Message
When running with b929eb4:
When running with 155a986
Other Steps to Reproduce
I let Claude Code analyze the problem and it came to the following conclusion:
I have checked existing issues, online documentation and the Troubleshooting Guide