🐱作者:一只大喵咪1201
🐱专栏:《Linux驱动》
🔥格言:你只管努力,剩下的交给时间!
USB协议
- 😼认识USB
- 🙀USB的电气信号
- 低速和全速信号电平
- 低速和全速的数据信号
- NRZI与位填充
- 😼USB协议
- 🙀包格式
- 令牌包
- 数据包
- 握手包
- 😼传输类型
- 🙀批量传输
- 🙀中断传输
- 🙀实时传输
- 🙀控制传输
- 😼设备描述符
- 🙀枚举过程
- 😼总结
😼认识USB
当我们把一个USB设备,比如U盘插入刀电脑中以后,就会弹出发现xxx设备,还会跳出一个对话框,提示我们安装驱动程序。这里有几个问题需要弄明白:
- 为什么一接入USB设备,电脑就能发现它?
如上图所示,USB接口有四根线,在PC的USB内部,这四根线的D+和D-各接一个15K的下拉电阻,在USB设备没有插入时,这两根线处于低电平状态。
在USB设备内部,D+或者D-接了一个1.5K的上拉电阻,当USB设备插入以后,PC和USB设备的这四根线就相连了。
- 此时D+或者D-就变成了高电平。
- USB控制器检测到电平变化以后就知道有USB设备插入了。
- USB设备插到电脑上去,插入的是PC的什么模块?
如上图所示是USB总线的拓扑图,USB设备插入电脑以后,是插入到PC的USB控制器,这是一个内嵌的root hub。
- USB Host:它跟处理器CPU相连,处理器通过USB Host跟各类USB设备通信,USB Host中集成有一个root hub。
- USB Device:这分为两类设备:
- Hub:用来扩展USB接口,就像是扩展坞
- Function:就是普通的USB设备,比如U盘、声卡等
可以看到,每一级Hub既可以连接一个USB设备,也可以连接一个Hub,但是Hub的连接是有限制的,就像扩展坞不能无限制的在扩展坞上继续接扩展坞。
- 包括USB Host在内的RootHub,最多有6级Hub。
- 第7层就必须接USB设备了,不能再接Hub了。
- 既然还没有USB的驱动程序,为何能知道这是一个U盘,从而让我们安装U盘的驱动程序呢?
在PC的系统中,存在很多USB设备的驱动程序,在没有相应USB设的时候没有安装,只是放在那里。
- 接入USB设备后,USB总线驱动程序能识别到这个USB设备。
- 识别到以后再提示我们安装存放在系统中的对应USB设备的驱动程序。
- USB设备种类非常多,为什么一接入电脑,就能识别出来它的种类?
PC和USB设备都遵循USB协议的规范,在USB设备插入PC以后,PC会向USB设备发出你是什么设备的询问,USB设备就会回答我是xxx,并且询问和回答的格式是固定的。
- 这个过程就是PC在获取USB设备的描述符信息。
- 电脑上一般都会插着很多USB设备,怎么分辨它们?
- 每一个USB设备在刚插入PC时,默认的地址编号是0,PC就和地址编号0进行通信。
- PC会再给不同的USB设备分配一个唯一的地址编号,之后就用分配的这个地址编号和USB设备之间进行通信。
软件框架:
如上图所示操作USB设备的软件框架:
- 应用层可以通过USB设备驱动,去使用USB控制器驱动中的方法去控制芯片中的USB控制器,进而去操作USB设备。
- 应用层也可以跳过USB设备驱动,直接去使用USB控制器驱动中的方法去控制芯片中的USB控制器,进而去操作USB设备。
🙀USB的电气信号
如上图所示USB设备和USB的Hub直接的电路图,USB设备和Hub之间的D+和D-两根线直接相连:
- 如果USB设备的D+接1.5K的上拉电阻,那么USB设备插入以后,Hub端的D+就会成为高电平,从而表明这是一个全速或者高速的USB设备。
- 如果USB设备的D-接1.5K的上拉电阻,那么USB设备插入以后,Hub端的D-就会成为高电平,从而表明这是一个低速的USB设备。
- USB设备不会同时具备高速和低速,但是可以同时具备者高速和全速或者是低速和全速。
- USB连接线有4条:5V、D+、D-、GND。
- 数据线D+、D-,只能表示4种状态。
USB协议中,很巧妙地使用D+和D-这两条线路实现了空闲(Idle)、开始(SOP)、传输数据(Data)、结束(EOP) 等功能。
低速和全速信号电平
这里本喵仅介绍低速和全速信号电平,高速的自己类推即可:
如上图所示是USB2.0中的低速和全速信号电平表:
- Differential “1”:这是差分信号1,D+线高电平,D-线低电平。
- Differential “2”:这是差分信号2,D+线低电平,D-线高电平。
- SE0:D+线和D-线都是低电平。
- SE1:D+线和D-线都是高电平。
以上就是两根线对应的四种状态,所有信号的都是由这四种状态来表示的:
- J状态:低速设备对应差分信号0,全速设备对应差分信号1。
- K状态:低速设备对应差分信号1,全速设备对应差分信号0。
- 空闲状态(Idle):低速和全速设备对应的都是J状态。
如上图所示,对于高速设备:
- 它先作为全速设备被识别出来,然后再被识别为高速设备。
- 识别出全速设备后,Hub端口发出SE0复位信号。
- USB设备检测到SE0信号号,就会回应一个信号来表明自己是否是高速设备。
工作于高速模式时,D+的上拉电阻是断开的,所以对于工作于高速模式的USB设备,无法通过D+的引脚电平变化监测到它已经断开。
- 工作于高速模式的设备,D+、D-两边有45欧姆的下拉电阻,用来消除反射信号。
如上图所示Hub内部电路图:
- 当断开高速设备后,Hub发出信号,得到的反射信号无法衰减,Hub监测到这些信号后就知道高速设备已经断开。
低速和全速的数据信号
如上图所示USB协议数据格式:
- SOP:Start Of Packet,也就是起始信号。
如上图所示,起初D+和D-两根线是处于J状态的,当这两根线变成K状态以后,表明Hub发起USB传输。
- SYNC:同步信号。
- 对于串口协议,需要通信双方约定好波特率,一方按照这个速度去改变TX数据线上的电平,另一方按照这个速度去读取RX线上的电平,从而进行通信。
- 对于I2C和SPI协议,通信双方之间有一根时钟线,时钟线每产生一个脉冲,发送方改变一次SDA或者MOSI线上的电平,与此同时,接收方读取对应线上的电平,从而进行通信。
无论是是同步通信还是异步通信,都需要双方知道改变数据线上电平的速率。
- USB协议中的同步信号就是Hub和USB设备在进行通信速率的交流。
如上图所示,SYNC格式为3对KJ信号外加2个K信号。
- USB设备根据SYNC信号速率,自行识别并且记录下这个速度,这一次传输就按照这个速度去读取或者改变D+和D-线上的电平。
- 这样一来,通信双方就完成了通信速率的交流。
- Packet Content:包内容。
后面会详细讲解USB数据包的内容。
- EOP:End Of Packet,也就是结束信号。
如上图所示EOP信号时序:
- 由数据的发送方先设置D+和D-为SE0状态,并持续2位的时间。
- 然后再设置位J状态,并持续1位的时间。
- 最后将D+和D-变为高阻态,此时D+和D-进入Idle空闲状态。
NRZI与位填充
如上图所示每一个比特位的电平信号:
- 对于串口或者I2C等协议,低电平用0表示,高电平用1表示。
- 对于USB协议,对于数据0,保持J或者K状态不变,对于数据1,反转波形,由J状态变成K状态或者由K状态变成J状态。
- USB协议采用的是反向不为零编码方式来传输每一位数据。
但是此时有一个问题:
- 如果传输非常多位的0,D+和D-不断在J状态和K状态之间切换,接收方识别到一次切换就知道这是一个0,并且可以调整频率,同步接收。
- 但是如果传输的是多位的1,D+和D-会保持J状态或者K状态不变,即使在同步信号中双方统一了传输速率,但是由于晶振频率等原因也会出现误差。
比如传输100个1时,如果接收方稍有偏差,就可能认为接收到了99位1、101位1。对于这种情况,USB采用了Bit-Stuffing位填充的处理方式:
- 在连续发送6个1后面会插入1个0,强制翻转发送信号,从而让接收方调整频率,同步接收。
- 而接收方在接收时只要接收到连续的6个1后,直接将后面的0舍弃即可得到正确的数据。
😼USB协议
如上图,一个USB物理设备里面可能有多个逻辑设备,Host可以外接多个逻辑设备。
- 比如我们的USB摄像头,它既有视频流功能,也有音频流功能。
- 视频流和音频流这两个功能在Host看来,这就是两个逻辑设备。
Host操作的是不同的逻辑设备,即使这些逻辑设备同属于一个物理设备。
🙀包格式
如上图所示USB数据格式,SOP,SYNC,以及EOP,前面在电气信号中本喵已经介绍了,还剩下最重要的Packet Content 包内容。
- 一个完整的USB数据包就是由以上几部分组成的。
- SOP,SYNC等四部分被称为一个一个的域。
PID域:
Packet Content中的PID就是令牌,PID有八位,用来表明这是一个什么类型的包:
- 前4位表示PID,也就是表明数据包的类型。
- 后4位是对应位的取反,接收方发现后4位不是前4位的取反的话,就认为发生了错误。
如上图所示,包的类型分为四类,PID的取值有16种:
- 令牌包(Token):四位中的低两位是01B,表明这是一个令牌包,又分为四种令牌包:
- OUT:设置设备将要输出数据。
- IN:通知设备将要读取数据。
- SOF:通知设备这是一个帧起始包。
- STEUP:通知设备将要开始一个控制传输。
- 数据包(Data):四位中的低两位是11B,表明这是一个数据包,又分为四种数据包:
- 有DATA0、DATA1等四种类型的数据包。
- 握手包(Handshake):四位中的低两位是10B,表明这是一个握手包,又分为四种握手包:
- ACK:确认应答。
- NAK:不确认应答。
- STALL:挂起应答。
- NYET:未准备好应答。
- 特殊包(Special):四位中的低两位是00B,表明这是一个特殊类的包,又分为四种:
- 一般情况下用不到这类包,本喵不做讲解。
令牌包
地址域:
如上图所示,USB设备的地址有7位,地址用来标识唯一的USB设备。
如上图所示,USB设备的端点号有4位:
- 端点号可以理解为USB设备中的寄存器。
7位USB地址和4位端点总共11位,组成地址域,也被叫做帧号域。
校验域:
这里是一个CRC校验码,用来校对通信的数据是否正确。
完整令牌包:
如上图所示是一个完整令牌包的格式:
- SOP启动传输后,SYNC同步通信速率。
- 在Packet Content里的PID表明这是一个输出还是输入等类型的令牌包。
- 令牌包后面必然跟的是USB设备的地址和端点号,表明要和访问的地址。
- 然后是一个CRC校验码。
- 最后是一个EOP结束信号。
数据包
Host使用OUT、IN、SETUP等令牌包来通知设备:我要传输数据了,真正的数据是通过数据包进行传输的。
Host和设备都会维护自己的数据包切换机制,当数据包成功发送或者接收时,数据包类型要进行切换。
当检测到对方使用的数据包类型不对时,USB系统认为发生了错误:
- Host发送DATA0给设备,设备返回ACK表示成功接收,设备期待下一个数据是DATA1
- 但是Host没有接收到ACK,Host认为数据没有发送成功,Host继续使用DATA0发送上一次的数据
- 设备再次接收到DATA0数据包,它就知道:哦,这是重传的数据包。
如上图所示是完整的数据包:
- SOP和SYNC和令牌包一样。
- PID中的值是DATA0或者DATA1,表明这是一个数据包。
- 接下来就是真正要传输的数据了。
- 后的CRC和EOP也是和令牌包一样的意义。
握手包
- ACK:数据接收方用来回复发送方,表示正确接收到了数据并且有足够的空间保存数据。
- NAK:Host发送数据给设备时,设备可以回应NAK表示"我还没准备好,没办法接收数据";Host想读取设备的数据时,设备可以回复NAK表示"我没有数据给你"。
- STALL:表示发生了错误,比如设备无法执行这个请求(不支持该端点等待)、端点已经挂起。设备返回STALL后,需要主机进行干预才能解除STALL状态。
- NYET:仅适用于高速设备。Host可以发出PING包用来确认设备有数据,设备可以回应NYET表示"还没呢"。Hub也可以回应NYET表示低速/全速传输还没完结。
如上图所示是完整的握手包:
- SOP和SYNC作用和前面一样。
- PID中是ACK或NAK等应答信号。
- 没有数据域或者帧域,也没有CRC校验码。
- EOP和前面一样。
😼传输类型
USB传输的基本单位是包(Packet),包的类型由PID表示。一个单纯的包,是无法传输完整的数据。
如上图所示,完整的数据传输,需要涉及多个包:令牌包、数据包、握手包,这个完整的数据传输过程,被称为事务(Transaction)。
有些事务需要握手包,有些事务不需要握手包,有些事务可以传输很大的数据,有些事务只能传输小量数据,所以事务又分为四类:
- 批量事务:用来传输大量的数据,数据的正确性有保证,时效没有保证。
- 中断事务:用来传输周期性的、小量的数据,数据的正确性和时效都有保证。
- 实时事务:用来传输实时数据,数据的正确性没有保证,时效有保证。
- 建立事务:跟批量事务类似,只不过令牌包是SETUP令牌包。
- 传输和事务其实是不一样的,但是在USB手册的中文翻译版中很多地方都将其归为了一类。
有四类传输(Transfer):
- 批量传输:就是使用批量事务实现数据传输,比如U盘。
- 中断传输:就是使用中断事务实现数据传输,比如鼠标。
- 实时传输:就是使用实时事务实现数据传输,比如摄像头。
- 控制传输:由建立事务、批量事务组成,所有的USB设备都必须支持控制传输,用于 识别/枚举 USB设备。
- 对于批量传输、中断传输、实时传输,它们分别由一个事务组成,不再细分为若干个过程。
- 但是控制传输由多个事务组成,这些事务分别处于3个过程:建立过程(stage)、数据过程(stage)、状态过程(stage)。每一个过程都是一个或者多个事务。
所以在USB协议中:
- bit组成域(Field)
- 域组成包(Packet)
- 包组成事务(Transaction)
- 事务组成传输(Transfer)
一个完整事务涉及到的三个阶段:
- 令牌阶段(Token phase):由令牌包实现
- 数据阶段(Data phase):由数据包实现
- 握手阶段(Handshake phase):由握手包实现
🙀批量传输
如上图所示USB手册中的批量传输架构图,各个矩形框就对应一个完整的包:
- 批量传输用批量事务来实现,用于传输大量的数据,数据的正确性有保证,时效没有保证。
- 批量事务由3个阶段(phase)组成:令牌阶段、数据阶段、握手阶段。每个阶段都是一个完整的包,含有SOP、SYNC、PID、EOP。
- 在这里,批量传输就等于批量事务。
如上图所示是一次正确的批量输入事务。
如上图所示是一次正确的批量输出事务。
🙀中断传输
如上图所示是USB手册的中断传输架构,一个矩形框就对应一个完整的包:
- 中断传输用中断事务来实现,用于传输小量的、周期性的数据,数据的正确性和时效都有保证。
- 中断事务由3个阶段(phase)组成:令牌阶段、数据阶段、握手阶段。每个阶段都是一个完整的包,含有SOP、SYNC、PID、EOP。
- 相比于批量传输来说,中断传输中没有PING事务,而且握手包中也少了一个NYET包。
中断事务跟批量事务非常类似,Host使用它来周期性地读数据、写数据,以鼠标为例:
-
我们需要及时获得鼠标的数据,不及时的话你会感觉鼠标很迟钝。
-
但是USB协议中并没有中断功能,它使用周期性的读、写来实现及时性。具体过程如下:
- Host每隔n毫秒发出一个IN令牌包。
- 鼠标有数据的话,发出DATA0或DATA1数据包给Host;鼠标没有数据的话,发出NAK给Host。
中断事务的优先级比批量事务更高,它要求实时性,而批量事务不要求实时性。
🙀实时传输
如上图所示是实时传输的框架,一个矩形框对应一个包:
- 实时传输用使用事务来实现,用于传输实时数据,对数据的正确性没有要求。
- 实时事务由2个阶段(phase)组成:令牌阶段、数据阶段。每个阶段都是一个完整的包,含有SOP、SYNC、PID、EOP。
- 实时事务不需要握手阶段,也就是不需要应答信号。
比如为了传输摄像头的实时数据,偶尔的数据错误是可以忍受的,大不了出现短暂的花屏。如果为了解决花屏而重传数据,那就会导致后续画面被推迟,实时性无法得到保证。
实时事务跟中断事务非常类似,Host也会周期性的发起实时事务,主要区别在于:
- 实时事务不要求准确性,没有握手阶段
- 实时事务传输的数据量比较大,中断事务传输的数据量比较小
🙀控制传输
如上图所示是USB手册中的控制传输框架,一个矩形框对应一个事务,而不是一个包。
在使用批量传输时,使用IN令牌包或OUT令牌包表示数据传输方向。控制传输的令牌包永远是SETUP:
- 发出SETUP令牌包后,还要发出DATA0数据包。
- 根据数据阶段的内容来确定后续是读数据,还是写数据。这个过程称为建立事务(SETUP Transaction)。
如上图所示是建立事务对应的框图。
但是控制传输由多个事务组成,这些事务分别处于3个过程:建立过程(stage)、数据过程(stage)、状态过程(stage)。
- 建立过程(stage),使用SETUP事务:Host发出SETUP令牌包、DATA0数据包、得到ACK握手包。
- 数据过程(stage),使用一个或者多个批量事务:
- 对于输出:Host发出OUT令牌包,发出DATA0、DATA1数据包、得到ACK握手包
- 对于输入:Host发出IN令牌包,读到DATA0、DATA1数据包、发出ACK握手包
- 状态过程(stage),使用批量事务:
- 对于输出:Host发出IN令牌包,读到DATA1数据包,发出ACK握手包。
- 对于输入:Host发出OUT令牌包,发出DATA1数据包,等待ACK握手包。
- 状态过程的批量事务中的数据阶段,数据大小是0,只是为了获得状态。
😼设备描述符
对于一个USB设备,它可以多种配置(Configuration)。比如4G上网卡就有2种配置:
- U盘、上网卡。第1次把4G上网卡插入电脑时,它是一个U盘,可以安装里面的驱动程序。
- 装好程序后,把它再次插入电脑,它就是一个上网卡。
- 驱动程序可以选择让它工作于哪种配置,同一时间只能有一种配置。大多数的USB设备只有一种配置。
如上图所示,在Linux中使用lsusb -v
查看USB设备的描述符信息,一个USB设备:
- 只有一个设备描述符:用来表示设备的ID、它有多少个配置、它的端点0一次最大能传输多少字节数据。
- 可能有多个配置描述符:用来表示它有多少个接口、供电方式、最大电流。
- 一个配置描述符下面,可能有多个接口描述符:用来表示它是哪类接口、有几个设置(Setting)、有几个端点。
- 一个接口描述符下面,可能有多个端点描述符:用来表示端点号、方向(IN/OUT)、类型(批量/中断/同步)。
如上图所示是描述符的组成关系。
- 描述符信息是USB设备中内嵌的,当USB设备插入以后,Hub会发起控制传输来获取描述符信息。
- 每一个接口就是一个逻辑设备,Hub会将其认为是一个设备。
在setup事务中:
- SETUP令牌包:用来通知设备,要开始控制传输了。
- DATA0数据包:它含有固定的格式,用来告诉设备是读还是写、读什么、写什么。
如上图所示,Host通过DATA0数据包发送8字节数据给设备,它的格式如上。
数据阶段中的8个字节的常用值如上图所示,USB设备根据这8个字节就可以知道Host想要控制什么。
- bRequest这列中的值是一个个宏,表示着不同的意义。
如上图所示是这些宏的取值。
🙀枚举过程
如上图所示是USB设备插入以后的设备状态变化过程,也就是枚举过程:
- 插入以后,Hub识别出USB设备,进行相应的驱动配置。
- 再获取默认设备描述符,此时是和端点0进行通信。
- 再给USB设备设置新的地址,之后使用新地址进行通信。
- 再次获取设备描述符,以及接口配置描述符。
- 最后再设置USB设备的配置,即使只有一个配置也要设置。
下面本喵使用一个USB数据捕获工具来看看USB设备的枚举过程,它可以详细的显示出USB数据。
如上图所示,是插入USB设备以后,第一次获取设备描述符:
- 在建立过程中,先出建立事务,在数据阶段发送一个8字节的数据表明要GET_DESCRIPTOR,也就是获取设备描述符。
- 在数据过程中,先发出一个读取数据的批量事务来获得设备描述符,但是此时USB设备还没有准备好,给出一个NAK应答。
- Host再次发起一个读取数据的批量事务,这次得到了USB设备发来的18个字节的设备描述符。
- 在状态过程中,发出一个批量事务,但是数据阶段中的数据是0字节,表明这是一个状态过程。
- 这个控制传输中,都是在和USB设备的ADDR0默认地址和ENDP0进行通信。
如上图所示,这是Host给USB设备设置新的地址:
- 在建立过程中,先发起建立事务,在数据阶段发出SET_ADDRESS,表明要设置地址,并且将要设置的新地址也在这8个字节中发送过程。
- 在状态过程中,读取USB设备的应答,数据节点的字节数为0,确定地址是否设置成功。
- 这个过程中并没有数据阶段,因为设置的新地址在建立过程中的8个字节里就发送了过去。
如上图所示,是Host根据设置的新地址获取设备描述符:
- 在建立过程中,使用新地址1和ENDP0向USB设备获取设备描述符。
- 在数据过程中,读取USB设备的设备描述符,大小是18个自己。
- 在状态过程中,发送字节数为0的批量事务表示收到设备描述符。
- 在建立过程的STEP事务中,数据阶段发送的8字节数据就是前面本喵列举的表中的数据组合。
😼总结
并没有涉及到Linux驱动,仅仅是讲解了USB协议的电平信号,协议格式,传输类型,以及它的枚举过程。