Skip to content

Stop UART1 from grabbing the SPI flash pins on ESP32 - #1271

Merged
gskjold merged 1 commit into
mainfrom
fix/uart1-default-flash-pins
Sep 17, 2026
Merged

gskjold merged 1 commit into
mainfrom
fix/uart1-default-flash-pins

Conversation

@gskjold

@gskjold gskjold commented Sep 17, 2026

Copy link
Copy Markdown
Member

Fixes the boot loop reported for WT32-ETH01 in #1270.

What happens

setupHanPort() calls hwSerial->begin(baud, serialConfig, -1, -1, invert) and then moves the UART to the configured pin with uart_set_pin(). But HardwareSerial::begin() substitutes the core's default pins when both rxPin and txPin are negative and the driver is not yet installed, and for UART1 on ESP32 classic those defaults are GPIO 9 and 10 — the SD_DATA2/SD_DATA3 lines of the SPI flash.

Muxing them away from the flash controller stops code fetch, so the device panics inside begin(), before uart_set_pin() is ever reached. It reboots, runs the same setup, and crashes again.

The register dump in the report identifies it precisely:

Register Value Meaning
A12 0x3FF49054 PERIPHS_IO_MUX_SD_DATA2_U, the IO_MUX register for GPIO9
A10, A3, A5 9 GPIO number 9
A11 17 U1RXD_IN_IDX, the UART1 RX input signal
PC contents ffffffff the flash read came back erased

It also explains why the (D) Hardware serial line is missing before some of the crashes: that PSTR format string lives in the flash that just went away.

Scope

Only ESP32 classic, and only when HAN RX is not GPIO16 — GPIO16 selects UART2, whose defaults 16/17 happen to be the right pins. S2, S3 and C3 default UART1 to 18, 15 and 18, none of which are flash pins. Not a v2.6 regression; the same call is in v2.5.7.

The change

Pass the pins to begin() so the core never falls back to its defaults. uartBegin() performs the same uart_set_pin() internally, so the existing call below it is now redundant; I left it rather than unwind the uart_num bookkeeping around it.

That routes an out-of-range pin into the ESP_ERROR_CHECK inside uartBegin(), where it would abort instead of being silently ignored, so the existing invalid-pin guard now also rejects pins the SoC does not have. This is reachable today: UartSelectOptions.svelte offers GPIO 20 and 24 on ESP32, neither of which exists.

Verification

All six device targets build and pio test -e native passes 24/24. I have no WT32-ETH01, so confirmation on hardware is still needed — the expected result is that setupHanPort completes and the boot loop stops.

Noticed while here, not changed

  • PassiveMeterCommunicator.cpp:630if(rxpin == 3 || rxpin == 113) hwSerial = &Serial; is dead on ESP32, because the #if defined(ESP32) block just below unconditionally overwrites hwSerial with &Serial1. HAN RX on GPIO3 therefore puts UART1 on the console pin instead of using UART0.
  • The UI offers GPIO 6–11 on ESP32. Picking 9 or 10 by hand reproduces this same panic, and 6/7/8/11 are the flash lines proper. Worth excluding in UartSelectOptions.svelte.

🤖 Generated with Claude Code

HardwareSerial::begin() substitutes the core's default pins when both rxPin
and txPin are negative and the driver is not installed yet. On ESP32 classic
those defaults are GPIO 9 and 10, which are the SD_DATA2/SD_DATA3 lines of the
SPI flash. Attaching UART1 to them stops code fetch from flash, so the device
panics with IllegalInstruction on a PC that reads back as 0xFFFFFFFF, reboots,
and does the same on the next boot.

The uart_set_pin() call right after begin() was meant to place the UART on the
configured pin, but the crash happens inside begin() before it is reached.

Only ESP32 classic is affected, and only when HAN RX is not GPIO16: GPIO16
selects UART2, whose defaults 16/17 are correct, while S2, S3 and C3 default
UART1 to pins that are not connected to the flash.

Passing the pins to begin() means an out-of-range pin now reaches the
ESP_ERROR_CHECK inside uartBegin() instead of being ignored, so reject pins the
SoC does not have along with the existing invalid-pin cases. The UI offers
GPIO 20 and 24 on ESP32, neither of which exists.

Reported for WT32-ETH01 in #1270.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🔧 PR Build Artifacts

Version: f5482fa

All environments built successfully. Download the zip files:

Artifacts expire after 7 days. View workflow run

@gskjold
gskjold merged commit fbf1221 into main Sep 17, 2026
8 checks passed
@gskjold
gskjold deleted the fix/uart1-default-flash-pins branch September 17, 2026 07:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant