============= 本系列参考 =============
《圈圈教你玩USB》、《Linux那些事儿之我是USB》
协议文档: usb_20_20190524/usb_20.pdf
调试工具:Beagle USB 480 逻辑分析仪
====================================
前言:
我们先不一上来讲USB大而全的协议规范文档, 会让人退而却步, 只要有协议, 在数据传输上波形就有规律可循, 翻译成数据, 也先不管USB1.1/2.0等版本, 因为最终的传输单元是一样的
一. 最基本传输单位 --包(packet)
1.电气信号:
a. 采用D+/D-差分信号传输, LSB在前, NRZI编码也就是0反转, 1不反转, 遇到连续6个1强插一个0
b. 低速Lowspeed 1.5Mb/s, 全速Fullspeed 12Mb/s, 高速Highspeed 480Mb/s, USB1.1支持L/F USB2.0支持L/F/H USB3.0也支持L/F/H 同时支持OTG功能
c. OTG(on the go) 就是多了根ID线, 用于判断主控器作Host还是device
d. L/F S采用电压传输(3.3v), HS采用电流传输(等效电阻后示波器显示400mv)
e. 传输方向以Host为准, 即IN表示device数据到Host, OUT表示Host数据到device
f. 插入上电波形分析(下面单独抽出来分析)
2. packet格式
SYNC同步域 + PID域 + 数据域 + CRC + EOP
a. 同步域: L/FS 固定00000001, HS前面31个0后一个1
b. PID占一个字节,高4bit是低4bit的反码, 用于校验PID本身, 而PID[3:0] 表示该packet的类型(协议文档8.3.1):
打*表示USB1.1不支持的, 而USB2.0全支持, 这里要特别注意令牌包, 任何事务传输, 必须先发个令牌包说明意图, 至于后面是否需要数据包还是握手吧取决事务类型(下面会说)
c. 数据域是可选的, 取决PID是不是数据包(DATA0/1/2/M)
d. CRC也是可选的, PID自校验,所以只对数据域校验, 若数据域没有那CRC也没有, 令牌包的数据域采用CRC5校验, 数据包的数据域采用CRC16校验
e. EOP结束包, 对于L/FS是两个数据位宽的SE0信号(D+/D-都是0), 对于HS使用故意位填充表示(待具体解释)
f. 空闲状态, 在SYNC同步域前和EOP后 总线上处于空闲状态, L/FS是一根高电平一根低电平(也就是J或K状态, 后面会讲), HS是SE0表示空闲状态
g. SYNC同步域、EOP、CRC是硬件发射器自动添加和硬件接收器自动解析的, 软件看到的只有PID域和数据域
2. packet类型
根据PID[3:0]可以将包的类型分成4类
a. 令牌包: 一次USB传输必须首发令牌包, 告知意图, 同时后面数据表示跟哪个设备及端点通信, 这点很重要, 设想一下一个Host接了很多外设, Host发出的信号会到达所有hub和普通外设, 如何避免串扰呢?
SOF(帧起始包) 相当于心跳包, 让所有外设知道Host还在活动(哪怕Host不是跟该设备通信但起码知道跟其他设备通信), L/FS每隔1ms发一次, 每发一次11bit帧号加1, HS把1ms分割8份即每隔125us发一次, 但这8份里面的11bit帧号是相同的
这个心跳包主要用于休眠唤醒用的, 当Host没有发SOF超过3ms时(一般是Host自己进入休眠或者想外设休眠), 外设设置自己进入低功耗状态(如果支持), 然后进入监听模式如果检测到总线有信号变化(只要跟睡眠前不一样)立即唤醒,
可能是Host要召唤设备了, 当然设备也可以唤醒Host, Host进入休眠也会设置监听总线状态,外设被人为唤醒改变总线信号接着唤醒Host
b. 数据包: 这没啥好说的就是PID表明自己是数据包(DATA0还是DATA1主要用于 确保对方收到), 后面就是字节数据了, 这里需要注意就是没有告知这个数据包到底多少个数据, 所以我猜想外设接收PID域后, 每接收一个字节counter计数器加1
直到EOP, 然后减2 CRC16校验值就是数据量, 接着对FIFO数据CRC16和最后两个字节对比, 不一致就产生数据错误中断, 一致就产生数据成功中断并将数据量填充RX counter寄存器
c. 握手包: 告知对方状态, 比如Host发送IN令牌包, 接着设备发送数据包, 然后Host接收完发送ACK握手包告知设备成功接收
d. 特殊包主要用于高速, 比如上面Host发完IN令牌包后, 设备应该要发数据包的, 但设备还没准备好数据, 导致Host等待超时, Host可以再次发IN包让设备进入发送数据, Host切换等待接收数据状态,
这里有两个小问题, 一是设备数据未准备好, 却没有有效方式告知Host, 只能啥都不做靠超时告知, 浪费Host时间, 二是IN包让设备进入发送数据模式, 设备有数据早发了还等你吹, 还让外设进入发送模式影响准备数据
而PING特殊包就是当第一次超时后, Host不发IN包改发PING包询问设备准备好没, 设备若准备好了回复ACK握手包, 接着Host再发IN包, 如果还没准备好就发NAK告知, Host就知道设备还没准备好而不用死等超时,
其他几个读者可自行查阅
不匹配继续不会理, 匹配的使能硬件接收数据功能, 并根据PID是IN OUT SETUP SOF再细分, 如果是OUT,产生OUT中断, 软件应该清空使能FIFO准备接收数据, 如果是IN, 产生IN中断, 软件要填充好即将发的数据然后使能端点发送,
如果是SETUP包(Host会接着发DATA0数据包数据域包含8个字节的标准请求), 设备要清空特殊FIFO并做好接受下一个数据, 接受完才产生SETUP中断, 软件就解析FIFO里的8byte标准请求, 然后准备数据, 比如是获取设备描述符请求
那软件得准备好设备描述符缓存并ACK(必须ACK不能NAK)回复, 然后Host会发IN包, 接着设备IN中断将刚才准备好的设备描述符缓存丢到端点0发出去!
如果是SOF包, 设备会重置时间计数器, 当3ms内没有新的SOF包, 就会产生中断, 设备知道总线现在是空闲状态, 可以自行决定是否休眠
二、 事务--四种传输类型
一个个packet只是一盘散沙, 通过组织起来作为一个有效传输我们称之为事务, 所以一个事务起码包含:
可选的数据包, 如果是IN/OUT/SETUP包那后续有数据包, 如果是 SOF则数据包和握手吧都没有
可选的握手包, 像视频聊天这种实时传输不需要ACK应该, 丢了就丢了, 省下带宽不如用来发数据
因此, 根据具体的使用场景, 事务可以分成四种传输类型:
1. 批量传输(Bulk transfers )
一个批量事务包含三个阶段, 令牌包阶段 + 数据包阶段 + 握手包阶段, 其中数据包阶段可以发一个或多个数据包
一个读取扇区信息命令分别为 Command + Data + Status, Command是一个写操作, 往设备发送数据告知想干嘛, 然后就是读数据, 最后检查状态, 可以看到这些操作都由三个packet构成 IN/OUT令牌包 + 数据包 + 握手包
因为U盘每次操作只能512byte/block, 所以想读取多个扇区只能分多次IN操作(传输最大字节数端点描述符有说明)
2. 中断传输(Interrupt transfers )
一个中断事务跟批量事务类似, 不同在于传输量比较少, 且希望Host每隔一段时间来访问设备(不是靠硬件中断告知系统, 而是端点描述符有个时间间隔变量, 告知Host最好小于这个时间间隔来访问设备), 像鼠标键盘都是这类传输模式,
以Beagle USB 480 逻辑分析仪抓键盘为例:
这里可以看出三点, 一是Host每间隔x时间就发起一次读取键盘数据操作(还是老样子 IN包 + DATA0包 + ACK包); 二是如果我没敲键盘, 则设备NAK告知Host没有数据; 三是间隔时间约 72/10 344/44 = 8ms
查看键盘端点描述符bInterval=1, 根据datasheet代表1ms, 即键盘希望Host每隔1ms读取一次数据, 但采不采纳在于Host端
3. 等时传输(Isochronous transfers )
等时事务跟前两种也差不多, 不同在于对时间敏感, 对数据准确性不关心, 所以不需要握手包, 主要用于音频、视频类设备
4. 控制传输(Control transfers )
控制传输稍微复杂一点, 上面三个一个传输就是一个事务, 但控制传输有三个状态, 每个状态对应一个事务, 所以需要三次事务
三次过程分别为:
建立过程:SETUP令牌包 + DATA0数据包(标准请求就在这) + ACK握手包(设备必须返回ACK, 不能NAK 如果设备连这个都不能保证的话就别玩了)
状态过程: 上面的数据过程必须是同一个方向的, 如果方向改变, 则就是状态过程, 如果没有数据过程, 则这个数据包就是状态过程不管哪个方向
以Beagle USB 480 逻辑分析仪抓U盘为例:
从捕捉的数据可看到, 建立过程的数据包包含着标准请求 80 06 00 01 00 00 12 00 (小端排序) , 前面的C3是PID, 后面E0 F4 是CRC16, 可以通过 验证
80 06 0100 0000 0012 struct usb_ctrlrequest { __u8 bRequestType; //0x80 __u8 bRequest; //0x06 __le16 wValue; //0x100 __le16 wIndex; //0 __le16 wLength; //0x12 } __attribute__ ((packed));
具体请参考协议文档9-4
上面log还有个有趣的现象: 状态过程发送1字节0x00数据包, U盘竟然返回NAK, 不知为何, 由于是高速模式下, 所以Host接下来会发PING包探测U盘是否ready, 直到U盘回复ACK才再次发送OUT包,如果是L/FS则继续发OUT包直到接收ACK
剩余数据的解析将在下一篇博文讲解!