ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

CherryUSB Demo 快速验证指南:从平台 SDK Demo 到 STM32 USB 设备/主机移植

CherryUSB Demo 快速验证指南:从平台 SDK Demo 到 STM32 USB 设备/主机移植
  • 嵌入式
  • 驱动开发
  • 物联网

【免费下载链接】CherryUSB

CherryUSB is a tiny and beautiful, high performance and portable USB host and device stack for embedded system with USB IP

项目地址:https://gitcode.com/gh_mirrors/ch/CherryUSB
点击查看免费下载

本文基于 CherryUSB 仓库的《基于现有 demo 快速验证》文档(docs/zh/quick_start/demo.rst)展开:先讲清"为什么先跑 demo",完整继承原文档罗列的各芯片平台官方 SDK demo 清单与 ESP32、STM32 两条实操路线,并结合仓库源码(glue 层、Kconfig、测试工程)补充每个步骤背后的实现依据,帮助你在动手移植前快速获得一次可运行的 USB 验证。

一、为什么推荐"先跑 Demo"

原文档开宗明义:在学习 USB 或 CherryUSB 源码之前,应优先基于现有 demo 做快速验证。理由很实际——跑通 demo 能提升对 USB 的兴趣、建立继续深入的信心;反过来,如果一上来就自己摸索写代码、或者先啃 USB 基本概念,最后"发现一点都看不懂,概念好多,根本记不住",往往会丧失继续学习 USB 的兴趣。因此官方建议的路径是:先跑通 demo → 再阅读概念 → 最后移植。

跑通 demo 后,你至少掌握三件事:

  1. 目标芯片的 USB IP(DWC2、FSDEV、MUSB、CHIPIDEA、EHCI/OHCI、XHCI 等)在 CherryUSB port 目录下的对应驱动文件如何接入;
  2. usb_config.h配置模板(cherryusb_config_template.h)中哪些宏必须打开;
  3. 设备端usbd_initialize()/ 主机端usbh_initialize()的最小调用方式。

二、官方 SDK 支持的 Demo 平台清单

原文档以表格形式罗列了当前支持的 demo 仓库("Version"列表示该 demo 对应的 CherryUSB 版本与主干的相对关系)。以下表格完整继承原文档内容:

芯片平台USB IP对应 demo 仓库/SDKCherryUSB 版本
BouffaloLab(官方 SDK 支持)FOTG210bouffalo_sdkless than latest
HPMicro(官方 SDK 支持)CHIPIDEAhpm_sdkless than latest
ESPressif(官方 SDK 支持)DWC2cherryusb_esp32(ESP-Registry 组件)less than latest
Phytium(官方 SDK 支持)PUSB2/XHCIphytium-free-rtos-sdkequal to v1.4.0
Essemi(官方 SDK 支持)MUSBcherryusb_es32less than latest
Artinchip(官方 SDK 支持)AIC/EHCI/OHCIluban-liteless than latest
Kendryte K230(官方 SDK 支持)DWC2Kendryte K230 RTOS 官方构建文档less than latest
SiFli(官方 SDK 支持)MUSBSiFli-SDKless than latest
WCHUSBHS/USBFS/USBSScherryusb_wch、wch-support-cherryusbgreater than v1.6.1
NXP MCXCHIPIDEA/kinetischerryusb_mcx、RT-Thread bsp/nxp/mcxless than latest
RP2040/RP2035(官方 SDK 即将支持)RP2040pico-examples、pico-sdkless than latest
STDWC2/FSDEVcherryusb_stm32less than latest
Sophgo(官方 SDK 支持)DWC2cvi_alios_openequal to v0.7.0
Bekencorp(官方 SDK 支持)MUSBbk_idk_aiequal to v0.7.0
Actionstech / Nuvoton / AllwinnerTech / ListenAI(官方 SDK 支持)—未开源,需联系芯片厂商官方—

表中仓库地址请以原文档 docs/zh/quick_start/demo.rst 中的链接为准,此处仅保留仓库名称。

从源码结构看,这套清单与 port/ 目录下的移植实现一一对应:DWC2 对应 port/dwc2/usb_dc_dwc2.c 与 port/dwc2/usb_hc_dwc2.c,FSDEV 对应 port/fsdev/usb_dc_fsdev.c,CHIPIDEA 对应 port/chipidea/usb_dc_chipidea.c,MUSB 对应 port/musb/usb_dc_musb.c 与 port/musb/usb_hc_musb.c,EHCI/OHCI 对应 port/ehci/ 与 port/ohci/,XHCI 对应 port/xhci/。如果你的芯片属于某类 USB IP,直接参考该目录下的 README 即可判断移植路径。

