Skip to content

feat(uart): add new API for RX internal pull and onewire UART#12744

Draft
SuGlider wants to merge 2 commits into
espressif:masterfrom
SuGlider:feat/uart_pull_internal_resistor
Draft

feat(uart): add new API for RX internal pull and onewire UART#12744
SuGlider wants to merge 2 commits into
espressif:masterfrom
SuGlider:feat/uart_pull_internal_resistor

Conversation

@SuGlider

@SuGlider SuGlider commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Description of Change

This pull request introduces support for UART one-wire mode and internal RX pull configuration, along with related improvements and fixes in the ESP32 hardware serial implementation. The changes add new APIs, update the internal UART state tracking, and enhance pin validation and attachment logic to support these features safely and flexibly.

The most important changes are:

UART one-wire mode support:

  • Added enableOneWireMode() to HardwareSerial, allowing RX and TX to share the same GPIO pin (one-wire UART) with proper validation and error handling. This includes new logic to handle pin attachment/detachment and Peripheral Manager integration for the shared pin. (HardwareSerial.cpp, HardwareSerial.h, esp32-hal-uart.c, esp32-hal-periman.h, esp32-hal-periman.c)

Internal RX pull configuration:

  • Added enableRxInternalPull() to HardwareSerial, allowing users to enable or disable the internal pull resistor on the RX pin, with proper handling depending on UART mode and pin inversion. (HardwareSerial.cpp, HardwareSerial.h, esp32-hal-uart.c)

Peripheral Manager and pin handling enhancements:

  • Introduced ESP32_BUS_TYPE_UART_RX_TX bus type for one-wire mode, and updated Peripheral Manager logic to support shared RX/TX pins. (esp32-hal-periman.h, esp32-hal-periman.c)
  • Improved pin validation and detachment logic to correctly handle one-wire mode scenarios, including new helper functions and refactored detachment callbacks. (esp32-hal-uart.c)

Bug fixes and refactoring:

  • Fixed logic in uart_register() and HardwareSerial::end() to ensure correct registration and cleanup of UART instances, especially with new one-wire and shared pin handling. (HardwareSerial.cpp)

These changes provide more flexible UART configuration options for advanced use cases while maintaining safety and compatibility with existing features.

Test Scenarios

Tested with eht examples and also with uart.ino (validation test) using ESP32, S2, S3, C3 and C6

Related links

Closes #12729
Closes #12734

@SuGlider SuGlider added this to the 3.3.0 milestone Jul 6, 2026
@SuGlider SuGlider self-assigned this Jul 6, 2026
Copilot AI review requested due to automatic review settings July 6, 2026 01:09
@SuGlider SuGlider added Type: Feature request Feature request for Arduino ESP32 Area: UART Related to the UART peripheral or its functionality. labels Jul 6, 2026

@espressif-bot espressif-bot Jul 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge request must include a release note, or it needs to be processed first.

Details

Open

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor
Warnings
⚠️

Some issues found for the commit messages in this PR:

  • the commit message "feat(uart): add new API for controlling RX internal pull resistor and setting onewire UART":
    • summary appears to be too long

Please fix these commit messages - here are some basic tips:

  • follow Conventional Commits style
  • correct format of commit message should be: <type/action>(<scope/component>): <summary>, for example fix(esp32): Fixed startup timeout issue
  • allowed types are: change,ci,docs,feat,fix,refactor,remove,revert,test
  • sufficiently descriptive message summary should be between 10 to 72 characters and start with upper case letter
  • avoid Jira references in commit messages (unavailable/irrelevant for our customers)

TIP: Install pre-commit hooks and run this check when committing (uses the Conventional Precommit Linter).

Messages
📖 This PR seems to be quite large (total lines of code: 1735), you might consider splitting it into smaller PRs

👋 Hello SuGlider, we appreciate your contribution to this project!


📘 Please review the project's Contributions Guide for key guidelines on code, documentation, testing, and more.

🖊️ Please also make sure you have read and signed the Contributor License Agreement for this project.

Click to see more instructions ...


This automated output is generated by the PR linter DangerJS, which checks if your Pull Request meets the project's requirements and helps you fix potential issues.

DangerJS is triggered with each push event to a Pull Request and modify the contents of this comment.

Please consider the following:
- Danger mainly focuses on the PR structure and formatting and can't understand the meaning behind your code or changes.
- Danger is not a substitute for human code reviews; it's still important to request a code review from your colleagues.
- Resolve all warnings (⚠️ ) before requesting a review from human reviewers - they will appreciate it.
- Addressing info messages (📖) is strongly recommended; they're less critical but valuable.
- To manually retry these Danger checks, please navigate to the Actions tab and re-run last Danger workflow.

Review and merge process you can expect ...


