feat(compiler): Serial = USB-CDC cuando boards.txt lo declara, como el hardware fisico

Tres piezas:
- _arduino_board_flags lee <id>.build.board y <id>.build.cdc_on_boot de
  boards.txt (cache, mismo patron que _arduino_variant).
- Cada compile escribe velxio_board.cmake (incluido OPTIONAL por el
  CMakeLists ANTES de project(), asi los defines llegan a TODOS los
  componentes — que Serial sea el CDC se decide dentro del core Arduino):
  ARDUINO_<BOARD> siempre que boards.txt lo nombre (los sketches de vendor
  hacen #ifdef ARDUINO_XIAO_ESP32S3), y ARDUINO_USB_CDC_ON_BOOT=1 +
  ARDUINO_USB_MODE=1 cuando cdc_on_boot=1. USB_MODE va FORZADO a 1 (HWCDC
  por USB-Serial-JTAG, la opcion de menu USBMode=hwcdc): el motor modela esa
  consola, no la pila OTG/TinyUSB. Ir por archivo y no por env hace el flag
  dependencia de configure: CMake reconfigura al cambiar.
- CONFIG_ESP_CONSOLE_SECONDARY_NONE en el sdkconfig: sin ella el ROM/IDF
  espejan cada byte de log al USB y el monitor unico del emulador (que
  mezcla ambos streams) mostraria el log entero dos veces en builds CDC.
This commit is contained in:
David Montero Crespo 2026-07-28 06:49:54 +02:00
parent a419b13cb1
commit e648f416ae
3 changed files with 85 additions and 0 deletions

View File

@ -10,5 +10,11 @@ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/user_libs")
list(APPEND EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/user_libs")
endif()
# Board identity defines written per-build by espidf_compiler (ARDUINO_<BOARD>,
# ARDUINO_USB_CDC_ON_BOOT/ARDUINO_USB_MODE when boards.txt declares CDC). Must
# run BEFORE project() so the definitions propagate to every component -- what
# `Serial` IS gets decided inside the arduino core's own sources.
include(${CMAKE_CURRENT_SOURCE_DIR}/velxio_board.cmake OPTIONAL)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(velxio-sketch)

View File

@ -57,6 +57,11 @@ CONFIG_FREERTOS_HZ=1000
# ── Console ──────────────────────────────────────────────────────────────
CONFIG_ESP_CONSOLE_UART_DEFAULT=y
# No secondary console: on USB-Serial-JTAG chips the ROM/IDF would mirror every
# log byte to the USB console too, and the emulator merges both streams into ONE
# serial monitor -- the whole log would show twice on CDC builds. Logs stay on
# UART0; the USB channel carries only what the sketch prints through HWCDC.
CONFIG_ESP_CONSOLE_SECONDARY_NONE=y
# ── Arduino-as-component: we provide our own app_main (main.cpp) ─────────
CONFIG_AUTOSTART_ARDUINO=n

View File

@ -546,6 +546,50 @@ class ESPIDFCompiler:
self._variant_cache[key] = variant
return variant
_board_flags_cache: dict = {}
def _arduino_board_flags(self, board_fqbn: str) -> dict:
"""USB/identity flags for this FQBN from arduino-esp32's boards.txt.
Returns {'board': str|None, 'cdc_on_boot': bool}:
- ``board``: the ``<id>.build.board`` name arduino-cli defines
``ARDUINO_<board>`` from it and vendor sketches #ifdef on it.
- ``cdc_on_boot``: ``<id>.build.cdc_on_boot=1`` on real hardware
``Serial`` is then the USB CDC, not UART0. Boards like the XIAO
ESP32S3 expose UART0's default RX pin (GPIO44) as a plain Dx pin,
so building them with Serial on UART0 is not just unfaithful: a
pinMode() on that pin tears the UART driver down mid-sketch.
"""
board_id = board_fqbn.split(':')[-1].split('?')[0].strip()
if board_id in self._board_flags_cache:
return self._board_flags_cache[board_id]
flags = {'board': None, 'cdc_on_boot': False}
roots = [
r
for r in (
os.environ.get('ARDUINO_ESP32_V3_PATH', '/opt/arduino-esp32-3'),
os.environ.get('ARDUINO_ESP32_PATH', '/opt/arduino-esp32'),
)
if r
]
for root in roots:
boards_txt = Path(root) / 'boards.txt'
if not boards_txt.is_file():
continue
try:
text = boards_txt.read_text(encoding='utf-8', errors='ignore')
except OSError:
continue
for line in text.splitlines():
if line.startswith(f'{board_id}.build.board='):
flags['board'] = line.split('=', 1)[1].strip() or None
elif line.startswith(f'{board_id}.build.cdc_on_boot='):
flags['cdc_on_boot'] = line.split('=', 1)[1].strip() == '1'
if flags['board'] is not None:
break
self._board_flags_cache[board_id] = flags
return flags
def _idf_target(self, board_fqbn: str) -> str:
"""Map FQBN to IDF_TARGET."""
if self._is_esp32c3(board_fqbn):
@ -2811,6 +2855,36 @@ class ESPIDFCompiler:
)
defaults_path.write_text(rendered_sdkconfig, encoding='utf-8')
# Board identity defines (velxio_board.cmake, included OPTIONAL by the
# template's top CMakeLists BEFORE project() so they reach EVERY
# component — `Serial`'s mapping is decided inside the arduino core's
# own sources, a sketch-only define cannot move it). Going through a
# file instead of an env var makes it a configure dependency: CMake
# re-runs when it changes, env vars it silently ignores.
board_cmake_lines = ['# generated by espidf_compiler — do not edit']
if arduino_mode and not pure_idf:
flags = self._arduino_board_flags(board_fqbn)
if flags['board']:
macro = re.sub(r'[^A-Z0-9_]', '_', str(flags['board']).upper())
board_cmake_lines.append(
f'add_compile_definitions(ARDUINO_{macro})'
)
if flags['cdc_on_boot']:
# USB_MODE is FORCED to 1 (HWCDC over USB-Serial-JTAG, the
# boards.txt "USBMode=hwcdc" menu choice) even when the board's
# default is 0 (USB-OTG/TinyUSB): the engine models the
# Serial-JTAG console; the OTG device stack it does not.
board_cmake_lines.append(
'add_compile_definitions(ARDUINO_USB_CDC_ON_BOOT=1 ARDUINO_USB_MODE=1)'
)
board_cmake = '\n'.join(board_cmake_lines) + '\n'
board_cmake_path = project_dir / 'velxio_board.cmake'
prev_board_cmake = (
board_cmake_path.read_text(encoding='utf-8') if board_cmake_path.exists() else None
)
if prev_board_cmake != board_cmake:
board_cmake_path.write_text(board_cmake, encoding='utf-8')
# ESP-IDF only SEEDS sdkconfig from sdkconfig.defaults when sdkconfig is
# ABSENT. Persistent build dirs live in the build volume and keep a
# stale sdkconfig across image rebuilds, so a defaults change (a new