三、ESP32 系列:通过 ESP-Registry 组件安装 Demo

原文档指出,ESPressif 平台的 demo "默认采用组件库安装的形式"。仓库根目录的 idf_component.yml 即该组件的注册元信息:当前组件版本为 1.6.1,依赖idf: ">=4.4.1",支持的 targets 为 esp32s2、esp32s3、esp32p4、esp32s31——也就是说只有带 DWC2 IP 的 ESP32-S 系列/P4 才在这个组件的适配范围内。

原文档推荐 VSCode + ESP-IDF 环境,操作路径为:

  1. Ctrl + Shift + P打开命令面板,选择ESP-IDF 欢迎界面,再选择Component manager;
  2. 在组件管理器中搜索cherryusb并安装;
  3. 打开menuconfig,进入 CherryUSB 配置项,根据实际情况选择主机模式(CHERRYUSB_HOST)或从机模式(CHERRYUSB_DEVICE)以及所需 class。

第 3 步对应仓库根目录的 Kconfig 文件:顶层开关为menuconfig CHERRYUSB,设备模式下还有CHERRYUSB_DEVICE及各 class 开关(如CHERRYUSB_DEVICE_CDC_ACM),Kconfig 中还内置了一组 demo template 开关(如cdc_acm_msc、winusbv1等),勾选后会直接编译 demo/ 目录下对应的 template 文件,方便"装上组件即可跑"。

组件内 Demo 工程长什么样

仓库自带两套 ESP32 验证工程,可直接对照阅读:

  • 设备端 tests/espressif/device/main/main.c:直接 includedemo/cdc_acm_msc_template.c,在app_main中一行完成初始化——cdc_acm_msc_init(0, 0x60080000)(0x60080000为 ESP32-S3 的 DWC2 寄存器基址);其 sdkconfig.defaults 打开了CONFIG_CHERRYUSB、CONFIG_CHERRYUSB_DEVICE、CONFIG_CHERRYUSB_DEVICE_CDC_ACM/HID/MSC。
  • 主机端 tests/espressif/host/main/main.c:includedemo/usb_host.c(内置 serial/HID/MSD 等 class 测试),初始化调用为usbh_initialize(0, 0x60080000, NULL),第三个参数event_handler可缺省为 NULL。

cdc_acm_msc_init的内部实现见 demo/cdc_acm_msc_template.c:先usbd_desc_register()注册描述符,再依次usbd_add_interface()/usbd_add_endpoint()挂接 CDC-ACM 与 MSC 接口,最后调用usbd_initialize(busid, reg_base, usbd_event_handler)。这正是后文"移植要点"中最后一步的标准形态。

四、STM32 系列:默认 Demo 工程与 Cache 处理

4.1 默认提供的 Demo 工程

原文档说明,ST 平台的默认 demo 涵盖F1/F4/H7,具体配置为:

  • F103:使用 FSDEV IP;
  • F429:主从共用 USB1(引脚 PB14/PB15),默认从机未开启 DMA 模式;
  • H7:设备使用 USB0(引脚 PA11/PA12,未开 DMA);主机使用 USB1(引脚 PB14/PB15),需要做 nocache 处理。

demo 工程目录下提供stm32xxx.ioc文件,用 STM32CubeMX 双击打开、点击Generate Code即可生成工程。原文档特别加了一条 caution:

生成代码后,请使用git reset将被覆盖的main.c和stm32xxx_it.c撤回,禁止被 CubeMX 覆盖。

F1/F4/H7 之间的差异可归纳为三点(原文档原话整理):

  1. USB IP 区别:F1 使用 FSDEV,F4/H7 使用 DWC2;
  2. DWC2 端口区别:USB0(PA11/PA12)与 USB1(PB14/PB15)。其中USB1 默认全速,可以外接 PHY 形成高速主机,并且带 DMA 功能;
  3. Cache 区别:F4 无 cache,H7 有 cache。

4.2 带 Cache 芯片(F7/H7)的 Nocache RAM 配置

对 STM32F7/H7 这类带 cache 的芯片,原文档给出的做法是把 USB 使用的 RAM 定位到 nocache 区域,代码示例为:

cpu_mpu_config(0, MPU_Normal_NonCache, 0x24070000, MPU_REGION_SIZE_64KB);

对应的 Keil SCT 链接脚本修改:

