auto-enable esp32s3 USB CDC companion in CMakeLists; board.overlay first in overlay order; esp32s3 download-mode reboot

This commit is contained in:
liquidraver
2026-08-28 10:32:49 +02:00
parent 7b8b66cb4c
commit 5372f1d8a2
10 changed files with 182 additions and 36 deletions
+1 -1
View File
@@ -27,7 +27,7 @@ All commands are sent over USB serial (CDC-ACM). Commands sent remotely over the
| `ver` | Firmware version and build date |
| `board` | Board manufacturer name |
| `reboot` | Reboot immediately |
| `start dfu` | Reboot into UF2 bootloader for drag-and-drop firmware update |
| `start dfu` | nRF52: reboot into the UF2 bootloader for drag-and-drop update. ESP32-S3: reboot into the ROM download mode so esptool can reach the chip |
| `start ota` | ESP32: start WiFi AP + HTTP OTA server. nRF52: reboot into BLE OTA DFU mode |
| `stop ota` | Stop WiFi OTA server (ESP32 only) |
| `clkreboot` | Set clock to a fixed reference time (15 May 2024 8:50pm UTC) then reboot |
+95 -22
View File
@@ -271,12 +271,21 @@ function(zephcore_auto_pair_overlay CONF_FILE)
endif()
string(REPLACE ".conf" ".overlay" OVERLAY_FILE "${CONF_FILE}")
if(EXISTS "${OVERLAY_FILE}")
# Append once, keeping the FIRST position. The user-extras pairing loop
# below walks the whole combined EXTRA_CONF_FILE, so it re-pairs every
# conf the auto chain already paired -- board.overlay used to land in
# the list three times that way. Position is precedence in devicetree,
# so silently re-appending an overlay also moved it later and let it
# override things it was never meant to.
if(EXTRA_DTC_OVERLAY_FILE)
set(EXTRA_DTC_OVERLAY_FILE "${EXTRA_DTC_OVERLAY_FILE};${OVERLAY_FILE}" PARENT_SCOPE)
if(NOT "${OVERLAY_FILE}" IN_LIST EXTRA_DTC_OVERLAY_FILE)
set(EXTRA_DTC_OVERLAY_FILE "${EXTRA_DTC_OVERLAY_FILE};${OVERLAY_FILE}" PARENT_SCOPE)
message(STATUS " [auto-pair] ${CONF_FILE} → ${OVERLAY_FILE}")
endif()
else()
set(EXTRA_DTC_OVERLAY_FILE "${OVERLAY_FILE}" PARENT_SCOPE)
message(STATUS " [auto-pair] ${CONF_FILE} → ${OVERLAY_FILE}")
endif()
message(STATUS " [auto-pair] ${CONF_FILE} → ${OVERLAY_FILE}")
endif()
endfunction()
@@ -364,6 +373,45 @@ if(NOT ZEPHCORE_BOARD_CONF AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOAR
set(ZEPHCORE_BOARD_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_BASE}/board.conf")
endif()
# ========== DTC Overlay Precedence ==========
# board.overlay goes in FIRST, so everything applied after it can override it.
#
# Devicetree is last-writer-wins per property, and this list is that order. The
# board overlay is the board's *baseline*; the overlays paired with the confs
# that follow (repeater/pm_esp32/esp32s3_usb/no_display/...) are deliberate
# per-role deviations from it, and the conf chain already documents that same
# precedence ("user extras last so they can override"). Until 2026-08-28 the
# overlay order was the exact opposite of the conf order, with two live
# consequences:
# - esp32s3_usb.overlay could not move the console off USB Serial/JTAG on the
# boards that pick a console in board.overlay (heltec V4/V43, wireless
# tracker v2) -- the reroute was overridden and the console pointed at a
# peripheral that no longer owned the pins.
# - pm_esp32.overlay could not hold the SX1262 NSS across light sleep on
# heltec_wireless_tracker, whose board.overlay re-declares spi2 cs-gpios
# without ESP32_GPIO_SLEEP_HOLD_EN -- i.e. the exact floating-NSS failure
# that overlay exists to prevent.
# partitions.overlay is appended after everything (see below) and stays there:
# it must also be fed verbatim to the MCUboot image.
set(ZEPHCORE_BOARD_OVERLAY "")
file(GLOB_RECURSE BOARD_OVERLAY_CANDIDATES "${CMAKE_CURRENT_SOURCE_DIR}/boards/*/${BOARD_BASE}/board.overlay")
if(BOARD_OVERLAY_CANDIDATES)
list(GET BOARD_OVERLAY_CANDIDATES 0 ZEPHCORE_BOARD_OVERLAY)
endif()
# Fallback: direct path without vendor subdirectory
if(NOT ZEPHCORE_BOARD_OVERLAY AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_BASE}/board.overlay")
set(ZEPHCORE_BOARD_OVERLAY "${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_BASE}/board.overlay")
endif()
# Append to EXTRA_DTC_OVERLAY_FILE (preserves any user-specified extras)
if(ZEPHCORE_BOARD_OVERLAY AND EXISTS ${ZEPHCORE_BOARD_OVERLAY})
if(EXTRA_DTC_OVERLAY_FILE)
set(EXTRA_DTC_OVERLAY_FILE "${EXTRA_DTC_OVERLAY_FILE};${ZEPHCORE_BOARD_OVERLAY}" CACHE STRING "" FORCE)
else()
set(EXTRA_DTC_OVERLAY_FILE "${ZEPHCORE_BOARD_OVERLAY}" CACHE STRING "" FORCE)
endif()
endif()
# Build the EXTRA_CONF_FILE list (append to any user-provided extras)
# Auto-pair each .conf with a same-named .overlay if it exists.
set(ZEPHCORE_CONF_FILES "")
@@ -466,6 +514,51 @@ if(ZEPHCORE_PLATFORM_CONF MATCHES "esp32_common" AND EXTRA_CONF_FILE MATCHES "re
endif()
endif()
# Auto-include the ESP32-S3 USB CDC-ACM companion transport.
#
# The policy lives here rather than in build.sh so that a locally built
# companion is the same firmware as the published release. The ESP32 release
# flow already diverges from a plain build in its *layout* (sysbuild/MCUboot);
# letting it diverge in which *transports* the app speaks as well would make
# user bug reports unreproducible from source.
#
# Detection is the board's own devicetree, not a name list: a board that
# includes esp32s3_usb_otg.dtsi has declared the cdc_acm_uart node, which is
# exactly the precondition the USB companion call sites are compiled behind
# (see ZEPHCORE_USB_STACK in src/main_companion.cpp). Boards without it are
# skipped automatically -- Heltec V3 (USB-C via CP2102, uses
# serial_companion.conf), Wireless Tracker V1, and every C3/C6/classic-ESP32
# part, which has no DWC2 controller at all.
#
# COMPANION ROLE ONLY. esp32s3_usb.conf sets CONFIG_ZEPHCORE_COMPANION_USB=y,
# which carries no role dependency of its own, and its paired overlay moves the
# console to uart0. In a repeater/observer/room-server build that would both
# compile the companion USB stack and strand the serial CLI on GPIO43/44.
# serial_companion.conf is excluded for a different reason: COMPANION_SERIAL
# `depends on !COMPANION_USB`, so adding this would silently disable it.
#
# DEBUG BUILDS ARE EXCLUDED. ESP32 has no RTT (debug_esp32.conf routes logs to
# the console backend) and this conf hands that USB port to the binary
# companion protocol, so an auto-enabled debug build would have nowhere to log.
# Pass both confs explicitly to debug the USB transport itself; logs then need
# a UART adapter on GPIO43/44.
if(ZEPHCORE_PLATFORM_CONF MATCHES "esp32_common" AND ZEPHCORE_BOARD_CONF
AND NOT EXTRA_CONF_FILE MATCHES "repeater|observer|room_server|serial_companion|esp32s3_usb|debug")
get_filename_component(ZEPHCORE_BOARD_DIR "${ZEPHCORE_BOARD_CONF}" DIRECTORY)
if(EXISTS "${ZEPHCORE_BOARD_DIR}/board.overlay")
file(READ "${ZEPHCORE_BOARD_DIR}/board.overlay" ZEPHCORE_BOARD_OVERLAY_TEXT)
if(ZEPHCORE_BOARD_OVERLAY_TEXT MATCHES "esp32s3_usb_otg\\.dtsi")
set(ZEPHCORE_USB_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/esp32s3_usb.conf")
if(EXISTS ${ZEPHCORE_USB_CONF})
list(APPEND ZEPHCORE_CONF_FILES ${ZEPHCORE_USB_CONF})
zephcore_auto_pair_overlay("${ZEPHCORE_USB_CONF}")
message(STATUS " USB CDC companion: auto-enabled (board declares USB OTG CDC-ACM)")
endif()
endif()
unset(ZEPHCORE_BOARD_OVERLAY_TEXT)
endif()
endif()
# Append to EXTRA_CONF_FILE — auto-generated configs first, user extras last.
# User-specified extras (repeater.conf, debug.conf) MUST come after the auto chain
# so they can override settings (e.g. CONFIG_BT=n in repeater.conf).
@@ -499,26 +592,6 @@ if(EXTRA_CONF_FILE)
endif()
# ========== DTC Overlay Hierarchy ==========
# Search for board.overlay in possible locations (same as board.conf)
set(ZEPHCORE_BOARD_OVERLAY "")
file(GLOB_RECURSE BOARD_OVERLAY_CANDIDATES "${CMAKE_CURRENT_SOURCE_DIR}/boards/*/${BOARD_BASE}/board.overlay")
if(BOARD_OVERLAY_CANDIDATES)
list(GET BOARD_OVERLAY_CANDIDATES 0 ZEPHCORE_BOARD_OVERLAY)
endif()
# Fallback: direct path without vendor subdirectory
if(NOT ZEPHCORE_BOARD_OVERLAY AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_BASE}/board.overlay")
set(ZEPHCORE_BOARD_OVERLAY "${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_BASE}/board.overlay")
endif()
# Append to EXTRA_DTC_OVERLAY_FILE (preserves any user-specified extras)
if(ZEPHCORE_BOARD_OVERLAY AND EXISTS ${ZEPHCORE_BOARD_OVERLAY})
if(EXTRA_DTC_OVERLAY_FILE)
set(EXTRA_DTC_OVERLAY_FILE "${EXTRA_DTC_OVERLAY_FILE};${ZEPHCORE_BOARD_OVERLAY}" CACHE STRING "" FORCE)
else()
set(EXTRA_DTC_OVERLAY_FILE "${ZEPHCORE_BOARD_OVERLAY}" CACHE STRING "" FORCE)
endif()
endif()
# Flash partition layout lives in a separate boards/<...>/<board>/partitions.overlay
# so the SAME file can be fed to the MCUboot image too (see sysbuild/CMakeLists.txt).
# The app and MCUboot MUST share one flash map — otherwise MCUboot stages/looks for
+5 -1
View File
@@ -134,7 +134,11 @@ config ZEPHCORE_COMPANION_USB
depends forces this to n automatically — no conf change needed.
For ESP32-S3 boards, USB_DEVICE_STACK_NEXT must be enabled first via
boards/common/esp32s3_usb.conf before this becomes active.
boards/common/esp32s3_usb.conf before this becomes active. CMakeLists.txt
adds that conf automatically to companion builds on the S3 boards whose
board.overlay declares the USB OTG CDC-ACM node, so it is on by default
there too — but not on repeater/observer/room-server or debug builds,
which keep the port for the console.
config ZEPHCORE_COMPANION_SERIAL
bool "Enable plain-UART companion transport"
+38
View File
@@ -33,6 +33,34 @@
#define ESP32_USB_SERIAL_DETACH 1
#endif
/* ESP32-S3: reboot into the ROM download mode rather than back into the app.
*
* The USB CDC companion transport (boards/common/esp32s3_usb.conf) hands the
* shared D+/D- pads to USB OTG and disables USB-Serial-JTAG. USJ is exactly
* what esptool drives to put the chip into download mode, so once our CDC-ACM
* device owns the port esptool's auto-reset is inert: DTR/RTS land on a Zephyr
* CDC endpoint that is not wired to EN/BOOT, and the only way back into the
* bootloader is the physical BOOT button.
*
* FORCE_DOWNLOAD_BOOT lives in the RTC domain, which a software system reset
* does not clear (only a power-on reset does), so setting it and rebooting
* brings the ROM up in download mode with USB-Serial-JTAG back on the pads.
* That gives both `start dfu` and the Arduino-style 1200-baud touch a way to
* hand the port back to esptool / esptool-js / the web configurator.
*
* Same register and sequence Arduino-ESP32 uses in usb_persist_restart()
* (RESTART_BOOTLOADER). OPTION1 carries no other bits on this SoC, but set the
* bit rather than writing the word so it stays correct if that changes. Scoped
* to the S3 deliberately: it is the only part we ship whose USB port can be
* taken away from the ROM this way (C3/C6 use a different LP_AON register, and
* classic ESP32 has no native USB — it always flashes through its UART bridge).
*/
#if defined(CONFIG_SOC_SERIES_ESP32S3)
#include <soc/rtc_cntl_reg.h>
#include <soc/soc.h>
#define ESP32_FORCE_DOWNLOAD_BOOT 1
#endif
/* LoRa TX activity LED (optional — defined per-board via DT alias) */
#if DT_NODE_EXISTS(DT_ALIAS(lora_tx_led))
static const struct gpio_dt_spec tx_led =
@@ -282,8 +310,18 @@ void ZephyrBoard::rebootToBootloader()
/* Write magic value to GPREGRET0 - enter UF2 bootloader mode.
* UF2 supports both drag-and-drop (.uf2) and serial DFU (nrfutil). */
nrf_power_gpregret_set(NRF_POWER, 0, BOOTLOADER_DFU_UF2_MAGIC);
#endif
#ifdef ESP32_FORCE_DOWNLOAD_BOOT
/* Come back up in the ROM download mode instead of the app, handing the
* USB port back to USB-Serial-JTAG so esptool can reach it. */
REG_SET_BIT(RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT);
#endif
k_msleep(50); /* Let UART/USB flush */
#ifdef ESP32_USB_SERIAL_DETACH
/* Clean USB disconnect before the soft reset — same reason as reboot(). */
usb_serial_jtag_ll_phy_enable_pad(false);
k_msleep(100);
#endif
sys_reboot(SYS_REBOOT_COLD);
}
@@ -13,9 +13,17 @@
* GPIO43/44. It is included from the auto-paired esp32s3_usb.overlay instead,
* so it follows esp32s3_usb.conf.
*
* Boards whose console really is a physical uart0 — Heltec V3/V4/V43, and
* ThinkNode M9 (USB-C via a CH9102-class bridge into UART0, native USB pads not
* bonded) — do include it unconditionally, which is correct for them.
* Boards whose console really is a physical uart0 — Heltec V3, and ThinkNode M9
* (USB-C via a CH9102-class bridge into UART0, native USB pads not bonded) — do
* include it unconditionally, which is correct for them.
*
* Heltec V4/V4.3 and Wireless Tracker V2 are NOT in that group even though
* their board DTS names uart0: their board.overlay re-chooses usb_serial, so
* they go through the conditional path above like every other native-USB board.
* That did not work until 2026-08-28 — board.overlay was applied AFTER the
* auto-paired overlays and simply won, leaving the console pointed at a USJ
* peripheral that no longer owned the pins. See the DTC overlay precedence
* block in CMakeLists.txt.
*
* uart0 is disabled by default in the ESP32-S3 SoC dtsi, so we enable it and
* pin it to the chip's ROM-default UART0 pads (GPIO43 TX / GPIO44 RX) — the
+10 -1
View File
@@ -5,9 +5,18 @@
# USB transport on S3 boards. Do NOT use on ESP32 (original) or C3/C6 — those
# chips have no DWC2 controller.
#
# Build with:
# Applied AUTOMATICALLY to companion builds on every S3 board whose board.overlay
# includes esp32s3_usb_otg.dtsi — see the "Auto-include the ESP32-S3 USB CDC-ACM
# companion transport" block in CMakeLists.txt for the gate (companion role only,
# and not on debug builds, which need that port for the console). Pass it by
# hand only to override that policy:
# -DEXTRA_CONF_FILE="boards/common/esp32s3_usb.conf"
#
# NOTE: this takes the USB port away from USB-Serial-JTAG, so esptool's
# auto-reset into download mode stops working on boards flashed over native USB.
# `start dfu` and the 1200-baud touch set FORCE_DOWNLOAD_BOOT to give it back
# (ZephyrBoard::rebootToBootloader); otherwise it is the BOOT button.
#
# Also add the cdc_acm_uart DTS node to the board overlay (the overlays include
# boards/common/esp32s3_usb_otg.dtsi). Without the DT node,
# DT_HAS_COMPAT_STATUS_OKAY(zephyr_cdc_acm_uart) is false and
+6 -3
View File
@@ -18,8 +18,11 @@
* CONFIG_ZEPHCORE_COMPANION_USB — the conf/overlay pairing is what makes this
* conditional.
*
* Boards whose console is already a physical uart0 (Heltec V3/V4/V43, ThinkNode
* M9) are unaffected: the include re-asserts the same uart0 + GPIO43/44 setup
* they already have.
* Boards whose console is already a physical uart0 (Heltec V3, ThinkNode M9) are
* unaffected: the include re-asserts the same uart0 + GPIO43/44 setup they
* already have. Heltec V4/V4.3 and Wireless Tracker V2 DO get moved — their
* board.overlay chooses usb_serial — which is the point, and only actually
* happens because board.overlay now goes into EXTRA_DTC_OVERLAY_FILE first
* (see the DTC overlay precedence block in CMakeLists.txt).
*/
#include "esp32s3_console_uart0.dtsi"
@@ -90,6 +90,8 @@
/* LittleFS auto-mount — standard /lfs mount point */
#include "../../common/filesystem.dtsi"
/* USB CDC-ACM — companion USB transport (esp32s3_usb.conf). Heltec has a
* dedicated UART console, so no console reroute is needed. */
/* USB CDC-ACM — companion USB transport, auto-enabled for companion builds via
* boards/common/esp32s3_usb.conf (see the auto-include block in CMakeLists.txt).
* The chosen console above is usb_serial, i.e. the same D+/D- pins, so the conf's
* paired overlay moves the console to uart0/GPIO43-44 for those builds. */
#include "../../common/esp32s3_usb_otg.dtsi"
@@ -90,6 +90,8 @@
/* LittleFS auto-mount — standard /lfs mount point */
#include "../../common/filesystem.dtsi"
/* USB CDC-ACM — companion USB transport (esp32s3_usb.conf). Heltec has a
* dedicated UART console, so no console reroute is needed. */
/* USB CDC-ACM — companion USB transport, auto-enabled for companion builds via
* boards/common/esp32s3_usb.conf (see the auto-include block in CMakeLists.txt).
* The chosen console above is usb_serial, i.e. the same D+/D- pins, so the conf's
* paired overlay moves the console to uart0/GPIO43-44 for those builds. */
#include "../../common/esp32s3_usb_otg.dtsi"
+8 -1
View File
@@ -141,8 +141,15 @@ void CommonCLI::scheduleReboot(uint8_t type)
*/
void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, char* reply) {
if (strcmp(command, "start dfu") == 0) {
/* Reboot into UF2 bootloader for firmware update */
/* Reboot into the chip's own firmware-update mode. nRF52: the Adafruit
* UF2 bootloader. ESP32-S3: the ROM download mode, which is the only
* way to get the USB port back from the CDC companion transport for
* esptool (see ZephyrBoard::rebootToBootloader). */
#if defined(CONFIG_SOC_SERIES_ESP32S3)
strcpy(reply, "OK - rebooting to ESP32 download mode");
#else
strcpy(reply, "OK - rebooting to UF2 DFU");
#endif
scheduleReboot(REBOOT_DFU);
} else if (memcmp(command, "start ota", 9) == 0) {
#if IS_ENABLED(CONFIG_ZEPHCORE_WIFI_OTA)