九、CherryUSB 设计架构与工作逻辑分析

九、CherryUSB 设计架构与工作逻辑分析

USB 协议学习目录

点击标题可跳转到对应的网络文章

本文基于 CherryUSB-1.6.1 源码(hid_mouse_template.c 应用示例、usbd_core.c 核心协议层、usb_dc_dwc2.c DWC2 驱动层)以及 USB 协议文档,对 CherryUSB 的设计架构与工作逻辑进行结构化分析。


目录

  • 一、整体架构设计:四层分离模型
    • [1.1 设计哲学](#1.1 设计哲学)
  • 二、应用层剖析:hid_mouse_template.c
    • [2.1 描述符的定义与组织](#2.1 描述符的定义与组织)
      • [2.1.1 设备描述符](#2.1.1 设备描述符)
      • [2.1.2 配置描述符](#2.1.2 配置描述符)
      • [2.1.3 字符串描述符](#2.1.3 字符串描述符)
      • [2.1.4 描述符回调注册](#2.1.4 描述符回调注册)
    • [2.2 HID 报告描述符 (Report Descriptor)](#2.2 HID 报告描述符 (Report Descriptor))
    • [2.3 接口与端点的注册](#2.3 接口与端点的注册)
    • [2.4 事件处理与数据发送流程](#2.4 事件处理与数据发送流程)
      • [2.4.1 设备事件处理](#2.4.1 设备事件处理)
      • [2.4.2 数据发送与状态同步](#2.4.2 数据发送与状态同步)
  • 三、核心层工作逻辑:usbd_core.c
    • [3.1 枚举流程的代码实现](#3.1 枚举流程的代码实现)
      • [3.1.1 标准请求处理入口](#3.1.1 标准请求处理入口)
    • [3.2 配置解析与端点使能](#3.2 配置解析与端点使能)
    • [3.3 EP0 控制传输状态机](#3.3 EP0 控制传输状态机)
    • [3.4 请求分发机制](#3.4 请求分发机制)
    • [3.5 事件回调路由](#3.5 事件回调路由)
  • 四、控制器驱动层剖析:usb_dc_dwc2.c
    • [4.1 硬件初始化 (usb_dc_init)](#4.1 硬件初始化 (usb_dc_init))
    • [4.2 中断处理 (USBD_IRQHandler)](#4.2 中断处理 (USBD_IRQHandler))
    • [4.3 端点操作与 FIFO 读写](#4.3 端点操作与 FIFO 读写)
  • [五、代码与 USB 协议的深层映射](#五、代码与 USB 协议的深层映射)
    • [5.1 描述符体系(代码 ↔ 协议)](#5.1 描述符体系(代码 ↔ 协议))
    • [5.2 枚举过程(代码 ↔ 协议)](#5.2 枚举过程(代码 ↔ 协议))
    • [5.3 传输类型(代码 ↔ 协议)](#5.3 传输类型(代码 ↔ 协议))
    • [5.4 Setup 包解析(代码 ↔ 协议)](#5.4 Setup 包解析(代码 ↔ 协议))
  • 六、工作全景图与总结

一、整体架构设计:四层分离模型

CherryUSB 采用经典的分层架构,将 USB 设备开发的硬件无关性硬件相关性严格分离,实现高度可移植性。
#mermaid-svg-BeTqfuCw5kXWqqh5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-BeTqfuCw5kXWqqh5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BeTqfuCw5kXWqqh5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BeTqfuCw5kXWqqh5 .error-icon{fill:#552222;}#mermaid-svg-BeTqfuCw5kXWqqh5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BeTqfuCw5kXWqqh5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BeTqfuCw5kXWqqh5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BeTqfuCw5kXWqqh5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BeTqfuCw5kXWqqh5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BeTqfuCw5kXWqqh5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BeTqfuCw5kXWqqh5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BeTqfuCw5kXWqqh5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BeTqfuCw5kXWqqh5 .marker.cross{stroke:#333333;}#mermaid-svg-BeTqfuCw5kXWqqh5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BeTqfuCw5kXWqqh5 p{margin:0;}#mermaid-svg-BeTqfuCw5kXWqqh5 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-BeTqfuCw5kXWqqh5 .cluster-label text{fill:#333;}#mermaid-svg-BeTqfuCw5kXWqqh5 .cluster-label span{color:#333;}#mermaid-svg-BeTqfuCw5kXWqqh5 .cluster-label span p{background-color:transparent;}#mermaid-svg-BeTqfuCw5kXWqqh5 .label text,#mermaid-svg-BeTqfuCw5kXWqqh5 span{fill:#333;color:#333;}#mermaid-svg-BeTqfuCw5kXWqqh5 .node rect,#mermaid-svg-BeTqfuCw5kXWqqh5 .node circle,#mermaid-svg-BeTqfuCw5kXWqqh5 .node ellipse,#mermaid-svg-BeTqfuCw5kXWqqh5 .node polygon,#mermaid-svg-BeTqfuCw5kXWqqh5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-BeTqfuCw5kXWqqh5 .rough-node .label text,#mermaid-svg-BeTqfuCw5kXWqqh5 .node .label text,#mermaid-svg-BeTqfuCw5kXWqqh5 .image-shape .label,#mermaid-svg-BeTqfuCw5kXWqqh5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-BeTqfuCw5kXWqqh5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-BeTqfuCw5kXWqqh5 .rough-node .label,#mermaid-svg-BeTqfuCw5kXWqqh5 .node .label,#mermaid-svg-BeTqfuCw5kXWqqh5 .image-shape .label,#mermaid-svg-BeTqfuCw5kXWqqh5 .icon-shape .label{text-align:center;}#mermaid-svg-BeTqfuCw5kXWqqh5 .node.clickable{cursor:pointer;}#mermaid-svg-BeTqfuCw5kXWqqh5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-BeTqfuCw5kXWqqh5 .arrowheadPath{fill:#333333;}#mermaid-svg-BeTqfuCw5kXWqqh5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-BeTqfuCw5kXWqqh5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-BeTqfuCw5kXWqqh5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BeTqfuCw5kXWqqh5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-BeTqfuCw5kXWqqh5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BeTqfuCw5kXWqqh5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-BeTqfuCw5kXWqqh5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-BeTqfuCw5kXWqqh5 .cluster text{fill:#333;}#mermaid-svg-BeTqfuCw5kXWqqh5 .cluster span{color:#333;}#mermaid-svg-BeTqfuCw5kXWqqh5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-BeTqfuCw5kXWqqh5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-BeTqfuCw5kXWqqh5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-BeTqfuCw5kXWqqh5 .icon-shape,#mermaid-svg-BeTqfuCw5kXWqqh5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BeTqfuCw5kXWqqh5 .icon-shape p,#mermaid-svg-BeTqfuCw5kXWqqh5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-BeTqfuCw5kXWqqh5 .icon-shape .label rect,#mermaid-svg-BeTqfuCw5kXWqqh5 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BeTqfuCw5kXWqqh5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-BeTqfuCw5kXWqqh5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-BeTqfuCw5kXWqqh5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 调用接口 API
调用 DCD 接口

usb_dc.h
操作寄存器

调用芯片初始化
Layer 1: Hardware Abstract Layer(硬件抽象层)
usb_glue_st.c

usb_glue_gd.c / ...
时钟使能

GPIO / 中断向量表
芯片特定配置

phy_type / fifo_size
Layer 2: Device Controller Driver(DCD 驱动层)
usb_dc_dwc2.c

usb_dc_fsdev.c / ...
硬件寄存器操作

PHY / FIFO / 端点
中断处理

Reset / Setup / IN / OUT
DMA / 非 DMA

数据传输
Layer 3: Core Layer(核心协议层)
usbd_core.c
USB 枚举状态机

EP0 控制传输
标准请求处理

GET_DESCRIPTOR / SET_ADDRESS
类 / 厂商请求分发
事件管理

回调路由
Layer 4: Application / Class Layer(应用/类层)
hid_mouse_template.c

usbd_hid.c / usbd_cdc.c / ...
定义描述符

报告描述符 / 字符串
注册接口与端点
业务逻辑

数据上报 / 回调处理

1.1 设计哲学

  1. 向下隔离 :核心层通过统一的 usb_dc.h 接口(如 usbd_ep_start_writeusbd_ep_open)调用 DCD 层,无需关心具体是 DWC2、FSDev 还是其他 USB IP。
  2. 向上扩展 :类层通过注册回调函数(usbd_interfaceusbd_endpoint)挂接到核心层,核心层在枚举或数据传输时自动调用这些回调。
  3. 协议驱动 :设备能力完全由描述符自描述,符合 USB 协议"主机通过描述符了解设备"的核心思想。

二、应用层剖析:hid_mouse_template.c

这是用户最直接接触的一层。它展示了一个 HID 鼠标设备需要提供的全部要素:描述符定义、接口/端点注册、事件处理与数据发送。

2.1 描述符的定义与组织

USB 协议中,描述符是设备的"身份证"和"说明书"。CherryUSB 通过宏和回调函数将其结构化。

2.1.1 设备描述符
c 复制代码
static const uint8_t device_descriptor[] = {
    USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0x00, 0x00, 0x00, USBD_VID, USBD_PID, 0x0002, 0x01)
};
  • USB_2_0(即 0x0200):表示设备兼容 USB 2.0 规范。
  • USBD_VID / USBD_PID:厂商 ID 与产品 ID,Host 通过这对值匹配驱动。
  • 最后一个参数 0x01bMaxPacketSize0,表示控制端点 EP0 的最大包大小为 64 字节。
2.1.2 配置描述符
c 复制代码
static const uint8_t config_descriptor[] = {
    USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x01, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
    HID_MOUSE_DESCRIPTOR_INIT(0x00, 0x01, HID_MOUSE_REPORT_DESC_SIZE, HID_INT_EP, HID_INT_EP_SIZE, HID_INT_EP_INTERVAL),
};

USB_CONFIG_DESCRIPTOR_INIT 宏内部构建了配置描述符(9B) + 接口描述符(9B) + HID 描述符(9B) + 端点描述符(7B) 的级联结构,总长度为 USB_CONFIG_SIZE = 34 字节。这与 USB 协议中配置描述符的 wTotalLength 概念完全一致------它包含了该配置下所有的子描述符。

2.1.3 字符串描述符
c 复制代码
static const char *string_descriptors[] = {
    (const char[]){ 0x09, 0x04 }, /* Langid = 0x0409 (English US) */
    "CherryUSB",                  /* Manufacturer */
    "CherryUSB HID DEMO",         /* Product */
    "2022123456",                 /* Serial Number */
};

核心层在处理 GET_DESCRIPTOR(String) 请求时,会将 ASCII 字符串动态编码为 UTF-16LE 格式返回给 Host。

2.1.4 描述符回调注册
c 复制代码
const struct usb_descriptor hid_descriptor = {
    .device_descriptor_callback = device_descriptor_callback,
    .config_descriptor_callback = config_descriptor_callback,
    .device_quality_descriptor_callback = device_quality_descriptor_callback,
    .string_descriptor_callback = string_descriptor_callback
};

核心层不直接保存描述符数据,而是保存获取描述符的函数指针。这种设计允许设备根据当前速度(Full Speed / High Speed)返回不同的描述符,支持双速设备。

2.2 HID 报告描述符 (Report Descriptor)

HID 设备特有的描述符,精确定义了上报数据的每一位含义:

c 复制代码
static const uint8_t hid_mouse_report_desc[HID_MOUSE_REPORT_DESC_SIZE] = {
    0x05, 0x01, // USAGE_PAGE (Generic Desktop)
    0x09, 0x02, // USAGE (Mouse)
    0xA1, 0x01, // COLLECTION (Application)
    0x09, 0x01, // USAGE (Pointer)
    0xA1, 0x00, // COLLECTION (Physical)
    0x05, 0x09, // USAGE_PAGE (Button)
    0x19, 0x01, // USAGE_MINIMUM (Button 1)
    0x29, 0x03, // USAGE_MAXIMUM (Button 3)
    0x15, 0x00, // LOGICAL_MINIMUM (0)
    0x25, 0x01, // LOGICAL_MAXIMUM (1)
    0x95, 0x03, // REPORT_COUNT (3)
    0x75, 0x01, // REPORT_SIZE (1)
    0x81, 0x02, // INPUT (Data,Var,Abs)  -> 3 个按钮,各占 1 bit
    0x95, 0x01, // REPORT_COUNT (1)
    0x75, 0x05, // REPORT_SIZE (5)
    0x81, 0x01, // INPUT (Cnst,Var,Abs)  -> 5 bit 填充位
    0x05, 0x01, // USAGE_PAGE (Generic Desktop)
    0x09, 0x30, // USAGE (X)
    0x09, 0x31, // USAGE (Y)
    0x09, 0x38, // Usage (Wheel)
    0x15, 0x81, // LOGICAL_MINIMUM (-127)
    0x25, 0x7F, // LOGICAL_MAXIMUM (127)
    0x75, 0x08, // REPORT_SIZE (8)
    0x95, 0x03, // REPORT_COUNT (3)
    0x81, 0x06, // INPUT (Data,Var,Rel)  -> X, Y, Wheel,各 8 bit 有符号相对值
    0xC0,       // END_COLLECTION
    0xC0        // END_COLLECTION
};

这段描述符告诉 Host:"我会发送 4 字节的数据,格式是 [Buttons(1B)][X(1B)][Y(1B)][Wheel(1B)],其中低 3 bit 是按钮状态,其余字节是带符号的相对位移量。"

2.3 接口与端点的注册

c 复制代码
struct usbd_interface intf0;

void hid_mouse_init(uint8_t busid, uintptr_t reg_base)
{
    // 1. 注册描述符回调
    usbd_desc_register(busid, &hid_descriptor);

    // 2. 注册 HID 接口(绑定类请求处理函数)
    usbd_add_interface(busid, usbd_hid_init_intf(busid, &intf0, hid_mouse_report_desc, HID_MOUSE_REPORT_DESC_SIZE));

    // 3. 注册 IN 端点(中断传输,用于上报鼠标数据)
    usbd_add_endpoint(busid, &hid_in_ep);

    // 4. 初始化底层硬件
    usbd_initialize(busid, reg_base, usbd_event_handler);
}

核心逻辑解析

  • usbd_add_interfaceintf0 注册到核心层的 g_usbd_core[busid].intf[] 数组中。

  • usbd_hid_init_intf 内部将 HID 类请求处理函数绑定到该接口:

    c 复制代码
    intf->class_interface_handler = hid_class_interface_request_handler;

    当 Host 发送 GET_REPORTSET_IDLE 等 HID 类请求时,核心层会自动路由到该函数。

  • usbd_add_endpoint 将端点 0x81(EP1 IN)的完成回调注册到核心层。当硬件完成数据发送后,usbd_hid_int_callback 会被触发。

2.4 事件处理与数据发送流程

2.4.1 设备事件处理
c 复制代码
static void usbd_event_handler(uint8_t busid, uint8_t event)
{
    switch (event) {
        case USBD_EVENT_RESET:
            break;
        case USBD_EVENT_CONFIGURED:
            hid_state = HID_STATE_IDLE; // 配置完成,可以开始发送数据
            break;
        case USBD_EVENT_SUSPEND:
            break;
        // ...
    }
}

USBD_EVENT_CONFIGURED 是关键的转折点。在枚举完成、Host 发送 SET_CONFIGURATION 后,设备进入 Configured State,此时非零端点已激活,应用层可以开始正常的数据传输。

2.4.2 数据发送与状态同步
c 复制代码
void hid_mouse_test(uint8_t busid)
{
    if (usb_device_is_configured(busid) == false) {
        return;
    }

    int counter = 0;
    while (counter < 1000) {
        draw_circle((uint8_t *)&mouse_cfg);  // 填充鼠标报告数据
        hid_state = HID_STATE_BUSY;
        usbd_ep_start_write(busid, HID_INT_EP, (uint8_t *)&mouse_cfg, 4); // EP1 IN 发送 4 字节
        while (hid_state == HID_STATE_BUSY) {
            // 等待发送完成
        }
        counter++;
    }
}

// 发送完成中断回调(由 DCD 层触发,经核心层路由)
static void usbd_hid_int_callback(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
    hid_state = HID_STATE_IDLE; // 标记为空闲,可发送下一包
}

完整数据流

  1. 应用调用 usbd_ep_start_write,进入核心层 → DCD 层。
  2. DCD 层(DWC2)将数据写入 Tx FIFO,使能端点,等待 Host 的 IN 令牌。
  3. Host 按照端点描述符中 bInterval 定义的轮询间隔(此处为 1ms),发起 IN 令牌包。
  4. 设备将 FIFO 中的数据返回给 Host,Host 回复 ACK。
  5. 传输完成后,DWC2 产生 IN 端点传输完成中断 (USB_OTG_DIEPINT_XFRC)。
  6. DCD 层调用核心层的 usbd_event_ep_in_complete_handler,核心层查表找到 EP1 注册的回调 usbd_hid_int_callback
  7. 应用层收到回调,状态从 BUSY 变为 IDLE

三、核心层工作逻辑:usbd_core.c

核心层是 CherryUSB 的大脑,负责实现 USB 协议 Chapter 9 规定的设备行为,特别是枚举过程控制传输状态机

3.1 枚举流程的代码实现

USB 设备插入后,Host 通过一系列标准请求完成枚举。核心层通过 EP0(控制端点)处理这些请求。
设备 (usbd_core.c) USB Host 设备 (usbd_core.c) USB Host #mermaid-svg-kOzD3wYwWV2xwFFD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kOzD3wYwWV2xwFFD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kOzD3wYwWV2xwFFD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kOzD3wYwWV2xwFFD .error-icon{fill:#552222;}#mermaid-svg-kOzD3wYwWV2xwFFD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kOzD3wYwWV2xwFFD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kOzD3wYwWV2xwFFD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kOzD3wYwWV2xwFFD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kOzD3wYwWV2xwFFD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kOzD3wYwWV2xwFFD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kOzD3wYwWV2xwFFD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kOzD3wYwWV2xwFFD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kOzD3wYwWV2xwFFD .marker.cross{stroke:#333333;}#mermaid-svg-kOzD3wYwWV2xwFFD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kOzD3wYwWV2xwFFD p{margin:0;}#mermaid-svg-kOzD3wYwWV2xwFFD .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-kOzD3wYwWV2xwFFD text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-kOzD3wYwWV2xwFFD .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-kOzD3wYwWV2xwFFD .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-kOzD3wYwWV2xwFFD .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-kOzD3wYwWV2xwFFD .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-kOzD3wYwWV2xwFFD #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-kOzD3wYwWV2xwFFD .sequenceNumber{fill:white;}#mermaid-svg-kOzD3wYwWV2xwFFD #sequencenumber{fill:#333;}#mermaid-svg-kOzD3wYwWV2xwFFD #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-kOzD3wYwWV2xwFFD .messageText{fill:#333;stroke:none;}#mermaid-svg-kOzD3wYwWV2xwFFD .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-kOzD3wYwWV2xwFFD .labelText,#mermaid-svg-kOzD3wYwWV2xwFFD .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-kOzD3wYwWV2xwFFD .loopText,#mermaid-svg-kOzD3wYwWV2xwFFD .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-kOzD3wYwWV2xwFFD .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-kOzD3wYwWV2xwFFD .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-kOzD3wYwWV2xwFFD .noteText,#mermaid-svg-kOzD3wYwWV2xwFFD .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-kOzD3wYwWV2xwFFD .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-kOzD3wYwWV2xwFFD .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-kOzD3wYwWV2xwFFD .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-kOzD3wYwWV2xwFFD .actorPopupMenu{position:absolute;}#mermaid-svg-kOzD3wYwWV2xwFFD .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-kOzD3wYwWV2xwFFD .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-kOzD3wYwWV2xwFFD .actor-man circle,#mermaid-svg-kOzD3wYwWV2xwFFD line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-kOzD3wYwWV2xwFFD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 阶段 1:获取设备描述符前 8 字节(得到 bMaxPacketSize0) usbd_std_device_req_handler ->> usbd_get_descriptor 阶段 2:分配设备地址 usbd_std_device_req_handler ->> usbd_set_address(busid, value) 阶段 3:获取完整设备描述符 阶段 4:获取完整配置描述符集 阶段 5:激活配置,设备就绪 usbd_std_device_req_handler ->> usbd_set_configuration ->> 遍历描述符,usbd_set_endpoint ->> 打开所有非零端点 ->> usbd_class_event_notify_handler ->> 触发 USBD_EVENT_CONFIGURED GET_DESCRIPTOR(Device, 8B) 前 8 字节(含 bMaxPacketSize0) SET_ADDRESS(addr) ACK GET_DESCRIPTOR(Device, 18B) 完整设备描述符 GET_DESCRIPTOR(Config, wTotalLength) 完整配置描述符集 SET_CONFIGURATION(value) ACK

3.1.1 标准请求处理入口
c 复制代码
static bool usbd_std_device_req_handler(uint8_t busid, struct usb_setup_packet *setup, uint8_t **data, uint32_t *len)
{
    uint16_t value = setup->wValue;
    bool ret = true;

    switch (setup->bRequest) {
        case USB_REQUEST_SET_ADDRESS:
            g_usbd_core[busid].device_address = value;
            usbd_set_address(busid, value); // 调用 DCD 层写 DCFG 地址寄存器
            *len = 0;
            break;

        case USB_REQUEST_GET_DESCRIPTOR:
            ret = usbd_get_descriptor(busid, value, data, len);
            break;

        case USB_REQUEST_SET_CONFIGURATION:
            value &= 0xFF;
            if (value == 0) {
                g_usbd_core[busid].configuration = 0;
            } else if (!usbd_set_configuration(busid, value, 0)) {
                ret = false; // 配置失败
            } else {
                g_usbd_core[busid].configuration = value;
                g_usbd_core[busid].is_suspend = false;
                // 通知类层和应用层
                usbd_class_event_notify_handler(busid, USBD_EVENT_CONFIGURED, NULL);
                g_usbd_core[busid].event_handler(busid, USBD_EVENT_CONFIGURED);
            }
            *len = 0;
            break;
        // ...
    }
    return ret;
}

SET_CONFIGURATION 成功后,设备进入 Configured State,非零端点被激活,应用层才能开始正常的数据传输。

3.2 配置解析与端点使能

usbd_set_configuration 是连接"协议"与"硬件"的关键桥梁:

c 复制代码
static bool usbd_set_configuration(uint8_t busid, uint8_t config_index, uint8_t alt_setting)
{
    const uint8_t *p = g_usbd_core[busid].descriptors->config_descriptor_callback(...);

    // 遍历配置描述符中的所有子描述符
    while (p[DESC_bLength] != 0U) {
        switch (p[DESC_bDescriptorType]) {
            case USB_DESCRIPTOR_TYPE_CONFIGURATION:
                cur_config = p[CONF_DESC_bConfigurationValue];
                desc_len = (p[CONF_DESC_wTotalLength]) | (p[CONF_DESC_wTotalLength + 1] << 8);
                break;

            case USB_DESCRIPTOR_TYPE_INTERFACE:
                cur_alt_setting = p[INTF_DESC_bAlternateSetting];
                break;

            case USB_DESCRIPTOR_TYPE_ENDPOINT:
                if ((cur_config == config_index) && (cur_alt_setting == alt_setting)) {
                    found = usbd_set_endpoint(busid, (struct usb_endpoint_descriptor *)p);
                    // -> 最终调用 DCD 层的 usbd_ep_open
                }
                break;
        }
        p += p[DESC_bLength]; // 跳到下一个描述符
    }
    return found;
}

核心层逐字节解析配置描述符,遇到端点描述符时,调用 usbd_set_endpointusbd_ep_open,将端点的地址、类型、最大包大小等配置写入硬件寄存器。

3.3 EP0 控制传输状态机

控制传输分为三个阶段:Setup → Data (可选) → Status 。核心层通过 ep0_next_state 维护这个状态机。

c 复制代码
#define USBD_EP0_STATE_SETUP      0
#define USBD_EP0_STATE_IN_DATA    1
#define USBD_EP0_STATE_OUT_DATA   2
#define USBD_EP0_STATE_IN_STATUS  3
#define USBD_EP0_STATE_OUT_STATUS 4
状态流转示例:GET_DESCRIPTOR (Device)

阶段 1:SETUP

  • DCD 层收到 Setup 包,调用 usbd_event_ep0_setup_complete_handler
  • 核心层解析 setup->bmRequestType = 0x80(IN 方向)、bRequest = 0x06(GET_DESCRIPTOR)。
  • 调用 usbd_setup_request_handlerusbd_get_descriptor,获取设备描述符指针和长度。
  • 状态变为 USBD_EP0_STATE_IN_DATA,调用 usbd_ep_start_write 通过 EP0 IN 发送数据。

阶段 2:DATA IN

  • Host 不断发 IN 令牌,设备分段发送数据(受限于 EP0 MaxPacketSize = 64B)。
  • 每完成一段,DCD 调用 usbd_event_ep0_in_complete_handler
  • 核心层检查 ep0_data_buf_residue,若还有剩余数据,继续 usbd_ep_start_write;若发完,进入 Status 阶段。
  • ZLP 处理 :如果主机请求的长度大于描述符实际长度,且实际长度是 MaxPacketSize 的整数倍,必须额外发送一个 Zero-Length Packet (ZLP) 来标记传输结束。核心层通过 zlp_flag 管理这一逻辑。

阶段 3:STATUS

  • 核心层调用 usbd_ep_start_read(busid, USB_CONTROL_OUT_EP0, NULL, 0),等待主机的 OUT Status (Zero Length Packet)。
  • 收到后状态回到 USBD_EP0_STATE_SETUP,准备接收下一个 Setup 包。
状态流转示例:SET_REPORT (OUT 方向类请求)

阶段 1:SETUP

  • 核心层解析 bmRequestType = 0x21(OUT, Class, Interface)、bRequest = 0x09(SET_REPORT)。
  • 发现 wLength > 0 且方向为 OUT,状态变为 USBD_EP0_STATE_OUT_DATA
  • 调用 usbd_ep_start_read 准备接收后续数据,不立即处理请求

阶段 2:DATA OUT

  • Host 通过 OUT 事务发送报告数据。
  • DCD 层收到数据后触发 usbd_event_ep0_out_complete_handler
  • 若所有数据接收完毕,此时才调用 usbd_setup_request_handlerusbd_class_request_handlerhid_class_interface_request_handler
  • 状态变为 USBD_EP0_STATE_IN_STATUS

阶段 3:STATUS

  • 核心层通过 EP0 IN 发送 ZLP(IN Status),完成整个控制传输。

3.4 请求分发机制

核心层根据 bmRequestTypeType 字段(Bit5~6)将请求分发到不同的处理器:

c 复制代码
static bool usbd_setup_request_handler(uint8_t busid, struct usb_setup_packet *setup, uint8_t **data, uint32_t *len)
{
    switch (setup->bmRequestType & USB_REQUEST_TYPE_MASK) {
        case USB_REQUEST_STANDARD:  // 00 = 标准请求
            return usbd_standard_request_handler(...);
        case USB_REQUEST_CLASS:     // 01 = 类请求
            return usbd_class_request_handler(...);
        case USB_REQUEST_VENDOR:    // 10 = 厂商请求
            return usbd_vendor_request_handler(...);
    }
}

类请求分发 会遍历所有已注册的接口,匹配 wIndex(接口号)找到对应的 class_interface_handler

c 复制代码
static int usbd_class_request_handler(uint8_t busid, struct usb_setup_packet *setup, uint8_t **data, uint32_t *len)
{
    for (uint8_t i = 0; i < g_usbd_core[busid].intf_offset; i++) {
        struct usbd_interface *intf = g_usbd_core[busid].intf[i];
        if (intf && intf->class_interface_handler && (intf->intf_num == (setup->wIndex & 0xFF))) {
            return intf->class_interface_handler(busid, setup, data, len);
        }
    }
    return -1;
}

这正是 usbd_hid.c 中的 hid_class_interface_request_handler 被调用的路径。HID 类请求(如 GET_REPORTSET_IDLEGET_PROTOCOL)在这里被解析并执行。

3.5 事件回调路由

核心层维护了两个消息表 tx_msg[]rx_msg[],用于将硬件中断路由到应用层注册的回调:

c 复制代码
void usbd_event_ep_in_complete_handler(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
    if (g_usbd_core[busid].tx_msg[ep & 0x7f].cb) {
        g_usbd_core[busid].tx_msg[ep & 0x7f].cb(busid, ep, nbytes);
    }
}

void usbd_event_ep_out_complete_handler(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
    if (g_usbd_core[busid].rx_msg[ep & 0x7f].cb) {
        g_usbd_core[busid].rx_msg[ep & 0x7f].cb(busid, ep, nbytes);
    }
}

当应用层调用 usbd_add_endpoint 时,回调函数就被注册到了这个表中:

c 复制代码
void usbd_add_endpoint(uint8_t busid, struct usbd_endpoint *ep)
{
    if (ep->ep_addr & 0x80) { // IN 端点
        g_usbd_core[busid].tx_msg[ep->ep_addr & 0x7f].ep = ep->ep_addr;
        g_usbd_core[busid].tx_msg[ep->ep_addr & 0x7f].cb = ep->ep_cb;
    } else { // OUT 端点
        g_usbd_core[busid].rx_msg[ep->ep_addr & 0x7f].ep = ep->ep_addr;
        g_usbd_core[busid].rx_msg[ep->ep_addr & 0x7f].cb = ep->ep_cb;
    }
}

四、控制器驱动层剖析:usb_dc_dwc2.c

DWC2(DesignWare Core 2)是 Synopsys 的 USB OTG IP 核,广泛应用于 STM32F4/F7/H7、ESP32S3、AT32 等芯片。CherryUSB 的 DWC2 驱动层负责将核心层的抽象操作转化为具体的寄存器读写。

4.1 硬件初始化 (usb_dc_init)

c 复制代码
int usb_dc_init(uint8_t busid)
{
    // 1. 读取硬件参数(从 GHWCFG 寄存器自动探测端点数量、FIFO 深度等)
    dwc2_get_hwparams(USBD_BASE, &g_dwc2_udc[busid].hw_params);
    dwc2_get_user_params(USBD_BASE, &g_dwc2_udc[busid].user_params);

    // 2. PHY 类型选择与内核软复位
    dwc2_core_init(busid); // 配置 ULPI/UTMI/FS Embedded PHY
    dwc2_set_mode(busid, USB_OTG_MODE_DEVICE); // 强制设为设备模式

    // 3. 速度配置
    if (phy_type != FS) {
        USB_OTG_DEV->DCFG |= USB_OTG_SPEED_HIGH;
    } else {
        USB_OTG_DEV->DCFG |= USB_OTG_SPEED_FULL;
    }

    // 4. FIFO 划分(关键!)
    USB_OTG_GLB->GRXFSIZ = device_rx_fifo_size; // Rx FIFO 深度
    for (uint8_t i = 0; i < (num_ep + 1); i++) {
        dwc2_set_txfifo(busid, i, device_tx_fifo_size[i]);
    }

    // 5. 中断使能
    USB_OTG_GLB->GINTMSK = USB_OTG_GINTMSK_USBRST |    // USB 复位
                           USB_OTG_GINTMSK_ENUMDNEM |  // 枚举完成
                           USB_OTG_GINTMSK_OEPINT |    // OUT 端点中断
                           USB_OTG_GINTMSK_IEPINT |    // IN 端点中断
                           USB_OTG_GINTMSK_USBSUSPM |  // 挂起
                           USB_OTG_GINTMSK_WUIM;       // 唤醒

    if (!dma_enable) {
        USB_OTG_GLB->GINTMSK |= USB_OTG_GINTMSK_RXFLVLM; // Rx FIFO 非空(非 DMA 模式需要)
    }

    // 6. 使能全局中断,解除软断开,真正连接到总线
    USB_OTG_GLB->GAHBCFG |= USB_OTG_GAHBCFG_GINT;
    USB_OTG_DEV->DCTL &= ~USB_OTG_DCTL_SDIS;
}
关键设计点

FIFO 管理:DWC2 只有一个物理 DFIFO,需要软件手动划分:

  • Rx FIFO:所有 OUT 端点(包括 EP0)共享一个接收 FIFO。
  • Tx FIFO :每个 IN 端点需要一个独立的发送 FIFO。dwc2_set_txfifo 负责计算每个 Tx FIFO 在 DFIFO 中的起始偏移和深度。
c 复制代码
static void dwc2_set_txfifo(uint8_t busid, uint8_t fifo, uint16_t size)
{
    tx_offset = USB_OTG_GLB->GRXFSIZ; // Rx FIFO 之后开始
    if (fifo == 0U) {
        USB_OTG_GLB->DIEPTXF0_HNPTXFSIZ = ((uint32_t)size << 16) | tx_offset;
    } else {
        // 累加前面所有 Tx FIFO 的大小,计算当前 FIFO 的偏移
        tx_offset += (USB_OTG_GLB->DIEPTXF0_HNPTXFSIZ) >> 16;
        for (i = 0; i < (fifo - 1); i++) {
            tx_offset += (USB_OTG_GLB->DIEPTXF[i] >> 16);
        }
        USB_OTG_GLB->DIEPTXF[fifo - 1] = ((uint32_t)size << 16) | tx_offset;
    }
}

DMA 支持 :如果芯片支持并使能 DMA,GAHBCFG 配置 DMA 模式后,数据直接在内存和端点之间传输,无需 CPU 通过 dwc2_ep_write/read 干预 FIFO 读写。

4.2 中断处理 (USBD_IRQHandler)

这是 DCD 层最复杂的部分,负责处理所有 USB 硬件事件并向上通知核心层。

c 复制代码
void USBD_IRQHandler(uint8_t busid)
{
    uint32_t gint_status = dwc2_get_glb_intstatus(busid);

    // ==========================================
    // 1. RX FIFO 非空中断(仅非 DMA 模式)
    // ==========================================
    if (!dma_enable && (gint_status & USB_OTG_GINTSTS_RXFLVL)) {
        USB_MASK_INTERRUPT(USB_OTG_GLB, USB_OTG_GINTSTS_RXFLVL);

        temp = USB_OTG_GLB->GRXSTSP; // 读取接收状态弹出寄存器
        ep_idx = temp & USB_OTG_GRXSTSP_EPNUM;
        pktsts = (temp & USB_OTG_GRXSTSP_PKTSTS) >> USB_OTG_GRXSTSP_PKTSTS_Pos;
        read_count = (temp & USB_OTG_GRXSTSP_BCNT) >> 4;

        if (pktsts == STS_DATA_UPDT) {
            // 普通 OUT 数据包:读取到应用缓冲区
            dwc2_ep_read(busid, g_dwc2_udc[busid].out_ep[ep_idx].xfer_buf, read_count);
            g_dwc2_udc[busid].out_ep[ep_idx].xfer_buf += read_count;
        } else if (pktsts == STS_SETUP_UPDT) {
            // Setup 数据包:读取到全局 setup 结构体
            dwc2_ep_read(busid, (uint8_t *)&g_dwc2_udc[busid].setup, read_count);
        }
        USB_UNMASK_INTERRUPT(USB_OTG_GLB, USB_OTG_GINTSTS_RXFLVL);
    }

    // ==========================================
    // 2. OUT 端点中断
    // ==========================================
    if (gint_status & USB_OTG_GINTSTS_OEPINT) {
        ep_intr = dwc2_get_outeps_intstatus(busid);
        while (ep_intr != 0U) {
            if (ep_intr & 0x1U) {
                epint = dwc2_get_outep_intstatus(busid, ep_idx);

                if (epint & USB_OTG_DOEPINT_XFRC) {
                    // 传输完成中断
                    if (ep_idx == 0) {
                        // EP0 特殊处理:根据当前状态判断是 Data 阶段完成还是 Status 阶段完成
                        usbd_event_ep_out_complete_handler(busid, 0x00, actual_len);
                    } else {
                        usbd_event_ep_out_complete_handler(busid, ep_idx, actual_len);
                    }
                }

                if (epint & USB_OTG_DOEPINT_STUP) {
                    // Setup 阶段完成:通知核心层处理 Setup 包
                    usbd_event_ep0_setup_complete_handler(busid, (uint8_t *)&g_dwc2_udc[busid].setup);
                }
            }
            ep_intr >>= 1;
            ep_idx++;
        }
    }

    // ==========================================
    // 3. IN 端点中断
    // ==========================================
    if (gint_status & USB_OTG_GINTSTS_IEPINT) {
        ep_intr = dwc2_get_ineps_intstatus(busid);
        while (ep_intr != 0U) {
            if (ep_intr & 0x1U) {
                epint = dwc2_get_inep_intstatus(busid, ep_idx);

                if (epint & USB_OTG_DIEPINT_XFRC) {
                    // IN 传输完成:通知核心层
                    usbd_event_ep_in_complete_handler(busid, ep_idx | 0x80, actual_len);
                }

                if (!dma_enable && (epint & USB_OTG_DIEPINT_TXFE)) {
                    // Tx FIFO 空中断:继续向 FIFO 填充数据
                    dwc2_tx_fifo_empty_procecss(busid, ep_idx);
                }
            }
            ep_intr >>= 1;
            ep_idx++;
        }
    }

    // ==========================================
    // 4. USB 复位(枚举开始)
    // ==========================================
    if (gint_status & USB_OTG_GINTSTS_USBRST) {
        USB_OTG_GLB->GINTSTS = USB_OTG_GINTSTS_USBRST;

        // 禁用所有非零端点,清空 FIFO
        for (uint8_t i = 0; i <= num_dev_ep; i++) {
            USB_OTG_INEP(i)->DIEPCTL = USB_OTG_DIEPCTL_SNAK;
            USB_OTG_OUTEP(i)->DOEPCTL = USB_OTG_DOEPCTL_SNAK;
        }
        dwc2_flush_txfifo(busid, 0x10U);
        dwc2_flush_rxfifo(busid);

        // 重新使能 EP0 中断掩码
        USB_OTG_DEV->DAINTMSK |= 0x10001U;
        USB_OTG_DEV->DOEPMSK = USB_OTG_DOEPMSK_STUPM | USB_OTG_DOEPMSK_XFRCM;
        USB_OTG_DEV->DIEPMSK = USB_OTG_DIEPMSK_XFRCM;

        // 清空端点状态,通知核心层
        memset(g_dwc2_udc[busid].in_ep, 0, ...);
        memset(g_dwc2_udc[busid].out_ep, 0, ...);
        usbd_event_reset_handler(busid);
    }

    // ==========================================
    // 5. 枚举完成(速度协商结束)
    // ==========================================
    if (gint_status & USB_OTG_GINTSTS_ENUMDNE) {
        USB_OTG_GLB->GINTSTS = USB_OTG_GINTSTS_ENUMDNE;

        // 根据协商好的速度设置 Turnaround Time
        dwc2_set_turnaroundtime(busid, system_clock, dwc2_get_devspeed(busid));

        // 清除全局 IN NAK,准备接收 Setup 包
        USB_OTG_DEV->DCTL |= USB_OTG_DCTL_CGINAK;
        dwc2_ep0_start_read_setup(busid, (uint8_t *)&g_dwc2_udc[busid].setup);
    }

    // ==========================================
    // 6. 挂起与唤醒
    // ==========================================
    if (gint_status & USB_OTG_GINTSTS_USBSUSP) {
        usbd_event_suspend_handler(busid);
    }
    if (gint_status & USB_OTG_GINTSTS_WKUINT) {
        usbd_event_resume_handler(busid);
    }
}
中断处理流程解析
中断类型 触发条件 DCD 层处理 向上通知
USBRST Host 发送总线复位信号 (>10ms) 清空所有端点和 FIFO,重置状态 usbd_event_reset_handler
ENUMDNE 速度协商完成 设置 TRDT,准备接收 Setup 无(直接准备硬件)
RXFLVL Rx FIFO 中有新数据(非 DMA) GRXSTSP,区分 Data/Setup,读到对应缓冲区 无(数据暂存,等 XFRC)
OEPINT / XFRC OUT 端点数据全部接收完成 计算实际接收长度 usbd_event_ep_out_complete_handler
OEPINT / STUP Setup 包接收完成 确保 8 字节 Setup 数据已就绪 usbd_event_ep0_setup_complete_handler
IEPINT / XFRC IN 端点数据全部发送完成 清空该端点状态 usbd_event_ep_in_complete_handler
IEPINT / TXFE IN 端点 Tx FIFO 空(非 DMA) 继续从内存向 FIFO 写数据

4.3 端点操作与 FIFO 读写

打开端点
c 复制代码
int usbd_ep_open(uint8_t busid, const struct usb_endpoint_descriptor *ep)
{
    uint8_t ep_idx = USB_EP_GET_IDX(ep->bEndpointAddress);

    if (USB_EP_DIR_IS_OUT(ep->bEndpointAddress)) {
        g_dwc2_udc[busid].out_ep[ep_idx].ep_mps = USB_GET_MAXPACKETSIZE(ep->wMaxPacketSize);
        g_dwc2_udc[busid].out_ep[ep_idx].ep_type = USB_GET_ENDPOINT_TYPE(ep->bmAttributes);

        // 写 DOEPCTL 寄存器:配置 MPS、类型、设置 DATA0 PID、激活端点
        USB_OTG_OUTEP(ep_idx)->DOEPCTL |= (ep_mps & USB_OTG_DOEPCTL_MPSIZ) |
                                          (type << 18) |
                                          USB_OTG_DOEPCTL_SD0PID_SEVNFRM |
                                          USB_OTG_DOEPCTL_USBAEP;
    } else {
        // 检查 Tx FIFO 是否足够大
        USB_ASSERT_MSG((fifo_size * 4) >= mps, "Ep addr %02x fifo overflow", ep_addr);

        g_dwc2_udc[busid].in_ep[ep_idx].ep_mps = mps;
        g_dwc2_udc[busid].in_ep[ep_idx].ep_type = type;

        // 写 DIEPCTL 寄存器
        USB_OTG_INEP(ep_idx)->DIEPCTL |= (ep_mps & USB_OTG_DIEPCTL_MPSIZ) |
                                          (type << 18) | (ep_idx << 22) |
                                          USB_OTG_DIEPCTL_SD0PID_SEVNFRM |
                                          USB_OTG_DIEPCTL_USBAEP;
    }
}
启动 IN 传输
c 复制代码
int usbd_ep_start_write(uint8_t busid, const uint8_t ep, const uint8_t *data, uint32_t data_len)
{
    uint8_t ep_idx = USB_EP_GET_IDX(ep);

    // 保存传输上下文
    g_dwc2_udc[busid].in_ep[ep_idx].xfer_buf = (uint8_t *)data;
    g_dwc2_udc[busid].in_ep[ep_idx].xfer_len = data_len;
    g_dwc2_udc[busid].in_ep[ep_idx].actual_xfer_len = 0;

    // 配置传输大小和包数量
    USB_OTG_INEP(ep_idx)->DIEPTSIZ &= ~(PKTCNT | XFRSIZ);
    if (data_len == 0) {
        USB_OTG_INEP(ep_idx)->DIEPTSIZ |= (1U << 19); // PKTCNT = 1
        USB_OTG_INEP(ep_idx)->DIEPCTL |= (CNAK | EPENA); // 清 NAK,使能端点
        return 0;
    }

    if (ep_idx == 0) {
        // EP0 特殊处理:不能超过 MaxPacketSize
        if (data_len > ep_mps) data_len = ep_mps;
        USB_OTG_INEP(0)->DIEPTSIZ |= (1U << 19) | data_len;
    } else {
        pktcnt = (data_len + ep_mps - 1) / ep_mps;
        USB_OTG_INEP(ep_idx)->DIEPTSIZ |= (pktcnt << 19) | data_len;
    }

    if (dma_enable) {
        // DMA 模式:直接写 DMA 地址,硬件自动搬运
        USB_OTG_INEP(ep_idx)->DIEPDMA = (uint32_t)data;
        USB_OTG_INEP(ep_idx)->DIEPCTL |= (CNAK | EPENA);
    } else {
        // 非 DMA 模式:使能端点,依靠 TXFE 中断手动写 FIFO
        USB_OTG_INEP(ep_idx)->DIEPCTL |= (CNAK | EPENA);
        if (data_len > 0) {
            USB_OTG_DEV->DIEPEMPMSK |= (1UL << ep_idx); // 使能该端点的 Tx FIFO 空中断
        }
    }
}
FIFO 读写(非 DMA 模式)
c 复制代码
void dwc2_ep_write(uint8_t busid, uint8_t ep_idx, uint8_t *src, uint16_t len)
{
    uint32_t *p32 = (uint32_t *)src;
    // DWC2 FIFO 是 32 位宽,按字写入
    for (uint32_t i = 0; i < (len / 4); i++) {
        USB_OTG_FIFO((uint32_t)ep_idx) = *p32++;
    }
    // 处理剩余字节(1~3 字节)
    if (remain) {
        uint32_t val = 0;
        uint8_t *p8 = (uint8_t *)p32;
        val = *p8++;
        if (remain > 1) val |= (*p8++) << 8;
        if (remain > 2) val |= (*p8++) << 16;
        USB_OTG_FIFO((uint32_t)ep_idx) = val;
    }
}

void dwc2_ep_read(uint8_t busid, uint8_t *dest, uint16_t len)
{
    uint32_t *p32 = (uint32_t *)dest;
    for (uint32_t i = 0; i < (len / 4); i++) {
        *p32++ = USB_OTG_FIFO(0U); // OUT 端点共享 EP0 的 FIFO 读取端口
    }
    // 处理剩余字节...
}

五、代码与 USB 协议的深层映射

5.1 描述符体系(代码 ↔ 协议)

协议概念 CherryUSB 实现 说明
设备描述符 USB_DEVICE_DESCRIPTOR_INIT 18 字节,包含 VID/PID/USB 版本/EP0 MPS
配置描述符级联 USB_CONFIG_DESCRIPTOR_INIT + HID_MOUSE_DESCRIPTOR_INIT 宏拼接 34 字节,包含配置+接口+HID+端点描述符
字符串描述符 string_descriptors[] 数组,核心层动态编码为 UTF-16LE GET_DESCRIPTOR(0x03) 时编码返回
HID 描述符 宏内部嵌入 bDescriptorType = 0x21 定义 HID 版本和报告描述符长度
报告描述符 hid_mouse_report_desc[] 通过类请求 GET_DESCRIPTOR(0x22) 返回
设备限定描述符 device_quality_descriptor_callback 高速设备在 full speed 环境下返回

5.2 枚举过程(代码 ↔ 协议)

USB 协议规定的枚举流程在 CherryUSB 中的完整映射:

步骤 协议动作 CherryUSB 核心层处理 DCD 层动作
1 设备插入 PHY 检测 D+/D- 上拉
2 Host 发送 RESET usbd_event_reset_handler USBRST 中断,清空 FIFO,打开 EP0
3 GET_DESCRIPTOR(Device, 8) usbd_get_descriptor 返回前 8 字节 EP0 IN 发送数据
4 SET_ADDRESS usbd_set_addressDCFG 地址写入硬件寄存器
5 GET_DESCRIPTOR(Device, 18) 返回完整设备描述符 EP0 IN 发送 18 字节
6 GET_DESCRIPTOR(Config, 9) 返回配置描述符前 9 字节 EP0 IN 发送 9 字节
7 GET_DESCRIPTOR(Config, wTotalLength) 返回完整 34 字节配置集 EP0 IN 分包发送
8 SET_CONFIGURATION(1) usbd_set_configuration 解析并打开所有端点 usbd_ep_open 写 DIEPCTL/DOEPCTL
9 设备就绪 触发 USBD_EVENT_CONFIGURED

5.3 传输类型(代码 ↔ 协议)

传输类型 协议特点 CherryUSB 中的体现
控制传输 可靠、双向、三阶段 (Setup/Data/Status) EP0 专属,usbd_core.c EP0 状态机管理,用于枚举和类控制
中断传输 可靠、有轮询保证、数据量小 HID 鼠标使用 EP1 IN,bmAttributes = 0x03 (Interrupt),bInterval = 1
批量传输 可靠、尽最大努力、数据量大 MSC/UAC 使用,bmAttributes = 0x02 (Bulk)
同步传输 实时、无握手、带宽预留 音频/视频使用,bmAttributes = 0x01 (Isochronous),DWC2 驱动中有 SODDFRM/SD0PID 帧同步处理

5.4 Setup 包解析(代码 ↔ 协议)

USB 协议的 Setup 包固定 8 字节:bmRequestType | bRequest | wValue | wIndex | wLength

GET_DESCRIPTOR(Device, 18) 为例:

字段 CherryUSB 解析
bmRequestType 0x80 Bit7=1 (Device→Host/IN),Bit6~5=00 (Standard),Bit4~0=00000 (Device)
bRequest 0x06 USB_REQUEST_GET_DESCRIPTOR
wValue 0x0100 高字节 0x01 = Device Descriptor,低字节 0x00 = Index 0
wIndex 0x0000 设备描述符无需此参数
wLength 0x0012 Host 期望接收 18 字节

核心层在 __usbd_event_ep0_setup_complete_handler 中解析这 8 字节,调用 usbd_setup_request_handlerusbd_standard_request_handlerusbd_std_device_req_handlerusbd_get_descriptor,最终通过 device_descriptor_callback 获取数据,再经 EP0 IN 发回 Host。


六、工作全景图与总结

#mermaid-svg-O1n2ckSPWgboOCBw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-O1n2ckSPWgboOCBw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-O1n2ckSPWgboOCBw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-O1n2ckSPWgboOCBw .error-icon{fill:#552222;}#mermaid-svg-O1n2ckSPWgboOCBw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-O1n2ckSPWgboOCBw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-O1n2ckSPWgboOCBw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-O1n2ckSPWgboOCBw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-O1n2ckSPWgboOCBw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-O1n2ckSPWgboOCBw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-O1n2ckSPWgboOCBw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-O1n2ckSPWgboOCBw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-O1n2ckSPWgboOCBw .marker.cross{stroke:#333333;}#mermaid-svg-O1n2ckSPWgboOCBw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-O1n2ckSPWgboOCBw p{margin:0;}#mermaid-svg-O1n2ckSPWgboOCBw .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-O1n2ckSPWgboOCBw .cluster-label text{fill:#333;}#mermaid-svg-O1n2ckSPWgboOCBw .cluster-label span{color:#333;}#mermaid-svg-O1n2ckSPWgboOCBw .cluster-label span p{background-color:transparent;}#mermaid-svg-O1n2ckSPWgboOCBw .label text,#mermaid-svg-O1n2ckSPWgboOCBw span{fill:#333;color:#333;}#mermaid-svg-O1n2ckSPWgboOCBw .node rect,#mermaid-svg-O1n2ckSPWgboOCBw .node circle,#mermaid-svg-O1n2ckSPWgboOCBw .node ellipse,#mermaid-svg-O1n2ckSPWgboOCBw .node polygon,#mermaid-svg-O1n2ckSPWgboOCBw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-O1n2ckSPWgboOCBw .rough-node .label text,#mermaid-svg-O1n2ckSPWgboOCBw .node .label text,#mermaid-svg-O1n2ckSPWgboOCBw .image-shape .label,#mermaid-svg-O1n2ckSPWgboOCBw .icon-shape .label{text-anchor:middle;}#mermaid-svg-O1n2ckSPWgboOCBw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-O1n2ckSPWgboOCBw .rough-node .label,#mermaid-svg-O1n2ckSPWgboOCBw .node .label,#mermaid-svg-O1n2ckSPWgboOCBw .image-shape .label,#mermaid-svg-O1n2ckSPWgboOCBw .icon-shape .label{text-align:center;}#mermaid-svg-O1n2ckSPWgboOCBw .node.clickable{cursor:pointer;}#mermaid-svg-O1n2ckSPWgboOCBw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-O1n2ckSPWgboOCBw .arrowheadPath{fill:#333333;}#mermaid-svg-O1n2ckSPWgboOCBw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-O1n2ckSPWgboOCBw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-O1n2ckSPWgboOCBw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-O1n2ckSPWgboOCBw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-O1n2ckSPWgboOCBw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-O1n2ckSPWgboOCBw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-O1n2ckSPWgboOCBw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-O1n2ckSPWgboOCBw .cluster text{fill:#333;}#mermaid-svg-O1n2ckSPWgboOCBw .cluster span{color:#333;}#mermaid-svg-O1n2ckSPWgboOCBw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-O1n2ckSPWgboOCBw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-O1n2ckSPWgboOCBw rect.text{fill:none;stroke-width:0;}#mermaid-svg-O1n2ckSPWgboOCBw .icon-shape,#mermaid-svg-O1n2ckSPWgboOCBw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-O1n2ckSPWgboOCBw .icon-shape p,#mermaid-svg-O1n2ckSPWgboOCBw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-O1n2ckSPWgboOCBw .icon-shape .label rect,#mermaid-svg-O1n2ckSPWgboOCBw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-O1n2ckSPWgboOCBw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-O1n2ckSPWgboOCBw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-O1n2ckSPWgboOCBw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 循环
数据传输阶段
应用层调用

usbd_ep_start_write(EP1 IN, data, 4)
DCD层: usbd_ep_start_write()

  • DIEPTSIZ = PKTCNT | XFRSIZ

  • DIEPCTL |= CNAK | EPENA

  • 非DMA: 使能 TXFE,写 FIFO

  • DMA: DIEPDMA = data
    Host 按 bInterval=1ms 轮询 IN 令牌

数据返回 Host,Host ACK
IEPINT/XFRC 中断

  • usbd_event_ep_in_complete_handler()

  • 查表 -> usbd_hid_int_callback()

  • hid_state = IDLE
    枚举阶段
    USBD_IRQHandler() - USBRST 中断

  • 清空 FIFO,禁用端点

  • usbd_event_reset_handler()

  • 地址清 0,重新打开 EP0
    ENUMDNE 中断

速度协商完成
SETUP 包接收 -> OEPINT/STUP 中断

  • dwc2_ep_read(setup, 8)

  • usbd_event_ep0_setup_complete_handler()

  • usbd_setup_request_handler()

  • 标准/类/厂商请求分发
    SET_CONFIGURATION 成功

  • usbd_set_configuration()

  • 遍历描述符,usbd_ep_open()

  • 触发 USBD_EVENT_CONFIGURED

  • 应用层 hid_state = IDLE
    初始化阶段
    hid_mouse_init()

  • usbd_desc_register() 注册描述符

  • usbd_add_interface() 注册 HID 接口

  • usbd_add_endpoint() 注册 EP1 IN
    usbd_initialize() -> usb_dc_init()

  • dwc2_core_init() PHY 初始化

  • dwc2_set_mode(DEVICE)

  • 配置 GINTMSK / DAINTMSK

  • DCTL &= ~SDIS 软连接上线
    设备上电 / 系统启动
    下一轮数据发送

核心设计精髓

  1. 协议与硬件解耦usbd_core.c 完全不触碰硬件寄存器,所有硬件操作通过 usb_dc.h 接口下发到 DCD 层。更换芯片只需替换 DCD 层和 Glue 层。

  2. 描述符驱动一切:设备的能力、端点配置、类属性全部编码在描述符中。核心层通过解析描述符自动完成枚举和端点配置,应用层只需提供数据。

  3. 回调与状态机分离:EP0 控制传输由核心层状态机管理,非零端点数据传输通过注册回调实现异步通知。这种设计既保证了协议合规性,又提供了高效的数据吞吐路径。

  4. DWC2 驱动的精细化:FIFO 划分、DMA/非 DMA 双路径、中断精细化处理(区分 XFRC/TXFE/STUP),确保了该驱动在 STM32、ESP32S3 等多种平台上的稳定运行。

相关推荐
smallerxuan1 天前
二、USB协议中的设备类
usb·usb协议·usb设备类
smallerxuan3 天前
三、USB协议通信过程
usb·usb协议·usb通信过程
smallerxuan3 天前
七、USB协议中的事务
usb·usb协议·usb事务
smallerxuan3 天前
五、USB协议中的请求
usb·usb协议·usb请求
smallerxuan3 天前
八、USB协议分析与调试实战
usb·usb协议分析·usb协议·usb协议调测
smallerxuan3 天前
四、USB协议中的描述符
usb·usb协议·usb描述符
ZenasLDR6 天前
Type-C接口水冷散热器
接口·芯片·usb
ZenasLDR14 天前
LDR6600适配器PD协议芯片
接口·芯片·usb
嵌入式科普18 天前
四、RA8P1移植CherryUSB尝鲜
ra8p1·cherryusb·rtthread titan