LR_IROM1 0x08000000 0x00200000 { ; load region size_region ER_IROM1 0x08000000 0x00200000 { ; load address = execution address *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) .ANY (+XO) } RW_IRAM2 0x24000000 0x00070000 { ; RW data .ANY (+RW +ZI) } USB_NOCACHERAM 0x24070000 0x00010000 { ; RW data *(.noncacheable) } }

这两段配置的原理与仓库文档《芯片通用移植指南》的 cache 章节(docs/zh/quick_start/transplant.rst 中usb_cache锚点)完全一致:协议栈与 port 层不会对 cache 区 RAM 做 clean/invalidate,因此需要用USB_NOCACHE_RAM_SECTION宏(在 cherryusb_config_template.h 中默认定义为__attribute__((section(".noncacheable"))))把关键变量放进.noncacheable段,再配合 MPU 属性保证该物理区域真的非缓存——光改链接脚本不够,必须同时配置 MPU。上面cpu_mpu_config(0, MPU_Normal_NonCache, 0x24070000, 64KB)与 SCT 中USB_NOCACHERAM 0x24070000地址一致,正是"链接脚本段地址 + MPU 属性"双管齐下的示范。

在 STM32 glue 层也能看到 cache 的配套实现:port/dwc2/usb_glue_st.c 末尾在CONFIG_USB_DCACHE_ENABLE宏下提供了usb_dcache_clean/usb_dcache_invalidate/usb_dcache_flush三个包装函数(底层调用SCB_CleanDCache_by_Addr等 CMSIS 接口),供带 cache 且走 DMA 的路径使用。

五、USB Device 移植要点(以 STM32 为例)

以下 10 步完整继承原文档的 USB Device 移植流程,并在关键步骤补充源码依据。

  1. 使用 STM32CubeMX 创建工程,配置基本 RCC 和 UART(作为 log 输出使用)。
  2. 选择 USB IP:FSDEV IP 勾选USB;DWC2 IP 勾选USB_OTG_FS或USB_OTG_HS。开启 USB 中断即可,"其他配置对我们没用,代码中不会使用任何 ST 的 USB 库"。
  3. 配置 USB 时钟为 48MHz。
  4. 选择编译器并生成代码:这里选择 Keil,设置好 stack 和 heap(原文档建议"如果使用 MSC 可以推荐设置大点"),点击Generate Code。
  5. 添加 CherryUSB 必需要源码:usbd_core.c+ IP 对应的dwc2/usb_dc_dwc2.c或fsdev/usb_dc_fsdev.c,以及想使用的 class 驱动;还可以把 demo/ 目录下对应的 class template 加入工程方便直接测试。
  6. 补齐头文件路径(建议把 common/class/core/port 的头文件目录全部加入 include path)。
  7. 复制配置模板:拷贝 cherryusb_config_template.h 到Core/Inc目录下,命名为usb_config.h。
  8. FSDEV 专属配置:如果使用 FSDEV IP(V1.5.0 起还需添加fsdev/usb_glue_st.c),必须在usb_config.h中定义:
#define CONFIG_USBDEV_FSDEV_PMA_ACCESS 2

从源码看这个宏并非可有可无:port/fsdev/usb_dc_fsdev.c 和 port/fsdev/usb_glue_st.c 中都有#error "please define CONFIG_USBDEV_FSDEV_PMA_ACCESS in usb_config.h"的编译期检查,且 glue 文件还会校验CONFIG_USBDEV_FSDEV_PMA_ACCESS != PMA_ACCESS时报错——该值取决于芯片寄存器头文件中PMA_ACCESS的定义,常见取 1 或 2,不同芯片可能不同(模板头文件中也注明 "maybe 1 or 2, many chips may have a difference")。

  1. 编译器设置:推荐使用AC6,勾选Microlib,并实现printf(重定向到 UART)方便查看 log。
  2. 中断与初始化接线。原文档注明:从 V1.5.0 开始,以下两个老步骤不再需要手工处理,因为fsdev/usb_glue_st.c、dwc2/usb_glue_st.c中已经实现:
    • 拷贝xxx_msp.c中HAL_PCD_MspInit的内容到usb_dc_low_level_init函数(屏蔽 ST 生成的 USB 初始化);
    • 在中断函数中调用USBD_IRQHandler并传入busid。

