关于USB的一些笔记理解感悟
最近工作中使用到了USB,也进行了一些浅显的学习,记录一下
一、USB底层
1.物理层
- 使用两根线进行进行通信,分别为D+、D-,差分电压幅度为0.4V左右
- 底层调制方式使用NRZI(Non-Return to Zero Inverted)编码 + 位填充
- USB1.1理论全速为800KB/S ,USB2.0理论全速为35MB/S左右
2.端点
- USB所有通信都是通过端点进行的,不同的芯片可能具有不同的端点数量,但是至少具有端点0
- 控制端点:所有设备必须存在控制端点,只能是端点0. 用来作为设备的枚举、配置、基础命令等,支持双向通信
- 同步端点:同步端点以实时性优先,固定带宽,无错误重传。通常是为音频、视频等设计的(数据量大、时间敏感、允许错误)
- 中断断点:并不会真正的产生中断,而是由主机定期询问设备是否有数据,这个时间间隔通常是1ms。通常是为键盘鼠标等准备的。(时间敏感、数据量小、不允许错误)
- 批量端点:是一种可靠传输,大块数据专用,保证数据正确,单次传输量大,但是只有在总线空闲时才进行传输。常用来做U盘、打印机等(时间不敏感、数据量大、不允许出错)
3. 组包方式
3.1 包类型
包类型 | 组成 | 作用 | 示例 |
---|---|---|---|
Token包 | SYNC + PID + ADDR + ENDP + CRC5 | 声明传输方向/目标地址 | IN(0x69) + 设备地址 + 端点号 |
Data包 | SYNC + PID + DATA + CRC16 | 携带实际数据 | DATA0 + 64字节负载 + CRC |
Handshake包 | SYNC + PID | 确认状态(ACK/NAK/STALL) | ACK(0xD2) |
3.2 传输类型组包方式
不同传输类型有独特的组包逻辑:
- 控制传输
- Setup阶段
- Data阶段
- Status阶段
- 批量传输
- 单事务组包
- 错误恢复
- 中断传输
- 组包方式类似批量传输
- 同步传输
- 精简组包
二、枚举流程
- 设备插入检测
- 物理层触发:主机检测VBUS电压变化(如从0V升到5V)
- 端口复位(Reset)
- 主机发出持续10ms的SE0信号(D+/D-同时拉低)
- 设备进入默认状态(地址=0,端点0激活)
- 首次控制传输(地址0)
主机通过端点0发起以下请求:- GetDescriptor(设备描述符):设备返回18字节基本描述符(含bcdUSB字段标识USB版本)
- SetAddress(新地址):主机分配唯一地址(1~127),设备后续使用该地址通信
- 深度描述符读取
主机用新地址获取更多信息:- GetDescriptor(配置描述符):设备返回配置集合(含接口/端点描述符)
- GetDescriptor(字符串描述符):获取厂商/产品名字等文本信息
- 设备配置
- 主机发送SetConfiguration(配置值)
- 设备启用指定配置(激活所有端点)
- 驱动加载
- 主机根据设备类(bDeviceClass)加载对应驱动
三、感悟
USB协议上给人的感觉很复杂,但是好在基本上所有的协议都是标准化的;
但是市面上存在各种品牌的处理器,是不是每个处理器的USB设备的实现都不同呢?
- 并不是,虽然市面上的处理器型号不同,但是如果端点通信是OK的,理论上就可以实现所有的USB设备功能
- 并且,不同的处理器,其USB的IP可能是一样的,这就意味着驱动层适配不用关心芯片,只用关心IP
在我看来,USB协议复杂的地方并不在于底层,而在于class层,因为class种类太多了,想要知道每一种USB的协议细节本身就比较困难
但是好在有开源大神做好了相关的工作,比如cherryUSB和tinyusb,这两个开源项目适配了很多芯片的USB IP,并且写了很多class的demo