USB协议简介
USB全称Universal Serial Bus,通用串行总线。在上个世纪90年代, IBM,Intel,微软,DEC,NEC,Compaq和Nortel等七家公司合力推出了USB的概念。之后,这些公司一起成立了非赢利组织USB-IF(USB Implementers Forum), 来维护USB协议。
USB的一个核心设计理念就是,为PC机上各种各样的接口提供一种统一的解决方案。在早期的PC机上,有串口、并口,就连鼠标键盘、 游戏手柄都有特定的接口。而且这些接口都不支持热插拔,必须在PC机开机之前插入设备才可以正常使用。 此外,这些不同的接口还需要占用宝贵的CPU资源(比如硬件中断、DMA通道等)。已经普及的USB协议淘汰了传统计算机上的串口、并口, 也为各种外设提供了一种支持热插拔的接口方式,是目前计算机最常用的外设扩展方式之一,它也成为了目前键盘鼠标手柄的默认连接方式。
在USB2.0的年代,有低速(Low Speed)、全速(Full Speed)和高速(High Speed)三种设备。 其中低速和全速设备是在USB1.0和USB1.1中就已经定义的设备,分别为1.5Mbit/s和12Mbit/s,USB2.0只是做到了向下兼容而已。 高速设备则是USB2.0定义的设备,速度可以达到480Mbit/s。为了满足对通信速率日益增长的需求,在2008年USB-IF推出了USB3.0的标准, 可以达到5Gbit/s的速率,称为SuperSpeed。之后的还有SuperSpeed+和SuperSpeed++,据说可以达到10Gbit/s和20Gbit/s的速率。
由于USB2.0仍然是目前广泛使用的标准,而且很多嵌入式设备上都使用的是这种协议,所以本文以USB2.0为研究对象,进行详细解读。 以下内容或摘自网友翻译的中文版USB2.0协议,或由我自己对原版协议的解读。
1. USB系统总述
USB系统是一种"阶梯式星型拓扑(Tiered star topology)"结构的主从通信系统。 它由一个主机(host)和最多127个外设(Devices或者Peripheral)构成。
外设有功能外设(Functions)和集线器(HUB)两种。功能外设是不能够挂载其他USB设备的,我们常见的鼠标键盘等都是功能外设。 而集线器则提供了扩展USB接口的能力,即可以挂载功能外设,也可以挂载集线器。
通过集线器级联(集线器上挂载集线器)可以扩展挂载更多的外设,最终形成左图所示的一个金字塔结构, 塔尖上是主机和根集线器,其上连接功能外设或者集线器,逐层向下,形成阶梯式星型连接拓扑,也就是所谓的"Tierd star topology"。 协议规定最多到7层网络,也就是5个集线器的级联。
USB采用轮询的广播机制传输数据,所有的传输都是由主机发起的,同一时刻只允许一个数据包传输。 设备通过7位总线地址加以区分,其中地址0用于未分配地址的设备,保留不能分配,因此一个主机最多支持127个设备。
一个外设挂在总线上之后,必须先通过枚举操作,才可以使用。枚举操作一方面是为了上面所说的分配地址, 另一方面是通过获取设备的描述符,确定设备类型、生产厂商等信息,供host选择相应的驱动。 在USB协议中把功能设备根据不同的功能,又作了细分比如HID, CDC等,我们将在后续介绍中予以详述。
我们已经提到USB上所有的通信都是由主机发起的。这就意味着任何两个设备之间是不能够直接通信的。 即使设备有数据需要发送给主机,也必须等待主机的指令。但有一种情况例外,当一个设备被主机置于挂起"Suspend"的状态下, 设备可以向总线上发送一个远程唤醒"remote wakeup"信号
早期的USB系统,一般都是用作PC的外设扩展,所以主机通常都是PC机,任何连接到PC机上的设备都是外设。 随着智能设备或者说是手持设备的兴起,产生了一种需求,希望这类设备即能够以外设的身份连接PC机, 又能够以主机的身份通过USB系统进行扩展。因而产生了所谓的OTG设备(On The Go),它在物理连线以及通信协议上都作了一些调整, 使得设备能够在主机和从机之间切换。
总体上来说,USB接口使用起来很方便,它为各种外设提供了一种统一的接口。但由于它的通用性,也就导致了协议相对来说比较复杂, 涉及了方方面面的内容。下面简要的对各个方面进行介绍。
2. USB物理接口
由于USB是一种主从结构的系统,所以一定是主机与外设之间的连接。为了防止连线错误,USB的线缆两端的接头一定是不同的。 接入主机端的接头一般被称为A型接头,外设端的接头则是B型接头。下图列出了一些常见的USB接头。
标准USB2.0 A型接头 | 标准USB3.0 A型接头 | 标准USB B接头 | mini型USB B接头 | micro型USB B接头 |
标准的USB2.0接口和线缆都是4线的,如下图1所示。其中VBUS和GND分别是电源线和地线,用于给一些USB设备供电。 VBUS通常提供的是5V的电压。D+和D-则是一对差分双绞线,用于传送数据, 这点与我们在STM32的串口通信中提到的485串口类似, 只是线上的电压不一样。
图1 USB四脚接口示意图 |
后来针对USB OTG设备,人们又提出了mini型接头和macro型接头,线缆和接头也从原来的四线变成了五线。 多出来的线被标记为ID信号线,用于区分设备的身份。ID线接地,表示设备初始身份为host,为A类设备;若悬空或者接高, 则标识着设备初始身份为device,为B类设备。
USB主机可以通过线缆上的VBUS线,给设备供电。设备可以选择从VBUS上获取电流,这类设备称为"bus-powered"设备; 也可以选择自供电,也就是"self-powered"设备;但是设备是不能够向总线上灌电流的。
我们知道,USB是一种支持热插拔的通信总线,那么主机是如何获知新设备插入的呢?
USB设备都是插在集线器上的,在集线器的每个端口上,D+和D-差分信号线都被一个1.5K的电阻下拉到地了。 在USB设备上,D+或者D-信号线上有一个1.5k的上拉电阻。低速设备上拉电阻接到D-上,全速和高速设备则接到D+上。
当设备插入端口上时,接有上拉电阻的信号线上的电平由上下拉电阻分压,在接线器看来就是一个高电平信号。 集线器就检测到了设备插入,它将这一信号直接或者经过上级集线器报告给主机。 USB系统根据D+或者D-上的电平来确定新插入设备的通信速率。对于高速设备则先判定为全速设备, 然后通过与设备的协商切换到高速模式下。
3. USB通信的分层结构
如下图所示,一个USB通信系统可以认为是一个三层的结构。从底层到顶层依次是,物理的接口层(USB Bus interface Layer)、 逻辑的数据层(USB Device Layer)和应用的功能层(Function Layer)。
从顶层来看,不同的USB设备的功能是不一样的。我们在PC机上使用它们的时候,并不会在意它是USB接口的, 只关心功能是否能够满足要求。在协议中,把设备功能抽象为接口(Interface)。用户通过驱动或者客户软件(Client SW), 直接访问功能接口。使用起来就好像直接在使用一个键盘、串口等各类设备似的。
在逻辑的数据层看来,Client SW并不能够直接访问USB总线,它必须通过驱动程序把客户软件收发数据的请求和内容转换成为USB传输事务。 我们可以将图中的USB系统软件(USB System SW)看作是完成这一功能的驱动(实际上两者可能没什么关系,协议的文档写的实在太乱, 没找到对应的解释,姑且这么理解)。
逻辑上USB设备是由若干个端点(EndPoint)构成的,端点则是USB设备上通信逻辑的最小对象。 驱动一个USB设备,往往会涉及到多个数据通道,在USB上可以通过对不同的端点通信来实现。 我们完全可以用一个端点专门发送和接收指令,一个端点专门发送数据,一个端点专门接收数据。 当然具体的实现还是要根据实际需求来决定的。主机的驱动或者用户程序与端点之间的通信就形成了一个数据通道, 在协议中将之抽象为一个管道(pipe)。
在最底层的接口上,主机需要不断的查询USB设备,并进行合理的通信调度,以保证挂载的设备能够正常地工作。 在设备上,则需要根据数据包对总线上的数据进行筛选,并且将之划分到不同的端点上。一般情况下,我们是不需要关心这些东西的, 因为主机上有操作系统,设备上有现成的PHY芯片或者物理模块帮忙搞定这些。
4. USB的通信端点Endpoint
端点(Endpoint)是USB设备上与主机通信中最细粒度的对象,每一个逻辑上的USB设备都是由若干个独立的端点构成的。 每一个端点在USB设备的设计过程中就已经赋予了一个唯一的端点编号,不需要在设备连接时与主机协商确定。 USB设备上用于接收来自主机数据的端点,称为输出端点(output endpoint);用于向主机发送数据的端点称为输入端点(input endpoint)。 端点被称为输出的或者输入的,是站在主机的角度来讲的。一个端点的传输方向只能有一种,也是在USB设备的设计过程中就已经确定了的。 USB设备连接时会与主机协商分配一个地址,我们就可以通过设备地址、端点编号以及端点的传输方向唯一的指定一个端点进行通信。
在USB协议中,为端点定义了四种传输方式:批量(bulk)传输、中断(interrupt)传输、控制(control)传输、同步(isochronous)传输。 前三种都是异步的传输方式,没有太大区别,只在时间限制、数据格式、数据长度、用途上有所不同。 而同步传输则是一种不可靠的传输方式,是不支持出错重传的。具体各种传输方式,在以后用到时再详细加以介绍。
需要强调的是,每个USB设备都有一组默认的端点工作在控制传输方式下,用于传输配置和控制信息的。 它们就是端点编号为0的输出和输入端点。在USB设备刚插入,且尚未分配地址时,主机通过总线地址0只能访问端点0。 此时,USB设备的通信能力是有限的,只有当主机通过端点0获取了设备的描述符,完成枚举操作后,设备才可以正常的与主机通信。
每个端点都有不同的工作方式和限制,都需要在枚举过程中通过设备描述符告知主机。描述端点的描述符我们称为端点描述符, 它记录了端点的编号、查询频率、传输方式、最大数据包长度等信息。
5. USB的枚举过程与描述符
把USB设备插入总线上后,主机就可以检测到并与之通信。但主机并不知道插入的什么设备,应该如何使用设备。 这就需要一个枚举过程,在该过程中,主机从设备上获取描述符。描述符则具体描述了插入的是什么设备,有什么功能, 可以与哪个端点进行通信。主机则根据描述符选择合适的USB设备驱动,应用软件才可以正常使用设备所提供的功能。
每个USB设备都只有一个设备描述符(Device Descriptor),该描述符定义了设备所用的USB版本号、设备类(class)代码、 设备子类(subclass)代码、协议(protocol)代码、厂商信息、可能的配置数量等信息。一个USB设备可以有多种配置, 但同一时间只能使用一种。
USB设备的每一种配置都对应着一个配置描述符,其中定义了设备的供电方式、支持的接口数量等信息。 据《圈圈教你玩USB》一书中的介绍,由多个接口并有接口来实现功能的设备被称为复合设备,比如USB音频设备,有一个音频控制接口, 同时可能还有一个或者多个音频流或者MIDI流接口。在主机端会把USB复合设备的每个接口当作一个功能设备来看待, 也就是逻辑上有多个设备。
同样的,每个接口都有一个接口描述符,它描述了接口所使用的端点信息,以及接口类型、子类型和协议等信息。 一个接口可能涉及到多个端点,而每个端点则有一个端点描述符。
为了方便管理USB设备驱动,人们又把USB设备划分成了各种类(class)和子类(subclass)。 例如,鼠标、键盘、手柄等被称为人机交互设备(Human Interface Device, HID), USB串口设备一般是USB通信设备类(Communication Device Class, CDC),U盘则是大型存储类(Mass Storage)。 这些类的划分体现在设备描述符和接口描述符的class、subclass、protocol相关的字段上,主要是接口描述符。 主机根据这些设备类型信息,以及设备描述符中提到的厂商信息选择驱动。
协议中定义了USB设备的六个状态:
- Attached: 指设备是否连接到USB总线上。
- Powered: 指设备已经连接上了,并且供电了。
- Default: 指设备已经供电了,并且复位结束,但还没有分配地址,主机可以通过默认地址0来访问该设备,并分配地址。
- Address: 设备已经被分配了一个唯一的地址。主机可以通过该地址访问设备。
- Configured: 在使用设备的功能之前,还需要在主机与设备之间经过一个协商的过程,用来配置设备。 主机则需要根据设备的具体类型和功能选择相应的驱动。只有在此状态下USB设备才是可用的。
- Suspended: 当设备发现总线上在一段时间内没有数据就会进入该状态,以节约能源。
当我们将一个USB设备插入PC上的可用接口时,PC机需要经过一系列的操作为设备分配地址,协商选择设备驱动, 对USB设备进行必要的配置。这一过程我们称之为枚举过程,具体如下:
- 由于USB接头的热插拔设计,当我们插入一个USB插入到HUB的接口上时,就会改变HUB的连接状态。 在主机查询HUB时,由HUB告知状态改变,标识着设备已经连接上了。此时主机就获知了新插入设备所在的端口。
- 主机至少等100ms,待设备完全插入并且供电稳定后,向对应的HUB发送一个使能和复位端口的指令。
- HUB执行指令,复位端口。设备进入Default状态,可以从VBUS引脚上获取不超过100mA的电流,用于完成基本的功能配置。 主机可以通过默认地址0访问该设备。
- 主机通过地址0与设备的端点0进行通信,请求设备描述符(Device Descriptor)。
- 主机请求配置信息。
- 主机分配地址
- 主机根据配置信息,选择驱动。此时设备进入Configured状态,可以正常使用了。
6. USB的数据传输和包
在本文的第二节中,我们已经介绍USB上的数据信号线采用的是一对差分信号。 这就意味着,USB是一种异步的串行通信方式,同一时间总线上只有一包数据在传送。在USB总线上,数据是小端的, 也就是说先发送最低位,再发送最高位。
在这组差分信号上,数据采用的是NRZI编码方式:当数据为'0'时,电平翻转,数据为'1'时,保持电平。 如果总线上连续发送很多个'1',将导致电平长时间不翻转,这样不利于主从设备两端的时钟信号同步。 因此,当数据线上连续发送了6个'1'之后,强制发送一个'0',这个操作称为位填充,数据的接收端需要相应的去除这一填充位。 一般情况下,我们是不要关心这些的,有芯片或者物理模块帮我们实现了。
USB总线上的数据是以包为基本单位发送的,包则被划分成了多个域。在USB协议中根据使用的目的不同,定义了各种包。 对于任何包,都是以一个同步域和一个PID(包标识符,Packet Identifier)开始,以EOP(包结束符,End Of Packet)结束的。
所谓同步域,就是一串儿'0'后面跟了一个'1'。根据NRZI的编码规则,在发送一串儿'0'时,将导致总线上的电平连续的翻转。 接收端可以根据电平翻转的频率,与发送端同步时钟。全速和低速设备发送7个'0',而高速设备则发送31个'0'。
包标识符PID是用来标识包类型的。它有8位,但我们只使用其中的低四位。高四位是低四位的反,用于校验PID。
包结束符EOP就是用来标识包结束的。全速和低速设备同时保持D+和D-为低电平两个数据位,也就是两个数据位的SE0信号。 高速设备则用的是位填充错误信号,需要检查CRC校验位,如果通过则是EOP,否则就是真的位填充错误。
USB协议把包分为了四类:令牌包(Token)、数据包(Data)、握手包(Handshake)和特殊包(Special)。 下表列出了USB2.0中定义的各种包:
PID类型 | PID名称 | PID[3:0] | 描述 |
---|---|---|---|
Token | OUT | 0001b | 通过设备地址和端点编号通知设备将要输出数据 |
IN | 1001b | 通过设备地址和端点编号通知设备需要读入数据 | |
SOF | 0101b | Start-of-Frame marker and frame number | |
SETUP | 1101b | 通过设备地址和端点编号通知设备上的一个控制端点将要进行控制传输。 | |
Data | DATA0 | 0011b | 各种不同类型的数据包。 |
DATA1 | 1011b | ||
DATA2 | 0111b | ||
MDATA | 1111b | ||
Handshake | ACK | 0010b | 数据接收方成功接收到数据。(主机和设备都可以使用) |
NAK | 1010b | 接收设备无法接收数据或者发送设备无法发送数据。(仅设备) | |
STALL | 1110b | 端点挂起或者不支持控制通道请求。 | |
NYET | 0110b | 接收方尚未准备好应答。 | |
Special | PRE | 1100b | (Token)主机发送的一个前导包,用于开启低速设备传输。 |
ERR | 1100b | (Handshake)分裂发送错误的握手包,PID与PRE复用。 | |
SPLIT | 1000b | (Token)高速包的分裂传送事务。 | |
PING | 0100b | (Token)用于高速设比的批量/控制传送的流量探测。 | |
—— | 0000b | 保留。 |
在USB系统中,所有的数据传送事务都是由主机发送一个令牌包开始的。上表中列出的4种令牌包中,OUT、IN、SETUP包的数据格式是一样的。 SOF令牌包则略有不同。每种令牌包的CRC5校验只校验PID之后的数据。
OUT、IN、SETUP令牌包数据格式
同步域 | 8位PID | 7位设备地址 | 4位端点编号 | 5位CRC5校验 | EOP |
SOF令牌包数据格式
同步域 | 8位PID | 11位帧号 | 5位CRC5校验 | EOP |
OUT和IN分别用于通知设备将要输出或者需要读入数据。SETUP包的作用于OUT类似,也是通知设备将要输出一个数据包。 不同的是,其后的数据包只是DATA0,而且只能发送到控制端点上。SOF令牌包则是主机与设备之间的一种同步机制,暂不作详细介绍。
数据包就是用来传输数据的。数据包的结构都一样,同步域和PID之后跟着N个字节的数据,最后是16位的CRC16校验和包结束符EOP。
在USB1.1协议中,就定义了DATA0和DATA1两种数据包。在数据包发送过程中,两种数据包交替传送,是一种容错纠错机制。 在主机和设备上都有自己的一个数据包切换机制,当成功发送或者接收数据时,就会及转换数据包类型。 在每次数据包传送过程中,都会对数据包类型进行匹配,当检测到对方使用的数据包类型不对时,就认为发生了一次错误。 数据包类型不匹配的主要原因是,A端已经正确接收到数据并返回确认信号,但确认信号并没有正确的到达B端。 这种情况发生时,B端因为不清楚A端是否已经正确接收了数据,将不改变自己的数据包类型。下次发送数据包时, 将会发现对方使用的数据包不一致说明上次已经成功接收了数据。如果一包数据发下去B端没有接收到确认信号, 并且下次数据包类型一致,则说明上次的数据并没有成功发送。
在USB2.0中又增加了DATA2和MDATA包,用于高速分裂事务和高速高带宽同步传输中。暂时不作详细介绍。
握手包是最简单的一种数据包,只有同步域、PID和EOP,用于确认一次传输。ACK用于主机和设备同时数据发送方成功接收, NAK只用于设备通知主机数据没有准备好,主机会在以后合适的时机重试传输。STALL表示设备的端点被挂起了, NYET只用在高速设备输出事务,表示设备本次数据成功接收,但没有足够的空间来接收下一次数据。
特殊包则是用于一些特定功能的。暂时不作详细介绍。