在 port/dwc2/usb_glue_st.c 中可以看到这套胶水层是如何代劳的:

  • usb_dc_low_level_init()(HAL_PCD_MODULE_ENABLED宏下)把reg_base指向g_usbdev_bus[busid],然后直接调用HAL_PCD_MspInit——即把 CubeMX 生成的 Msp 初始化(时钟、GPIO、NVIC)纳入协议栈调用链,无需用户再拷贝;
  • 文件末尾的OTG_FS_IRQHandler()/OTG_HS_IRQHandler()直接按寄存器基址区分是 USB0 还是 USB1(0x40040000为 USB_OTG_HS 基址),记录对应busid后调用USBD_IRQHandler(busid);
  • 对 STM32F722/723/730/732/733 还会额外调用usb_hsphy_init(25000000)初始化内部 HS PHY(使能 LDO、配置 PLL、TUNE、等待 2ms)。

FSDEV 侧的 port/fsdev/usb_glue_st.c 同理:usb_dc_low_level_init()内调用HAL_PCD_MspInit,USB_IRQHandler/USB_LP_IRQHandler统一转发到USBD_IRQHandler(0)。

  1. 如果芯片带 cache,按 4.2 节修改链接脚本与 MPU(参考 transplant 文档的usb_cache章节)。
  2. 调用 template 初始化:注册描述符并调用usbd_initialize,填入busid和 USB IP 的reg base;busid从 0 开始,不能超过CONFIG_USBDEV_MAX_BUS。对应声明见 core/usbd_core.h:
int usbd_initialize(uint8_t busid, uintptr_t reg_base, usbd_event_handler_t event_handler);

usbd_initialize内部第一步就是USB_ASSERT_MSG(busid < CONFIG_USBDEV_MAX_BUS, "bus overflow\r\n")(见 core/usbd_core.c),所以CONFIG_USBDEV_MAX_BUS的默认值(模板中为 1)决定了最多能注册几路 USB 控制器。

六、USB Host 移植要点(以 STM32 DWC2 为例)

原文档指出:host 驱动只支持带 DMA 的 HS port(引脚 PB14/PB15),FS port(PA11/PA12)不做支持(原文原话:"没有 DMA 你玩什么主机")。这一点与 port/dwc2/usb_glue_st.c 中param_pb14_pb15参数表设置了host_rx_fifo_size/host_nperio_tx_fifo_size/host_perio_tx_fifo_size等主机专用 FIFO,而param_pa11_pa12(F1/F4 等平台)没有主机 FIFO 配置的结构完全吻合。

移植步骤(前 6 步与 Device 相同,此处列差异部分):

  1. 添加源码:usbh_core.c、usbh_hub.c、usb_hc_dwc2.c、usb_glue_st.c,以及osal目录下对应操作系统的适配层文件(osal/ 下有 FreeRTOS、RT-Thread、Zephyr、NuttX、LiteOS、ThreadX、IDF 等实现),加上想使用的 host class 驱动;可加入demo/usb_host.c方便直接测试(内置 cdc/hid/msc 等 host 侧演示,参考 demo/usb_host.c)。
  2. 编译器设置:同 Device,推荐 AC6 + Microlib + 自定义printf。
  3. 复制配置模板:cherryusb_config_template.h→Core/Inc/usb_config.h。主机相关的关键宏在模板中集中定义为:
#define CONFIG_USBHOST_MAX_RHPORTS 1 #define CONFIG_USBHOST_MAX_EXTHUBS 1 #define CONFIG_USBHOST_MAX_EHPORTS 4 #define CONFIG_USBHOST_MAX_INTERFACES 8 #define CONFIG_USBHOST_MAX_ENDPOINTS 4 #define CONFIG_USBHOST_MAX_SERIAL_CLASS 4 #define CONFIG_USBHOST_MAX_HID_CLASS 4 #define CONFIG_USBHOST_MAX_MSC_CLASS 2

这些宏决定主机栈能同时枚举的 hub 端口、接口与各 class 实例上限,裁剪协议栈内存的主要抓手。

  1. 低层初始化与中断:原文档注明从 V1.5.0 起无需手工拷贝HAL_HCD_MspInit内容,也无需自己写中断转发,glue 层已实现。对照 port/dwc2/usb_glue_st.c 的usb_hc_low_level_init()(HAL_HCD_MODULE_ENABLED宏下):按reg_base是否为0x40040000区分 USB1/USB0,登记g_usb_dwc2_irq[1] = USBH_IRQHandler,随后调用HAL_HCD_MspInit完成时钟/GPIO/NVIC 配置。
  2. 链接脚本修改(参考 transplant 文档usbh_link_script章节):主机栈需要在链接脚本中保留一个 section 存放 class 描述信息,否则链接会报__usbh_class_info_start__/__usbh_class_info_end__未定义。Keil 无需修改;GCC 需在 ld 文件的 flash 段追加:
. = ALIGN(4); __usbh_class_info_start__ = .; KEEP(*(.usbh_class_info)) __usbh_class_info_end__ = .;

