fix(esp32): release UART0 console when the template uses its pins (#25047)

On ESP32-S3, C3 and C6 the command console falls back to UART0 when no
USB host is detected at boot. Those SoCs expose UART0 on ordinary GPIOs
(S3 43/44, C3/C6 20/21), so a template can assign them to another
function. UART0 then keeps driving those pads and silently corrupts
whatever the template put there.

Found on a KinCony AG8, where GPIO43 and GPIO44 are the MOSI and MISO of
the W5500. The ethernet chip is detected, since esp_eth_driver_install
reads its version register over SPI and succeeds, but the link never
comes up. Plugging a USB cable moves the console to CDC and the link
closes in 2.1 seconds, which isolates the console as the only variable.

GpioInit now releases the console with Serial.end() in the window after
SetPin has built the pin map and before SPI is configured, instead of
letting UART0 keep pads the template already owns. SerialLog 0 is not a
workaround: it only silences logging without freeing the pins, and
Serial.begin runs before the settings are loaded.
This commit is contained in:
Gustavo Cateim authored and GitHub committed 2026-09-21 18:11:07 +02:00
1 parent 8a7e815f6d
commit c34a8ce467
1 file changed
+19
@@ -2206,6 +2206,25 @@ void GpioInit(void)
SetSerialSwap();
#endif
#if defined(ESP32) && defined(USE_USB_CDC_CONSOLE)
// The command console falls back to UART0 when no USB host is detected at boot
// (see tasmota.ino, "Fall back to serial port"). On SoCs whose default UART0 pins
// are ordinary GPIOs (ESP32-S3 43/44, C3/C6 20/21) a template may assign them to
// another function. UART0 keeps driving those pads, which silently corrupts that
// function, so release the console rather than the pins.
if (tasconsole_serial &&
(TasmotaGlobal.gpio_pin[(uint32_t)SOC_TX0] || TasmotaGlobal.gpio_pin[(uint32_t)SOC_RX0])) {
AddLog(LOG_LEVEL_INFO, PSTR("CMD: Serial console released, GPIO%d/GPIO%d used by template"),
(int)SOC_TX0, (int)SOC_RX0);
TasmotaGlobal.seriallog_level = 0; // Stop logging to a console that is about to go away
Settings->seriallog_level = 0;
Serial.flush();
Serial.end(); // Detaches the pads so the template function owns them
TasConsole = Serial; // Refresh the copy, it must not point at a released UART
tasconsole_serial = false;
}
#endif // ESP32 && USE_USB_CDC_CONSOLE
uint32_t sspi_mosi = (PinUsed(GPIO_SSPI_SCLK) && PinUsed(GPIO_SSPI_MOSI)) ? SPI_MOSI : SPI_NONE;
uint32_t sspi_miso = (PinUsed(GPIO_SSPI_SCLK) && PinUsed(GPIO_SSPI_MISO)) ? SPI_MISO : SPI_NONE;
TasmotaGlobal.soft_spi_enabled = sspi_mosi + sspi_miso;