九、CherryUSB 设计架构与工作逻辑分析
USB 协议学习目录
点击标题可跳转到对应的网络文章
- 一、USB 协议结构详解
- 二、USB 协议中的设备类
- 三、USB 协议通信过程
- 四、USB 协议中的描述符
- 五、USB 协议中的请求
- 六、USB 协议中的接口与端点
- 七、USB 协议中的事务
- 八、USB 协议分析与调试实战
- 九、CherryUSB 设计架构与工作逻辑分析
本文基于 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 数据发送与状态同步)
- [2.1 描述符的定义与组织](#2.1 描述符的定义与组织)
- 三、核心层工作逻辑:
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 事件回调路由)
- [3.1 枚举流程的代码实现](#3.1 枚举流程的代码实现)
- 四、控制器驱动层剖析:
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 读写)
- [4.1 硬件初始化 (
- [五、代码与 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 设计哲学
- 向下隔离 :核心层通过统一的
usb_dc.h接口(如usbd_ep_start_write、usbd_ep_open)调用 DCD 层,无需关心具体是 DWC2、FSDev 还是其他 USB IP。 - 向上扩展 :类层通过注册回调函数(
usbd_interface、usbd_endpoint)挂接到核心层,核心层在枚举或数据传输时自动调用这些回调。 - 协议驱动 :设备能力完全由描述符自描述,符合 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 通过这对值匹配驱动。- 最后一个参数
0x01:bMaxPacketSize0,表示控制端点 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_interface将intf0注册到核心层的g_usbd_core[busid].intf[]数组中。 -
usbd_hid_init_intf内部将 HID 类请求处理函数绑定到该接口:cintf->class_interface_handler = hid_class_interface_request_handler;当 Host 发送
GET_REPORT、SET_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; // 标记为空闲,可发送下一包
}
完整数据流:
- 应用调用
usbd_ep_start_write,进入核心层 → DCD 层。 - DCD 层(DWC2)将数据写入 Tx FIFO,使能端点,等待 Host 的 IN 令牌。
- Host 按照端点描述符中
bInterval定义的轮询间隔(此处为 1ms),发起 IN 令牌包。 - 设备将 FIFO 中的数据返回给 Host,Host 回复 ACK。
- 传输完成后,DWC2 产生 IN 端点传输完成中断 (
USB_OTG_DIEPINT_XFRC)。 - DCD 层调用核心层的
usbd_event_ep_in_complete_handler,核心层查表找到 EP1 注册的回调usbd_hid_int_callback。 - 应用层收到回调,状态从
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_endpoint → usbd_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_handler→usbd_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_handler→usbd_class_request_handler→hid_class_interface_request_handler。 - 状态变为
USBD_EP0_STATE_IN_STATUS。
阶段 3:STATUS
- 核心层通过 EP0 IN 发送 ZLP(IN Status),完成整个控制传输。
3.4 请求分发机制
核心层根据 bmRequestType 的 Type 字段(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_REPORT、SET_IDLE、GET_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_address 写 DCFG |
地址写入硬件寄存器 |
| 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_handler → usbd_standard_request_handler → usbd_std_device_req_handler → usbd_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 软连接上线
设备上电 / 系统启动
下一轮数据发送
核心设计精髓
-
协议与硬件解耦 :
usbd_core.c完全不触碰硬件寄存器,所有硬件操作通过usb_dc.h接口下发到 DCD 层。更换芯片只需替换 DCD 层和 Glue 层。 -
描述符驱动一切:设备的能力、端点配置、类属性全部编码在描述符中。核心层通过解析描述符自动完成枚举和端点配置,应用层只需提供数据。
-
回调与状态机分离:EP0 控制传输由核心层状态机管理,非零端点数据传输通过注册回调实现异步通知。这种设计既保证了协议合规性,又提供了高效的数据吞吐路径。
-
DWC2 驱动的精细化:FIFO 划分、DMA/非 DMA 双路径、中断精细化处理(区分 XFRC/TXFE/STUP),确保了该驱动在 STM32、ESP32S3 等多种平台上的稳定运行。