From e648f416ae49acea9ed1b1d175bd95d566b94f1d Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Tue, 28 Jul 2026 06:49:54 +0200 Subject: [PATCH] feat(compiler): Serial = USB-CDC cuando boards.txt lo declara, como el hardware fisico MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tres piezas: - _arduino_board_flags lee .build.board y .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_ 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. --- .../services/esp-idf-template/CMakeLists.txt | 6 ++ .../esp-idf-template/sdkconfig.defaults.in | 5 ++ backend/app/services/espidf_compiler.py | 74 +++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/backend/app/services/esp-idf-template/CMakeLists.txt b/backend/app/services/esp-idf-template/CMakeLists.txt index 64be6a19..cded97b5 100644 --- a/backend/app/services/esp-idf-template/CMakeLists.txt +++ b/backend/app/services/esp-idf-template/CMakeLists.txt @@ -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_, +# 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) diff --git a/backend/app/services/esp-idf-template/sdkconfig.defaults.in b/backend/app/services/esp-idf-template/sdkconfig.defaults.in index 1d9d9c00..f9d1536f 100644 --- a/backend/app/services/esp-idf-template/sdkconfig.defaults.in +++ b/backend/app/services/esp-idf-template/sdkconfig.defaults.in @@ -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 diff --git a/backend/app/services/espidf_compiler.py b/backend/app/services/espidf_compiler.py index 96dadc0e..ecb6efbb 100644 --- a/backend/app/services/espidf_compiler.py +++ b/backend/app/services/espidf_compiler.py @@ -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 ``.build.board`` name — arduino-cli defines + ``ARDUINO_`` from it and vendor sketches #ifdef on it. + - ``cdc_on_boot``: ``.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