We do welcome contributions in the form of bug reports, feature requests and pull requests.

1. An internal issue has been created for the PR, we assign it to the relevant engineer.
2. They review the PR and either approve it or ask you for changes or clarifications.
3. Once the GitHub PR is approved we do the final review, collect approvals from core owners and make sure all the automated tests are passing.
- At this point we may do some adjustments to the proposed change, or extend it by adding tests or documentation.
4. If the change is approved and passes the tests it is merged into the default branch.

Generated by 🚫 dangerJS against 410a9fd

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds user-facing APIs and HAL support for (1) UART one-wire (shared RX/TX GPIO) and (2) configurable internal pull on the RX pin, including updates to Peripheral Manager handling, validation tests, examples, and documentation.

Changes:

  • Added HardwareSerial::enableOneWireMode() and HardwareSerial::enableRxInternalPull() APIs with HAL plumbing and pin validation.
  • Introduced ESP32_BUS_TYPE_UART_RX_TX and updated UART pin attach/detach logic to support shared-pin operation.
  • Expanded UART validation tests plus added new Serial examples and updated Serial API docs.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/validation/uart/uart.ino Extends validation coverage for one-wire mode, RX pull behavior, and periman interactions.
tests/validation/uart/README.md Documents the updated UART validation suite behavior, ordering, and pin usage.
libraries/ESP32/examples/Serial/RxPull_Demo/RxPull_Demo.ino New example demonstrating RX internal pull behavior (floating RX, inversion behavior, optional wired loopback).
libraries/ESP32/examples/Serial/RxPull_Demo/README.md Usage notes and wiring instructions for RX pull demo.
libraries/ESP32/examples/Serial/OneWire_UART_Demo/OneWire_UART_Demo.ino New example demonstrating one-wire UART on a single GPIO (self + optional peer loopback).
libraries/ESP32/examples/Serial/OneWire_UART_Demo/README.md Usage notes, wiring, and electrical cautions for one-wire UART demo.
docs/en/api/serial.rst Documents the new APIs, one-wire constraints, RX pull vs inversion, and periman implications.
cores/esp32/HardwareSerial.h Adds public API declarations for RX pull and one-wire mode.
cores/esp32/HardwareSerial.cpp Implements the new APIs and adjusts end()/registration behavior.
cores/esp32/esp32-hal-uart.h Exposes HAL-level enable functions for RX pull and one-wire mode.
cores/esp32/esp32-hal-uart.c Implements RX pull application, one-wire shared-pin attach/detach, and updated loopback behavior.
cores/esp32/esp32-hal-periman.h Adds ESP32_BUS_TYPE_UART_RX_TX bus type.
cores/esp32/esp32-hal-periman.c Adds name mapping for the new periman bus type.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +419 to +425
if (_uartRxPadInverted(uart)) {
esp_rom_gpio_connect_in_signal(GPIO_FUNC_IN_LOW, UART_PERIPH_SIGNAL(uart_num, SOC_UART_RX_PIN_IDX), false);
} else {
esp_rom_gpio_connect_in_signal(GPIO_FUNC_IN_HIGH, UART_PERIPH_SIGNAL(uart_num, SOC_UART_RX_PIN_IDX), false);
}
gpio_pullup_dis((gpio_num_t)rxPin);
gpio_pulldown_dis((gpio_num_t)rxPin);
Comment on lines +1075 to +1081
bool oneWireRequest = (rxPin >= 0 && txPin >= 0 && rxPin == txPin);
bool sharedAttach = false;
if (oneWireRequest) {
peripheral_bus_type_t pinType = perimanGetPinBusType(rxPin);
sharedAttach = (pinType != ESP32_BUS_TYPE_UART_RX_TX || perimanGetPinBusNum(rxPin) != (int8_t)uart_num);
}

@WebDust21

Copy link
Copy Markdown
Contributor

Is there a specific reason for adding "enableOneWireMode()" instead of just having the Serial functionality automatically assume "OneWireMode" when the RX and TX pins are configured to the same GPIO pin?

@SuGlider SuGlider marked this pull request as draft July 6, 2026 01:20
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Test Results

  148 files    148 suites   40m 28s ⏱️
  346 tests   346 ✅ 0 💤 0 ❌
3 011 runs  3 003 ✅ 8 💤 0 ❌

Results for commit 410a9fd.

@PhiJu5

PhiJu5 commented Jul 6, 2026

Copy link
Copy Markdown

Added all #12744 changes to my installed Arduino ESP32 v3.3.10.
Used ESP32 DevKit v1, only GPIO 18 and 19 are wired together.
Used bellow Test12744.ino, it loops forever.
So OK.

