summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/dual/CMakeLists.txt1
-rw-r--r--examples/dual/dynamic_switch/CMakeLists.txt30
-rw-r--r--examples/dual/dynamic_switch/CMakePresets.json6
-rw-r--r--examples/dual/dynamic_switch/Makefile16
-rw-r--r--examples/dual/dynamic_switch/README.md60
-rw-r--r--examples/dual/dynamic_switch/only.txt10
-rw-r--r--examples/dual/dynamic_switch/src/CMakeLists.txt4
-rw-r--r--examples/dual/dynamic_switch/src/main.c496
-rw-r--r--examples/dual/dynamic_switch/src/tusb_config.h158
-rw-r--r--examples/dual/dynamic_switch/src/usb_descriptors.c199
10 files changed, 980 insertions, 0 deletions
diff --git a/examples/dual/CMakeLists.txt b/examples/dual/CMakeLists.txt
index 4978f1fab..8727ea938 100644
--- a/examples/dual/CMakeLists.txt
+++ b/examples/dual/CMakeLists.txt
@@ -12,6 +12,7 @@ else ()
set(EXAMPLE_LIST
host_hid_to_device_cdc
host_info_to_device_cdc
+ dynamic_switch
)
foreach (example ${EXAMPLE_LIST})
diff --git a/examples/dual/dynamic_switch/CMakeLists.txt b/examples/dual/dynamic_switch/CMakeLists.txt
new file mode 100644
index 000000000..7cad30727
--- /dev/null
+++ b/examples/dual/dynamic_switch/CMakeLists.txt
@@ -0,0 +1,30 @@
+cmake_minimum_required(VERSION 3.20)
+
+include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake)
+
+project(dynamic_switch C CXX ASM)
+
+# Checks this example is valid for the family and initializes the project
+family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR})
+
+# Espressif has its own cmake build system
+if(FAMILY STREQUAL "espressif")
+ return()
+endif()
+
+add_executable(${PROJECT_NAME})
+
+# Example source
+target_sources(${PROJECT_NAME} PUBLIC
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/usb_descriptors.c
+ )
+
+# Example include
+target_include_directories(${PROJECT_NAME} PUBLIC
+ ${CMAKE_CURRENT_SOURCE_DIR}/src
+ )
+
+# Configure compilation flags and libraries for the example without RTOS.
+# See the corresponding function in hw/bsp/FAMILY/family.cmake for details.
+family_configure_dual_usb_example(${PROJECT_NAME} noos)
diff --git a/examples/dual/dynamic_switch/CMakePresets.json b/examples/dual/dynamic_switch/CMakePresets.json
new file mode 100644
index 000000000..5cd8971e9
--- /dev/null
+++ b/examples/dual/dynamic_switch/CMakePresets.json
@@ -0,0 +1,6 @@
+{
+ "version": 6,
+ "include": [
+ "../../../hw/bsp/BoardPresets.json"
+ ]
+}
diff --git a/examples/dual/dynamic_switch/Makefile b/examples/dual/dynamic_switch/Makefile
new file mode 100644
index 000000000..44ab54ad7
--- /dev/null
+++ b/examples/dual/dynamic_switch/Makefile
@@ -0,0 +1,16 @@
+include ../../../hw/bsp/family_support.mk
+
+INC += \
+ src \
+
+# Example source
+EXAMPLE_SOURCE += $(wildcard src/*.c)
+SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE))
+
+# Include device and host stack
+SRC_C += \
+ src/class/cdc/cdc_device.c \
+ src/host/hub.c \
+ src/host/usbh.c
+
+include ../../../hw/bsp/family_rules.mk
diff --git a/examples/dual/dynamic_switch/README.md b/examples/dual/dynamic_switch/README.md
new file mode 100644
index 000000000..034787f18
--- /dev/null
+++ b/examples/dual/dynamic_switch/README.md
@@ -0,0 +1,60 @@
+# Dynamic Switch Example
+
+This example demonstrates TinyUSB's dual-role capability by allowing runtime switching between USB device and host modes.
+
+## Features
+
+- **Button-triggered mode switching**: Press the board button to switch between device and host modes
+- **Device Mode**: Acts as a USB CDC (Virtual Serial Port) that echoes all received data
+- **Host Mode**: Enumerates connected USB devices and prints device information
+- **Dynamic switching**: Deinitializes the current stack and reinitializes in the new mode
+
+## Usage
+
+1. **Build and flash** the example to your board
+2. **Default behavior**: The board starts in **Device mode**
+3. **Device mode**:
+ - Connect the board to a PC
+ - Open a serial terminal (e.g., `screen /dev/ttyACM0` or PuTTY)
+ - Type characters - they will be echoed back to you
+4. **Switch to Host mode**:
+ - Press the board button
+ - Connect a USB device to the board
+ - The board will enumerate the device and print its descriptors to the debug console
+5. **Switch back to Device mode**: Press the button again
+
+## LED Patterns
+
+The onboard LED indicates the USB connection status:
+
+- **Fast blink (250ms)**: Not mounted/connected
+- **Slow blink (1000ms)**: Successfully mounted/connected
+- **Very slow blink (2500ms)**: Suspended (device mode only)
+
+## Serial Output
+
+The example prints status messages to the debug UART:
+
+```
+======================================
+TinyUSB Dynamic Switch Example
+Press button to switch between device and host modes
+Starting in DEVICE mode...
+======================================
+
+[DEVICE] Mounted
+
+--- Switching USB mode ---
+Stopping DEVICE mode...
+Starting HOST mode...
+Mode switch complete!
+
+[HOST] Device attached, address = 1
+Device 1: ID 1234:5678 SN ABC123
+Device Descriptor:
+ bLength 18
+ bDescriptorType 1
+ bcdUSB 0200
+ bDeviceClass 239
+ ...
+```
diff --git a/examples/dual/dynamic_switch/only.txt b/examples/dual/dynamic_switch/only.txt
new file mode 100644
index 000000000..8508780e6
--- /dev/null
+++ b/examples/dual/dynamic_switch/only.txt
@@ -0,0 +1,10 @@
+family:espressif
+mcu:STM32C0
+mcu:STM32G0
+mcu:STM32H5
+mcu:STM32F2
+mcu:STM32F4
+mcu:STM32U5
+mcu:STM32F7
+mcu:STM32H7
+mcu:STM32H7RS
diff --git a/examples/dual/dynamic_switch/src/CMakeLists.txt b/examples/dual/dynamic_switch/src/CMakeLists.txt
new file mode 100644
index 000000000..cef2b46ee
--- /dev/null
+++ b/examples/dual/dynamic_switch/src/CMakeLists.txt
@@ -0,0 +1,4 @@
+# This file is for ESP-IDF only
+idf_component_register(SRCS "main.c" "usb_descriptors.c"
+ INCLUDE_DIRS "."
+ REQUIRES boards tinyusb_src)
diff --git a/examples/dual/dynamic_switch/src/main.c b/examples/dual/dynamic_switch/src/main.c
new file mode 100644
index 000000000..c9ad2a835
--- /dev/null
+++ b/examples/dual/dynamic_switch/src/main.c
@@ -0,0 +1,496 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+/* This example demonstrates dynamic switching between device and host modes:
+ * - Press button to switch between device and host modes
+ * - Device mode: CDC echo (echoes input back to output)
+ * - Host mode: Prints connected device information
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "bsp/board_api.h"
+#include "tusb.h"
+
+#if CFG_TUSB_OS == OPT_OS_FREERTOS
+ #ifdef ESP_PLATFORM
+ #define USBD_STACK_SIZE 4096
+ #define USBH_STACK_SIZE 4096
+ #else
+ // Increase stack size when debug log is enabled
+ #define USBD_STACK_SIZE (3*configMINIMAL_STACK_SIZE/2) * (CFG_TUSB_DEBUG ? 2 : 1)
+ #define USBH_STACK_SIZE (3*configMINIMAL_STACK_SIZE/2) * (CFG_TUSB_DEBUG ? 2 : 1)
+ #endif
+
+ #define CDC_STACK_SIZE (configMINIMAL_STACK_SIZE * (CFG_TUSB_DEBUG ? 2 : 1))
+ #define BLINKY_STACK_SIZE configMINIMAL_STACK_SIZE
+#endif
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF PROTOTYPES
+//--------------------------------------------------------------------+
+
+// English
+#define LANGUAGE_ID 0x0409
+
+/* Blink pattern
+ * - 250 ms : not mounted
+ * - 1000 ms : mounted
+ * - 2500 ms : suspended
+ */
+enum {
+ BLINK_NOT_MOUNTED = 250,
+ BLINK_MOUNTED = 1000,
+ BLINK_SUSPENDED = 2500,
+};
+
+#if CFG_TUSB_OS == OPT_OS_FREERTOS
+// static task for FreeRTOS
+#if configSUPPORT_STATIC_ALLOCATION
+StackType_t blinky_stack[BLINKY_STACK_SIZE];
+StaticTask_t blinky_taskdef;
+
+StackType_t usb_stack[USBD_STACK_SIZE > USBH_STACK_SIZE ? USBD_STACK_SIZE : USBH_STACK_SIZE];
+StaticTask_t usb_taskdef;
+
+StackType_t cdc_stack[CDC_STACK_SIZE];
+StaticTask_t cdc_taskdef;
+#endif
+#endif
+
+static uint32_t blink_interval_ms = BLINK_NOT_MOUNTED;
+static tusb_role_t current_role = TUSB_ROLE_DEVICE;
+
+#if CFG_TUSB_OS == OPT_OS_FREERTOS
+static void usb_task(void *param);
+void led_blinking_task(void *param);
+void cdc_task(void *params);
+#else
+void led_blinking_task(void);
+void cdc_task(void);
+#endif
+void usb_mode_switch(void);
+static void print_device_info(uint8_t daddr);
+static void print_utf16(uint16_t* temp_buf, size_t buf_len);
+
+// Declare buffer for USB transfer
+CFG_TUH_MEM_SECTION struct {
+ TUH_EPBUF_TYPE_DEF(tusb_desc_device_t, device);
+ TUH_EPBUF_DEF(serial, 64*sizeof(uint16_t));
+ TUH_EPBUF_DEF(buf, 128*sizeof(uint16_t));
+} desc;
+
+//--------------------------------------------------------------------+
+// Main
+//--------------------------------------------------------------------+
+
+int main(void) {
+ board_init();
+
+ printf("\r\n======================================\r\n");
+ printf("TinyUSB Dynamic Switch Example\r\n");
+ printf("Press button to switch between device and host modes\r\n");
+ printf("Starting in DEVICE mode...\r\n");
+ printf("======================================\r\n\r\n");
+
+#if CFG_TUSB_OS == OPT_OS_FREERTOS
+ // Create FreeRTOS tasks
+#if configSUPPORT_STATIC_ALLOCATION
+ xTaskCreateStatic(led_blinking_task, "blinky", BLINKY_STACK_SIZE, NULL, 1, blinky_stack, &blinky_taskdef);
+ xTaskCreateStatic(usb_task, "usb", USBD_STACK_SIZE > USBH_STACK_SIZE ? USBD_STACK_SIZE : USBH_STACK_SIZE,
+ NULL, configMAX_PRIORITIES-1, usb_stack, &usb_taskdef);
+ xTaskCreateStatic(cdc_task, "cdc", CDC_STACK_SIZE, NULL, configMAX_PRIORITIES - 2, cdc_stack, &cdc_taskdef);
+#else
+ xTaskCreate(led_blinking_task, "blinky", BLINKY_STACK_SIZE, NULL, 1, NULL);
+ xTaskCreate(usb_task, "usb", USBD_STACK_SIZE > USBH_STACK_SIZE ? USBD_STACK_SIZE : USBH_STACK_SIZE,
+ NULL, configMAX_PRIORITIES - 1, NULL);
+ xTaskCreate(cdc_task, "cdc", CDC_STACK_SIZE, NULL, configMAX_PRIORITIES - 2, NULL);
+#endif
+
+#ifndef ESP_PLATFORM
+ // only start scheduler for non-espressif mcu
+ vTaskStartScheduler();
+#endif
+
+#else
+ // Initialize in device mode by default
+ tusb_rhport_init_t dev_init = {
+ .role = TUSB_ROLE_DEVICE,
+ .speed = TUSB_SPEED_AUTO
+ };
+ tusb_init(BOARD_RHPORT, &dev_init);
+ current_role = TUSB_ROLE_DEVICE;
+
+ board_init_after_tusb();
+
+ while (1) {
+ // Check for button press to switch modes
+ static bool pending_switch = false;
+ if (board_button_read()) {
+ if (!pending_switch) {
+ pending_switch = true;
+ usb_mode_switch();
+ }
+ } else {
+ pending_switch = false;
+ }
+
+ // Process USB tasks based on current mode
+ if (current_role == TUSB_ROLE_DEVICE) {
+ tud_task();
+ cdc_task();
+ } else {
+ tuh_task();
+ }
+
+ led_blinking_task();
+ }
+#endif
+}
+
+#ifdef ESP_PLATFORM
+void app_main(void) {
+ main();
+}
+#endif
+
+#if CFG_TUSB_OS == OPT_OS_FREERTOS
+// USB Task for FreeRTOS
+// This top level thread processes all usb events and mode switching
+static void usb_task(void *param) {
+ (void) param;
+
+ // init device stack on configured roothub port
+ // This should be called after scheduler/kernel is started.
+ // Otherwise it could cause kernel issue since USB IRQ handler does use RTOS queue API.
+ tusb_rhport_init_t dev_init = {
+ .role = TUSB_ROLE_DEVICE,
+ .speed = TUSB_SPEED_AUTO
+ };
+ tusb_init(BOARD_RHPORT, &dev_init);
+ current_role = TUSB_ROLE_DEVICE;
+
+ board_init_after_tusb();
+
+ // RTOS forever loop
+ while (1) {
+ // Check for button press to switch modes
+ static bool pending_switch = false;
+ if (board_button_read()) {
+ if (!pending_switch) {
+ pending_switch = true;
+ usb_mode_switch();
+ }
+ } else {
+ pending_switch = false;
+ }
+
+ // Process USB tasks based on current mode
+ // Use _ext version to allow return and read button state
+ if (current_role == TUSB_ROLE_DEVICE) {
+ tud_task_ext(10, false);
+ } else {
+ tuh_task_ext(10, false);
+ }
+ }
+}
+#endif
+
+//--------------------------------------------------------------------+
+// Mode Switching
+//--------------------------------------------------------------------+
+
+void usb_mode_switch(void) {
+ printf("\r\n--- Switching USB mode ---\r\n");
+
+ // Deinitialize current mode
+ if (current_role == TUSB_ROLE_DEVICE) {
+ printf("Stopping DEVICE mode...\r\n");
+ tusb_deinit(BOARD_RHPORT);
+ } else {
+ printf("Stopping HOST mode...\r\n");
+ tusb_deinit(BOARD_RHPORT);
+ }
+
+#if CFG_TUSB_OS == OPT_OS_FREERTOS
+ vTaskDelay(pdMS_TO_TICKS(100)); // Small delay for clean transition
+#else
+ tusb_time_delay_ms_api(100); // Small delay for clean transition
+#endif // Switch to the other mode
+ if (current_role == TUSB_ROLE_DEVICE) {
+ printf("Starting HOST mode...\r\n");
+ tusb_rhport_init_t host_init = {
+ .role = TUSB_ROLE_HOST,
+ .speed = TUSB_SPEED_AUTO
+ };
+ tusb_init(BOARD_RHPORT, &host_init);
+ current_role = TUSB_ROLE_HOST;
+ } else {
+ printf("Starting DEVICE mode...\r\n");
+ tusb_rhport_init_t dev_init = {
+ .role = TUSB_ROLE_DEVICE,
+ .speed = TUSB_SPEED_AUTO
+ };
+ tusb_init(BOARD_RHPORT, &dev_init);
+ current_role = TUSB_ROLE_DEVICE;
+ }
+
+ blink_interval_ms = BLINK_NOT_MOUNTED;
+ printf("Mode switch complete!\r\n\r\n");
+}
+
+//--------------------------------------------------------------------+
+// Device Mode: CDC Task
+//--------------------------------------------------------------------+
+
+#if CFG_TUSB_OS == OPT_OS_FREERTOS
+void cdc_task(void *params) {
+ (void) params;
+
+ // RTOS forever loop
+ while (1) {
+ // Only process CDC when in device mode
+ if (current_role == TUSB_ROLE_DEVICE) {
+ // Connected and there are data available
+ while (tud_cdc_available()) {
+ uint8_t buf[64];
+
+ // Read data
+ uint32_t count = tud_cdc_read(buf, sizeof(buf));
+
+ // Echo back
+ tud_cdc_write(buf, count);
+
+ // Add newline for carriage return
+ for (uint32_t i = 0; i < count; i++) {
+ if (buf[i] == '\r') {
+ tud_cdc_write_char('\n');
+ break;
+ }
+ }
+ }
+
+ tud_cdc_write_flush();
+ }
+
+ vTaskDelay(pdMS_TO_TICKS(10));
+ }
+}
+#else
+void cdc_task(void) {
+ // Connected and there are data available
+ if (tud_cdc_available()) {
+ uint8_t buf[64];
+
+ // Read data
+ uint32_t count = tud_cdc_read(buf, sizeof(buf));
+
+ // Echo back
+ for (uint32_t i = 0; i < count; i++) {
+ tud_cdc_write_char(buf[i]);
+
+ if (buf[i] == '\r') {
+ tud_cdc_write_char('\n');
+ }
+ }
+
+ tud_cdc_write_flush();
+ }
+}
+#endif
+
+//--------------------------------------------------------------------+
+// Device Callbacks
+//--------------------------------------------------------------------+
+
+// Invoked when device is mounted
+void tud_mount_cb(void) {
+ printf("[DEVICE] Mounted\r\n");
+ blink_interval_ms = BLINK_MOUNTED;
+}
+
+// Invoked when device is unmounted
+void tud_umount_cb(void) {
+ printf("[DEVICE] Unmounted\r\n");
+ blink_interval_ms = BLINK_NOT_MOUNTED;
+}
+
+// Invoked when usb bus is suspended
+void tud_suspend_cb(bool remote_wakeup_en) {
+ (void) remote_wakeup_en;
+ printf("[DEVICE] Suspended\r\n");
+ blink_interval_ms = BLINK_SUSPENDED;
+}
+
+// Invoked when usb bus is resumed
+void tud_resume_cb(void) {
+ printf("[DEVICE] Resumed\r\n");
+ blink_interval_ms = tud_mounted() ? BLINK_MOUNTED : BLINK_NOT_MOUNTED;
+}
+
+//--------------------------------------------------------------------+
+// Host Callbacks
+//--------------------------------------------------------------------+
+
+// Invoked when device is mounted (configured)
+void tuh_mount_cb(uint8_t daddr) {
+ printf("[HOST] Device attached, address = %d\r\n", daddr);
+ blink_interval_ms = BLINK_MOUNTED;
+ print_device_info(daddr);
+}
+
+// Invoked when device is unmounted (unplugged)
+void tuh_umount_cb(uint8_t daddr) {
+ printf("[HOST] Device removed, address = %d\r\n", daddr);
+ blink_interval_ms = BLINK_NOT_MOUNTED;
+}
+
+//--------------------------------------------------------------------+
+// Host Device Info
+//--------------------------------------------------------------------+
+
+static void print_device_info(uint8_t daddr) {
+ // Get Device Descriptor
+ uint8_t xfer_result = tuh_descriptor_get_device_sync(daddr, &desc.device, 18);
+ if (XFER_RESULT_SUCCESS != xfer_result) {
+ printf("Failed to get device descriptor\r\n");
+ return;
+ }
+
+ printf("Device %u: ID %04x:%04x SN ", daddr, desc.device.idVendor, desc.device.idProduct);
+
+ xfer_result = XFER_RESULT_FAILED;
+ if (desc.device.iSerialNumber != 0) {
+ xfer_result = tuh_descriptor_get_serial_string_sync(daddr, LANGUAGE_ID, desc.serial, sizeof(desc.serial));
+ }
+ if (XFER_RESULT_SUCCESS != xfer_result) {
+ uint16_t* serial = (uint16_t*)(uintptr_t) desc.serial;
+ serial[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (2 * 3 + 2));
+ serial[1] = 'n';
+ serial[2] = '/';
+ serial[3] = 'a';
+ serial[4] = 0;
+ }
+ print_utf16((uint16_t*)(uintptr_t) desc.serial, sizeof(desc.serial)/2);
+ printf("\r\n");
+
+ printf("Device Descriptor:\r\n");
+ printf(" bLength %u\r\n", desc.device.bLength);
+ printf(" bDescriptorType %u\r\n", desc.device.bDescriptorType);
+ printf(" bcdUSB %04x\r\n", desc.device.bcdUSB);
+ printf(" bDeviceClass %u\r\n", desc.device.bDeviceClass);
+ printf(" bDeviceSubClass %u\r\n", desc.device.bDeviceSubClass);
+ printf(" bDeviceProtocol %u\r\n", desc.device.bDeviceProtocol);
+ printf(" bMaxPacketSize0 %u\r\n", desc.device.bMaxPacketSize0);
+ printf(" idVendor 0x%04x\r\n", desc.device.idVendor);
+ printf(" idProduct 0x%04x\r\n", desc.device.idProduct);
+ printf(" bcdDevice %04x\r\n", desc.device.bcdDevice);
+
+ // Get Manufacturer string
+ if (desc.device.iManufacturer) {
+ if (XFER_RESULT_SUCCESS == tuh_descriptor_get_manufacturer_string_sync(daddr, LANGUAGE_ID, desc.buf, sizeof(desc.buf))) {
+ printf(" iManufacturer %u ", desc.device.iManufacturer);
+ print_utf16((uint16_t*)(uintptr_t) desc.buf, sizeof(desc.buf)/2);
+ printf("\r\n");
+ }
+ }
+
+ // Get Product string
+ if (desc.device.iProduct) {
+ if (XFER_RESULT_SUCCESS == tuh_descriptor_get_product_string_sync(daddr, LANGUAGE_ID, desc.buf, sizeof(desc.buf))) {
+ printf(" iProduct %u ", desc.device.iProduct);
+ print_utf16((uint16_t*)(uintptr_t) desc.buf, sizeof(desc.buf)/2);
+ printf("\r\n");
+ }
+ }
+
+ // Get Serial string
+ if (desc.device.iSerialNumber) {
+ printf(" iSerialNumber %u ", desc.device.iSerialNumber);
+ print_utf16((uint16_t*)(uintptr_t) desc.serial, sizeof(desc.serial)/2);
+ printf("\r\n");
+ } else {
+ printf(" iSerialNumber 0\r\n");
+ }
+
+ printf(" bNumConfigurations %u\r\n", desc.device.bNumConfigurations);
+ printf("\r\n");
+}
+
+static void print_utf16(uint16_t* temp_buf, size_t buf_len) {
+ if (temp_buf[0] == 0 || (temp_buf[0] >> 8) != TUSB_DESC_STRING) {
+ printf("(invalid)");
+ return;
+ }
+
+ size_t chr_count = (temp_buf[0] & 0xff) / 2 - 1;
+ if (chr_count > buf_len - 1) {
+ chr_count = buf_len - 1;
+ }
+
+ for (size_t i = 0; i < chr_count; i++) {
+ uint16_t ch = temp_buf[1 + i];
+ if (ch <= 0x7F) {
+ putchar((char) ch);
+ } else {
+ // TODO support UTF16 to UTF8 conversion
+ putchar('?');
+ }
+ }
+}
+
+//--------------------------------------------------------------------+
+// Blinking Task
+//--------------------------------------------------------------------+
+
+#if CFG_TUSB_OS == OPT_OS_FREERTOS
+void led_blinking_task(void *param) {
+ (void) param;
+ static bool led_state = false;
+
+ // RTOS forever loop
+ while (1) {
+ board_led_write(led_state);
+ led_state = 1 - led_state; // toggle
+ vTaskDelay(pdMS_TO_TICKS(blink_interval_ms));
+ }
+}
+#else
+void led_blinking_task(void) {
+ static uint32_t start_ms = 0;
+ static bool led_state = false;
+
+ // Blink every interval ms
+ if (tusb_time_millis_api() - start_ms < blink_interval_ms) {
+ return; // not enough time
+ }
+ start_ms += blink_interval_ms;
+
+ board_led_write(led_state);
+ led_state = 1 - led_state; // toggle
+}
+#endif
diff --git a/examples/dual/dynamic_switch/src/tusb_config.h b/examples/dual/dynamic_switch/src/tusb_config.h
new file mode 100644
index 000000000..af0d55f14
--- /dev/null
+++ b/examples/dual/dynamic_switch/src/tusb_config.h
@@ -0,0 +1,158 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef _TUSB_CONFIG_H_
+#define _TUSB_CONFIG_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//--------------------------------------------------------------------+
+// Board Specific Configuration
+//--------------------------------------------------------------------+
+
+// RHPort number used can be defined by board.mk, default to port 0
+#ifndef BOARD_RHPORT
+ #if defined(BOARD_TUD_RHPORT)
+ #define BOARD_RHPORT BOARD_TUD_RHPORT
+ #else
+ #define BOARD_RHPORT 0
+ #define BOARD_TUD_RHPORT 0
+ #endif
+#endif
+
+#if defined(BOARD_TUH_RHPORT)
+ #if BOARD_TUH_RHPORT != BOARD_RHPORT
+ #undef BOARD_TUH_RHPORT
+ #define BOARD_TUH_RHPORT BOARD_RHPORT
+ #endif
+#else
+ #define BOARD_TUH_RHPORT BOARD_RHPORT
+#endif
+
+// RHPort max operational speed can defined by board.mk
+#ifndef BOARD_MAX_SPEED
+ #if defined(BOARD_TUD_MAX_SPEED)
+ #define BOARD_MAX_SPEED BOARD_TUD_MAX_SPEED
+ #else
+ #define BOARD_MAX_SPEED OPT_MODE_DEFAULT_SPEED
+ #endif
+#endif
+
+//--------------------------------------------------------------------
+// COMMON CONFIGURATION
+//--------------------------------------------------------------------
+
+// defined by compiler flags for flexibility
+#ifndef CFG_TUSB_MCU
+#error CFG_TUSB_MCU must be defined
+#endif
+
+#ifndef CFG_TUSB_OS
+#define CFG_TUSB_OS OPT_OS_NONE
+#endif
+
+#ifndef CFG_TUSB_DEBUG
+#define CFG_TUSB_DEBUG 0
+#endif
+
+// Enable Device and Host stacks (dual role)
+#define CFG_TUD_ENABLED 1
+#define CFG_TUH_ENABLED 1
+
+// Default is max speed that hardware controller could support with on-chip PHY
+#define CFG_TUD_MAX_SPEED BOARD_MAX_SPEED
+#define CFG_TUH_MAX_SPEED BOARD_MAX_SPEED
+
+/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment.
+ * Tinyusb use follows macros to declare transferring memory so that they can be put
+ * into those specific section.
+ * e.g
+ * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
+ * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4)))
+ */
+#ifndef CFG_TUD_MEM_SECTION
+#define CFG_TUD_MEM_SECTION
+#endif
+
+#ifndef CFG_TUD_MEM_ALIGN
+#define CFG_TUD_MEM_ALIGN __attribute__((aligned(4)))
+#endif
+
+#ifndef CFG_TUH_MEM_SECTION
+#define CFG_TUH_MEM_SECTION CFG_TUD_MEM_SECTION
+#endif
+
+#ifndef CFG_TUH_MEM_ALIGN
+#define CFG_TUH_MEM_ALIGN CFG_TUD_MEM_ALIGN
+#endif
+
+//--------------------------------------------------------------------
+// DEVICE CONFIGURATION
+//--------------------------------------------------------------------
+
+#ifndef CFG_TUD_ENDPOINT0_SIZE
+#define CFG_TUD_ENDPOINT0_SIZE 64
+#endif
+
+//------------- CLASS -------------//
+#define CFG_TUD_CDC 1
+#define CFG_TUD_MSC 0
+#define CFG_TUD_HID 0
+#define CFG_TUD_MIDI 0
+#define CFG_TUD_VENDOR 0
+
+// CDC FIFO size of TX and RX
+#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+
+// CDC Endpoint transfer buffer size, more is faster
+#define CFG_TUD_CDC_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+
+//--------------------------------------------------------------------
+// HOST CONFIGURATION
+//--------------------------------------------------------------------
+
+// Size of buffer to hold descriptors and other data used for enumeration
+#define CFG_TUH_ENUMERATION_BUFSIZE 256
+
+#define CFG_TUH_HUB 1
+// max device support (excluding hub device)
+#define CFG_TUH_DEVICE_MAX (CFG_TUH_HUB ? 4 : 1) // hub typically has 4 ports
+
+#define CFG_TUH_CDC 0
+#define CFG_TUH_HID 0
+#define CFG_TUH_MSC 0
+#define CFG_TUH_VENDOR 0
+
+// max endpoint pair supported by each device
+#define CFG_TUH_ENDPOINT_MAX 16
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _TUSB_CONFIG_H_ */
diff --git a/examples/dual/dynamic_switch/src/usb_descriptors.c b/examples/dual/dynamic_switch/src/usb_descriptors.c
new file mode 100644
index 000000000..54ffc2c18
--- /dev/null
+++ b/examples/dual/dynamic_switch/src/usb_descriptors.c
@@ -0,0 +1,199 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "bsp/board_api.h"
+#include "tusb.h"
+
+/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug.
+ * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC.
+ *
+ * Auto ProductID layout's Bitmap:
+ * [MSB] AUDIO | MIDI | HID | MSC | CDC [LSB]
+ */
+#define PID_MAP(itf, n) ((CFG_TUD_##itf) ? (1 << (n)) : 0)
+#define USB_PID (0x4000 | PID_MAP(CDC, 0) | PID_MAP(MSC, 1) | PID_MAP(HID, 2) | \
+ PID_MAP(MIDI, 3) | PID_MAP(VENDOR, 4))
+
+#define USB_VID 0xCafe
+#define USB_BCD 0x0200
+
+//--------------------------------------------------------------------+
+// Device Descriptors
+//--------------------------------------------------------------------+
+static tusb_desc_device_t const desc_device = {
+ .bLength = sizeof(tusb_desc_device_t),
+ .bDescriptorType = TUSB_DESC_DEVICE,
+ .bcdUSB = USB_BCD,
+ .bDeviceClass = TUSB_CLASS_MISC,
+ .bDeviceSubClass = MISC_SUBCLASS_COMMON,
+ .bDeviceProtocol = MISC_PROTOCOL_IAD,
+
+ .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
+
+ .idVendor = USB_VID,
+ .idProduct = USB_PID,
+ .bcdDevice = 0x0100,
+
+ .iManufacturer = 0x01,
+ .iProduct = 0x02,
+ .iSerialNumber = 0x03,
+
+ .bNumConfigurations = 0x01
+};
+
+// Invoked when received GET DEVICE DESCRIPTOR
+// Application return pointer to descriptor
+uint8_t const *tud_descriptor_device_cb(void) {
+ return (uint8_t const *) &desc_device;
+}
+
+//--------------------------------------------------------------------+
+// Configuration Descriptor
+//--------------------------------------------------------------------+
+
+enum {
+ ITF_NUM_CDC = 0,
+ ITF_NUM_CDC_DATA,
+ ITF_NUM_TOTAL
+};
+
+#if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX
+ // LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number
+ // 0 control, 1 In, 2 Bulk, 3 Iso, 4 In, 5 Bulk etc ...
+ #define EPNUM_CDC_NOTIF 0x81
+ #define EPNUM_CDC_OUT 0x02
+ #define EPNUM_CDC_IN 0x82
+
+#elif defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY)
+ // MCUs that don't support a same endpoint number with different direction IN and OUT defined in tusb_mcu.h
+ // e.g EP1 OUT & EP1 IN cannot exist together
+ #define EPNUM_CDC_NOTIF 0x81
+ #define EPNUM_CDC_OUT 0x02
+ #define EPNUM_CDC_IN 0x83
+
+#else
+ #define EPNUM_CDC_NOTIF 0x81
+ #define EPNUM_CDC_OUT 0x02
+ #define EPNUM_CDC_IN 0x82
+
+#endif
+
+#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN)
+
+static uint8_t const desc_fs_configuration[] = {
+ // Config number, interface count, string index, total length, attribute, power in mA
+ TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
+
+ // Interface number, string index, EP notification address and size, EP data address (out, in) and size.
+ TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64),
+};
+
+#if TUD_OPT_HIGH_SPEED
+static uint8_t const desc_hs_configuration[] = {
+ // Config number, interface count, string index, total length, attribute, power in mA
+ TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
+
+ // Interface number, string index, EP notification address and size, EP data address (out, in) and size.
+ TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 512),
+};
+#endif
+
+// Invoked when received GET CONFIGURATION DESCRIPTOR
+// Application return pointer to descriptor
+// Descriptor contents must exist long enough for transfer to complete
+uint8_t const *tud_descriptor_configuration_cb(uint8_t index) {
+ (void) index; // for multiple configurations
+
+#if TUD_OPT_HIGH_SPEED
+ // Although we are highspeed, host may be fullspeed.
+ return (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_hs_configuration : desc_fs_configuration;
+#else
+ return desc_fs_configuration;
+#endif
+}
+
+//--------------------------------------------------------------------+
+// String Descriptors
+//--------------------------------------------------------------------+
+
+// String Descriptor Index
+enum {
+ STRID_LANGID = 0,
+ STRID_MANUFACTURER,
+ STRID_PRODUCT,
+ STRID_SERIAL,
+};
+
+// array of pointer to string descriptors
+static char const *string_desc_arr[] = {
+ (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
+ "TinyUSB", // 1: Manufacturer
+ "TinyUSB Device", // 2: Product
+ NULL, // 3: Serials, will use unique ID if possible
+ "TinyUSB CDC", // 4: CDC Interface
+};
+
+static uint16_t _desc_str[32 + 1];
+
+// Invoked when received GET STRING DESCRIPTOR request
+// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
+uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
+ (void) langid;
+ size_t chr_count;
+
+ switch (index) {
+ case STRID_LANGID:
+ memcpy(&_desc_str[1], string_desc_arr[0], 2);
+ chr_count = 1;
+ break;
+
+ case STRID_SERIAL:
+ chr_count = board_usb_get_serial(_desc_str + 1, 32);
+ break;
+
+ default:
+ // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors.
+ // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors
+
+ if (!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0]))) { return NULL; }
+
+ const char *str = string_desc_arr[index];
+
+ // Cap at max char
+ chr_count = strlen(str);
+ size_t const max_count = sizeof(_desc_str) / sizeof(_desc_str[0]) - 1; // -1 for string type
+ if (chr_count > max_count) { chr_count = max_count; }
+
+ // Convert ASCII string into UTF-16
+ for (size_t i = 0; i < chr_count; i++) {
+ _desc_str[1 + i] = str[i];
+ }
+ break;
+ }
+
+ // first byte is length (including header), second byte is string type
+ _desc_str[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (2 * chr_count + 2));
+ return _desc_str;
+}