From 4ddf4cdab041bea09db23bfe5b572d2c46cf726f Mon Sep 17 00:00:00 2001 From: wulu Date: Sat, 3 Oct 2026 16:55:55 +0800 Subject: [PATCH 1/4] feat(mosaico): add USB CDC console and automatic download --- THIRD_PARTY_LICENSES.md | 22 ++ docs/validation/mosaico-gestures.md | 2 +- .../components/usb_hs_console/CMakeLists.txt | 12 + firmware/components/usb_hs_console/Kconfig | 19 ++ firmware/components/usb_hs_console/LICENSE | 202 ++++++++++++++ .../components/usb_hs_console/UPSTREAM.md | 24 ++ .../usb_hs_console/idf_component.yml | 3 + .../components/usb_hs_console/usb_console.c | 249 ++++++++++++++++++ .../components/usb_hs_console/usb_console.h | 35 +++ firmware/targets/esp_mosaico/CMakeLists.txt | 1 + firmware/targets/esp_mosaico/README.md | 16 ++ .../targets/esp_mosaico/dependencies.lock | 39 ++- .../esp_mosaico/main/gesture_camera.cpp | 2 +- .../targets/esp_mosaico/sdkconfig.defaults | 6 +- 14 files changed, 628 insertions(+), 4 deletions(-) create mode 100644 firmware/components/usb_hs_console/CMakeLists.txt create mode 100644 firmware/components/usb_hs_console/Kconfig create mode 100644 firmware/components/usb_hs_console/LICENSE create mode 100644 firmware/components/usb_hs_console/UPSTREAM.md create mode 100644 firmware/components/usb_hs_console/idf_component.yml create mode 100644 firmware/components/usb_hs_console/usb_console.c create mode 100644 firmware/components/usb_hs_console/usb_console.h diff --git a/THIRD_PARTY_LICENSES.md b/THIRD_PARTY_LICENSES.md index b6a5f7a..0d7ff72 100644 --- a/THIRD_PARTY_LICENSES.md +++ b/THIRD_PARTY_LICENSES.md @@ -18,6 +18,14 @@ For ESP Component Registry entries, `Registry license` is the API value for the - SPDX: `Apache-2.0` - Copyright/notice: https://github.com/esp-mosaico/esp-mosaico-bsp/blob/392860b1d1a123c3377947074b2af1f600e86c5d/LICENSE +## `usb_hs_console` + +- Version: `9ea326da1f2a017ba81f1d391862f977b355a03e` +- Source: https://github.com/esp-mosaico/esp-mosaico-claw/tree/9ea326da1f2a017ba81f1d391862f977b355a03e/boards/esp_mosaico/components/usb_hs_console +- SPDX: `Apache-2.0` +- Copyright/notice: [LICENSE](firmware/components/usb_hs_console/LICENSE) +- Local integration: [UPSTREAM.md](firmware/components/usb_hs_console/UPSTREAM.md) + ## `esp_desktop_buddy` - Version: `b6bac05db208717676e70180e5269d79f32b2d68` @@ -167,6 +175,20 @@ For ESP Component Registry entries, `Registry license` is the API value for the - ESP-Mosaico version: `1.3.2~1`; source: https://components.espressif.com/components/espressif/zlib/versions/1.3.2~1; copyright/notice: https://components.espressif.com/components/espressif/zlib/versions/1.3.2~1/license +## `espressif/esp_tinyusb` + +- Version: `2.2.1` +- Source: https://components.espressif.com/components/espressif/esp_tinyusb/versions/2.2.1 +- SPDX: `Apache-2.0` +- Copyright/notice: https://github.com/espressif/esp-usb/blob/8e779566ef71d43928cbf7e125e8eb54bab3f542/device/esp_tinyusb/LICENSE + +## `espressif/tinyusb` + +- Version: `0.21.0~1` +- Source: https://components.espressif.com/components/espressif/tinyusb/versions/0.21.0~1 +- SPDX: `MIT` +- Copyright/notice: https://github.com/espressif/tinyusb/blob/7049c58a0e895acc92c6407574b05b5536eddfc8/LICENSE + ## `espressif/usb` - Version: `1.3.0` diff --git a/docs/validation/mosaico-gestures.md b/docs/validation/mosaico-gestures.md index dfae811..5a5fde4 100644 --- a/docs/validation/mosaico-gestures.md +++ b/docs/validation/mosaico-gestures.md @@ -46,6 +46,6 @@ eim run "idf.py -C firmware/targets/esp_mosaico build" v6.1 ## 维护入口 -应用日志从 UART0 输出:右侧排针 H1 的 15 脚 TX0 / GPIO58,或底板背面 TX 调试焊盘,115200 波特率。Type-C 口使用 USB OTG 下载,摄像头占用的 USB Serial/JTAG 引脚位于左侧排针。日志包含手部坐标、置信度、动作和运行耗时,不包含图像。 +应用启动 USB CDC 后,日志通过 Type-C USB OTG 串口输出;连接监视器时使用 `monitor --no-reset` 可避免重启。启动早期的 UART0 输出可从右侧排针 H1 的 15 脚 TX0 / GPIO58,或底板背面 TX 调试焊盘读取,波特率为 115200。摄像头占用的 USB Serial/JTAG 引脚位于左侧排针,与 Type-C USB OTG 独立。日志包含手部坐标、置信度、动作和运行耗时,不包含图像。 代码结构、识别参数见[手势设计](../design/mosaico-gesture-input.md);固定依赖、模型摘要和引脚所有权见[摄像头依赖说明](../../firmware/targets/esp_mosaico/CAMERA_UPSTREAM.md)。 diff --git a/firmware/components/usb_hs_console/CMakeLists.txt b/firmware/components/usb_hs_console/CMakeLists.txt new file mode 100644 index 0000000..7dc8427 --- /dev/null +++ b/firmware/components/usb_hs_console/CMakeLists.txt @@ -0,0 +1,12 @@ +idf_component_register( + SRC_DIRS "." + INCLUDE_DIRS "." + PRIV_REQUIRES esp_system esp_timer esp_rom soc +) + +if(CONFIG_USB_HS_CONSOLE_USB_CDC_AUTO_INIT) + target_link_libraries(${COMPONENT_LIB} INTERFACE + "-Wl,--wrap=app_main" + "-u __wrap_app_main" + ) +endif() diff --git a/firmware/components/usb_hs_console/Kconfig b/firmware/components/usb_hs_console/Kconfig new file mode 100644 index 0000000..e05d5c7 --- /dev/null +++ b/firmware/components/usb_hs_console/Kconfig @@ -0,0 +1,19 @@ + +menu "USB HS Console Config" + + config USB_HS_CONSOLE_USB_CDC_AUTO_DOWNLOAD + bool "Enable custom USB CDC auto-download reset" + depends on TINYUSB_CDC_ENABLED + depends on !ESP_CONSOLE_USB_CDC + default n + help + Enable USB-Serial/JTAG-compatible DTR/RTS reset handling for the + custom USB CDC console. + + config USB_HS_CONSOLE_USB_CDC_AUTO_INIT + bool "Automatically initialize custom USB CDC console" + depends on USB_HS_CONSOLE_USB_CDC_AUTO_DOWNLOAD + default n + help + Initialize the custom USB CDC console before app_main() starts. +endmenu diff --git a/firmware/components/usb_hs_console/LICENSE b/firmware/components/usb_hs_console/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/firmware/components/usb_hs_console/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/firmware/components/usb_hs_console/UPSTREAM.md b/firmware/components/usb_hs_console/UPSTREAM.md new file mode 100644 index 0000000..b676f13 --- /dev/null +++ b/firmware/components/usb_hs_console/UPSTREAM.md @@ -0,0 +1,24 @@ +# Upstream source + +- Repository: https://github.com/esp-mosaico/esp-mosaico-claw +- Path: `boards/esp_mosaico/components/usb_hs_console` +- Commit: `9ea326da1f2a017ba81f1d391862f977b355a03e` +- License: Apache-2.0 (see `LICENSE` and source SPDX headers). + +This component initializes a TinyUSB CDC console on the Mosaico Type-C USB +OTG port. It implements the USB Serial/JTAG DTR/RTS reset protocol in software; +it does not expose hardware JTAG. Only the ESP-Mosaico target includes it. + +## Local modifications + +- Declare the ESP-IDF system, timer, ROM and SoC dependencies explicitly. +- Require ESP-IDF >=6.1 and constrain esp_tinyusb to the compatible 2.x API; + the target lockfile records the resolved version. +- Include the ROM print declaration and correct obsolete Kconfig names in the + public header. +- Continue application startup with a warning if USB console initialization + fails, preserving the display and BLE recovery path. + +The reset state machine and USB descriptor are retained from upstream. +Software download requires a running application; BOOT plus power-on remains +necessary for recovery when USB initialization or application startup fails. diff --git a/firmware/components/usb_hs_console/idf_component.yml b/firmware/components/usb_hs_console/idf_component.yml new file mode 100644 index 0000000..267e59d --- /dev/null +++ b/firmware/components/usb_hs_console/idf_component.yml @@ -0,0 +1,3 @@ +dependencies: + idf: ">=6.1" + espressif/esp_tinyusb: "^2.0.1~1" diff --git a/firmware/components/usb_hs_console/usb_console.c b/firmware/components/usb_hs_console/usb_console.c new file mode 100644 index 0000000..aebcc64 --- /dev/null +++ b/firmware/components/usb_hs_console/usb_console.c @@ -0,0 +1,249 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" +#include "esp_err.h" + +extern void __real_app_main(void); + +#include +#include + +#include "esp_log.h" +#include "esp_rom_sys.h" +#include "esp_system.h" +#include "esp_timer.h" +#include "soc/lp_system_reg.h" +#include "soc/soc.h" +#include "tinyusb.h" +#include "tinyusb_cdc_acm.h" +#include "tinyusb_console.h" +#include "tinyusb_default_config.h" + +static const char *TAG = "bsp_usb_console"; +static bool s_initialized; + +#if CONFIG_USB_HS_CONSOLE_USB_CDC_AUTO_DOWNLOAD + +/* + * esptool and idf_monitor select the USB-Serial/JTAG reset strategy when they + * see Espressif's USB-Serial/JTAG VID/PID. This device implements the matching + * CDC reset protocol in software; it does not implement a JTAG interface. + */ +#define BSP_USB_SERIAL_JTAG_PID 0x1001 +#define BSP_USB_RESTART_DELAY_US (50 * 1000) + +static const tusb_desc_device_t s_usb_device_descriptor = { + .bLength = sizeof(s_usb_device_descriptor), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .idVendor = TINYUSB_ESPRESSIF_VID, + .idProduct = BSP_USB_SERIAL_JTAG_PID, + .bcdDevice = CONFIG_TINYUSB_DESC_BCD_DEVICE, + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + .bNumConfigurations = 0x01, +}; + +typedef enum { + BSP_USB_REBOOT_NONE, + BSP_USB_REBOOT_NORMAL, + BSP_USB_REBOOT_BOOTLOADER, +} bsp_usb_reboot_t; + +static atomic_int s_reboot_mode = ATOMIC_VAR_INIT(BSP_USB_REBOOT_NONE); +static atomic_bool s_download_mode = ATOMIC_VAR_INIT(false); +static esp_timer_handle_t s_restart_timer; + +static void usb_console_before_restart(void) +{ + const bsp_usb_reboot_t mode = atomic_load_explicit(&s_reboot_mode, memory_order_acquire); + if (mode == BSP_USB_REBOOT_BOOTLOADER) { + REG_SET_BIT(LP_SYSTEM_REG_SYS_CTRL_REG, LP_SYSTEM_REG_FORCE_DOWNLOAD_BOOT); + } else if (mode == BSP_USB_REBOOT_NORMAL) { + REG_CLR_BIT(LP_SYSTEM_REG_SYS_CTRL_REG, LP_SYSTEM_REG_FORCE_DOWNLOAD_BOOT); + } +} + +static void restart_timer_callback(void *arg) +{ + (void)arg; + esp_restart(); +} + +static void cdc_line_state_changed_callback(int itf, cdcacm_event_t *event) +{ + (void)itf; + const bool dtr = event->line_state_changed_data.dtr; + const bool rts = event->line_state_changed_data.rts; + + /* + * Match the USB-Serial/JTAG CDC state table exactly: + * + * RTS DTR action + * 0 0 clear download-mode latch + * 0 1 set download-mode latch + * 1 0 reset using the latched mode + * 1 1 no action + * + * esptool's USBJTAGSerialReset generates these effective phases: + * + * (0,0) --100 ms-- (0,1) --100 ms-- (1,1) -> (1,0) + * --100 ms-- (0,0) + * + * Its RTS helper also re-writes the current DTR state for Windows + * usbser.sys. Repeated states are therefore intentional. The reboot-mode + * compare/exchange below accepts only the first (1,0) event. + */ + if (!rts) { + atomic_store_explicit(&s_download_mode, dtr, memory_order_release); + return; + } + if (dtr) { + return; + } + + const bsp_usb_reboot_t requested_mode = + atomic_load_explicit(&s_download_mode, memory_order_acquire) ? + BSP_USB_REBOOT_BOOTLOADER : BSP_USB_REBOOT_NORMAL; + int expected_mode = BSP_USB_REBOOT_NONE; + if (!atomic_compare_exchange_strong_explicit(&s_reboot_mode, &expected_mode, requested_mode, + memory_order_acq_rel, memory_order_acquire)) { + return; + } + + /* Delay restart long enough for SET_CONTROL_LINE_STATE to complete. */ + if (esp_timer_start_once(s_restart_timer, BSP_USB_RESTART_DELAY_US) != ESP_OK) { + atomic_store_explicit(&s_reboot_mode, BSP_USB_REBOOT_NONE, memory_order_release); + } +} + +static esp_err_t auto_download_init(void) +{ + const esp_timer_create_args_t timer_args = { + .callback = restart_timer_callback, + .name = "usb_reboot", + }; + esp_err_t ret = esp_timer_create(&timer_args, &s_restart_timer); + if (ret != ESP_OK) { + return ret; + } + + ret = esp_register_shutdown_handler(usb_console_before_restart); + if (ret != ESP_OK) { + esp_timer_delete(s_restart_timer); + s_restart_timer = NULL; + } + return ret; +} + +static void auto_download_deinit(void) +{ + (void)esp_unregister_shutdown_handler(usb_console_before_restart); + if (s_restart_timer) { + (void)esp_timer_delete(s_restart_timer); + s_restart_timer = NULL; + } + atomic_store_explicit(&s_reboot_mode, BSP_USB_REBOOT_NONE, memory_order_release); + atomic_store_explicit(&s_download_mode, false, memory_order_release); +} + +#endif /* CONFIG_USB_HS_CONSOLE_USB_CDC_AUTO_DOWNLOAD */ + +esp_err_t bsp_usb_console_init(void) +{ + if (s_initialized) { + return ESP_OK; + } +#if CONFIG_USB_HS_CONSOLE_USB_CDC_AUTO_DOWNLOAD + esp_err_t ret = auto_download_init(); + if (ret != ESP_OK) { + return ret; + } + tinyusb_config_t tusb_config = TINYUSB_DEFAULT_CONFIG(); + tusb_config.descriptor.device = &s_usb_device_descriptor; +#else + esp_err_t ret; + tinyusb_config_t tusb_config = TINYUSB_DEFAULT_CONFIG(); +#endif + + ret = tinyusb_driver_install(&tusb_config); + if (ret != ESP_OK) { +#if CONFIG_USB_HS_CONSOLE_USB_CDC_AUTO_DOWNLOAD + auto_download_deinit(); +#endif + return ret; + } + + const tinyusb_config_cdcacm_t cdc_config = { + .cdc_port = TINYUSB_CDC_ACM_0, +#if CONFIG_USB_HS_CONSOLE_USB_CDC_AUTO_DOWNLOAD + .callback_line_state_changed = cdc_line_state_changed_callback, +#endif + }; + ret = tinyusb_cdcacm_init(&cdc_config); + if (ret != ESP_OK) { + (void)tinyusb_driver_uninstall(); +#if CONFIG_USB_HS_CONSOLE_USB_CDC_AUTO_DOWNLOAD + auto_download_deinit(); +#endif + return ret; + } + + ret = tinyusb_console_init(TINYUSB_CDC_ACM_0); + if (ret != ESP_OK) { + (void)tinyusb_cdcacm_deinit(TINYUSB_CDC_ACM_0); + (void)tinyusb_driver_uninstall(); +#if CONFIG_USB_HS_CONSOLE_USB_CDC_AUTO_DOWNLOAD + auto_download_deinit(); +#endif + return ret; + } + + /* Keep application logs observable even when the host opens CDC after app_main starts. */ + setvbuf(stdout, NULL, _IONBF, 0); + setvbuf(stderr, NULL, _IONBF, 0); + + s_initialized = true; + ESP_LOGI(TAG, "USB-OTG CDC console ready%s", +#if CONFIG_USB_HS_CONSOLE_USB_CDC_AUTO_DOWNLOAD + "; USB-Serial/JTAG-compatible reset enabled" +#else + "" +#endif + ); + return ESP_OK; +} + +bool bsp_usb_console_is_initialized(void) +{ + return s_initialized; +} + +#if CONFIG_USB_HS_CONSOLE_USB_CDC_AUTO_INIT + + +/* + * IDF calls app_main from its FreeRTOS main task. Linker wrapping puts TinyUSB + * initialization at the exact point where the scheduler is available but the + * application has not started yet. + */ +void __wrap_app_main(void) +{ + esp_rom_printf("Initializing USB-OTG CDC console...\n"); + const esp_err_t ret = bsp_usb_console_init(); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "USB CDC unavailable: %s; continuing application startup", esp_err_to_name(ret)); + } + __real_app_main(); +} + +#endif /* CONFIG_USB_HS_CONSOLE_USB_CDC_AUTO_INIT */ diff --git a/firmware/components/usb_hs_console/usb_console.h b/firmware/components/usb_hs_console/usb_console.h new file mode 100644 index 0000000..654e380 --- /dev/null +++ b/firmware/components/usb_hs_console/usb_console.h @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Start the USB-OTG CDC console. + * + * CONFIG_USB_HS_CONSOLE_USB_CDC_AUTO_INIT enables this automatically before + * app_main(). Call it manually only when automatic initialization is disabled. + * On success, stdin, stdout, stderr and subsequent ESP_LOG output use TinyUSB + * CDC-ACM interface 0. Repeated calls are harmless. + * + * When CONFIG_USB_HS_CONSOLE_USB_CDC_AUTO_DOWNLOAD is enabled, the same interface also + * emulates the USB-Serial/JTAG DTR/RTS reset behavior and uses its VID/PID, so + * idf.py can reset into ROM download mode without extra esptool arguments. + */ +esp_err_t bsp_usb_console_init(void); + +/** @return true after the USB CDC console has initialized successfully. */ +bool bsp_usb_console_is_initialized(void); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/esp_mosaico/CMakeLists.txt b/firmware/targets/esp_mosaico/CMakeLists.txt index 1a24f49..8cccc84 100644 --- a/firmware/targets/esp_mosaico/CMakeLists.txt +++ b/firmware/targets/esp_mosaico/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.16) set(EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/../../components/usage_json" "${CMAKE_CURRENT_LIST_DIR}/../../components/esp-mosaico-bsp" + "${CMAKE_CURRENT_LIST_DIR}/../../components/usb_hs_console" "${CMAKE_CURRENT_LIST_DIR}/../../components/usage_core" "${CMAKE_CURRENT_LIST_DIR}/../../components/usage_fonts" "${CMAKE_CURRENT_LIST_DIR}/../../components/usage_panel_state" diff --git a/firmware/targets/esp_mosaico/README.md b/firmware/targets/esp_mosaico/README.md index 1858f20..0e43e39 100644 --- a/firmware/targets/esp_mosaico/README.md +++ b/firmware/targets/esp_mosaico/README.md @@ -27,6 +27,22 @@ eim run "idf.py -C firmware/targets/esp_mosaico build" v6.1 构建产物为 `build/mosaico_usage_panel.bin`,两个 app 分区均为 7 MiB,Folder Push 总传输限额为 7 MiB + 512 字节,额外空间用于 manifest。 +## Type-C CDC 自动下载 + +目标默认启用官方出厂固件的 [USB HS Console 组件](../../components/usb_hs_console/UPSTREAM.md), +通过 Type-C USB OTG 提供 CDC 串口日志和 esptool 自动进入 ROM 下载模式的功能。 +首次安装仍需按住 BOOT 再开机;安装后,应用正常运行时可由电脑触发下载模式。 +USB 复位时串口会重新枚举,必要时重新选择端口。连接日志监视器但不希望重启时使用 +`monitor --no-reset`。应用无法启动或 USB 初始化失败时,仍使用 BOOT 手动恢复。 + +该路径不使用左槽 CameraBoard 占用的 USB Serial/JTAG 引脚。大镜像的分段写入要求 +仍见[有线烧录说明](../../../docs/validation/mosaico-gestures.md#有线烧录)。 +2026-10-03 已在 macOS 使用 esptool 5.3.1 的 USBJTAGSerialReset 序列验证: +应用 CDC → ROM 下载模式 → 看门狗复位 → 应用 CDC,全程无需按键。 +应用 CDC 与 ROM 使用不同的 USB 标识,端口会变化;此项验证不代表单条 +`idf.py flash` 命令已自动处理端口切换。摄像头运行时已连续读取 CDC 日志 +15 秒,未发生断连;长时间并行运行的稳定性仍待验证。 + ## 当前实现 界面、用量数据路径、BLE 与 OTA 都已接入,UI 从 ESP32-S3-Touch-AMOLED-2.16 移植而来(同为 480×480 CO5300 + CST9217): diff --git a/firmware/targets/esp_mosaico/dependencies.lock b/firmware/targets/esp_mosaico/dependencies.lock index ef2fab9..de6e699 100644 --- a/firmware/targets/esp_mosaico/dependencies.lock +++ b/firmware/targets/esp_mosaico/dependencies.lock @@ -333,6 +333,26 @@ dependencies: registry_url: https://components.espressif.com type: service version: 0.0.8 + espressif/esp_tinyusb: + component_hash: 9a73a76a6bc17907f6e523342ff6b1e440023e8aac2c11f4691212106ec24e66 + dependencies: + - name: idf + require: private + version: '>=5.0' + - name: espressif/tinyusb + registry_url: https://components.espressif.com + require: public + version: '>=0.17.0~2' + source: + registry_url: https://components.espressif.com/ + type: service + targets: + - esp32s2 + - esp32s3 + - esp32p4 + - esp32h4 + - esp32s31 + version: 2.2.1 espressif/esp_video: component_hash: 5694b16c12b8bb26b56ad3f999ad10119f1314d64b2d1c518bc7dfed902df9e1 dependencies: @@ -467,6 +487,22 @@ dependencies: registry_url: https://components.espressif.com/ type: service version: 1.4.4 + espressif/tinyusb: + component_hash: a72b7d67472914ab76309340fd50d578b31e310963d45ad0f81144bde3314752 + dependencies: + - name: idf + require: private + version: '>=5.0' + source: + registry_url: https://components.espressif.com + type: service + targets: + - esp32s2 + - esp32s3 + - esp32p4 + - esp32h4 + - esp32s31 + version: 0.21.0~1 espressif/usb: component_hash: 6c2cff118141dbbca181d0d27d71801fdddfc78f7ac03ae43f6e5a8cbd89cb99 dependencies: @@ -570,6 +606,7 @@ direct_dependencies: - espressif/esp_lcd_co5300 - espressif/esp_lcd_touch_cst9220 - espressif/esp_lvgl_adapter +- espressif/esp_tinyusb - espressif/esp_video - espressif/hand_detect - espressif/hand_gesture_recognition @@ -578,6 +615,6 @@ direct_dependencies: - idf - lvgl/lvgl - tangyumei3535/bmm150_sensorapi -manifest_hash: 53e3bd813b8217d5baee702eb587a7358830bd13a6fd85c8f6f41b0109f559f2 +manifest_hash: 1dc20ce54ce98026017ccc890f8fb7d2105f22c091675f3500f9fa8f6a9ce56d target: esp32s31 version: 3.0.0 diff --git a/firmware/targets/esp_mosaico/main/gesture_camera.cpp b/firmware/targets/esp_mosaico/main/gesture_camera.cpp index 31e3881..d08e61d 100644 --- a/firmware/targets/esp_mosaico/main/gesture_camera.cpp +++ b/firmware/targets/esp_mosaico/main/gesture_camera.cpp @@ -57,7 +57,7 @@ bool GestureCamera::start() failure_code_ = 0; if (pins_claimed_ || fd_ >= 0) return fail("start while resources are held"); if (const esp_err_t error = bsp_subboard_init(); error != ESP_OK) return fail("subboard init", error); - // D2 shares GPIO33 with Serial/JTAG. The UART console remains available. + // D2 shares GPIO33 with Serial/JTAG; the Type-C USB OTG console uses separate pins. restore_usb_pad_ = usb_serial_jtag_ll_phy_is_pad_enabled(); restore_usb_clock_ = usb_serial_jtag_ll_module_is_enabled(); restore_usb_interrupts_ = restore_usb_clock_ ? usb_serial_jtag_ll_get_intr_ena_status() : 0; diff --git a/firmware/targets/esp_mosaico/sdkconfig.defaults b/firmware/targets/esp_mosaico/sdkconfig.defaults index 0482530..068921f 100644 --- a/firmware/targets/esp_mosaico/sdkconfig.defaults +++ b/firmware/targets/esp_mosaico/sdkconfig.defaults @@ -72,8 +72,12 @@ CONFIG_ESP_DESKTOP_BUDDY_FOLDER_PUSH_MAX_DECODED_CHUNK=2880 CONFIG_ESP_DESKTOP_BUDDY_FOLDER_PUSH_MAX_TRANSFER_BYTES=7340544 CONFIG_ESP_DESKTOP_BUDDY_FOLDER_PUSH_FLAT_PATHS_ONLY=y -# CameraBoard D2 occupies the USB Serial/JTAG pad; retain UART diagnostics. +# CameraBoard D2 occupies the USB Serial/JTAG pad. Type-C CDC uses USB OTG. CONFIG_ESP_CONSOLE_SECONDARY_NONE=y +CONFIG_TINYUSB_CDC_ENABLED=y +CONFIG_USB_HS_CONSOLE_USB_CDC_AUTO_DOWNLOAD=y +CONFIG_USB_HS_CONSOLE_USB_CDC_AUTO_INIT=y +CONFIG_TINYUSB_DESC_PRODUCT_STRING="QuotaFrame Mosaico" CONFIG_ESP_VIDEO_ENABLE_DVP_VIDEO_DEVICE=y CONFIG_CAMERA_OV3640=y CONFIG_CAMERA_OV3640_DVP_YUV422_UYVY_640X480_30FPS=y From 9afc47cca04f11b4232f86d000f47fe50ec5303b Mon Sep 17 00:00:00 2001 From: wulu Date: Sat, 3 Oct 2026 17:07:22 +0800 Subject: [PATCH 2/4] docs(mosaico): clarify CDC flashing and validation scope --- docs/validation/mosaico-gestures.md | 44 ++++++++++++++++++- .../targets/esp_mosaico/CAMERA_UPSTREAM.md | 7 +-- firmware/targets/esp_mosaico/README.md | 6 ++- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/docs/validation/mosaico-gestures.md b/docs/validation/mosaico-gestures.md index 5a5fde4..643e91c 100644 --- a/docs/validation/mosaico-gestures.md +++ b/docs/validation/mosaico-gestures.md @@ -38,9 +38,49 @@ eim run "idf.py -C firmware/targets/esp_mosaico build" v6.1 ## 有线烧录 -按住 BOOT 再开机进入 ROM 下载模式。整段擦写大应用可能断连,应用按 1 MiB 分段烧录,不直接使用 `idf.py flash` 或未拆分的 `@flash_args`。 +首次安装、应用无法启动或 USB CDC 初始化失败时,按住 BOOT 再开机进入 ROM 下载模式。已安装支持 CDC 自动下载的固件且应用正常运行时,可通过 Type-C 触发下载,无需按键。 -地址从目标应用槽起点逐段递增。使用 ESP-IDF v6.1 环境中的 esptool,参数为 `--chip esp32s31 --no-stub`、460800 波特率、DIO/80 MHz/16 MB,并指定 `--after no-reset`。烧录前核对分段拼接内容与构建应用一致,烧录后确认每段设备哈希校验成功,再通过 POWER 关机重启。 +先关闭占用串口的监视器,再列出端口: + +```sh +eim run "python -m serial.tools.list_ports -v" v6.1 +``` + +确认产品为 `QuotaFrame Mosaico`、USB VID:PID 为 `303A:1001` 的应用端口。以下 macOS/Linux shell 示例使用 esptool 的复位序列;该流程已在 macOS 验证。将 `APP_PORT` 替换为刚确认的端口: + +```sh +cat > /tmp/mosaico-enter-download.py <<'PYTHON' +import sys +import serial +from esptool.reset import USBJTAGSerialReset + +port = serial.Serial(port=None, baudrate=115200, timeout=0.2) +port.dtr = True +port.rts = True +port.port = sys.argv[1] +port.open() +try: + USBJTAGSerialReset(port).reset() +except (OSError, serial.SerialException) as error: + print(f"CDC reset interrupted: {error}; verify the ROM port below.") +finally: + port.close() +PYTHON +eim run "python /tmp/mosaico-enter-download.py APP_PORT" v6.1 +eim run "python -m serial.tools.list_ports -v" v6.1 +``` + +等待设备重新枚举,确认 `303A:0020` 的 ESP32-S31 ROM 端口后,将它用于后续烧录。端口名称可能变化,不能继续沿用应用端口;若未出现 ROM 端口,应停止烧录并检查连接,必要时使用 BOOT 恢复。复位期间的串口断开提示本身不代表已进入下载模式。 + +整段擦写大应用可能断连,应用按 1 MiB 分段烧录,不直接使用 `idf.py flash` 或未拆分的 `@flash_args`。 + +地址从目标应用槽起点逐段递增。使用 ESP-IDF v6.1 环境中的 esptool,参数为 `--chip esp32s31 --no-stub`、460800 波特率、DIO/80 MHz/16 MB,并指定 `--after no-reset`。烧录前核对分段拼接内容与构建应用一致,烧录后确认每段设备哈希校验成功,再执行以下命令读取芯片标识并通过看门狗复位返回应用。将 `ROM_PORT` 替换为重新枚举后的 ROM 端口: + +```sh +eim run "python -m esptool --chip esp32s31 --port ROM_PORT --no-stub --before no-reset --after hard-reset chip-id" v6.1 +``` + +应用 CDC 会再次枚举,查看日志前重新确认端口。软件复位失败时,可通过 POWER 关机重启。 仅更新当前应用时,不写入 NVS、分区表或初始 OTA 元数据。完整安装使用构建产物匹配的 bootloader、分区表及初始化数据,应用仍分段写入。整片擦除或写入包含填充区域的合并镜像会影响已保存的配对和设置。 diff --git a/firmware/targets/esp_mosaico/CAMERA_UPSTREAM.md b/firmware/targets/esp_mosaico/CAMERA_UPSTREAM.md index 766fac5..cb89660 100644 --- a/firmware/targets/esp_mosaico/CAMERA_UPSTREAM.md +++ b/firmware/targets/esp_mosaico/CAMERA_UPSTREAM.md @@ -50,9 +50,10 @@ or the esp-claw video wrapper. Preserve the copyright notice in the adapted file The camera uses its onboard oscillator and the existing BSP subboard I2C bus. GPIO14 is D4 during capture; GPIO33 is D2 and cannot concurrently serve USB -Serial/JTAG. GPIO34 is the active-low illuminator and remains high. UART logging -is retained. Start/stop and frame ownership belong to the vision worker; the -application task exchanges context and bounded events only. +Serial/JTAG. GPIO34 is the active-low illuminator and remains high. Early boot +logs use UART0. After USB CDC initialization, application logs use the independent +Type-C USB OTG port. Start/stop and frame ownership belong to the vision worker; +the application task exchanges context and bounded events only. Start-up timing follows `boards/ESP32_S31_MOSAICO/camera.c` in [esp-vision](https://github.com/espressif/esp-vision) and `mosaico_module_camera` diff --git a/firmware/targets/esp_mosaico/README.md b/firmware/targets/esp_mosaico/README.md index 0e43e39..2b48e37 100644 --- a/firmware/targets/esp_mosaico/README.md +++ b/firmware/targets/esp_mosaico/README.md @@ -37,11 +37,13 @@ USB 复位时串口会重新枚举,必要时重新选择端口。连接日志 该路径不使用左槽 CameraBoard 占用的 USB Serial/JTAG 引脚。大镜像的分段写入要求 仍见[有线烧录说明](../../../docs/validation/mosaico-gestures.md#有线烧录)。 -2026-10-03 已在 macOS 使用 esptool 5.3.1 的 USBJTAGSerialReset 序列验证: +2026-10-03 的实机验证使用包含 BLE OTA 改动与 CDC 功能的组合固件, +在 macOS 使用 esptool 5.3.1 的 USBJTAGSerialReset 序列验证: 应用 CDC → ROM 下载模式 → 看门狗复位 → 应用 CDC,全程无需按键。 应用 CDC 与 ROM 使用不同的 USB 标识,端口会变化;此项验证不代表单条 `idf.py flash` 命令已自动处理端口切换。摄像头运行时已连续读取 CDC 日志 -15 秒,未发生断连;长时间并行运行的稳定性仍待验证。 +15 秒,未发生断连;长时间并行运行的稳定性仍待验证。独立 CDC 分支已通过 +ESP-IDF v6.1 构建,尚未单独烧录验证。 ## 当前实现 From a5eaff0bad60ccbc061e2b28c0b4580d4c422bdb Mon Sep 17 00:00:00 2001 From: wulu Date: Sat, 3 Oct 2026 17:17:13 +0800 Subject: [PATCH 3/4] docs(mosaico): specify USB console API guarantees --- firmware/components/usb_hs_console/UPSTREAM.md | 3 ++- .../components/usb_hs_console/usb_console.c | 2 +- .../components/usb_hs_console/usb_console.h | 17 ++++++++++++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/firmware/components/usb_hs_console/UPSTREAM.md b/firmware/components/usb_hs_console/UPSTREAM.md index b676f13..33335a0 100644 --- a/firmware/components/usb_hs_console/UPSTREAM.md +++ b/firmware/components/usb_hs_console/UPSTREAM.md @@ -15,7 +15,8 @@ it does not expose hardware JTAG. Only the ESP-Mosaico target includes it. - Require ESP-IDF >=6.1 and constrain esp_tinyusb to the compatible 2.x API; the target lockfile records the resolved version. - Include the ROM print declaration and correct obsolete Kconfig names in the - public header. + public header; document initialization errors, serialized access and port + re-enumeration. Clarify the stdio buffering comment. - Continue application startup with a warning if USB console initialization fails, preserving the display and BLE recovery path. diff --git a/firmware/components/usb_hs_console/usb_console.c b/firmware/components/usb_hs_console/usb_console.c index aebcc64..2d14c10 100644 --- a/firmware/components/usb_hs_console/usb_console.c +++ b/firmware/components/usb_hs_console/usb_console.c @@ -208,7 +208,7 @@ esp_err_t bsp_usb_console_init(void) return ret; } - /* Keep application logs observable even when the host opens CDC after app_main starts. */ + /* Disable stdio buffering so subsequent logs are forwarded without waiting for a full buffer. */ setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); diff --git a/firmware/components/usb_hs_console/usb_console.h b/firmware/components/usb_hs_console/usb_console.h index 654e380..596d069 100644 --- a/firmware/components/usb_hs_console/usb_console.h +++ b/firmware/components/usb_hs_console/usb_console.h @@ -19,15 +19,26 @@ extern "C" { * CONFIG_USB_HS_CONSOLE_USB_CDC_AUTO_INIT enables this automatically before * app_main(). Call it manually only when automatic initialization is disabled. * On success, stdin, stdout, stderr and subsequent ESP_LOG output use TinyUSB - * CDC-ACM interface 0. Repeated calls are harmless. + * CDC-ACM interface 0. Calls must be serialized; a successful initialization + * makes subsequent calls return ESP_OK without reinstalling the driver. + * Initialization and state queries must not run concurrently. * * When CONFIG_USB_HS_CONSOLE_USB_CDC_AUTO_DOWNLOAD is enabled, the same interface also * emulates the USB-Serial/JTAG DTR/RTS reset behavior and uses its VID/PID, so - * idf.py can reset into ROM download mode without extra esptool arguments. + * esptool can select the matching reset strategy from the USB identifier. + * The ROM port may re-enumerate under a different device name. + * + * @return ESP_OK if initialization succeeds or the console is already initialized; + * otherwise, the error from timer setup, shutdown-handler registration or TinyUSB + * initialization. */ esp_err_t bsp_usb_console_init(void); -/** @return true after the USB CDC console has initialized successfully. */ +/** + * @brief Query initialization state without probing the USB host connection. + * Calls must not overlap bsp_usb_console_init(). + * @return true after successful initialization; false before it or after failure. + */ bool bsp_usb_console_is_initialized(void); #ifdef __cplusplus From 007287da25c72803d09c64607d9faa292c3dcee8 Mon Sep 17 00:00:00 2001 From: wulu Date: Sat, 3 Oct 2026 17:39:38 +0800 Subject: [PATCH 4/4] docs(mosaico): record standalone CDC flash verification --- firmware/targets/esp_mosaico/README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/firmware/targets/esp_mosaico/README.md b/firmware/targets/esp_mosaico/README.md index 2b48e37..d124234 100644 --- a/firmware/targets/esp_mosaico/README.md +++ b/firmware/targets/esp_mosaico/README.md @@ -37,13 +37,16 @@ USB 复位时串口会重新枚举,必要时重新选择端口。连接日志 该路径不使用左槽 CameraBoard 占用的 USB Serial/JTAG 引脚。大镜像的分段写入要求 仍见[有线烧录说明](../../../docs/validation/mosaico-gestures.md#有线烧录)。 -2026-10-03 的实机验证使用包含 BLE OTA 改动与 CDC 功能的组合固件, -在 macOS 使用 esptool 5.3.1 的 USBJTAGSerialReset 序列验证: -应用 CDC → ROM 下载模式 → 看门狗复位 → 应用 CDC,全程无需按键。 +2026-10-03 已在 macOS 使用 esptool 5.3.1 验证独立 CDC 固件(`a5eaff0`): +从应用 CDC 免按键进入 ROM,将 5,461,504 字节应用分为六段写入当前应用槽, +逐段及完整应用校验均通过;复位前确认 bootloader、分区表、NVS、PHY 和 +OTA 元数据未被改写。新固件启动后,再次完成应用 CDC → ROM 下载模式 → +看门狗复位 → 应用 CDC,全程无需按键。 + +两次启动均检测到 SC101IOT 并持续采集;分别读取 CDC 日志 30 秒、20 秒, +未见崩溃或串口断连。这是短时并行验证,长期稳定性和 Windows 行为仍待验证。 应用 CDC 与 ROM 使用不同的 USB 标识,端口会变化;此项验证不代表单条 -`idf.py flash` 命令已自动处理端口切换。摄像头运行时已连续读取 CDC 日志 -15 秒,未发生断连;长时间并行运行的稳定性仍待验证。独立 CDC 分支已通过 -ESP-IDF v6.1 构建,尚未单独烧录验证。 +`idf.py flash` 命令已自动处理端口切换。 ## 当前实现