自制工业控制系统:USB协议详解——设备类、四种传输与键鼠U盘的数据通路
Contents
起因
系列里 EHCI 篇讲了"怎么驱动主机控制器",键鼠篇讲了"怎么解析报表"——中间那层"USB 协议本身怎么运作"一直是背景板。这层的性格全写在出身上:九十年代中期 Intel、微软、康柏这批厂商要终结"装机开机箱拨跳线"的日子,定下的却是一条 Host 主导的主从总线——设备永远被动应答、时间切固定帧,后文的规矩几乎都能从这两条推出来。这篇把它拉到前台单独讲清:设备类是什么、四种传输类型各管什么、Host 与 Device 谁说话、鼠标的坐标和键盘的键值到底是怎么一步步传回来的、U 盘的读写又是一套什么机制。这层想通了,EHCI 和键鼠两篇的代码为什么那么写、U 盘驱动为什么长那个结构,自己就能对上号。
一、USB 的世界模型:Host 主导一切
USB 是主从式总线,规矩只有一条:Host(主机侧)发起一切通信,Device(设备侧)永远只应答。设备不会"主动通知"主机任何事情——所谓"鼠标动了,通知电脑",真相是 Host 按固定节奏发问,设备有新数据就答,没有就回 NAK(我不说话但也没事)。
- 我们的工控板是固定的 Host 角色;键鼠、U 盘都是 Device
- Host/Device 是硬件角色不是软件姿态:OTG(On-The-Go)规范允许一个口在两种角色间切换(手机既插电脑当 U 盘、又插 U 盘读文件),工控场景用不上,但概念要分清——角色决定了谁发 token
- 时间被切成帧:全速 1ms 一帧、高速 125µs 一微帧。Host 在每帧里排班:哪些端点轮到了、控制传输进行到哪个阶段——EHCI 的周期列表和异步列表就是这张排班表的硬件化身
把"轮询而非中断"这个模型想通,USB 就懂了一半:键盘鼠标走的中断传输(Interrupt Transfer),名字里的"中断"指的是保证延迟上限,不是设备能产生中断。
二、四种传输类型
USB 按数据的要求分四种传输,端点生下来就注定属于其中一种:
| 类型 | 特点 | 保证 | 用在哪 |
|---|---|---|---|
| 控制传输 | 三阶段事务,走端点 0 | 一定传(重试) | 枚举、配置、类协议命令——管理通道 |
| 中断传输 | Host 按承诺间隔轮询 | 延迟上限(间隔内必被问到) | 键盘、鼠标 |
| 批量传输 | 占用空闲带宽,尽量传 | 可靠(CRC+重试),不保证时间 | U 盘读写、打印机 |
| 等时传输 | 固定带宽预留 | 带宽,不重传 | 音频、摄像头 |
控制传输的三阶段(SETUP → DATA → STATUS)是 USB 一切"配置动作"的统一格式:8 字节请求包说清"要干嘛",数据阶段可选,状态阶段收尾——枚举的 SET_ADDRESS、HID 的 SET_PROTOCOL、U 盘的批量命令都骑在这条通道上。批量传输没有延迟保证但保证正确,U 盘读写大数据全靠它。等时传输我们用不上,不展开。
四种传输都靠握手包收口:ACK 收好、NAK 这轮没有、STALL 出问题了。STALL 要记牢——设备用它说两句话:控制传输里说"这条命令我不支持"(比如向只读盘发 WRITE),批量端点上说"我出故障了,先别理我"。STALL 之后端点挂起,必须走端点 0 发 CLEAR_FEATURE(ENDPOINT_HALT) 清掉才能继续;U 盘驱动写坏一块盘、设备失电重连,撞上的都是它,恢复路径写不写直接决定驱动健不健壮。
三、设备类:同一类设备说同一种话
USB 用**类码(Class Code)**给设备分方言,Host 按类写驱动,一份驱动通吃所有厂商的同类设备:
| 类码 | 类 | 我们系统的角色 |
|---|---|---|
| 0x03 | HID(人机接口设备) | 键盘、鼠标 |
| 0x08 | Mass Storage | U 盘、移动硬盘 |
| 0x09 | Hub | 集线器(TT 方案里机内那个高速 Hub) |
| 0x02 | CDC | 虚拟串口(调试备用通道);4G 模块用的 NCM 也属 CDC 家族 |
| 0xFF | 厂商自定义 | 完全私有协议(不走类驱动的那种设备) |
类信息挂在接口描述符上(设备级类声明已少见),U 盘的典型配置:接口类 0x08、子类 0x06(SCSI 透明命令集)、协议 0x50(Bulk-Only Transport)——这三个数字就是"这是一只标准 U 盘"的出生证明。
四、鼠标怎么把坐标传回来
端点配置:EP0(控制,走配置和 SET_PROTOCOL)+ 一个中断 IN 端点(bInterval = 8ms 之类)。数据通路按时间轴展开:
- Host 每 8ms 对这个端点发一次 IN token(EHCI 周期列表排好的班)
- 鼠标上次报表之后动过 → 回一个报表包;没动过 → 回 NAK,这一轮作废不占线
- 报表落在 Host 给的缓冲区,驱动被"传输完成"唤醒
报表内容(Boot 协议,3 字节起步):
|
|
三个关键认知:
- 相对坐标:鼠标报的是"这 8ms 里动了多少",不是"光标该在哪"。绝对位置由主机累积维护——这样手在桌面上无限移动、光标却停在屏幕里;触摸屏/数字化仪才用绝对坐标(另一套报表格式)
- int8 的补码:向左向上是负值,快速甩动一轮报表能跳几十像素,驱动解析时
as i8转换不能丢(键鼠篇踩过的坑在此有源头) - 报表是最新状态快照不是增量流水——同一轮询周期内多次移动会被合并成最后一个位移,这是"轮询模型"的天然行为
五、键盘怎么传数据
同样的中断 IN 通道,报表换成 8 字节:
|
|
和鼠标同样的三个关键认知,再加两条:
- 数组快照语义:报表回答的是"现在按着谁",不是"谁刚按下"。按下/弹起事件要靠前后两帧对比:上帧没有这帧有 = 按下,反之 = 弹起——这是键盘驱动核心逻辑就一个双层循环的原因
- 只有 6 个槽:同时按超过 6 键,设备回 Usage 0x01(rollover),全帧作废——经典折衷,游戏玩家骂的"键位冲突"源头就在这。修饰键不占槽(在 byte0 位图里),所以 Ctrl+Shift+5 键随便组合
- 长按不会连发:设备按住不放报表内容就不变;“自动重复"是主机软件(UI 层定时器)自己实现的。SET_IDLE 是让设备连"无变化的重复报表"都省了,总线更清净
- Usage 码不是 ASCII(0x04 才是 a,0x1E 是 1),键盘布局(US/欧洲)到字符的映射表在主机侧——所以换语言布局不用换键盘
六、U 盘:三层协议叠出来的块设备
U 盘的读写比键鼠多一层,整体是三层洋葱:
|
|
端点配置:EP0 + Bulk OUT(命令、写出数据)+ Bulk IN(读回数据、状态)。
每次读写是一个固定三段式事务(Bulk-Only Transport):
第一段:CBW(命令块包,31 字节),Host 通过 Bulk OUT 发出:
| 字段 | 偏移 | 内容 |
|---|---|---|
| dCBWSignature | 0 | 0x43425355("USBC") |
| dCBWTag | 4 | 本次事务的编号,CSW 里原样带回用于配对 |
| dCBWDataTransferLength | 8 | 数据阶段总字节数 |
| bmCBWFlags | 12 | bit7 = 0 写出 / 1 读回 |
| bCBWLUN | 13 | 逻辑单元号(多卡读卡器区分槽位用) |
| bCBWCBLength | 14 | SCSI 命令长度 |
| CB | 15 | SCSI 命令块(16 字节区) |
第二段:数据阶段,按 CBW 声明的方向和长度走批量传输(读盘 = Bulk IN,写盘 = Bulk OUT)。
第三段:CSW(命令状态包,13 字节),设备通过 Bulk IN 回:
| 字段 | 偏移 | 内容 |
|---|---|---|
| dCSWSignature | 0 | 0x53425355("USBS") |
| dCSWTag | 4 | 对应 CBW 的 tag |
| dCSWDataResidue | 8 | 实际比声明的少传了多少 |
| bCSWStatus | 12 | 0 成功;1 失败(要跟 REQUEST SENSE 问原因);2 相位错误(复位重来) |
SCSI 子集一只 U 盘只需要几个命令:INQUIRY(0x12) 设备识别、READ CAPACITY(0x25) 问容量、READ(10)(0x28)/WRITE(10)(0x2A) 读写、REQUEST SENSE(0x03) 失败后问细节。tag 配对 + 状态机三段式是 MSC 驱动的全部骨架:发 CBW → 搬数据 → 收 CSW → 核对 tag → 下一事务。
七、回到我们的系统
把这篇映射回系列代码:EHCI 篇实现的是第一节那张"排班表”(周期列表=中断传输的班次,异步列表=控制/批量的通道);键鼠篇解析的正是第四、五节的报表格式;U 盘驱动篇把第六节的 CBW/CSW 状态机 + SCSI 命令子集落成了 NVMe 篇定义的 BlockDevice——exFAT 文件系统没改一行,同时服务 NVMe 盘和 U 盘。这条链走完,分层的好处不用再讲:传输层搬运、类协议说话、块设备抽象收口。
总结
USB 的心智模型一句话:Host 排班、设备应答,一切"事件"都是轮询出来的。四种传输按需求分工——控制管配置、中断保延迟、批量保正确、等时保带宽。设备类让一份驱动通吃同类设备:键鼠(HID)靠中断传输传状态快照(鼠标是 int8 相对位移、键盘是 6 槽 Usage 数组,都要前后帧对比出事件);U 盘(MSC)靠批量传输跑 CBW→数据→CSW 三段式,里面包着 SCSI 命令。把这篇和 EHCI、键鼠两篇叠起来读,USB 栈从控制器到报表的每一层就都透明了。
参考链接
- USB.org 规范下载——USB 2.0 规范、MSC Bulk-Only Transport、HID 类规范
- OSDev Wiki: USB / USB Mass Storage
- 本系列 EHCI 篇:USB 2.0主机控制器实战、键鼠篇:键盘与鼠标驱动实战
- 本系列第一篇:自制工业控制系统:从UEFI到内核
Author 软件开发大郭
LastMod 2026-09-11