Skip to content

UART communication broken on ESP32-WROOM after commit 155a9860c (uart_set_pin refactor) - (continued #12277) #12386

@Bascy

Description

@Bascy

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:

  1. Use the test case in this issue (two UARTs with cross-connected TX/RX)
  2. Expected: Messages pass through, success rate ~100%
  3. 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

  • I confirm I have checked existing issues, online documentation and Troubleshooting guide.

Metadata

Metadata

Assignees

Labels

Area: UARTRelated to the UART peripheral or its functionality.Resolution: Unable to reproduceWith given information issue is unable to reproduce

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions