From 1c9c394197f2596ab428464621b8ff42c9e9d737 Mon Sep 17 00:00:00 2001 From: JiangGuangMing Date: Thu, 28 May 2026 11:39:19 +0800 Subject: [PATCH] feat(usb): add usb wakeup from light sleep example --- examples/peripherals/.build-test-rules.yml | 4 + .../device/tud_cdc_acm_wakeup/CMakeLists.txt | 8 + .../usb/device/tud_cdc_acm_wakeup/README.md | 83 +++++++++ .../tud_cdc_acm_wakeup/main/CMakeLists.txt | 3 + .../tud_cdc_acm_wakeup/main/idf_component.yml | 4 + .../main/tud_cdc_acm_wakeup_main.c | 167 ++++++++++++++++++ .../tud_cdc_acm_wakeup/sdkconfig.defaults | 40 +++++ 7 files changed, 309 insertions(+) create mode 100644 examples/peripherals/usb/device/tud_cdc_acm_wakeup/CMakeLists.txt create mode 100644 examples/peripherals/usb/device/tud_cdc_acm_wakeup/README.md create mode 100644 examples/peripherals/usb/device/tud_cdc_acm_wakeup/main/CMakeLists.txt create mode 100644 examples/peripherals/usb/device/tud_cdc_acm_wakeup/main/idf_component.yml create mode 100644 examples/peripherals/usb/device/tud_cdc_acm_wakeup/main/tud_cdc_acm_wakeup_main.c create mode 100644 examples/peripherals/usb/device/tud_cdc_acm_wakeup/sdkconfig.defaults diff --git a/examples/peripherals/.build-test-rules.yml b/examples/peripherals/.build-test-rules.yml index 1700a3324af..ab438750322 100644 --- a/examples/peripherals/.build-test-rules.yml +++ b/examples/peripherals/.build-test-rules.yml @@ -817,6 +817,10 @@ examples/peripherals/usb/device/cherryusb_serial_device: temporary: true reason: CherryUSB does not support esp32h4 or esp32s31 +examples/peripherals/usb/device/tud_cdc_acm_wakeup: + disable: + - if: IDF_TARGET not in ["esp32p4", "esp32s31"] + examples/peripherals/usb/device/tusb_ncm: disable: - if: SOC_USB_OTG_SUPPORTED != 1 or SOC_WIFI_SUPPORTED != 1 diff --git a/examples/peripherals/usb/device/tud_cdc_acm_wakeup/CMakeLists.txt b/examples/peripherals/usb/device/tud_cdc_acm_wakeup/CMakeLists.txt new file mode 100644 index 00000000000..c16aac83db3 --- /dev/null +++ b/examples/peripherals/usb/device/tud_cdc_acm_wakeup/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +idf_build_set_property(MINIMAL_BUILD ON) +project(tud_cdc_acm_wakeup) diff --git a/examples/peripherals/usb/device/tud_cdc_acm_wakeup/README.md b/examples/peripherals/usb/device/tud_cdc_acm_wakeup/README.md new file mode 100644 index 00000000000..8e021256d46 --- /dev/null +++ b/examples/peripherals/usb/device/tud_cdc_acm_wakeup/README.md @@ -0,0 +1,83 @@ +| Supported Targets | ESP32-P4 | ESP32-S31 | +| ----------------- | -------- | --------- | + +# TinyUSB CDC ACM Wakeup Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example shows how to set up an ESP chip as a USB CDC ACM device that enters light sleep when the USB bus is suspended and wakes up from USB activity. + +As a USB stack, a TinyUSB component is used. + +## How to use example + +The example implements a USB CDC ACM echo device. When the host suspends the USB bus, the `tud_suspend_cb` callback configures the USB peripheral suspend state and enters light sleep. USB bus activity from the host wakes the chip and the CDC ACM echo device continues running. + +### Hardware Required + +Any ESP board that supports USB wakeup from light sleep. + +#### Pin Assignment + +_Note:_ In case your board doesn't have micro-USB connector connected to USB-OTG peripheral, you may have to DIY a cable and connect **D+** and **D-** to the pins listed below. + +See common pin assignments for USB Device examples from [upper level](../../README.md#common-pin-assignments). + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +```bash +idf.py -p PORT flash monitor +``` + +(Replace PORT with the name of the serial port to use.) + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +After the flashing you should see this output: + +``` +I (285) tud_cdc_acm_wakeup: USB initialization +I (455) TinyUSB: TinyUSB Driver installed +I (465) tud_cdc_acm_wakeup: USB initialization DONE +``` + +Connect to the serial port (e.g. on Linux, it should be `/dev/ttyACM0`) by any terminal application (e.g. `picocom /dev/ttyACM0`). +Now you can send data strings to the device, the device will echo back the same data string. + +When the USB host suspends and resumes the bus, the monitor output should include: + +``` +I (122142) tud_cdc_acm_wakeup: 0x4ff3afa4 48 65 6c 6c 6f |Hello| +I (122272) tud_cdc_acm_wakeup: Data from channel 0: +I (122272) tud_cdc_acm_wakeup: 0x4ff3afa4 48 65 6c 6c 6f |Hello| +I (122402) tud_cdc_acm_wakeup: Data from channel 0: +I (122402) tud_cdc_acm_wakeup: 0x4ff3afa4 48 65 6c 6c 6f |Hello| +I (122622) tud_cdc_acm_wakeup: Data from channel 0: +I (122622) tud_cdc_acm_wakeup: 0x4ff3afa4 48 65 6c 6c 6f |Hello| +I (127432) tud_cdc_acm_wakeup: Line state changed on channel 0: DTR:0, RTS:0 +I (132402) tud_cdc_acm_wakeup: USB suspended, entering light sleep +I (132422) tud_cdc_acm_wakeup: Woke up from: USB +I (132422) tud_cdc_acm_wakeup: USB resumed +I (145762) tud_cdc_acm_wakeup: Line state changed on channel 0: DTR:0, RTS:0 +I (145812) tud_cdc_acm_wakeup: Line state changed on channel 0: DTR:0, RTS:0 +I (145812) tud_cdc_acm_wakeup: Line state changed on channel 0: DTR:0, RTS:0 +I (145822) tud_cdc_acm_wakeup: Line state changed on channel 0: DTR:0, RTS:0 +I (145822) tud_cdc_acm_wakeup: Line state changed on channel 0: DTR:0, RTS:0 +I (145832) tud_cdc_acm_wakeup: Line state changed on channel 0: DTR:0, RTS:0 +I (148762) tud_cdc_acm_wakeup: Data from channel 0: +I (148762) tud_cdc_acm_wakeup: 0x4ff3afa4 48 65 6c 6c 6f |Hello| +I (148902) tud_cdc_acm_wakeup: Data from channel 0: +I (148902) tud_cdc_acm_wakeup: 0x4ff3afa4 48 65 6c 6c 6f |Hello| +I (149032) tud_cdc_acm_wakeup: Data from channel 0: +I (149032) tud_cdc_acm_wakeup: 0x4ff3afa4 48 65 6c 6c 6f |Hello| +I (149252) tud_cdc_acm_wakeup: Data from channel 0: +I (149252) tud_cdc_acm_wakeup: 0x4ff3afa4 48 65 6c 6c 6f |Hello| +I (151382) tud_cdc_acm_wakeup: Line state changed on channel 0: DTR:0, RTS:0 +I (155312) tud_cdc_acm_wakeup: USB suspended, entering light sleep +``` diff --git a/examples/peripherals/usb/device/tud_cdc_acm_wakeup/main/CMakeLists.txt b/examples/peripherals/usb/device/tud_cdc_acm_wakeup/main/CMakeLists.txt new file mode 100644 index 00000000000..10b20b81a8c --- /dev/null +++ b/examples/peripherals/usb/device/tud_cdc_acm_wakeup/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "tud_cdc_acm_wakeup_main.c" + INCLUDE_DIRS . + PRIV_REQUIRES esp_hal_usb) diff --git a/examples/peripherals/usb/device/tud_cdc_acm_wakeup/main/idf_component.yml b/examples/peripherals/usb/device/tud_cdc_acm_wakeup/main/idf_component.yml new file mode 100644 index 00000000000..e1a93a262ee --- /dev/null +++ b/examples/peripherals/usb/device/tud_cdc_acm_wakeup/main/idf_component.yml @@ -0,0 +1,4 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/esp_tinyusb: + version: "^2.0.1~1" diff --git a/examples/peripherals/usb/device/tud_cdc_acm_wakeup/main/tud_cdc_acm_wakeup_main.c b/examples/peripherals/usb/device/tud_cdc_acm_wakeup/main/tud_cdc_acm_wakeup_main.c new file mode 100644 index 00000000000..59c8496d07a --- /dev/null +++ b/examples/peripherals/usb/device/tud_cdc_acm_wakeup/main/tud_cdc_acm_wakeup_main.c @@ -0,0 +1,167 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include "esp_log.h" +#include "esp_rom_serial_output.h" +#include "esp_sleep.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "hal/usb_utmi_hal.h" +#include "sdkconfig.h" +#include "soc/soc_caps.h" +#include "tinyusb.h" +#include "tinyusb_cdc_acm.h" +#include "tinyusb_default_config.h" + +#if !SOC_PM_SUPPORT_USB_WAKEUP || !SOC_PM_SUPPORT_CNNT_PD +#error This example requires a target with SOC_PM_SUPPORT_USB_WAKEUP. +#endif + +static const char *TAG = "tud_cdc_acm_wakeup"; +static uint8_t rx_buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE + 1]; + +/** + * @brief Application Queue + */ +static QueueHandle_t app_queue; +typedef struct { + uint8_t buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE + 1]; // Data buffer + size_t buf_len; // Number of bytes received + uint8_t itf; // Index of CDC device interface +} app_message_t; + +/** + * @brief CDC device RX callback + * + * CDC device signals, that new data were received + * + * @param[in] itf CDC device index + * @param[in] event CDC event type + */ +void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event) +{ + (void) event; + + size_t rx_size = 0; + + esp_err_t ret = tinyusb_cdcacm_read(itf, rx_buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size); + if (ret == ESP_OK) { + app_message_t tx_msg = { + .buf_len = rx_size, + .itf = itf, + }; + + memcpy(tx_msg.buf, rx_buf, rx_size); + xQueueSend(app_queue, &tx_msg, 0); + } else { + ESP_LOGE(TAG, "Read Error"); + } +} + +/** + * @brief CDC device line change callback + * + * CDC device signals, that the DTR, RTS states changed + * + * @param[in] itf CDC device index + * @param[in] event CDC event type + */ +void tinyusb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event) +{ + int dtr = event->line_state_changed_data.dtr; + int rts = event->line_state_changed_data.rts; + ESP_LOGI(TAG, "Line state changed on channel %d: DTR:%d, RTS:%d", itf, dtr, rts); +} + +void tud_suspend_cb(bool remote_wakeup_en) +{ + (void) remote_wakeup_en; + + ESP_LOGI(TAG, "USB suspended, entering light sleep"); + usb_utmi_hal_set_suspend_state(true); + + esp_rom_output_tx_wait_idle(CONFIG_ESP_CONSOLE_UART_NUM); + + esp_err_t err = esp_light_sleep_start(); + if (err != ESP_OK) { + ESP_LOGW(TAG, "Light sleep rejected: %s", esp_err_to_name(err)); + } else { + uint32_t causes = esp_sleep_get_wakeup_causes(); + if (causes & BIT(ESP_SLEEP_WAKEUP_UNDEFINED)) { + ESP_LOGI(TAG, "Woke up from: unknown"); + printf("%lx\n", causes); + return; + } + if (causes & BIT(ESP_SLEEP_WAKEUP_USB)) { + ESP_LOGI(TAG, "Woke up from: USB"); + } + } + usb_utmi_hal_set_suspend_state(false); + usb_utmi_hal_clear_wakeup_status(); +} + +void tud_resume_cb(void) +{ + ESP_LOGI(TAG, "USB resumed"); +} + +void tud_reset_cb(void) +{ + ESP_LOGI(TAG, "USB reset"); +} + +void app_main(void) +{ + // Create FreeRTOS primitives + app_queue = xQueueCreate(5, sizeof(app_message_t)); + assert(app_queue); + app_message_t msg; + + ESP_ERROR_CHECK(esp_sleep_cpu_retention_init()); + ESP_ERROR_CHECK(esp_sleep_enable_usb_wakeup()); + ESP_ERROR_CHECK(esp_sleep_pd_config(ESP_PD_DOMAIN_CNNT, ESP_PD_OPTION_ON)); + + ESP_LOGI(TAG, "USB initialization"); + const tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); + ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg)); + + tinyusb_config_cdcacm_t acm_cfg = { + .cdc_port = TINYUSB_CDC_ACM_0, + .callback_rx = &tinyusb_cdc_rx_callback, // the first way to register a callback + .callback_rx_wanted_char = NULL, + .callback_line_state_changed = NULL, + .callback_line_coding_changed = NULL + }; + + ESP_ERROR_CHECK(tinyusb_cdcacm_init(&acm_cfg)); + /* the second way to register a callback */ + ESP_ERROR_CHECK(tinyusb_cdcacm_register_callback( + TINYUSB_CDC_ACM_0, + CDC_EVENT_LINE_STATE_CHANGED, + &tinyusb_cdc_line_state_changed_callback)); + + ESP_LOGI(TAG, "USB initialization DONE"); + while (1) { + if (xQueueReceive(app_queue, &msg, portMAX_DELAY)) { + if (msg.buf_len) { + /* Print received data */ + ESP_LOGI(TAG, "Data from channel %d:", msg.itf); + ESP_LOG_BUFFER_HEXDUMP(TAG, msg.buf, msg.buf_len, ESP_LOG_INFO); + + /* Write back */ + tinyusb_cdcacm_write_queue(msg.itf, msg.buf, msg.buf_len); + esp_err_t err = tinyusb_cdcacm_write_flush(msg.itf, 0); + if (err != ESP_OK) { + ESP_LOGE(TAG, "CDC ACM write flush error: %s", esp_err_to_name(err)); + } + } + } + } +} diff --git a/examples/peripherals/usb/device/tud_cdc_acm_wakeup/sdkconfig.defaults b/examples/peripherals/usb/device/tud_cdc_acm_wakeup/sdkconfig.defaults new file mode 100644 index 00000000000..d541888bd98 --- /dev/null +++ b/examples/peripherals/usb/device/tud_cdc_acm_wakeup/sdkconfig.defaults @@ -0,0 +1,40 @@ +# This file was generated using idf.py save-defconfig or menuconfig [D] key. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) 6.2.0 Project Minimal Configuration +# +# default: +CONFIG_IDF_TARGET="esp32p4" + +# +# Component config +# + +# +# Hardware Settings +# + +# +# Chip revision +# +CONFIG_ESP32P4_REV_MIN_300=y +# end of Chip revision +# end of Hardware Settings + +# +# Power Management +# +CONFIG_PM_ENABLE=y +CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP=y +# end of Power Management + +# +# TinyUSB Stack +# +CONFIG_TINYUSB_DEBUG_LEVEL=1 + +# +# Communication Device Class (CDC) +# +CONFIG_TINYUSB_CDC_ENABLED=y +# end of Communication Device Class (CDC) +# end of TinyUSB Stack +# end of Component config