Segger Embedded Studio 则用define block cherryusb_usbh_class_info { section .usbh_class_info };等方式定义导出符号。 6.Cache 处理:带 cache 芯片参考usb_cache章节(同 Device 端做法)。 7.初始化并启动:调用usbh_initialize,填入busid、USB IP 的reg base和event_handler(可缺省为 NULL);busid从 0 开始,不能超过CONFIG_USBHOST_MAX_BUS。声明见 core/usbh_core.h:

int usbh_initialize(uint8_t busid, uintptr_t reg_base, usbh_event_handler_t event_handler);

实现(core/usbh_core.c)中先USB_ASSERT_MSG(busid < CONFIG_USBHOST_MAX_BUS, "bus overflow\r\n"),event_handler为 NULL 时回落到内部的dummy_event_handler,之后创建 PSC 线程(模板中默认CONFIG_USBHOST_PSC_PRIO 0、CONFIG_USBHOST_PSC_STACKSIZE 2048)完成主机栈启动,即原文档最后一步"启动线程"。ESP32 主机 demo 中的usbh_initialize(0, 0x60080000, NULL)就是最小完整调用。

七、配置模板速查与延伸阅读

无论走哪条平台路线,usb_config.h(由 cherryusb_config_template.h 复制而来)都是核心入口。与本文 demo 路线直接相关的宏归纳如下:

宏默认/取值作用
CONFIG_USB_PRINTF默认printf(RT-Thread 下为rt_kprintf)协议栈日志输出重定向
CONFIG_USB_DBG_LEVEL默认USB_DBG_INFO日志等级
CONFIG_USB_ALIGN_SIZE4(开 dcache 时 32/64)DMA/dcache 路径下的数据对齐
USB_NOCACHE_RAM_SECTION.noncacheablesection带 cache 芯片的变量放置段
CONFIG_USBDEV_MAX_BUS/CONFIG_USBHOST_MAX_BUS默认 1设备/主机控制器数量上限,busid不可超出
CONFIG_USBDEV_FSDEV_PMA_ACCESS1 或 2(芯片相关)FSDEV IP 的 PMA 访问方式,未定义则编译报错
CONFIG_USB_DWC2_DMA_ENABLE注释(关闭)DWC2 设备 DMA 模式;STM32 上仅 PB14/PB15 支持
CONFIG_USB_HS注释(关闭)高速模式;STM32 上仅 PB14/PB15 可 HS
CONFIG_USBHOST_MAX_EHPORTS等见上文主机枚举资源裁剪

延伸阅读(均为仓库内相对路径):

  • 通用移植全流程与链接脚本/cache 细节:docs/zh/quick_start/transplant.rst
  • RT-Thread 环境下的快速上手:docs/zh/quick_start/rtthread.rst
  • 各 USB IP 移植入口:port/(每个 IP 目录含 README)
  • 可直接复制的设备/主机 class demo:demo/(如 demo/cdc_acm_msc_template.c、demo/usb_host.c)
  • ESP32 验证工程:tests/espressif/
  • 各平台实际接入示例:platform/(rtthread、zephyr、nuttx、lvgl、lwip、fatfs 等适配层)

八、小结

按原文档"先跑 demo"的思路落地,验证 CherryUSB 的标准动作是:先选一条有官方 SDK 支持的平台路线(ESP32 走组件管理器、STM32 走 CubeMX +.ioc+ glue 文件),用 demo 自带的 template 完成一次最小枚举/读写,再回到 core/、port/ 源码理解usbd_initialize/usbh_initialize的初始化链。V1.5.0 起usb_glue_*.c已经接管了 Msp 初始化和中断转发,移植者真正需要关心的只剩下:usb_config.h里的 IP 相关宏(如CONFIG_USBDEV_FSDEV_PMA_ACCESS)、带 cache 芯片的 nocache 段与 MPU 配置、以及主机模式的 class info 链接脚本——这三点也是原文档中专门标注 caution 与 note 的地方,跑 demo 时逐条对照验证即可。

  • 嵌入式
  • 驱动开发
  • 物联网

【免费下载链接】CherryUSB

CherryUSB is a tiny and beautiful, high performance and portable USB host and device stack for embedded system with USB IP

项目地址:https://gitcode.com/gh_mirrors/ch/CherryUSB
点击查看免费下载
上一篇:Nacos 默认数据源方言插件实现规范:内置数据库族、Mapper 覆盖与兼容性契约
下一篇:突破3D打印数据壁垒:基于rea/reader的模型提取全攻略

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进