手搓 USB 协议栈:枚举过程与描述符解析
提到 USB,很多嵌入式工程师会“谈虎色变”。毕竟相比于 UART/SPI 的几页寄存器手册,USB 2.0 规范洋洋洒洒 650 页。但如果只想实现一个简单的 USB 设备(Device),比如把开发板模拟成一个串口或键盘,其实并不需要啃完整个协议栈。
1. 物理层:D+/D- 的舞蹈
USB 使用差分信号 D+ 和 D-。
- **全速 (Full Speed, 12Mbps)**:D+ 上拉 1.5k 电阻到 3.3V。
- **高速 (High Speed, 480Mbps)**:握手成功后切换。
当我们将 USB 设备插入电脑(Host)时,Host 检测到 D+ 或 D- 被拉高,就会发起复位(Reset)信号(拉低 D+ / D- 至少 10ms),开始枚举过程。
2. 核心概念:端点 (Endpoint)
USB 通信的终点是端点(Endpoint, EP)。
- EP0:控制端点,双向,所有设备必须支持。用于传输控制命令(枚举就是靠它)。
- EP1~EP15:数据端点,单向(IN 或 OUT)。
- IN:Device -> Host
- OUT:Host -> Device
3. 枚举状态机 (Enumeration)
枚举就是 Host 问 Device:“你是谁?你能干什么?” Device 乖乖交出“简历”(描述符)的过程。
- Powered:设备插入,上电。
- Default:Host 发送复位。此时设备地址为
0。 - Address:
- Host 发送
SET_ADDRESS命令(如设为 5)。 - 设备确认,从此以后只响应地址 5 的包。
- Host 发送
- Configured:
- Host 索要各种描述符。
- Host 发送
SET_CONFIGURATION,设备进入工作状态,驱动加载成功。
4. 描述符 (Descriptors)
这即使 USB 的“简历”结构,这也是手写协议栈最繁琐的部分。
1 | Device Descriptor (设备描述符,只有1个) |
关键坑点
- **小端序 (Little Endian)**:USB 协议规定数据低字节在前。构建多字节字段时要小心。
- 字节对齐:某些结构体最好加上
__attribute__((packed)),防止编译器自动填充导致数据错位。
5. 实现一个 CDC (虚拟串口)
CDC (Communication Device Class) 是最常用的类。
- 无需驱动:Windows 10+ 和 Linux 自带驱动。
- 实现要点:
- 需要两个接口:
- **通信接口类 (CIC)**:管理串口状态(波特率等),使用 1 个中断 IN 端点。
- **数据接口类 (DIC)**:传输实际数据,使用 1 个批量 IN 和 1 个批量 OUT 端点。
- 需要两个接口:
总结
自己手写一遍 USB 枚举代码,是对协议理解最好的检验。当你看到电脑右下角弹出“USB 设备已识别”的那一刻,那种成就感是调用厂商库无法比拟的。
- Title: 手搓 USB 协议栈:枚举过程与描述符解析
- Author: Evek Golden
- Created at : 2024-02-05 10:00:00
- Updated at : 2026-06-12 08:57:02
- Link: https://blog.cocodemo.uno/posts/usb8w2n/
- License: This work is licensed under CC BY-NC-SA 4.0.
Comments