verbose output:
[ 5730][V][esp32-hal-uart.c:1972] uartEnableRxInternalPull(): UART1 RX internal pull enabled
[ 5739][V][esp32-hal-uart.c:1262] uartBegin(): UART1 baud(115200) Mode(0x800001c) rxPin(18) txPin(16)
[ 5748][V][esp32-hal-uart.c:1349] uartBegin(): UART1 not installed. Starting installation
...
[ 5956][V][esp32-hal-uart.c:1972] uartEnableRxInternalPull(): UART2 RX internal pull enabled
[ 5965][V][esp32-hal-uart.c:1262] uartBegin(): UART2 baud(115200) Mode(0x800001c) rxPin(19) txPin(17)
[ 5974][V][esp32-hal-uart.c:1349] uartBegin(): UART2 not installed. Starting installation
...

#include <Arduino.h>

#define SERIAL1_RPIN 18 // Connected to 19
#define SERIAL1_TPIN 16 // Not wired
#define SERIAL2_RPIN 19 // Connected to 18
#define SERIAL2_TPIN 17 // Not wired

HardwareSerial *pSerial1 = &Serial1;
HardwareSerial *pSerial2 = &Serial2;

void set_rx_mode(int serialNb) {
if (serialNb == 1) {
pSerial1->end();
pSerial1->enableRxInternalPull(true);
pSerial1->begin(115200, SERIAL_8N1, SERIAL1_RPIN, SERIAL1_TPIN); // RX, TX
}
if (serialNb == 2) {
pSerial2->end();
pSerial2->enableRxInternalPull(true);
pSerial2->begin(115200, SERIAL_8N1, SERIAL2_RPIN, SERIAL2_TPIN);
}
}

void set_tx_mode(int serialNb) {
if (serialNb == 1) {
pSerial1->end();
pSerial1->enableRxInternalPull(true);
pSerial1->begin(115200, SERIAL_8N1, SERIAL1_TPIN, SERIAL1_RPIN);
}
if (serialNb == 2) {
pSerial2->end();
pSerial2->enableRxInternalPull(true);
pSerial2->begin(115200, SERIAL_8N1, SERIAL2_TPIN, SERIAL2_RPIN);
}
}

void setup() {
delay(5000);
Serial.begin(115200);
Serial.println("setup");
pSerial1->enableRxInternalPull(true);
pSerial1->begin(115200, SERIAL_8N1, SERIAL1_RPIN, SERIAL1_TPIN);
pSerial2->enableRxInternalPull(true);
pSerial2->begin(115200, SERIAL_8N1, SERIAL2_RPIN, SERIAL2_TPIN);
Serial.println("loop");
}

void loop() {
int c;

Serial.println("Serial1:write:1");
set_tx_mode(1);
set_rx_mode(2);
pSerial1->write(0x1);
pSerial1->flush();
while (pSerial2->available() == 0)
;
c = pSerial2->read();
Serial.println("Serial2:read:");
Serial.println(c);

delay(1000);
Serial.println("\nReverse RX/TX");

Serial.println("Serial2:write:2");
set_tx_mode(2);
set_rx_mode(1);
pSerial2->write(0x2);
pSerial2->flush();
while (pSerial1->available() != 1)
;
c = pSerial1->read();
Serial.println("Serial1:read:");
Serial.println(c);

delay(1000);
}

@PhiJu5

PhiJu5 commented Jul 6, 2026

Copy link
Copy Markdown

enableOneWireMode:

Used a device sending continuously data at 125000 baud, 8N1.
With below sketch, when STANDARD is defined, data is read as expected.
When STANDARD is not defined, enableOneWireMode is used, but
nothing is read.
Is this expected behavior ?

#include <Arduino.h>

//#define STANDARD

#define SERIAL2_RPIN 19
#ifdef STANDARD
#define SERIAL2_TPIN 18
#endif

HardwareSerial *pSerial2 = &Serial2;

void setup() {
delay(5000);
Serial.begin(115200);
Serial.println("setup");

#ifdef STANDARD
pSerial2->begin(125000, SERIAL_8N1, SERIAL2_RPIN, SERIAL2_TPIN);
#else
pSerial2->enableOneWireMode(true); // before begin()
pSerial2->begin(125000, SERIAL_8N1, SERIAL2_RPIN, SERIAL2_RPIN);
#endif
}

void loop() {
int c;

while (pSerial2->available() > 0) {
c = pSerial2->read();
Serial.println("Serial2:read:");
Serial.println(c);
}
}

When STANDARD is defined:
rep1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: UART Related to the UART peripheral or its functionality. Type: Feature request Feature request for Arduino ESP32

Projects

Development

Successfully merging this pull request may close these issues.

Serial begin/end are NOT IDEMPOTENT any longer since v3.3.8 UART half-duplex on single GPIO pin hard blocked in v3.3.8

5 participants