ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

TinyUSB 双角色动态切换实战:详解 dynamic_switch 示例的 Device/Host 运行时模式切换

TinyUSB 双角色动态切换实战:详解 dynamic_switch 示例的 Device/Host 运行时模式切换 嵌入式驱动开发通信物联网【免费下载链接】tinyusbAn open source cross-platform USB stack for embedded system项目地址https://gitcode.com/gh_mirrors/ti/tinyusb点击查看免费下载本指南以 TinyUSB 仓库中的dynamic_switch双角色示例examples/dual/dynamic_switch/README.md为核心深入讲解如何在一块同时具备设备Device与主机Host能力的 USB 控制器上通过按键在两种模式之间运行时来回切换设备模式下板卡作为 CDC 虚拟串口回显字符主机模式下板卡枚举接入的 USB 设备并打印其描述符信息。读完本文你将掌握 TinyUSB 新一代tusb_init(rhport, rh_init)/tusb_deinit(rhport)双角色 API 的使用方式、模式切换的状态机设计以及无 RTOS 与 FreeRTOS 两种环境下的任务组织方法。示例概览TinyUSB 的双角色Dual-Role能力TinyUSB 是一个开源、跨平台的嵌入式 USB 协议栈设备Device栈与主机Host栈可以同时编译进同一个固件。传统的 USB 应用只使用其中一个角色而本示例演示的是 TinyUSB 的**双角色dual-role**能力——在运行时通过tusb_init()/tusb_deinit()让同一个根端口roothub port在 USB 设备与 USB 主机两种角色之间动态切换作为USB 设备Device板卡以 CDC 类虚拟串口枚举到 PC回显终端输入的所有字符作为USB 主机Host板卡枚举接入的 USB 设备如鼠标、键盘、U 盘并把设备的厂商 ID、产品 ID、序列号以及完整设备描述符打印到调试串口两种模式通过板载按键一键切换切换过程会先反初始化当前栈再以新角色重新初始化全程无需复位 MCU。该示例属于examples/dual/目录下的三个双角色示例之一examples/dual/CMakeLists.txt 中与host_hid_to_device_cdc、host_info_to_device_cdc并列与另外两个常驻双角色示例不同dynamic_switch强调的是单一时刻只运行一个角色、且角色可动态互换。功能特性一览按原文档整理本示例的核心特性包括按键触发模式切换按下板载按键即可在设备模式与主机模式之间来回切换设备模式Device Mode作为 USB CDC虚拟串口工作回显所有收到的数据主机模式Host Mode枚举接入的 USB 设备并打印设备信息VID/PID、序列号、描述符等动态切换Dynamic switching先反初始化当前栈再以新模式重新初始化。工程结构与构建方式示例的完整工程位于examples/dual/dynamic_switch/结构如下src/main.c主循环、模式切换逻辑、CDC 回显任务、主机设备信息打印任务、LED 闪烁任务src/tusb_config.hTinyUSB 栈与类驱动的编译期配置src/usb_descriptors.c设备/配置/字符串描述符Makefile与CMakeLists.txt传统 make 与 CMake 两套构建入口only.txt该示例适用的 MCU 系列白名单。从 Makefile 可以看到它同时拉入了设备栈的src/class/cdc/cdc_device.c与主机栈的src/host/hub.c、src/host/usbh.c即设备与主机两套栈代码在同一固件中并存这正是双角色切换的编译前提。CMake 侧则通过family_configure_dual_usb_example(${PROJECT_NAME} noos)CMakeLists.txt按 no-OS 配置初始化工程。only.txt 列出的支持系列包括espressif、LPC43XX、MIMXRT1XXX、STM32C0、STM32C5、STM32G0、STM32H5、STM32F2、STM32F4、STM32U5、STM32F7、STM32H7、STM32H7RS。构建前请确认目标板卡属于上述系列之一且硬件上具备可供 Host 模式使用的 USB 控制器通常为 OTG 类双模式控制器。配置解析tusb_config.h原文档列出了 4 个关键配置项结合 src/tusb_config.h 完整解读如下#define CFG_TUD_CDC 1 #define CFG_TUH_HUB 1 #define CFG_TUH_DEVICE_MAX (CFG_TUH_HUB ? 4 : 1) #define CFG_TUH_ENUMERATION_BUFSIZE 256各配置项的深层含义CFG_TUD_CDC 1启用设备端 CDC 类驱动这是设备模式下虚拟串口回显功能的支撑。同段配置中CFG_TUD_MSC / CFG_TUD_HID / CFG_TUD_MIDI / CFG_TUD_VENDOR均被置 0即设备侧只保留 CDC 一个类src/tusb_config.hCFG_TUH_HUB 1启用主机端 Hub 驱动。注意启用后CFG_TUH_DEVICE_MAX会按hub 通常有 4 个端口的经验值扩展到 4不含 hub 设备本身即最多可同时接入 4 个下游设备src/tusb_config.hCFG_TUH_ENUMERATION_BUFSIZE 256主机枚举阶段用于暂存描述符等数据的缓冲区大小256 字节足以容纳典型设备在枚举期需要读取的设备/配置描述符数据src/tusb_config.h主机侧其余类CFG_TUH_CDC / HID / MSC / VENDOR均置 0src/tusb_config.h因为本示例在主机模式下只做枚举 读描述符不挂载具体类驱动CFG_TUH_ENDPOINT_MAX 16则限制了每个设备可打开的最大端点对数量。文件开头的公共与双栈配置同样值得注意src/tusb_config.h// Enable Device and Host stacks (dual role) #define CFG_TUD_ENABLED 1 #define CFG_TUH_ENABLED 1 #define CFG_TUD_MAX_SPEED BOARD_MAX_SPEED #define CFG_TUH_MAX_SPEED BOARD_MAX_SPEEDCFG_TUD_ENABLED与CFG_TUH_ENABLED同时为 1 是双角色模式切换的配置前提BOARD_MAX_SPEED默认取硬件控制器配合片内 PHY 能支持的最大速度OPT_MODE_DEFAULT_SPEED。CDC 缓冲配置CFG_TUD_CDC_RX_BUFSIZE / TX_BUFSIZE与端点传输缓冲CFG_TUD_CDC_RX_EPSIZE / TX_EPSIZE均按TUD_OPT_HIGH_SPEED在三态HS 512 / FS 64之间选择src/tusb_config.h。核心原理运行时模式切换 API 与调用链新一代双角色初始化 APITinyUSB 为双角色场景引入了显式指定角色与速度的初始化结构。角色与速度枚举定义在 src/common/tusb_types.htypedef enum { TUSB_ROLE_INVALID 0u, TUSB_ROLE_DEVICE 0x1, TUSB_ROLE_HOST 0x2, } tusb_role_t; typedef enum { TUSB_SPEED_FULL 0, TUSB_SPEED_LOW 1, TUSB_SPEED_HIGH 2, TUSB_SPEED_AUTO 0xaa, TUSB_SPEED_INVALID 0xff, } tusb_speed_t;初始化参数结构tusb_rhport_init_t只有两个字段src/common/tusb_types.htypedef struct { tusb_role_t role; // 希望该根端口扮演的角色TUSB_ROLE_DEVICE 或 TUSB_ROLE_HOST tusb_speed_t speed; // 期望链路速度示例统一使用 TUSB_SPEED_AUTO } tusb_rhport_init_t;顶层入口tusb_init(...)是支持 02 个参数的宏src/tusb.h传(void)时为向后兼容的旧式双栈同时初始化传(rhport, rh_init)时则为显式单角色初始化。真正实现位于 src/tusb.c它会先把角色写入全局数组_tusb_rhport_role[rhport]再按角色分发到tud_rhport_init()设备栈或tuh_rhport_init()主机栈二者向下分别调用 DCD 的dcd_init()与 HCD 的hcd_init()如 src/host/hcd.h 所声明。配套的tusb_deinit(rhport)同样依据_tusb_rhport_role决定反初始化设备还是主机栈并把角色置回TUSB_ROLE_INVALIDsrc/tusb.c。中断分发tusb_int_handler()也依赖该角色数组把 IRQ 路由给dcd_int_handler()或hcd_int_handler()src/tusb.c。注意当使用 RTOS 时tusb_init()应在调度器启动之后调用因为 USB 中断处理会使用 RTOS 队列 APIsrc/tusb.h。usb_mode_switch()切换状态机模式切换的核心实现在 src/main.c其流程为快照并清空角色先把prev_role current_role随即把current_role置为TUSB_ROLE_INVALID。这一步是关键的状态边界——在 RTOS 环境下并发的cdc_task/print_devinfo_task看到 INVALID 后就不会再调用已反初始化的设备/主机栈 API反初始化当前栈tusb_deinit(BOARD_RHPORT)切换缓冲FreeRTOS 下vTaskDelay(pdMS_TO_TICKS(100))无 OS 下tusb_time_delay_ms_api(100)留出干净的切换过渡时间以另一角色重新初始化构造新的tusb_rhport_init_t角色取反、速度TUSB_SPEED_AUTO调用tusb_init(BOARD_RHPORT, init)并更新current_role复位 LED 闪烁周期为未连接档打印Mode switch complete!。核心代码结构如下节选自 src/main.cconst tusb_role_t prev_role current_role; current_role TUSB_ROLE_INVALID; // 让并发任务看到切换边界 tusb_deinit(BOARD_RHPORT); // ... 100ms 延迟 ... if (prev_role TUSB_ROLE_DEVICE) { 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 { 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; }启动路径与默认角色无 RTOS 时main()默认以设备角色启动src/main.ctusb_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();BOARD_RHPORT默认取 0可由board.mk覆盖src/tusb_config.h。board_init_after_tusb()由 BSP 提供用于在 USB 栈初始化后再完成板级初始化hw/bsp/board_api.h。主循环与任务划分无 OS 与 FreeRTOS 两套运行模型示例同时支持CFG_TUSB_OS OPT_OS_NONE与OPT_OS_FREERTOS两种模型。无 OS 模型src/main.cmain()的while(1)中先用board_button_read()检测按键——按键持续按住时只触发一次切换pending_switch标志去抖然后按当前角色分发任务if (current_role TUSB_ROLE_DEVICE) { tud_task(); // 处理设备栈事件 cdc_task(NULL); // CDC 回显 } else { tuh_task(); // 处理主机栈事件 print_devinfo_task(NULL); // 打印设备信息 } led_blinking_task(NULL);FreeRTOS 模型src/main.c创建 4 个任务——blinkyLED 闪烁、usb最高优先级处理 USB 事件与模式切换、cdcCDC 回显、devinfo设备信息打印支持动态与静态分配configSUPPORT_STATIC_ALLOCATION。其中usb_task在调度器启动后调用tusb_init()并改用tud_task_ext(10, false)/tuh_task_ext(10, false)以便带超时返回、及时读取按键状态src/main.c。Espressif 平台则通过app_main()入口并在main()中跳过vTaskStartScheduler()src/main.c。设备模式CDC 虚拟串口回显cdc_task()只在current_role TUSB_ROLE_DEVICE时触碰设备端 CDC APIsrc/main.c用tud_cdc_available()/tud_cdc_read()读取 PC 发来的数据再以tud_cdc_write()原样写回最后tud_cdc_write_flush()冲刷发送缓冲——即回显所有收到的数据。在 FreeRTOS 下该任务每 10ms 轮询一次。配套的设备描述符定义在 src/usb_descriptors.cVID 0xCafe、PID 0x4020每个示例使用唯一 PID保证重新烧录后触发重新枚举与主机驱动重新匹配见 src/usb_descriptors.c、bcdUSB 0x0200、设备类为TUSB_CLASS_MISC MISC_SUBCLASS_COMMON MISC_PROTOCOL_IAD配置描述符由TUD_CONFIG_DESCRIPTORTUD_CDC_DESCRIPTOR组成并带远程唤醒属性src/usb_descriptors.c。序列号通过board_usb_get_serial()从芯片唯一 ID 生成hw/bsp/board_api.h。设备事件回调src/main.c负责维护 LED 状态并打印状态信息tud_mount_cb()挂载成功 → 打印[DEVICE] MountedLED 切到已挂载档tud_umount_cb()拔出 → 打印[DEVICE] UnmountedLED 回到未挂载档tud_suspend_cb()/tud_resume_cb()总线挂起/恢复挂起时 LED 切到最慢档。主机模式枚举设备并打印描述符主机侧的事件回调src/main.ctuh_mount_cb(daddr)设备枚举完成configured后触发打印[HOST] Device attached, address %d并把对应地址的need_devinfo[daddr]标志置位tuh_umount_cb(daddr)设备拔出打印移除消息并清标志。注意回调运行在主机任务上下文要求保持极简——真正读取描述符的工作放在print_devinfo_task()中完成这也是不要在主机栈回调里做同步描述符读取的典型工程实践源码注释明确指出 sync 辅助函数应避免在回调内使用。print_devinfo_task()扫描need_devinfo[]标志数组其大小为CFG_TUH_DEVICE_MAX 1地址从 1 开始对每个待打印设备调用print_one_device()src/main.c。print_one_device()src/main.c使用的都是主机栈的同步描述符 API定义于 src/host/usbh.htuh_descriptor_get_device_sync(daddr, desc.device, 18)读取 18 字节设备描述符tuh_descriptor_get_serial_string_sync(daddr, LANGUAGE_ID, ...)读取序列号字符串LANGUAGE_ID 0x0409即英语tuh_descriptor_get_manufacturer_string_sync()/tuh_descriptor_get_product_string_sync()厂商与产品字符串。打印内容覆盖设备描述符全部标准字段bLength、bDescriptorType、bcdUSB、bDeviceClass/SubClass/Protocol、bMaxPacketSize0、idVendor/idProduct、bcdDevice、iManufacturer/iProduct/iSerialNumber、bNumConfigurations序列号读取失败时如设备未提供回退输出n/a。字符串以 UTF-16 存储print_utf16()负责转成 ASCII 打印非 ASCII 字符暂以?代替源码中有 TODO 标注见 src/main.c。为 DMA 访问安全描述符缓冲通过CFG_TUH_MEM_SECTION声明src/main.c。LED 指示模式板载 LED 反映 USB 连接状态由led_blinking_task()以blink_interval_ms为周期翻转src/main.c闪烁周期含义快速闪烁250ms未挂载 / 未连接慢速闪烁1000ms成功挂载 / 连接极慢闪烁2500ms总线挂起仅设备模式对应枚举常量定义在 src/main.c。模式切换完成后会先把闪烁周期复位为 250ms未挂载档等待新角色下的挂载/连接事件回调更新。串口输出示例示例通过调试 UART 打印状态信息原文档给出的典型输出如下注意[HOST] Device attached后是print_devinfo_task异步打印的 VID/PID、序列号与设备描述符 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 ...使用步骤按原文档整理完整操作流程如下构建并烧录将示例编译后烧录到你的板卡构建前请确认板卡系列在 only.txt 白名单内且 USB 控制器支持双角色默认行为板卡上电后以设备模式启动设备模式操作将板卡连接到 PC打开串口终端例如screen /dev/ttyACM0或 PuTTY输入字符——它们会被原样回显给你切换到主机模式按下板载按键将 USB 设备接到板卡上板卡会枚举该设备并在调试控制台打印其描述符信息地址、VID:PID、序列号、设备描述符等切回设备模式再次按下按键即可。小结与工程启示dynamic_switch示例为单控制器、单时刻单角色、按键动态互换给出了完整的参考实现几个可直接复用的工程要点双栈共存CFG_TUD_ENABLED与CFG_TUH_ENABLED同时开启设备类与主机类驱动按需裁剪本示例设备侧仅 CDC、主机侧仅 Hub 枚举角色状态机用tusb_role_t全局变量跟踪当前角色切换前先置TUSB_ROLE_INVALID作为并发任务的安全边界再tusb_deinit() 100ms 缓冲 反向角色tusb_init()上下文纪律主机/设备回调保持极简描述符同步读取下沉到专用任务FreeRTOS 下使用_ext版本任务 API 保证超时返回以响应按键状态可视化以 LED 三档闪烁周期区分未挂载 / 已挂载 / 挂起是嵌入式调试 USB 状态的低成本手段。若需在此基础上扩展可从 src/main.c 出发将 CDC 回显替换为任意设备类逻辑、把描述符打印升级为真正的类驱动如 MSC/HID 挂载或结合 TinyUSB 的 Type-C / 电源管理能力实现更复杂的角色协商方案。赞分享嵌入式驱动开发通信物联网【免费下载链接】tinyusbAn open source cross-platform USB stack for embedded system项目地址https://gitcode.com/gh_mirrors/ti/tinyusb点击查看免费下载相关推荐ESP32 USB OTG 动态切换 Host/Device 模式实战CDC 设备与 MSC 主机手动切换esp-iot-solutionESP32 USB OTG 动态切换 Host/Device 模式实战CDC 设备与 MSC 主机手动切换esp iot solution 本文基于 es物联网嵌入式驱动开发硬件开发lottie-web渲染模式切换运行时动态切换SVG/Canvaslottie web渲染模式切换运行时动态切换SVG/Canvas 引言为什么需要动态切换渲染模式 你是否曾遇到过这样的困境开发复杂动画时希望使用SVG前端图形学pyvideotrans语音合成角色切换动态切换不同音色pyvideotrans语音合成角色切换动态切换不同音色 pyvideotrans 是一款强大的视频翻译和配音工具它不仅能将视频从一种语言翻译成另一种语言音视频AI 应用语音本地部署上一篇Google zx 项目安装与配置完全指南下一篇Spring Framework中的WebFlux缓存机制深度解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表