USB的一种传输模式--控制传输;
在设备的枚举过程中都是使用控制传输;控制传输分为3个过程:建立过程、可选的数据过程、状态过程。
建立过程(setup)都是由USB主机发起的。开始于一个SETUP令牌包,后面紧跟一个DATA0数据包;
数据过程:如果是控制读传输,那么数据过程就是输入数据;如果是控制写传输,那么数据过程是输出数据。如果在建立过程中,指定了数据长度为0,则没有数据过程。
状态过程:状态过程刚好与数据过程的数据传输方向相反;
1.8枚举详细过程:
- ①USB主机检测到USB设备插入,对设备复位。USB设备复位后其地址为0,主机通过0地址与刚插入的设备通讯。USB主机往地址0的设备的端点0发送获取设备描述符的标准请求。(控制传输的建立过程--读传输控制);②设备收到该请求后,会按照主机请求的参数,在数据过程将设备描述符返回给主机。(数据过程);③主机在成功成功获取到一个数据包的设备描述符并且确认没有错误后,就会返回一个0长度的确认数据包给设备,(状态过程)从而进入到接下来的设置地址阶段。
- ①主机对设备又一次复位。此时进入到设置地址阶段。USB主机往地址为0的设备的端点0发出一个设置地址的请求;(控制传输的建立过程--写传输控制);新的设备地址包含在建立过程的数据包中。具体的地址由USB主机负责管理,主机会分配一个唯一的地址给刚接入的设备。②USB接收到这个建立过程后,就直接进入状态过程,因为这个过程没有数据过程。设备等待主机请求状态返回(一个输入令牌包),收到输入令牌包后,设备就返回0长度的状态数据包。
- 主机再次获取设备描述符。这次主机不再使用0地址访问设备,而是使用新的设备地址,另外这个要获取全部的18字节的设备描述符。如果端点0最大包长小于18字节,那么就会多次请求数据输入(即发送多个IN令牌)。
- 主机获取配置描述符。配置描述符共9字节。主机在获取到配置描述符后,根据配置描述符中所描述的配置集合总长度,获取配置集合。获取配置描述符和获取配置描述集合的请求是差不多的,只是指定的长度不一样。(有些主机干脆不单独获取配置描述符,而是直接使用最大长度来获取配置描述符集合。因为设备实际返回的数据可以少于指定的字节数。)配置集合包括(配置描述符、接口描述符、类特殊描述符(如果有)、端点描述符等)。接口描述符、端点描述符、类特殊描述符是不能单独获取的,必须跟配置描述符以一个集合的方式一并返回。
1.9 USB的包结构及传输过程
1.9.1 USB的包结构以及包的分类:
USB是串行总线,所以数据是一位一位地在数据线上传送的。USB使用的是LSB在前的方式,即先出来的是低字节数据,接下来是次低位,最后是最高位(MSB)。一个包,又被分为很多个域(field),而LSB、MSB就是以域为单位来划分的。
在USB系统中,主机处于主导地位,所以把从设备到主机的数据交输入,从主机到设备的数据叫输出。
USB总线上传输数据是以包为基本单位的。一个包被分成不同的域。根据不同类型的包,所包含的域是不一样的。但是不同的包有个共同的特点,就是都要以(同步域)开始,紧跟着一个包包标识PID(Packet Identifier),最终以包结束EOP(End Of Packet)来结束这个包。
同步域是用来告诉USB的串行接口引擎数据要开始传输了。同步域还可以用来同步主机端和设备端的数据时钟,因为同步域以一串0开始的,而0在USB总线上就被编码为电平翻转,结果就是每个数据都发生电平变换,这让串口引擎很容易就能恢复出采样时钟信号;对于全速设备和低速设备,同步域使用的是00000001(二进制数,总线上的发送顺序);对于高速设备,同步域使用的是31个0,后面跟1一个1(需要注意的是,这是对发送端的要求,接收解码时,0的个数可以少于这个数)。
包结束符EOP,对于高速设备和全速/低速设备也是一样的。全速/低速设备的EOP是一个大约为2个数据位宽度的单端0(SE0)信号。SE0的意思就是,D+和D-同时都保持为低电平。由于USB使用的是差分数据线,通常都是一高一低的,而SE0不同,是一种都为低特殊的状态。SE0用来表示一些特殊的意义,例如包结束、复位信号等。(前面提到的USB集线器对USB设备进行复位的操作,就是通过将总线设置为SE0状态大约10ms来实现的)。对于告诉设备的EOP,使用故意的位填充错误来表示。那么如何判断一个位填充错误是真的位填充错误还是包结束呢?这个CRC校验来判断。如果CRC校验正确,则说明这个位填充错误是EOP;否则,说明传输错误。
包标识符PID用来标识一个包的类型。它总共有8位,其中USB协议使用的只有4位(PID0~PID3),另外4位(PID4~PID7)是PID0~PID3的取反,用来校验PID。USB协议规定了4类包,分别是:令牌包(token packet;PID1~0为01)、数据包(data packet,PDI1~0为11)、握手包(handshake packet,PID1~0为10)和特殊包(special packet,PID1~0为00)。不同类的包又分成几种具体的包。
USB2.0协议中规定的各种PID,其中有些是在USB1.1协议中没有的,用*标出
PID类型 | PID名 | PID[3:0] | 说明 |
令牌包 | OUT | 0001B | 通知设备将要输出数据 |
IN | 1001B | 通知设备将要输入数据 | |
SOF | 0101B | 通知设备这是一个帧起始包 | |
SETUP | 1101B | 通知设备将要开始一个控制传输 | |
数据类 | DATA0 | 0011B | 不同类的数据包 |
DATA1 | 1011B | ||
DATA2* | 0111B | ||
MDATA* | 1111B | ||
ACK | 0010B | 确认 | |
握手类 | NAK | 1010B | 不确认 |
STALL | 1110B | 挂起 | |
NYET* | 0110B | 未准备好 | |
PRE | 1100B | 前导(这是一个令牌包) | |
特殊类 | ERR* | 1100B | 错误(这是一个握手包) |
SPLIT* | 1000B | 分裂事务(这是一个令牌包) | |
PING* | 0100B | PING测试(这是一个令牌包) | |
- | 0000B | 保留,未使用 |
1.9.2 令牌包。
令牌包用来启动一次USB传输。因为USB是主从结构的拓扑结构,所以所有的数据传输都是由主机发起的,设备只能被动的接听数据(唯一的例外是支持远程唤醒的设备能够主动改变总线的状态让集线器感知到设备的唤醒信号,但是这个过程并不传数据,只是改变一些总线的状态)。这就需要主机发送一个令牌来通知那个设备进行响应,如何响应。
令牌包有4种,分别为输出(OUT)、输入(IN)、建立(SETUP)和帧起始(SOF Start Of Frame)。
输出令牌包用来通知设备将要输出一个数据包。
输入令牌包用来通知设备返回一个数据包。
建立令牌包只用在控制传输中,它跟输出令牌包的作用一样,也是通知设备将要输出一个数据包,两者区别在于:SETUP令牌包只使用DATA0数据包,且只能发到设备的控制端点,并且设备必须要接收,而OUT令牌包没有这些限制。
帧起始包在每帧(或微帧)开始时发送,它以广播的形式发送,所有USB全速设备和高速设备都可以接收到SOF包。USB全速设备每毫米产生一个帧,而高速设备每125us产生一个微帧。USB主机会对当前帧号进行计数,在每次帧开始时(或者微帧开始时,每毫秒有8个微帧,这8个微帧的帧号是一样的,即相同的SOF)通过SOF包发送帧号。SOF中的帧号是11位的。在4种令牌包中,只有SOF令牌包之后不跟随数据传输,其他都有数据传输。
每个令牌包,最后都有一个CRC5的校验,它只校验PID之后的数据,不包括PID本身,因为PID本身已经有4个取反的位进行校验了。
SOF令牌包的结构图
同步域 | 8位包标识PID | 11位帧号 | 5位CRC5校验 | EOP |
(同步域:USB数据要开始传输了)(包标识:当前是什么包)(EOP:包结束符)
OUT(通知设备将要输出数据)、IN(将要输入数据)、SETUP(要开始一个控制传输)令牌包具有相同的结构:同步域、包标识域、地址域、端点域、CRC5校验域和包结束。地址域:要访问设备的地址;端点域:要访问设备的端点号;CRC5校验只计算PID之后的地址域和端点域。数据在总线上传输是LSB在前。7位地址在总线上传输的先后就是A0、A1、A2、A3、A4、A5、A6。
OUT、IN、SETUP令牌包的结构图:
同步域 | 8位包标识PID | 7位地址 | 4位端点号 | 位CRC5校验 | EOP |
1.9.3 数据包
数据包就是用来传输数据的。在USB1.1协议中,只有两种数据包:DATA0包和DATA1包。在USB2.0中又增加了DATA2和MDATA包,主要用在告诉分裂事务和告诉带宽同步传输中。
数据包都具有相同的结构:一个同步域,后面跟整数字节的数据,然后是CRC16校验,最后是包结束符EOP。
数据包的结构图:
同步域 | 8位包标识PID | 字节0 | 字节1 | 。。。。。。 | 字节N | 16位CRC16校验 | EOP |
之所以有不同类型的数据包,是用在握手包出错时纠错。下面以DATA0包和DATA1包的切换为例进行具体的解释。
主机和设备都会维护自己的一个数据包类型切换机制:当数据包成功发送或者接收时,数据包类型切换。当检测到对方所使用的数据包类型不对时,USB系统认为这发生了一个错误,并试图从错误中恢复。数据包类型不匹配主要发生在握手包被破坏的情形。当一端已经正确接收到数据并返回确认信号时,确认信号却在传输过程中被损坏。这时另一端就无法知道刚刚发生的数据是否已经成功,这时它只好保持自己的数据包的类型不变。如果对方下一次使用的数据包类型跟自己的不一致,则说明它刚刚已经成功接收到数据包了(因为它已经做了数据包切换,只有正确接收才会如此);如果对方下一次使用的数据包类型跟自己的一致,则说明对方没有切换数据包类型,也就是说,刚刚的数据包没有发生成功,这时上一次的重试操作。
1.9.4 握手包
握手包用来表示一个传输是否被对方确认。握手包只有同步域、PID和EOP,是最简单的一种数据包。
握手包结构图:
同步域 | 包标识PID | EOP |
握手包有ACK、NAK、STALL和NYET。
ACK:表示正确接收数据,并且有足够的空间来容纳数据。主机和设备都可以用ACK来确认,而NAK、STALL、NYET只有设备能够返回,主机不能使用这些握手包。
NAK:表示没有数据需要返回,或者数据正确接收但是没有足够的空间来容纳它们。当主机接收到NAK时,知道设备还没准备好,主机会在以后合适的时机进行重试传输。
STALL:表示设备无法执行这个请求,或者端点已经被挂起了,它表示一种错误的状态。设备返回STALL后,需要主机进行干预才能解除这种STALL状态。
NYET:只在USB2.0的高速这边输出事务中使用,它表示设备本次数据成功接收,但是没有足够的空间来接收下一次数据。主机在下一次输出数据时,将先使用PING令牌包来试探设备是否有空间接收数据,以避免不必要的带宽浪费。
需要注意的是,返回NAK并不表示数据出错,只是说明设备暂时没有数据传输或者没有能力接收数据。当USB主机或者设备检测到数据出错时(如CRC校验错、PID校验错、位填充错等),将什么都不返回。这时等待接收握手包的一方就会收不到握手包从而等待超时。
1.9.5 特殊包
特殊包是一些在特殊场合使用的包。总共有4种:PRE、ERR、SPLIT和PING。其中PRE、SPLIT、PING是令牌包,ERR是握手包。ERR、SPLIT、PING三个是在USB2.0协议中新增的。
PRE是通知集线器打开其低速端口的一种前导包。PRE只使用在全速模式中。平时,为了防止全速信号使低速设备误动作,集线器是没有将全速信号传送给低速设备的。只有当收到PRE令牌包时,才打开其低速端口。
PRE令牌包结构图:
同步域 | 包标识PID | EOP |
当需要传送低速事务时,主机首先发送一个PRE令牌包(以全速模式发送)。对于全速设备,将会忽略这个令牌包。集线器在收到这个令牌包后,打开其连接了低速设备的端口。接着,主机就会以低速模式给低速设备发送令牌包、数据包等。
PING令牌包的结构图:
同步域 | 8位包标识PID | 7位地址 | 4位端点号 | 位CRC5校验 | EOP |
PING令牌包与OUT令牌包具有一样的结构,但是PING令牌包后并不发送数据,而是等待设备返回ACK或者NAK,以判断设备是否能够传送数据。USB1.1中没有PING令牌包的。只有在USB2.0高速环境下才会使用PING令牌包,它只被使用在批量传输和控制传输的输出事务中。(直接使用OUT令牌包发送数据时,不管设备是否有空间接收数据,都会在OUT令牌包之后跟着发送一个数据包,如果设备没有空间接收数据,都会在OUT令牌包之后跟着发送一个数据包,如果设备没有空间接收数据,就会返回一个NAK,这样的结果就是浪费了总线带宽,白白传送了数据。在高速设备中增加了这个PING机制,主机先用PING令牌包试试设备是否有空间接收数据,而不用实现把数据发送出去。)
SPLIT令牌包是高速事务分裂令牌包,通知集线器将高速数据包转化为全速或者低速数据包发送给其他下面的端口。
ERR握手包是在分裂事务中表示错误使用。
1.9.6如何处理数据包
一般的USB接口芯片会完成如CRC校验、位填充、PID识别、数据包切换、握手等协议的处理。
当USB接口芯片正确接收到数据时,如果有空间保存,则它将数据保存并返回ACK,同时,设置一个标志表示已经正确接收到数据;如果没有空间保存数据,则自动返回NAK。
当收到输入请求时,如果有数据需要发送,则发送数据,并等待接收ACK。只有当数据成功发送出去(即接收到应答信号ACK)之后,它才设备标志,表示数据已成功发送;如果无数据需要发送,则它自动返回NAK。
1.10 USB的四种传输类型
1.10.1 USB事务
事务通常由两个或者三个包组成:令牌包、数据包和握手包。
令牌包用来启动一个事务,总是由主机发送。
数据包传送数据,可以从设备到主机,也可以从设备到主机,方向由令牌包来指定。
握手包的发送者通常为数据接收者,当数据接收正确后,发送握手包。设备也可以使用NAK握手包来表示数据还没准备好。
USB规定了4种传送类型:批量传输、等时传输(也有翻译成同步传输)、中断传输和控制传输。其中,批量传输、等时传输、中断传输每传输一次数据都是一个事务;控制传输包括3个过程,建立过程和状态过程分别是一个事务,数据过程则可能包含多个事务。
1.10.2 批量传输
批量传输使用批量事务(bulk transaction)传输数据。一次批量事务有三个阶段:令牌包阶段、数据包阶段和握手包阶段。批量传输分为批量读和批量写(输入还是输出是以主机为参考的),批量读使用批量输入事务,批量写使用批量输出事务。
批量传输没有规定数据包中数据的意义和结构,具体的数结构要由设备自己定义。批量传输通常用在数据量大、对数据的实时性要求不高的场合,例如USB打印机、扫描仪、大容量存储设备等。
批量输出事务。主机先发出一个OUT令牌包,这个令牌包中包含了设备地址、端点号。然后,再发送一个DATA包(具体是什么类型的DATA包,要看数据切换位),这时地址和端点匹配的设备就会收下这个数据包。然后主机切换到接收模式,等待设备返回握手包。
批量输入事务。主机首先发出一个IN令牌包,IN令牌包中包含了设备地址和端点号。然后主机切换到接收数据状态,等待设备返回数据。
PING令牌包,它不发出数据,直接等待设备的握手包。因此PING事务只有令牌包和握手包。
一次正确的批量输入事务:
同步域 | IN PID | 7位地址 | 4位端点号 | 5位CRC5校验 | EOP | 主机发送 | |||||||
同步域 | DATA0 PID | 字节0 | 字节1 | 16位CRC16校验 | EOP | 设备返回 | |||||||
同步域 | ACK PID | EOP | 主机应答 |
一次正确的批量输出事务:
同步域 | OUT PID | 7位地址 | 4位端点号 | 5位CRC5校验 | EOP | 主机发送 | |||||||
同步域 | DATA0 PID | 字节0 | 字节1 | 16位CRC16校验 | EOP | 主机发送 | |||||||
同步域 | ACK PID | EOP | 设备应答 |
1.10.3 中断传输
中断传输是一种保证查询频率的传输。中断端点在端点描述符中要报告它的查询间隔,主机会保证在小于这个时间间隔的范围内安排一次传输。
这里的中断,跟硬件上的中断是不一样的。它不是由设备主动的发出一个中断请求,而是由主机保证在不大于某个时间间隔内安排一次传输。中断传输通常用在数据量不大,但是对时间要求较严格的设备中,例如人机接口设备(HID)中的鼠标、键盘等。中断传输也可以用来不断地检测某个状态,当条件满足后再使用批量传输来传送大量的数据。
1.10.4 等时传输
等时传输(同步传输)用在数据量大、对实时性要求高的场合,例如音频设备、视频设备等,这些设备对数据延迟很敏感。对数据的100%正确要求不高,少量数据的错误还是可以容忍的,主要的是要保证不能停顿;等时传输不能保证数据100%正确,当数据错误时,并不进行重传操作。因此等时传输没有应答包。数据是否正确,可以由数据包的CRC校验来确认。至于出错的数据如何处理,由软件决定。等时传输使用等时事务(isochronous transaction)来传输数据。
1.10.5 控制传输
控制传输分为三个过程:第一个过程是建立过程;第二个过程是可选的数据过程;第三个过程是状态过程。
建立过程使用一个建立事务。建立事务是一个输出数据的过程,与批量传输的输出事务相比,不一样地方:1.令牌包不同:建立过程使用SETUP令牌包;2.数据包类型不同;SETUP只能使用DATA0令牌包;3.握手包不同:设备只能使用ACK来应答(除非出错了,不应答),而不能使用NAK或者STALL来应答。即设备必须要接收建立事务的数据。
数据过程是可选的。即一个控制传输可能没有数据过程。如果有,一个数据过程可以包含一笔或者多笔数据事务。控制传输所使用的数据事务与批量传输中的批量事务是一样的。(需要注意的是,在数据过程中,所有的数据事务必须是同一个传输方向的。也就是说,在控制读传输中,数据过程中的所有数据事务都必须是输入的;在控制写传输中,数据过程中的所有数据事务都必须是输出的。一旦数据传输方向发生改变,就会认为进入到了状态过程。数据过程的第一个数据包必须是DATA1包,然后每次正确传输一个数据包后就在DATA0和DATA1之间交替)。
状态过程也是一笔批量事务,它的传输方向刚好跟前面的数据阶段相反,即控制写传输在状态过程使用一个批量输入事务;控制读传输在状态过程使用一个批量输出事务。状态过程只使用DATA1包。
控制传输的实例。
1.10.6 端点类型与传输类型的关系
一个具体的端点,只能工作在一种传输模式下。通常,我们把工作在什么模式下的端点,就叫做什么端点。例如,控制端点、批量端点等。
端点0是每个USB设备都必须句柄的默认控制端点,它一上电就存在并且可用。设备的各种描述符以及主机发生的一些命令,都是通过端点0传输的。其他端点是可选的,需要根据具体的设备来决定。非0端点只有在Set Config之后才能使用。
1.10.7 传输类型与端点支持的最大包长
每个端点描述符中都规定了端点所支持的最大数据包长。主机每次发送数据包,都不能超过端点的最大包长。
- 对于控制传输的端点,低速模式最大包长固定为8字节,高速模式最大包长固定为64字节,而全速模式可在8、16、32、64字节中选择。
- 对于等时传输的端点,全速模式最大包长上限为1023字节,高速模式最大包长上限为1024字节,低速模式不支持等时传输。
- 对于中断传输的端点,低速模式最大包长上限为8字节,全速模式最大包长上限为64字节,高速模式最大包长上限为1024字节。
- 对于批量传输的端点,高速模式固定为512字节,全速模式最大包长度可在8、16、32、64字节中选择,低速模式不支持批量传输。
3.4 读取从主机发送到端点0的数据
D12 中断处理:
读取中断源指令:Read Interrupt Register,0xF4;发送改指令,可以读取两个自己数据,第一个字节内容是端点和总线状态的中断,第二个字节是与DMA相关的一位有效。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
挂起状态改变 | 总线复位 | 端点2输入 | 端点2输出 | 端点1输入 | 端点1输出 | 端点0输入 | 端点0输出 |
中断状态寄存器第一字节
中断标志的清除:其中0~5要通过Read Last Transaction Status命令读取端点最后传输状态后,被清零。
7~6则在读取本寄存器后,被自动清零。
读取端点数据:
- 选择端点,select endpoint命令。(0x00~0x05)(端点0输出,端点0输入)。
- 读取缓冲区,read buffer命令。0xF0。数据的第一个字节是保留的,没有意义;第二个字节接收到数据的字节数。剩余的为数据。
在发送端点0~3的输入和输出中断,要使用读最后传输状态命令,read last transation status register命令,该命令代码为0x40~0x45,分别对于着3个端点的输入和输出。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
前一次状态未读 | DATA0/1包 | 建立包 | 出错代码 | 数据接收/发送成功 |
最后传输状态寄存器
位0:该位为1表示数据成功接收或发送。
位1~4:出错代码。
位5:如果该位为1,表示收到的是建立(setup)过程的数据包。
位6:该位为0表示收到的是DATA0数据包,位1表示收到的是DATA1数据包。
位7:该位为1表示前一次状态没有读取,前面的状态已经被覆盖。
清除数据缓冲区的命令Clear Buffer,命令码是0xF2,如果一个端点接收数据后没有使用Clear Buffer命令清除,对于以后发往该端点的数据包(建立过程的数据包除外,设备必须接收)将使用NAK来应答。在读数据之后,要将端点缓冲区清除。
接收到建立包后必须要使用一个特殊的命令,才能让Clear Buffer命令和Validate Buffer命令生效,这个命令就是Acknowledge Setup。这样做的目的是为了保证控制传输建立过程的数据不会丢失。
3.5 USB标准请求
USB协议规定了一个8字节的标准设备请求,主要用在设备的枚举过程中。这8字节的数据是在控制传输的建立过程通过默认控制端点0发出的。在这8字节数据中,包含了数据过程所需要传输数据传输的方向、长度以及数据类型等信息。正是由于8字节标准请求的原因,USB协议规定,端点0的最大包长度至少为8字节。也就是说,任何一个USB设备都能(而且必须要)接收8字节的标准请求。
3.5.1USB标准设备请求的结构
USB标准设备请求的数据结构
0字节:域,bmRequestType
D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
数据传输方向 | 请求的类型 | 请求的接收者 |
D7:数据传输方向 D6~D5:请求的类型 D4~D0:请求的接收者
0=主机到设备 0=标准 0=设备
1=设备到主机 1=类 1=接口
2=厂商 2=端点
3=保留 3=其他
4~31=保留
1字节:域,bRequest
D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
数值,请求代码
2~3字节:域,wValue
数值,该域的意义由具体的请求决定。
4~5字节:域,wIndex
索引或偏移量,该域的意义由具体的请求决定。
6~7字节:域,wLength
字节数,数据过程(如果有)所需要传输的字节数。
USB协议规定了11个标准请求(bRequest)===bmRequestType的D6和D5为00情况
bRequest | Value | bRequest | Value | |
GET_STATUS | 0 | GET_CONFIGURATION | 8 | |
CLEAR_FEATURE | 1 | SET_CONFIGURATION | 9 | |
SET_FEATURE | 3 | GET_INTERFACE | 10 | |
SET_ADDRESS | 5 | SET_INTERFACE | 11 | |
GET_DESCRIPTOR | 6 | SYNCH_FRAME | 12 | |
SET_DESCRIPTOR | 7 |
标准请求
不同的请求对于其接收者、wValue和wIndex,其各字段的意义是不一样的。各个标准请求的结构以及数据过程需要传输的数据。其中第一列有的有多个,主要是最低5位不同,即表示接收者不同。有的请求只能发送到设备,而有的请求可以发送到设备、接口和端点。常用的几个请求为GET_DESCRIPOR、SET_ADDRESS和SET_CONFIGURATION。
各种标准请求的结构以及需要传输的数据
bmRequestType | bRequest | wValue | wIndex | wLength | 数据过程 |
00000000B 00000001B 00000010B | CLEAR_FEATURE | 特性选择 | 0 接口号 端点号 | 0 | 没有 |
10000000B | GET_CONFIGURATION | 0 | 0 | 1 | 配置值 |
10000000B | GET_DESCRIPTOR | 描述符类型和索引 | 0或者语言ID | 描述符的长度 | 描述符 |
10000001B | GET_INTERFACE | 0 | 接口号 | 1 | 备用接口号 |
10000000B 10000001B 10000010B | GET_STATUS | 0 | 0 接口号 端点号 | 2 | 设备、接口或者端点状态 |
00000000B | SET_ADDRESS | 设备地址 | 0 | 0 | 没有 |
00000000B | SET_CONFIGURATION | 配置值 | 0 | 0 | 没有 |
00000000B | SET_DESCRIPTOR | 描述符类型和索引 | 0或者语言ID | 描述符的长度 | 描述符 |
00000000B 00000001B 00000010B | SET_FEATURE | 特性选择 | 0 接口号 端点号 | 0 | 没有 |
00000001B | SET_INTERFACE | 备用接口号 | 接口号 | 0 | 没有 |
10000010B | SYNCH_FRAME | 0 | 端点号 | 2 | 帧号 |
3.5.2 GET_DESCRIPTOR请求
GET_DESCRIPTOR(获取描述符)请求是在枚举过程中用得最多的一个请求。主机通过发送获取描述符请求读取设备的各种描述符,从而可以获知设备类型、端点情况等众多重要信息。获取描述符的接收者只能是设备,从bmRequestType的第7位可以看出,它是请求数据输入的。bRequest的值为0x06(GET_DESCRIPTOR)。
GET_DESCRIPTOR请求的结构
bmRequestType | bRequest | wValue | wIndex | wLength | 数据过程 |
10000000B (0x80) | GET_DESCRIPTOR (0x06) | 描述符类型和索引 | 0或者语言ID | 描述符长度 | 描述符 |
wValue域的第一字节(低字节)表示的是索引号,用来选择同一种描述符(例如字符串描述符和配置描述符)中具体的某个描述符。wValue域的第二字节表示描述符的类型编号。
wIndex域只在获取描述符中有用,它表示字符串的语言ID号,获取除字符串描述符的其他描述符时,wIndex的值为0。
wLength域为请求设备返回数据的字节数据,设备实际返回的字节数可以比该域指定的字节数少。
设备在收到获取描述符的请求后,应该按照所请求的描述符类型编号,在数据过程中返回相应的描述符。对于全速模式和低速模式,获取描述符的标准请求只有三种:获取设备描述符、获取配置描述符和获取字符串描述符。另外的接口描述符和端点描述符是跟随配置描述符一并返回的,不能单独请求返回(如果单独返回,主机无法确认它们属于那个配置)。
描述符类型及其编号,wValue域的第二个字节
描述符类型 | 编号 |
设备描述符(DEVICE) | 1 |
配置描述符(CONFIGURATION) | 2 |
字符串描述符(STRING) | 3 |
接口描述符(INTERFACE) | 4 |
端点描述符(ENDPOINT) | 5 |
3.5.3 SET_ADDRESS请求
SET_ADDRESS(设置地址)请求是主机请求设备使用指定地址的请求,指定地址包含在8字节数据中的wValue字段中。每个连接在同一个主控制器上的USB设备都需要有一个唯一的设备地址,这样主机才能区分每一个不同的设备。当设备复位后,都使用默认的地址0。主机从地址为0的设备获取设备描述符,一旦收到第一次设备描述符之后,主机就会发送设置地址的请求,以尽量减少设备使用公共地址0的时间。设置地址请求是没有数据过程的,因而wLength的值为0。wIndex也用不到,值为0。当设备收到设置地址请求后,就直接进入状态过程,等待主机读取0长度的状态数据包。主机成功读取到状态数据包(用ACK响应设备)后,设备将启用新的地址。这以后的传输中,主机都将使用新的地址与设备进行通信。
bmRequestType | bRequest | wValue | wIndex | wLength | 数据过程 |
00000000B (0x00) | SET_ADDRESS (0x05) | 设备地址 | 0x0000 | 0x0000 | 没有 |
3.5.4 SET_CONFIGURATION请求
SET_CONFIGURATION(设置配置)请求和设置地址请求很类似。区别在于wValue域的意义。在设置地址请求中,wValue的第一字节(低字节)为设备的地址。而在配置请求中,wValue的第一字节为配置值。当该值与某配置描述符中的配置编号一致时,表示选中该配置。该值通常为1,因为大多少USB设备只有一种配置,配置编号为1;如果该值为0,则会让设备进入设置地址状态。设备只有在收到非0的配置值后,才能启用它的非0端点。
bmRequestType | bRequest | wValue | wIndex | wLength | 数据过程 |
00000000B (0x00) | SET_CONFIGURATION (0x09) | 配置值 | 0x0000 | 0x0000 | 没有 |
端点0接收的数据为
D7 D6 D5 D4 D3 D2 D1 D0
0x80 0x06 0x00 0x01 0x00 0x00 0x40 0x00
0x80 bmRequestType 传输方向-设备到主机
0x06 bRequest 获取设备描述符
0x00 0x01 wValue 描述符类型和索引(1设备描述符)
0x00 0x00 wIndex 0或者语言ID
0x40 0x00 wLength 描述符长度
3.6 设备描述符的实现
既然知道主机请求设备返回设备描述符,那就应该在数据过程中返回设备的设备描述符。
设备描述符结构
偏移量/字节 | 域 | 大小/字节 | 说明 |
0 | bLength | 1 | 该描述符长度(18字节) |
1 | bDescriptorType | 1 | 描述符类型(设备描述符为0x01) |
2 | bcdUSB | 2 | 本设备所使用的USB协议版本 |
4 | bDeviceClass | 1 | 类代码 |
5 | bDeviceSubClass | 1 | 子类代码 |
6 | bDeviceProtocol | 1 | 设备所使用的协议 |
7 | bMaxPackeSize0 | 1 | 端点0最大包长 |
8 | idVender | 2 | 厂商ID |
10 | idProduct | 2 | 产品ID |
12 | bcdDevice | 2 | 设备版本号 |
14 | iManufacturer | 1 | 描述厂商的字符串的索引 |
15 | iProduct | 1 | 描述产品的字符的索引 |
16 | iSerialNumber | 1 | 产品序列号字符串的索引 |
17 | bNumConfigurations | 1 | 可能的配置数 |
bLength 长度为1字节,表示该描述符的长度。设备描述符的长度为18字节,写成16进制为0x12;
bDescriptorType 长度为1字节,表示描述符的类型。设备描述符的编号为0x01;
bcdUSB 长度为2字节,该设备所使用的USB协议的版本。可以取2.0或者1.1等版本号。它是使用BCD码表示的,例如USB2.0协议就是0x0200,而USB1.1是0x0110。USB协议中使用的是小端结构,所以实际数据在传输时,是低字节在先的,USB2.0协议的bcdUSB拆成两个字节就是0x00,0x02。而USB1.1的bcdUSB拆成两个字节就是0x10,0x01。
bDeviceClass 长度为1字节,是设备所使用的类代码。对于大多数标准的USB设备类,该字段通常设置为0,而在接口描述符中的bInterfaceClass中指定接口所实现的功能,当bDeviceClass为0时,下面的bDeviceSubClass也必须为0。如果bDeviceClass为0xff,表示是厂商自定义的设备类。
bDeviceSubClass 长度为1字节,是设备所使用的子类代码。当类代码不为0和0xff时,子类代码由USB协议规定。当bDeviceClass为0时,bDeviceSubClass也必须为0。
bDeviceProtocol 长度为1字节,是设备所使用的协议,协议代码由USB协会规定。当该字段为0时,表示设备不使用类所定义的协议。当该字段为0xFF时,表示设备使用厂商自定义的协议。bDeviceProtocol必须要结合设备类和设备子类联合使用才有意义,因此当类代码为0时,bDeviceProtocol应该为0。
bMaxPackeSize0 长度为1字节,是端点0的最大包长度。它的取值可以为8、16、32、64。
idVender 长度为2字节,它是厂商的ID号。该ID号由USB协议分配,不能随意使用。主机通常靠厂商的ID号、产品ID号以及产品序列号来安装和加载驱动的。
IdProduct 长度为2字节,是产品ID号。与产生ID号不一样,它是由生产商自己根据产品来编号的。
BcdDevice 长度为2字节,是设备的版本号,当同一个产品升级后(例如修改了固件增加了某些功能),可以通过修改设备的版本号来区别。
iManufacturer 长度为1字节,是描述厂商的字符串的索引值。当该值为0时,表示没有厂商字符串。主机获取设备描述符时,会将索引值放在wValue的第一字节中,用来选择不同的字符串。
iProduct 长度为1字节,是描述产品的字符串的索引值。当该位为0时,表示没有产品字符串。当第一次插上某个USB设备时,会在Windows的右下角弹出一个对话框,显示发现新硬件,并且会显示该设备的名称。这里显示的信息就是从产品字符串里面获取来的。如果想让他显示出所需要的信息,应该修改产品字符串。
iSerialNumber 长度为1字节,是设备的序列号索引值。设备序列号可能被主机联合VID和PID用来区别不同的设备,有时同时连接多个具有相同的VID、PID以及设备序列号的设备,可能导致设备无法正确识别。当该值为0时,表示没有序列号字符串。
bNumConfigurations 长度为1字节,表示设备有多种配置。每种配置都会有一个配置描述符,主机通过发生设置配置来选择某一种配置。大部分的USB设备只有一个配置,即该字段的值为1。
3.7 设备描述符的返回
1、选择端点,select endpoint命令。(0x00~0x05)(端点0输出,端点0输入)。
2、写数据到端点缓冲区,Write Buffer命令,0xF0。写入的数据第一个字节为0,第二个字节为要发生数据的长度。后面是要写入的数据。
3、将缓冲区种的数据设置为有效,Validate Buffer命令,0xFA。
3.8 设置地址请求的处理
每个USB设备都具有一个唯一的设备地址,这个地址是主机在设置地址请求时分配给设备的。设备在收到设置地址请求后,应该返回一个0长度的状态数据包(因为设置地址请求是没有数据过程的),然后等待主机确认这个数据包(即用ACK应答设备)。设备在正确接收到状态数据包的ACK之后,就开始使用新的设备地址了。
D12芯片提供一条设置地址的命令:Set Address/Enable命令,命令码为0xD0。设置地址命令后跟一个字节数据写入操作,该字节的D7位用来控制设备是否使能,只有当D7位设置为1时,D12的普通端点才能通过使能端点(Set Endpoint Enable)命令启用。D6~D0位是设备的7位地址,应该将接收到的标准请求中的地址写到D6~D0中。
3.9 配置描述符集合的结构
每种USB设备至少都要有一个配置描述符,在设备描述符中规定了该设备有多少中配置,每种配置都有一个描述符。
3.9.1 配置描述符的结构
USB协议规定的标准配置描述符的结构。
偏移量/字节 | 域 | 大小/字节 | 说明 |
0 | bLength | 1 | 该描述符的长度(9字节) |
1 | bDescriptorType | 1 | 描述符类型(配置描述符为0x02) |
2 | wTotalLength | 2 | 配置描述符集合总长度 |
4 | bNumInterfaces | 1 | 该配置所支持的接口数 |
5 | bConfiguration Value | 1 | 该配置的值 |
6 | iConfiguration | 1 | 描述该配置的字符串的索引值 |
7 | bmAttributes | 1 | 该设备的属性 |
8 | bMaxPower | 1 | 设备所需要的电流(单位为2mA) |
bLength 大小为1字节,表示该描述符的长度。标准USB配置描述符的长度为9字节。
bDescriptorType 大小为1字节,表示该描述符的类型。配置描述符的类型编码为0x02。
wTotalLength 大小为2字节,表示整个配置描述符集合的总长度,包括配置描述符、接口描述符、类特殊描述符(如果有)和端点描述符。注意低字节在先。
bNumInterfaces 大小为1字节,表示该配置所支持的接口数量。通常,功能单一的设备只具有一个接口(例如鼠标),而复合设备则具有多个接口(例如音频设备)。
bConfigurationValue 大小为1字节,表示该配置的值。通常一个USB设备可以支持多个配置,bConfigurationValue就是每个配置的标识。设置配置请求时会发送一个配置值,如果某个配置的bConfigurationValue值与它相匹配,就表示该配置被激活,为当前配置。
iConfiguration 大小为1字节,是配置描述该配置的字符串的索引值。如果该值为0,则表示没有字符串。
bmAttributes 大小为1字节,用来描述设备的一些特性。其中,D7是保留的,必须要设置为1。D6表示供电方式,当D6为1时,表示设备是自供电的;当D6为0时,表示设备是总线供电的。D5表示是否支持远程唤醒,当D5为1时,支持远程唤醒。D4~D0保留,设置为0。
bMaxPower 大小为1字节,表示设备需要从总线获取的最大电流量,单位为2mA。例如需要200mA的最大电流,则该字节的值为100。
3.9.2 接口描述符的结构
USB协议规定的标准接口描述符的结构。接口描述符不能单独返回,必须附着在配置描述符后一并返回。
偏移量/字节 | 域 | 大小/字节 | 说明 |
0 | bLength | 1 | 该描述符的长度(9字节) |
1 | bDescriptorType | 1 | 描述符类型(接口描述符为0x04) |
2 | bInterfaceNumber | 1 | 该接口的编号(从0开始) |
3 | bAlternateSetting | 1 | 该接口的备用编号 |
4 | bNumEndpoints | 1 | 接口所使用的端点数 |
5 | bInterfaceClass | 1 | 该接口所使用的类 |
6 | bInterfaceSubClass | 1 | 该接口所使用的子类 |
7 | bInterfaceProtocol | 1 | 该接口所使用的协议 |
8 | iInterface | 1 | 描述该接口的字符串的索引值 |
bLength 大小为1字节,表示该描述符的长度。标准的USB接口描述符的长度为9字节。
bDescriptorType 大小为1字节,是描述符的类型。接口描述符的类型编码为0x04。
bInterfaceNumber 大小为1字节,表示该接口的编号。当一个配置具有多个接口时,每个接口的编号都不相同。从0开始依次递增对一个配置的接口进行编号。
bAlternateSetting 大小为1字节,是该接口的备用编号。编号规则与bInterfaceNumber一样,很少会使用该字段,设置为0。
bNumEndpoints 大小为1字节,是该接口所使用的端点数(不包括0端点)。如果该字段为0,则表示没有非0端点,只使用默认的控制端点。
bInterfaceClass、bInterfaceSubClass、bInterfaceProtocol 分别是接口所使用的类、子类以及协议,它们的代码由USB协会定义,跟设备描述符中的意义类似。通常在接口中定义设备的功能,而在设备描述符中将类、子类以及协议字段的值设置为0。
iInterface 大小为1字节,是描述该接口的字符串的索引值。如果该值为0,则表示没有字符串。
3.9.3 端点描述符的结构
USB协议规定的标准端点描述符的结构。端点描述符不能单独返回,必须附着在配置描述符后一并返回。
偏移量/字节 | 域 | 大小/字节 | 说明 |
0 | bLength | 1 | 该描述符的长度(7字节) |
1 | bDescriptorType | 1 | 描述符的类型(端点描述符为0x05) |
2 | bEndpointAddress | 1 | 该端点的地址 |
3 | bmAttributes | 1 | 该端点的属性 |
4 | wMaxPackeSize | 2 | 该端点支持的最大包长度 |
6 | bInterval | 1 | 端点的查询时间 |
bLength 大小为1字节,表示该描述符的长度。标准的USB端点描述符的长度为7字节。
bDescriptorType 大小为1字节,表示描述符的类型。端点描述符的类型编码为0x05。
bEndpointAddress 大小为1字节,表示该端点的地址。最高位D7为端点的传输方向,1为输入(有点像Input的第一个字母),0为输出(有点像Output的第一个字母)。D3~D0为端点号。D6~D4保留,设为0。
bmAttributes 大小为1字节,是该端点的属性。最低两位D1~D0表示该端点的传输类型,0为控制传输,1为等时传输,2为批量传输,3为中断传输。如果该端点是非等时传输的端点,那么D7~D2为保留值,设为0。如果该端点是等时传输的,则D3~D2表示同步的类型,0为无同步,1为异步,2为适配,3为同步;D5~D4表示用途,0为数据端点,1为反馈端点,2为暗含反馈的数据端点,3是保留值。D7~D6保留。
wMaxPackeSize 大小为2字节,是该端点所支持的最大包长度。注意低字节在先。对于全速模式和低速模式,D10~D0表示端点的最低包长,其他保留为0。对于高速模式,D12~D11为每帧附件的传输次数。
bInterval 大小为1字节,表示该端点查询的时间。对于中断端点,表示查询的帧间隔数。对于等时传输以及高速模式的中断、批量传输,该字段的意义参考USB2.0。
3.9.4 HID描述符的结构
USB鼠标是属于USB HID类的。通过查看USB HID类的官方文档,HID类的设备在配置描述符中还需要一个HID描述符。它是一个类描述符,应该跟在接口描述符后面。
偏移量/字节 | 域 | 大小/字节 | 说明 |
0 | bLength | 1 | 该描述符的长度 |
1 | bDescirptorType | 1 | 描述符类型(HID描述符为0x21) |
2 | bcdHID | 2 | HID协议的版本 |
4 | bCountyCode | 1 | 国家代码 |
5 | bNumDescriptors | 1 | 下级描述符的数量 |
6 | bDescriptorType | 1 | 下级描述符的类型 |
7 | wDescriptorLength | 2 | 下级描述符的长度 |
9 | bDescriptorType | 1 | 下级描述符的类型(可选) |
10 | wDescriptorLength | 2 | 下级描述符的长度(可选) |
。。。。。。 | 。。。。。。 | 。。。(可选) |
bLength 大小为1字节,是该描述符的总长度。它的大小与该描述符中下级描述符的个数有关。例如,只有一个下级描述符时,总长度为1+1+2+1+1+1+2=9字节。多一个下级描述符多3个字节。
bDescriptorType 大小为1字节,是该描述符的编号。HID描述符的编号为0x21。
bcdHID 大小为2字节,是该设备所使用的HID协议的版本号。USB1.1,是0x0110。
bCountyCode 大小为1字节,是设备所适用的国家。通常我们的键盘是美式键盘,代码为33,即0x21。
bNumDescriptors 大小为1字节,是下级描述符的数量。该值至少为1,即要有一个报告描述符。下级描述符可以是报告描述符或者物理描述符。
bDescriptorType 大小为1字节,是下级描述符的类型。报告描述符的编号为0x22,物理描述符编号为0x23。
bDescriptorLength 大小为2字节,是下级描述符的长度。当有多个下级描述符时,bDescriptorType和bDescriptorLength交替重复下去。
3.10 配置描述符集合的实现以及返回
配置描述符
[0]-bLength字段,配置描述符的长度9字节。
[1]-bDescriptorType字段,配置描述符编号为0x02。
[2-3]-wTotalLength字段,配置描述符集合的总长度,包括配置描述符本身、接口描述符、类描述符、端点描述符等。(9+9+9+7)配置描述符总长度。
- bNumInterfaces字段,该配置包含的接口数,只有一个接口0x01。
- bConfiguration字段,该配置的值为1。
- iConfiguration字段,该配置字符串索引,没有,为0
- bmAttributes字段,该设备的属性。由于我们的板子是总线供电的,并且不想实现远程唤醒功能,所以该字段的值为0x80。
- bMaxPower字段,该设备需要的最大电流量。由于我们的板子需要的电流不到100mA,所以这里设置为100mA;由于每单位电流为2mA,所以这里设置为50(0x32)
接口描述符
- bLength字段,配置描述符的长度9字节。
- bDescriptorType字段,接口描述符的编号为0x04。
- bInterfaceNumber 字段,该接口的编号,第一个接口编号为0。
- bAlternateSetting 字段,该接口的备用编号为0。
- bNumEndpoints 字段,非0端点的数目。由于USB鼠标只需要一个中断输入端点,因此该值为1。
- bInterfaceClass 字段,该接口所使用的类。USB鼠标是HID类,HID类的编码为0x03。
- bInterfaceSubClass 字段,该接口所使用的子类。在HID1.1协议中,只规定了一种子类:支持BIOS引导启动的子类。USB键盘、鼠标属于该子类,子类代码为0x01。
- bInterfaceProtocol 字段,如果子类为支持引导启动的子类,则协议可选择鼠标和键盘,键盘代码为0x01,鼠标代码为0x02。
- iConfiguration 字段,该接口的字符串索引值。没有为0。
HID描述符
- Length 字段,HID描述符的长度,本HID描述符下只有一个下级描述符,所以长度为9字节。
- bDescriptorType 字段,HID描述符的编号为0x21。
- bcdHID 字段,本协议使用HID1.1协议。注意低字节在先,0x10
- 0x01。
- bCountyCode 字段,设备适用的国家代码,这里选择美国,代码为0x21。
- bNumDescriptors 字段,下级描述符的数目,这里只有一个报告描述符,0x01。
- bDescriptorType 字段,下级描述符的类型为报告描述符,编号为0x22。
- bDescriptorLength 字段,下级描述符的长度。Sizeof(ReportDescriptor)&0xFF。
- Sizeof(ReportDescriptor)>>8&0xff。
端点描述符
- bLength 字段,端点描述符长度为7字节。
- bDescriptorType 字段,端点描述符编号为0x05。
- bEndpointAddress 字段,端点的地址。我们使用D12的输入端点1,D7位表示数据方向,输入端点D7为1,所以输入端点1的地址为0x81。
- bmAttributes 字段,D1~D0为端点传输类型选择。该端点为中断端点。中断端点的编号为3,其他位保留位0,0x03。
- wMaxPacketSize 字段,该端点的最大包长。端点1的最大包长为16字节,注意低字节在先。0x10,
- 0x00
- bInterval 字段,端点查询的时间,我们设置为10个帧时间,即10ms,0x0A。
USB报告描述符。
ReportDescriptor[] = {
0x00,
},
3.11 字符串及语言ID请求的实现
在USB协议中,字符串描述符是可选的。当某个描述符中的字符串索引值为非0时,就表示它具有那个字符串描述符,注意索引值不能重复。在设备描述符中,申请了3个非0的索引值,分别是厂商字符串、
产品字符串以及产品序列号;其索引值分别为1,2,3。USB主机使用获取字符串描述符和索引值来获取对应的字符串。当索引值为0时,表示获取语言ID。语言ID是一个描述该设备支持的语言种类的数组,每个ID号占2字节。
语言ID描述符的结构
偏移量/字节 | 域 | 大小/字节 | 说明 |
0 | bLength | 1 | 该描述符的长度 |
1 | bDescriptorType | 1 | 描述符类型(字符串为0x03) |
2 | wLANGID[0] | 2 | 语言ID号0 |
..... | ..... | ..... | ..... |
2*n+2 | wLANGID[n] | 2 | 语言ID号n |
字符串描述符的结构
偏移量/字节 | 域 | 大小/字节 | 说明 |
0 | bLength | 1 | 该描述符的长度 |
1 | bDescriptorType | 1 | 描述符类型(字符串为0x03) |
2 | bString | N | UNICODE编码的字符串 |
语言ID,这里只使用美式英语一种,即0x0409。
字符串描述符中的bString字段是使用UNICODE编码的字符串。UNICODE用2字节来表示一个字符,如果是英文字符,则直接在ASCII码前补1字节的0扩充为2字节的UNICODE码。
语言ID LanguageID
- 该描述符的长度,0x04
- 描述符类型,字符串,0x03
- 语言ID号0,美式英语,0x0409;0x09
- 0x04
厂商字符串 ManufacturerStringDescriptor[]
- 描述符长度
- 描述符类型,字符串0x03
- UNICODE编码的字符串。。。。
........
产品字符串 ProductStringDescriptor[]
- 描述符长度
- 描述符类型,字符串0x03
- UNICODE编码的字符串.....
- .......
产品序列号字符串
- 描述符长度
- 描述符类型,字符串0x03
- UNICODE编码的字符串......
- .......
3.12 设置配置请求的实现
设置配置请求是一个输出请求,只要根据所请求的配置值,使能相应的端点即可。由于我们鼠标只有一个配置,所以配置值都可以忽略,直接使能端点,然后返回一个0长度的状态数据包即可。注意,只有收到非0的配置值之后才可以使能非0端点,否则则要禁用非0端点。
使能D12的非0端点,要用到D12的SetEndpointEable的命令,命令代码为0xD8,后面跟1字节的数据写入。该字节的最低位D0为1时,使能端点。其他位保留,为0。该命令只有在设置地址命令时使能了设备(D7为1)后才能起作用。
3.13 报告描述符的结构及实现
USB HID设备是通过报告(report)来传输数据的,报告有输入报告和输出报告。
输入报告是USB设备发送给主机的,例如USB鼠标将数据移动和鼠标点击等信息返回给计算机,键盘将按键数据返回给计算机等。
输出报告是主机发送给USB设备的,例如:键盘上的数字键盘锁定灯和大写字母锁定灯的控制等。
报告里面包含的是所要传送的数据,数量为整数字节,被划分成一个个域。通常,输入报告是通过中断输入端点返回的,而输出报告有点区别,当没有中断输出端点时,可以通过控制输出端点0发送,当有中断输出端点时,通过中断输出端点发出。当然,不管设备是否具有中断输出端点(中断输入端点是必须要的),主机都可以通过获取报告和设置报告的请求从端点0来获取或者发送报告。
而报告描述符(report descriptor),是用来描述一个报告的结构以及该报告里面的数据是用来干什么用的。通过报告描述符,USB主机可以分析出报告里面的数据所表示的意义。报告描述符和普通描述符一样,都是通过控制输入端点0来返回,主机使用获取报告描述符请求来获取报告描述符,注意这个请求是发送到接口的,而不是到设备。一个报告描述符可以描述多个报告,不同的报告通过报告ID来识别。报告ID放在报告的最前面,即第一个字节。当报告描述符中没有规定报告ID时,报告中就没有ID字段,开始就是数据。
报告描述符和前面的描述符结构不一样,它并没有描述符长度和描述符类型等信息,而是由一个个条目(item)组成的。通常,在写报告描述符时,一个条目占据一行,这样看起来就清晰一些。
HID协议中规定了两种条目:短条目和长条目。
短条目是由1字节的前缀后面跟上可选的数据字节组成。可选的数据字节可以为0字节、1字节、2字节或者4字节。实际所使用的条目,大部分是只有1字节可选数据的,少数会使用0字节或2字节数据。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
bTag | bType | bSize |
条目前缀的结构
bSize-表示后面所跟数据的字节数,0为0字节、1为1字节、2为2字节、3为4字节。
bType-表示条目的类型,0为主(main)条目,1为全局(global)条目,2为局部(local)条目,3是保留值。
bTag-表示该条目的功能,具体参考HID协议以及HID用途表。
主条目总共有5个,分别为Input(输入)、Output(输出)、Feature(特性)、Collection(集合)和End Collection(关集合)。主条目用来定义或者分组报告的数据域,例如,可以使用输入条目将输入报告划分为不同的数据域,以及指定该域的属性。对于Input、Output、Feature三个主条目,后面跟的第一个字节数据每个位的数据表示一种属性,例如:位0表示该数据域是变量还是常量,位1表示是数组还是单一变量,位2表示是相对值还是绝对值等。
全局条目主要用来选择用途页,定义数据域的长度、数量、报告ID等。全局条目在出现后对接下来的所有主条目都有效,除非遇到另外一个全局条目来改变它。常用的全局条目有:Usage Page(用途页)、Logical Minimum(逻辑最小值)、Logical Maximum(逻辑最大值)、Physical Minimum(物理最小值)、Physical Maximum(物理最大值)、Report Size(数据域大小)、Report Count(数据域数量)和Report ID(报告ID)。其中,Report Size用来描述某个数据域有多少个位;Report Count用来描述这样的数据域有多少个;Logical Minimum和Logical Maximum用来描述数据域的取值范围。
局部条目用来定义控制的特性,例如数据域的用途、用途最小值、用途最大值等。局部条目只在局部有效,遇到一个主条目后,它的效用就结束了。常用的局部条目有:Usage(用途)、Usage Minimum(用途最小值)和Usage Maximum(用途最大值)。
USB报告描述符的定义:
ReportDescriptor[]={
//没行开始的第一个字节为该条目的前缀,前缀的格式为:
//D7~D4:bTag;D3~D2:bType;D1~D0:bSize。以下分别对每个条目注释。
//这是一个全局(bType为1)条目,选择用途页为普通桌面Generic Desktop Page(0x01)
0x05,0x01, //USAGE_PAGE(Generic Desktop)
//这是一个局部(bType为2)条目,说明接下来的应用集合用途用于鼠标
0x09,0x02, //USAGE(Mouse)
//这是一个主(bType为0)条目,开集合,后面跟的数据0x01表示该集合是一个应用集合。
//它的性质在前面由用途页和用途定义为普通桌面用的鼠标
0xA1,0x01, //COLLECTION(Application)
//这是一个局部条目。说明用途为指针合集。
0x09,0x01, //USAGE(Pointer)
//这是一个主条目,开集合,后面跟的数据0x00表示该集合是一个物理集合
//用途由前面的局部条目定义为指针集合
0xA1,0x00, //COLLECTION(Physical)
//这是一个全局条目,选择用途页为按键(Button Page(0x09))
0x05,0x09, //USAGE_PAGE(Button)
//这是一个局部条目,说明用途的最小值为1。实际上是鼠标左键
0x19,0x01, //USAGE_MINIMUM(Button1)
//这是一个局部条目,说明用途的最大值为3。实际上是鼠标中键
0x29,0x03, //USAGE_MAXIMUM(Button3)
//这是一个全局条目,说明返回的数据的逻辑值(就是我们返回的数据域的值)最小为0
//因为这里用”位”来表示一个数据域,因此最小为0,最大为1。
0x15,0x00, //LOGOCAL_MINIMUM(0)
//这是一个全局条目,说明逻辑值最大为1
0x25,0x01 //LOGICAL_MAXIMUM(1)
//这是一个全局条目,说明数据域的数量为三个
0x95,0x03, //REPORT_COUNT(3)
//这是一个全局条目,说明这个数据域的长度为1个位
0x75,0x01, //REPORT_SIZE(1)
//这是一个主条目,说明有2个长度为1位的数据域(数量和长度由前面的量全局条目所定义)
//用来作为输入,属性为:Data,Var,Abs。Data表示这些数据可以变动;Var表示这些数据域是独立的变量。
//即每个域表示一个意思;Abs表示绝对值。
//这个定义的结果就是,第一个数据域位0表示按键1(左键)是否按下,第二个数据域位1表示按键2(右键)是否按下,第三个数据域位2表示按键3(中键)是否按下。
0x81,0x02, //INPUT(Data,Var,Abs)
//这是一个全局条目,说明数据域数量为1个
0x95,0x01, //REPORT_COUNT(1)
//这是一个全局条目,说明每个数据域的长度为5位
0x75,0x05, //REPORT_SIZE(5)
//这是一个主条目,输入用。由前面两个全局条目可知,长度为5位,数量为1个
//它的属性为常量(即返回的数据一直是0)
//这个只是为了凑齐1字节(前面用了3个位)而填充的一些数据而已,所以它是没有实际用途的
0x81,0x03, //INPUT(Cnst,Var,Abs)
//这是一个全局条目,选择用途页为普通桌面Generic Desktop Page(0x01)
0x05,0x01 //USAGE_PAGE(Generic Desktop)
//这是一个局部条目,说明用途为X轴
0x09,0x30, //USAGE(X)
//这是一个局部条目,说明用途为Y轴
0x09,0x31, //USAGE(Y)
//这是一个局部条目,说明用途为滚轮
0x09,0x38, //USAGE(Wheel)
//下面两个为全局条目,说明返回的逻辑最小和最大值。因为鼠标指针移动时,通常是用相对值来表示的,相对的意思是,当指针移动时,只发送移动量。
//往右移动时,X值为正;往下移动时,Y值为正。对于滚轮,当滚轮网上滚时,值为正
0x15,0x81, //LOGICAL_MINIMUM(-127)
0x25,0x7f, //LOGICAL_MAXIMUM(127)
//这是一个全局条目,说明数据域的长度为8位
0x75,0x08, //REPORT_SIZE(8)
//这是一个全局条目,说明数据域的个数为3个
0x95,0x03, //REPORT_COUNT(3)
//这是一个主条目,它说明这三个8位的数据域是输入用的,属性为Data,Var,Rel
//Data说明数据是可以变的,Var说明这些数据域是独立的,即第一个8位表示X轴,第二个8位表示Y轴,第三个8位表示滚轮。Rel表示这些值是相对值
0x81,0x06, //INPUT(Data,Var,Rel)
//下面这两个主条目用来关闭前面的集合用。
//因为开了两个集合,所以要关两次。bSize为0,所以后面没数据
0xc0, //END_COLLECTION
0xc0, //END_COLLECTION
}
通过上面的报告描述符的定义可知,返回的输入报告具有4字节。第一字节的低3位用来表示按键是否按下,高5位为常数0,无用;第二个字节表示X轴的改变量;第三个字节表示Y轴的改变量;第4个字节表示滚轮的改变量。在中断输入端点1中应该要按照上面的格式返回实际的鼠标数据。