九、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 等多种平台上的稳定运行。

相关推荐
sinat_399010273 天前
snps usb ip及 vip 使用
usb
Championship.23.2414 天前
Linux 3.0 USB机制深度解析:USB 3.0支持与传统外设驱动架构
linux·运维·架构·usb
ZenasLDR20 天前
Type-C接口iPad键盘皮套
接口·芯片·usb
smallerxuan21 天前
二、USB协议中的设备类
usb·usb协议·usb设备类
smallerxuan23 天前
三、USB协议通信过程
usb·usb协议·usb通信过程
smallerxuan23 天前
七、USB协议中的事务
usb·usb协议·usb事务
smallerxuan23 天前
五、USB协议中的请求
usb·usb协议·usb请求
smallerxuan23 天前
八、USB协议分析与调试实战
usb·usb协议分析·usb协议·usb协议调测
smallerxuan23 天前
四、USB协议中的描述符
usb·usb协议·usb描述符