ESP IoT Solution 使用原生 TinyUSB 开发 USB 设备工程搭建、配置宏与 UVC 实战指南【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution本篇技术指南以 ESP IoT Solution 仓库的 TinyUSB 开发指南 为骨架系统讲解如何在 ESP-IDF 工程中直接使用原生 TinyUSB 协议栈开发 USB 设备。你将掌握完整工程目录的搭建方法、tusb_config.h中全部关键配置宏的含义与取值、usb_descriptors.c中描述符弱函数的实现要点以及 USB PHY 初始化、协议栈启动和设备层回调的完整流程。文中所有配置与代码均可在仓库的 usb_device_uvcUVC 摄像头设备与 usb_device_uacUAC 音频设备组件中找到真实对应实现。一、工程目录结构使用原生 TinyUSB 开发 USB 设备时需要建立以下目录结构project_name | |-- main |-- CMakeLists.txt |-- idf_component.yml |-- main.c |-- tusb |-- tusb_config.h |-- usb_descriptors.c |-- usb_descriptors.h其中main/是 ESP-IDF 工程的默认应用组件包含入口main.c和组件构建文件CMakeLists.txtmain/idf_component.yml用于声明组件依赖main/tusb/目录专门放置反向提供给 TinyUSB 的文件tusb_config.h、usb_descriptors.c/h单独放在一个文件夹中可以保证依赖关系的简单与清晰。在该工程的main组件中通过idf_component.yml添加组件依赖espressif/tinyusb即可拉取 TinyUSB 组件。二、解决反向依赖CMakeLists.txt 的关键配置工程需要依赖 TinyUSB同时又要向 TinyUSB 提供tusb_config.h和描述符源文件这会不可避免地产生反向依赖问题。目前的解决方案是将 TinyUSB 的全部关键文件作为源码直接编译到main组件中。具体做法是在main/CMakeLists.txt中、idf_component_register之后添加以下语句# espressif__tinyusb 应匹配当前依赖的 tinyusb 名称 idf_component_get_property(tusb_lib espressif__tinyusb COMPONENT_LIB) target_include_directories(${tusb_lib} PUBLIC ${COMPONENT_DIR}/tusb) target_sources(${tusb_lib} PUBLIC ${COMPONENT_DIR}/tusb/usb_descriptors.c)idf_component_get_property获取名为espressif__tinyusb的组件库对象该名称需与idf_component.yml中声明的依赖名称一致命名规则为命名空间__组件名target_include_directories将main/tusb目录加入 TinyUSB 组件的头文件搜索路径使 TinyUSB 内部的tusb_config.h引用得以解析target_sources将usb_descriptors.c作为源文件编译进 TinyUSB 组件从而让描述符回调函数TinyUSB 以弱符号方式声明与协议栈链接在一起。三、tusb_config.h功能开关的宏配置总览TinyUSB 大部分功能的启用和关闭都是通过宏来控制的因此需要在tusb_config.h中声明所需的功能。以下宏分为系统设置、USB 设备、USB Class 三类。3.1 系统设置的宏宏作用与取值CFG_TUSB_RHPORT0_MODE定义连接到 USB Phy 的方式和速率。下面的定义表示 USB device 设备速率为USB 全速#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE \| OPT_MODE_FULL_SPEED)CFG_TUSB_RHPORT1_MODE定义连接到 USB Phy 的方式和速率。下面的定义表示 USB device 设备速率为USB 高速#define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE \| OPT_MODE_HIGH_SPEED)ESP_PLATFORM使用 ESP-IDF 平台进行编译需启用该宏#define ESP_PLATFORM 1CFG_TUSB_OS定义 TinyUSB 使用的操作系统。若使用 FreeRTOS 需启用该宏也可以不启用操作系统#define CFG_TUSB_OS OPT_OS_FREERTOSCFG_TUSB_OS_INC_PATH在 ESP-IDF 中include 路径要求添加freertos/前缀#define CFG_TUSB_OS_INC_PATH freertos/CFG_TUSB_DEBUG启用 TinyUSB 的 LOG 打印等级共三级0 关闭、1 基本、2 详细#define CFG_TUSB_DEBUG 0CFG_TUSB_DEBUG_PRINTF定义 TinyUSB 的 log 打印函数#define CFG_TUSB_DEBUG_PRINTF esp_rom_printfCFG_TUD_ENABLED设为 1 启用 TinyUSB device 功能#define CFG_TUD_ENABLED 1CFG_TUSB_MEM_SECTION启用后可将 TinyUSB 的内存分配到特定内存段例如 DMA 受限的 SRAM 区域#define CFG_TUSB_MEM_SECTION __attribute__ ((section(.usb_ram)))CFG_TUSB_MEM_ALIGN定义内存对齐方式#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4)))在仓库的 usb_device_uvc/tusb/tusb_config.h 中可以看到这些宏的真实组合方式并且它通过CONFIG_TINYUSB_RHPORT_HS与CONFIG_IDF_TARGET_ESP32P4的组合自动决定使用高速端口CFG_TUSB_RHPORT1_MODE适用于 ESP32-P4 的内置 HS PHY还是全速端口CFG_TUSB_RHPORT0_MODE适用于 ESP32-S2/S3 等并同步设置CONFIG_USB_HS供上层逻辑使用。该文件还通过#ifndef CFG_TUSB_MCU / #error强制要求CFG_TUSB_MCU由编译器标志传入。3.2 USB 设备的宏CFG_TUSB_ENDPOINT0_SIZE用于定义端点 0 的最大包大小通常为 64 字节#define CFG_TUD_ENDPOINT0_SIZE 64。3.3 USB Class 的宏每个 USB Class 都有单独的宏定义这里以 UVC Class 为例CFG_TUD_VIDEO配置视频控制接口Video Control Interface的数量CFG_TUD_VIDEO_STREAMING配置视频流接口Video Streaming Interface的数量。在 usb_device_uvc/tusb/tusb_config.h 中这两个宏会随CONFIG_UVC_SUPPORT_TWO_CAM双摄像头支持动态取值单摄像头时为 1双摄像头时为 2#if CONFIG_UVC_SUPPORT_TWO_CAM #define CFG_TUD_VIDEO 2 #define CFG_TUD_VIDEO_STREAMING 2 #else #define CFG_TUD_VIDEO 1 #define CFG_TUD_VIDEO_STREAMING 1 #endif此外不同的 USB Class 还会有一些特殊宏用于定义软件 FIFO 大小或启用某些功能。例如 UVC Class 中的CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE用于定义视频传输流端点的 buffer 大小。从 usb_device_uvc/tusb/tusb_config.h 可以看出该宏与速率/传输类型的强关联全速FS等时传输时为 512高速HS等时传输时为 1023而 Bulk 模式CFG_TUD_CAM1_VIDEO_STREAMING_BULK下为 64FS/ 512HS。这印证了宏决定端点 buffer 大小这一核心用法。可参考的tusb_config.h完整示例usb_device_uac/tusb/tusb_config.hUAC 音频设备usb_device_uvc/tusb/tusb_config.hUVC 视频设备usb_hid_device/hid_device/tusb_config.hHID 设备四、usb_descriptors.h自定义 USB 描述符可选该文件主要用来放置自定义的 USB 描述符。TinyUSB 提供了很多描述符的模板如果默认模板不满足需求就需要自己定义一套 USB 描述符。需要注意的是尽量使用 TinyUSB 中预定义好的一些描述符这样可以很方便地进行描述符组装和长度计算。以 usb_device_uvc/tusb/usb_descriptors.h 为例它基于 TinyUSB 的TUD_VIDEO_*系列宏模板通过宏组合的方式定义了一整套 UVC 1.5 描述符生成器TUD_VIDEO_CAPTURE_DESCRIPTOR_MJPEG、_UNCOMPR、_H264、_BULK等并定义了端点地址如EPNUM_CAM1_VIDEO_IN 0x81、接口编号枚举ITF_NUM_VIDEO_CONTROL、ITF_NUM_VIDEO_STREAMING以及各段描述符的长度计算宏TUD_VIDEO_CAPTURE_DESC_*_LEN。这种模板 长度宏的做法正是利用 TinyUSB 预定义描述符实现描述符组装和长度计算的典型范例。可参考的usb_descriptors.h示例usb_device_uac/tusb_uac/uac_descriptors.husb_device_uvc/tusb/usb_descriptors.husb_hid_device/hid_device/usb_descriptors.h五、usb_descriptors.c实现三个描述符弱函数该文件主要实现了几个获取描述符的弱函数分别是获取设备描述符、配置描述符和字符串描述符uint8_t const *tud_descriptor_device_cb(void); uint8_t const *tud_descriptor_configuration_cb(uint8_t index); uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid);在 usb_device_uvc/tusb/usb_descriptors.c 中可以直观看到这三个函数的完整实现逻辑tud_descriptor_device_cb返回静态定义的tusb_desc_device_t desc_device其中使用TUSB_CLASS_MISC / MISC_SUBCLASS_COMMON / MISC_PROTOCOL_IAD声明这是一个通过 IADInterface Association Descriptor组合多接口的复合设备UVC 的视频控制 视频流接口VID/PID 由CONFIG_TUSB_VID、CONFIG_TUSB_PID配置tud_descriptor_configuration_cb返回静态数组desc_fs_configuration它以TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0, 500)开头随后按CONFIG_FORMAT_MJPEG_CAM1、CONFIG_FORMAT_H264_CAM1等编译选项拼装对应格式的描述符tud_descriptor_string_cb通过string_desc_arr[]字符串表制造商、产品名、序列号、UVC CAM1等将 ASCII 字符串转换为 UTF-16LE 的字符串描述符。注意点配置描述符的长度一定要等于实际的长度。仓库通过CONFIG_TOTAL_LEN TUD_CONFIG_DESC_LEN TUD_CAM1_VIDEO_CAPTURE_DESC_LEN ...用宏精确计算总长度再由TUD_CONFIG_DESCRIPTOR写入描述符头部从机制上避免长度不一致配置描述符中各个端点描述符的端点号要避免重复。在 UVC 双摄像头配置中摄像头 1 使用0x81、摄像头 2 使用0x82正是为了规避端点号冲突。可参考的usb_descriptors.c示例usb_device_uvc/tusb/usb_descriptors.cusb_device_uac/tusb/usb_descriptors.cusb_hid_device/hid_device/usb_descriptors.c六、初始化 USB PhyUSB 协议栈运行前需要先初始化 USB PHY。初始化内部 USB Phy的代码如下static void usb_phy_init(void) { // Configure USB PHY usb_phy_config_t phy_conf { .controller USB_PHY_CTRL_OTG, .otg_mode USB_OTG_MODE_DEVICE, .target USB_PHY_TARGET_INT, }; usb_new_phy(phy_conf, s_uvc_device.phy_hdl); }关键字段说明.controller USB_PHY_CTRL_OTG选择 USB-OTG 控制器.otg_mode USB_OTG_MODE_DEVICE以 device设备模式运行.target USB_PHY_TARGET_INT使用芯片内部 PHYusb_new_phy返回的句柄保存在phy_hdl中用于后续释放usb_del_phy。关于 PHY 的背景知识可参考仓库的 USB PHY/Transceiver 介绍ESP32-S2/S3/P4 内置 USB Full-speed PHYESP32-P4 还内置 USB High-Speed PHY。内部 PHY 对应固定 GPIO如 ESP32-S3 的 D 为 GPIO20、D- 为 GPIO19同一时间 USB-OTG 与 USB-Serial-JTAG 只能有一个占用内部 PHY。如果使用外部 USB Phy仅 ESP32-S2/S3 支持用于让 OTG 与 Serial-JTAG 同时工作则需要参考 usb_phy.rst 中external_phy章节的配置方式SP5301 或同等功能 PHY占用至少 6 个 GPIO。七、初始化 TinyUSB 协议栈PHY 初始化完成后调用tusb_init()启动协议栈并创建独立任务循环调用tud_task()处理 USB 事件static void tusb_device_task(void *arg) { while (1) { tud_task(); } } int main(void) { usb_phy_init(); bool usb_init tusb_init(); if (!usb_init) { ESP_LOGE(TAG, USB Device Stack Init Fail); return ESP_FAIL; } xTaskCreatePinnedToCore(tusb_device_task, TinyUSB, 4096, NULL, 5, NULL, 0); }实现要点tusb_init()返回值用于判断协议栈是否初始化成功失败时直接返回错误tud_task()必须被持续调用通常放在独立任务中才能响应 USB 事件示例中创建了名为TinyUSB的任务栈大小 4096优先级 5并固定到 0 号核运行xTaskCreatePinnedToCore。八、实现设备层的弱函数TinyUSB 提供了设备层Device的弱函数用于获取设备的插入、拔出、暂停、恢复等事件// Invoked when device is mounted void tud_mount_cb(void) { } // Invoked when device is unmounted void tud_umount_cb(void) { } // Invoked when device is suspended void tud_suspend_cb(bool remote_wakeup_en) { } // Invoked when usb bus is resumed void tud_resume_cb(void) { }这些回调是弱符号定义应用层按需实现即可例如在tud_mount_cb中通知应用主机已连接在tud_suspend_cb带remote_wakeup_en参数指示远程唤醒是否使能中进入低功耗逻辑。即使不实现协议栈也能正常运行。九、实现 USB Class 的特殊回调函数除了设备层回调每个 USB Class 还提供了一些弱函数来完成基本功能。下面以 UVC 驱动为例展开。通过观察 UVC Class 的 API 可以发现它提供了两个函数和一个回调函数bool tud_video_n_streaming(uint_fast8_t ctl_idx, uint_fast8_t stm_idx); bool tud_video_n_frame_xfer(uint_fast8_t ctl_idx, uint_fast8_t stm_idx, void *buffer, size_t bufsize); TU_ATTR_WEAK void tud_video_frame_xfer_complete_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx);典型用法调用tud_video_n_streaming查询/确认指定索引的流接口是否处于 streaming 状态调用tud_video_n_frame_xfer传输一帧图像传入图像数据缓冲区buffer与长度bufsize通过实现tud_video_frame_xfer_complete_cb回调来检查这一帧是否传输完成在回调中继续提交下一帧形成逐帧提交 完成回调的视频流驱动模式。完整的传输流程可在 usb_device_uvc.c 组件源码中印证其测试程序位于 usb_device_uvc/test_apps/main/usb_device_uvc_test.c配套示例工程为 examples/usb/device/usb_webcam。同一模式的 UAC 音频流驱动见 usb_device_uac.c示例工程为 examples/usb/device/usb_uac。十、总结从配置到运行的最小闭环综合以上步骤一个基于原生 TinyUSB 的 USB 设备开发闭环是按第一节建立工程目录在idf_component.yml中添加espressif/tinyusb依赖在main/CMakeLists.txt中通过target_include_directories与target_sources把tusb/目录反向注入 TinyUSB 组件在tusb_config.h中通过宏声明速率FS/HS、OSFreeRTOS、调试等级、内存对齐以及所需 Class 数量与端点 buffer 大小在usb_descriptors.c/h中实现/复用三个描述符弱函数确保配置描述符长度精确、端点号不重复初始化 USB PHY内部或外部参考 usb_phy.rst后调用tusb_init()并创建任务持续执行tud_task()按需实现设备层回调tud_mount_cb等与 Class 层回调如 UVC 的tud_video_frame_xfer_complete_cb驱动具体业务功能。除 UVC/UAC 外仓库还提供大量基于 TinyUSB 的完整示例工程例如 usb_hid_deviceHID 输入设备、usb_msc_wireless_disk大容量存储、usb_dual_uvc_device双摄像头 UVC等可直接作为二次开发的起点结合 usb_device_solutions.rst 中介绍的设备解决方案按需选用。【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考