USB协议

学习框架:
USB CDC协议,USB SCSI 协议

USB协议的第9章讲到USB可见设备状态,分为连接(Attached),上电(Powered),默认(Default),地址(Address),配置(Configured)和挂起(Suspended)6个状态。所谓可见,即USB系统和主机可见的状态,其他状态属于USB设备内部而不可见。
其中有关电源的,大致可分下面三类:

  1. 连接状态(Attached):设备连接,但未提供电源。
  2. 上电状态(Powered):设备被复位(Reset),或者说处于地址、配置状态。(参见USB枚举过程,USB Specification, page 241)
  3. 挂起状态(Suspended):设备检测到总线连续3 ms未活动,就会进入挂起状态,设备不可用,但仍然保持原有的USB地址和配置。在挂起模式下,若设备是总线供电,则从总线取点不可过2.5mA(高功耗设备) / 0.5mA(低功耗设备)
    USB主机为了防止设备进入挂起模式,必须周期性的发送SOF 或者 KEEP ALIVE信号
    高速信道上:主机按照125us(+/- 65ns)的周期发送SOF
    全速信道上:1ms(+/- 500ns)的周期发送SOF
    低速信道上:1ms的周期发送KEEP ALIVE (EOP信号)

如何从挂起模式退出?
1.主机在总线上发送Resume信号
2.设备自己发送Resume信号(远程唤醒)

USB信号电气特性:

我们定义差分数据线上可能出现的四个状态:
Data J state:D+=1,D-=0;(VOH > 2.8V, VOL <0.3V )
Data K state:D+=0,D-=1;
SE0:D+=D-=0;
SE1:D+=D-=1;
这里写图片描述
各种状态解释
在这里插入图片描述

  1. 差分信号1:D+ = 1 ,D- = 0;差分信号0:D+ = 0,D- = 1;
  2. Reset: D+ & D- = 0(SE0) 大于等于10ms,主机在要和设备通信之前,会发送Reset信号来把设备设置到默认的未配置状态
  3. Idle: J状态,数据发送前后总线的状态
  4. Suspend状态:3ms以上的J状态
  5. SYNC:3个K 和 J状态的切换,后跟随2bit时间的K状态
    在这里插入图片描述
  6. SOP:从Idle切换到K状态
  7. EOP:持续2bit时间的SE0信号,后跟随1bit时间的J状态(KEEP ALIVE信号)
  8. Resume信号:20ms的K状态 + 低速通讯的EOP
    主机唤醒:挂起设备后,通过翻转数据线的极性,并保持20ms,以EOP信号结尾
    设备唤醒:带远程唤醒功能的设备,可以自己发起唤醒信号,前提是设备已进入Idle至少5ms,然后发出唤醒K信号,维持1~15ms,由主机在1ms内接管来继续驱动唤醒信号
    在这里插入图片描述
    USB总线工作原理
    一、问:当一个USB设备插入PC机,PC机怎么知道有设备插入?
    答:如图1-1和图1-2所示,USB接口只有4条线: VCC(5V),GND,D-,D+。 PC机的USB插孔的D-和D+数据线均连接15K欧姆的下拉电阻,见图1-3 USB HOST上电检测。而USB设备端的D-或D+数据线连接1.5K欧姆的上拉电阻。当设备插入PC机的时候,会将PC机的D-或D+端的电压拉高,当PC机在D-或D+端检测到高电平时,就知道有设备插入了。如果是PC机D-端被拉高,接入的则是USB低速设备;如果是PC机D+端被拉高,接入的则是USB全速或高速设备,具体是全速设备还是高速设备,会由PC机和USB设备发包握手确定。

这里写图片描述

图1-1 USB低速设备硬件接线图

这里写图片描述
图1-2 USB全速(高速)设备硬件接线图

这里写图片描述
图1-3 USB HOST上电检测

这里写图片描述
图1-4 USB如何检测设备插入

这里写图片描述
图1-5 USB如何检测设备弹出

二、问:当USB设备插入后,PC机 会提醒我们“某某设备接入”,PC机怎么知道我们插入的设备的信息的呢?

答:如图2-1所示,当PC机检测到有USB设备插入后,会主动向设备发送命令包,要求设备告诉PC机,设备信息。这时设备必须向PC机回复自己的信息(以描述符形式)。明确一点:USB设备不会主动给PC机发数据,只能被动的等待PC机来拿。

这里写图片描述
图2-1 USB设备获取设备信息过程

三、问:PC机上接有非常多的USB设备,怎么分辨它们?
答:每一个USB设备接入PC时,USB总线驱动程序都会给它分配一个编号,接在USB总线上的每一个USB设备都有自己的编号(地址)。PC机想访问某个USB设备时,发出的命令都含有对应的编号(地址)。

四、问:USB设备刚接入PC时,还没有编号;那么PC怎么把"分配的编号"告诉它?
答: 新接入的USB设备的默认编号是0,在未分配新编号前,PC使用0编号和它通信。

五、其他一些概念
1、USB是主从结构所有的USB传输,都是从USB主机这方发起;USB设备没有"主动"通知USB主机的能力。例子:USB鼠标滑动一下立刻产生数据,但是它没有能力通知PC机来读数据,只能被动地等得PC机来读。
2、USB传输速度有:低速1.5Mbps(USB1.0协议)、全速12Mbps(USB1.1协议)、高速480Mbps(USB2.0协议)和超高速5Gbps(USB3.0协议)。
3、 数据编解码和位填充
USB采用NRZI(非归零编码)对发送的数据包进行编码
输入数据0, 编码成“电平翻转”
输入数据1, 编码成“电平不变”
编码出来的序列,高电平:J状态;低电平:K状态
这里写图片描述
位填充是为了保证发送的数据序列中有足够多的电平变化
填充的对象是(输入数据),即先填充再编码
数据流中每6个连续的“1”,就要插入1个“0”,从而保证编码
数据出现电平变化
接收方赋值解码NRZI码流,然后识别出填充位,并丢弃它们

主机送数据:

  1. 发第1个packet给从机,声明数据传送方向,数据传输地址,数据传输类型。
  2. 发送第2至N个packet给从机
  3. 从机返回一个packet(一个ACK包)

从机发送数据:

  1. 主机发第1个packet给从机,声明数据传送方向,数据传输地址,数据传输类型。
  2. 从机收到主机送来的第一个packet后,再发第2个至第n个packet载有实际数据
  3. 最后,主机返回一个packet(一个ACK包),报告数据传输的结果,比如接受出错或成功等信息,这样从机就可以借此了解到这次传输情况,从而有可能来作出相应措施如决定是否重发。

总之,无论usb接受数据还是发送数据,都是由usb host首先发起。即传输的第一个packet总是由usb host发出的。这个packet将声明本次即将进行的数据传输方向,数据传输地址和数据传输类型。

这里写图片描述
一个数据包的组成
SYNC(同步域):
用来告诉USB接口引擎数据要开始传输了,请做好准备。还可以用来同步主机和设备端的数据时钟,因为SYNC为 00000001(全速和低速,高速为31个0 , 最后面加个1),所以在总线上被编码成电平翻转(每个数据位都变化),可以让串行接口引擎恢复出采样时钟信号
EOP(结束符):
全速和低速的EOP是一个大约为2个数据位宽度的SE0信号(D+ & D-是低电平)
在这里插入图片描述

这里写图片描述

包(Packet):分为四类,分别是命令(Token令牌类),帧首(Start of Frame),数据(Data),握手(Handshake)
1)包内容介绍
a. PID
这里写图片描述
这里只用(PID04),PID47是PID0~4的取反,用来校验PID,低两位决定是什么类的包
这里写图片描述
上图有 * 号的,为USB1.1不支持的
b. 地址
这里写图片描述

c. 帧号
这里写图片描述
为了防止设备挂起,设备每1ms发送一次SOF信号,每个SOF信号中的帧号逐帧递加
d. 数据
这里写图片描述

e. CRC
这里写图片描述

2)各类型包的介绍(由PID来决定)
a. Token Packet (命令)令牌包
这里写图片描述
I. IN包
用来通知设备送来一个数据包
II. OUT包
用来通知设备将要送出一个数据包
III. SETUP包
只用在控制传输(发送SETUP包即控制传输)中,和输出(OUT)令牌包作用一样,也是通知设备将要输出一个数据包,
两者区别在于:
SETUP令牌包后只使用(数据类)DATA0数据包,且只能发送到设备的控制端点,并且设备必须要接收,而OUT令牌包没有这些限制
例子:
这里写图片描述
以上三种包具有相同的数据结构,CRC5只校验PID后的地址域和端点域,不包括PID,注传输时低位在前,例A0,A1,A2…
IV. 帧首(Start of Frame)Packet(属于Token Packet)
这里写图片描述
帧起始包:在每帧(或微帧)开始时发送,以广播的形式发送,所有USB全速设备和高速设备都可以接收到SOF包。(高速设备每125us产生一个帧),USB主机会对当前帧号进行计数,在每帧开始的时候(或者高速设备下每微帧,每毫秒8个微帧,这些帧的编号的都是一样的),通过SOF发送帧号。令牌包中,唯独SOF包后不跟随数据传输。注:CRC校验校验的是PID后的数据(不包括PID,PID有4位用来校验)
例子:
这里写图片描述
b. 数据包(Data) Packet
这里写图片描述
在这里插入图片描述
DATA2 和 DATAM在USB2.0才能使用,主要用在 高速分裂事务 和 高速高带宽同步传输中,
之所以要使用DATA0 和 DATA1,是因为主机和设备都会维护一个数据包类型切换机制,当成功发送和接接收的时候,数据包类型会切换,比如 A 和 B 通讯,当B已经成功接收到A的数据,并返回ACK确认信号给A时,但ACK信号在传输时被破坏,所以A没有收到 由B返回的ACK信号,无法确认发送成功没,A只好保持自己的数据类型不变。
在这里插入图片描述
在这里插入图片描述
正确的数据传输过程: 发送方发DATA0包,若接收方正确收到DATA0包后,然后接收方回复ACK,当发送方收到回复的ACK信号后,将数据包切为DATA1类型
当数据被破坏或者没有正确接收:发送方发送DATA0包,接收方因为送来的数据校验错误,所以回复NAK,并且不改变接受的数据要求类型(DATA0);发送方没有收到ACK的话,还是重传DATA0包
当ACK信号被破坏:当接收方正确接收数据后(校验没问题),切换PID类型,且将ACK回复给发送方,但此时ACK信号遭到破坏,导致发送方没接收到ACK,所以发送方的数据不改变DATA PID;所以发送还是使用原来的DATA PID发送数据,虽然接收方收到的数据DATA PID对不上,但是仍回复ACK,并忽略这个数据,若此时发送方还是没收到ACK的话,会一直发送原数据,直到收到ACK;当发送方终于接收到ACK后,切换到正确的DATA PID,接下来的通讯就恢复正常了。

例子:
这里写图片描述

c. 握手包(Handshake) Packet
这里写图片描述
这里写图片描述
握手包只有同步域,PID,和EOP
ACK: 表示正确接收数据,并且还有足够空间来容纳数据。ACK信号主机 和 设备都可以使用ACK来确认,但其余三个只有设备可以使用。
NAK: 表示没有数据要返回,或者数据正确接收但没空间容纳。
当主机收到NAK时,知道设备还没准备好,会在以后再重试传输
STALL: 表示设备无法执行此请求,或者端点已被挂起,是一种表示错误的状态
NYET: 只在USB2.0的高速设备输出事务中使用,表示设备本次数据接收成功,但没有空间接收下一次数据。主机在下一次输出数据前,会先使用PING令牌包试探设备有没空间,避免带宽浪费

**注:**返回NAK不代表出错,什么都不返回才代表着出错(如CRC校验失败,PID校验错误),这时等待的一方会收不到握手包而等待超时。

d. 特殊包
PRE、SPLIT、PING为令牌包(USB2.0新增的),ERR为握手包
起始为SOF,EOF
PRE: 通知集线器打开低速端口的前导包(只用在全速模式),平时使用时,为防止全速信号使低速设备误动作,集线器是不会传递全速信号给低速设备的。只有收到PRE令牌包时,才打开低速端口。其结构和握手包结构一样:同步域、PID、EOP。当需要用到低速事务时,主机先以全速模式发送PRE令牌包(全速设备会忽略此包),再集线器收到此包并打开低速设备端口后,主机就会用低速模式给低速设备发送数据。
PING: 和OUT令牌包有同样结构,但PING后并不发数据,而是等待设备返回ACK 或者 NACK,以此判断设备能不能传送数据。**注:**在USB1.1中,是没有PING包的,在USB2.O的高速环境才会使用PING(在批量传输和控制传输的输出事务中使用)。若只使用OUT的话,当设备没有空间接收时,都会在OUT令牌包后接着发送一个数据包,然后设备返回NAK,这样会导致带宽的浪费。若使用PING试探,有空间再发数据的话,能提高效率
SPLIT: 是高速分裂事务令牌包,通知集线器将高速数据包转化为全速或者低速数据包发给下面的端口。
ERR: 在高速分裂事务中表示错误使用,具体可阅读USB 2.0协议
这里写图片描述

USB传输:
一次传输由几个事务组成,一个事务由几个包组成,如图 2-1
这里写图片描述
图2-1 传输组成

事务类型: 三种,
DATA IN事务:主机用来读取设备的数据
DATA OUT事务:主机用来向设备发送数据
SETUP事务:向设备发送控制命令
事务通常由两到三个包组成:令牌包,数据包,握手包
令牌包:用来启动一个事务,总是由主机发送
数据包:传输事务的数据负载,可以从主到从, 从到主,方向由主机发送的令牌包决定
握手包:(可选)握手包的发送者通常为数据接受者,接收正确用ACK,数据未准备好用NACK
USB协议一共四种传输类型:批量,等时,中断,控制传输,控制传输包括三个过程,建立Setup 和 状态Status过程分别是一个事务,但Data数据过程可能包含多个事务;除了控制传输之外,其他都是一个事务就完成。
在这里插入图片描述
a. 控制式传输
非周期性,突发
用于命令和状态的传输
分为控制写传输,控制读传输,无数据控制传输
在设备枚举过程中,用到控制式传输
分三个过程:建立,数据(可选有或者无),状态过程
1. 建立过程:
建立过程由建立事务组成。
是一个输出数据的过程,和批量传输的输出事务相比,
首先,令牌包不同,建立过程用的是SETUP令牌包
其次,数据包类型只能用DATA0包
最后,设备回复的握手包只能用ACK(或者数据出错不应答),而非NAK或者STALL
在这里插入图片描述
2. 数据过程
数据过程由数据事务组成。
数据过程是可选的,控制传输可以没有数据过程。若有的话,一个数据过程可以包含一笔或者多笔的数据传输事务。数据事务和批量传输的批量事务是一样的
注: 数据过程中,所有数据事务一定要是同一个方向的,在控制读传输中,数据过程的所有数据事务都必须要是输入的;在控制写传输中也是这样。一旦数据方向变化,就会被认为进入了状态过程。
另外,数据过程的第一个数据包必须是 DATA1包,之后每正确传输一个数据包,就在DATA0 和 DATA1 间交替。

3. 状态过程
此过程也是一笔批量事务,但其传输方向和前面数据阶段相反(所以数据过程的方向要是同一个,一旦数据方向变化,就被认为进入状态过程),即控制写传输 在 此过程中使用批量输入事务;控制读传输使用一个批量输出事务。状态过程只能用DATA1包。
在这里插入图片描述

  1. Setup Stage
  2. Data Stage
  3. Status Stage
    这里写图片描述

b. 块传输(批量传输)
非周期性,突发
大容量数据的通信,数据可以占用任意带宽,并容忍延迟
一次批量事务有令牌包阶段,数据包阶段,握手包阶段,每个阶段都是独立的包
批量传输分成 批量读(使用批量输入事务) 和 批量写(使用批量输出事务)(读写方向以主机为参考)
适用于大数据量传输,但是对实时性,延迟性和带宽没严格要求的场合,大容量传输可以占用任意可用的数据带宽
方向:大容量传输是单向的
批量输出:

  1. 先发一个OUT令牌包,包中包含设备地址、端点号
  2. 然后发送一个数据包,是DATA0或者DATA1要看数据切换位,地址和端点匹配的设备收下这个数据包,主机切换到接收模式,等待接收握手包(ACK信号等)
  3. 当令牌包、数据包都无误且有空间存储数据,设备就会使用ACK或者NYET(NYET在高速模式使用,表这次成功,但没能力接收下次)
    若设备没空间接收数据,就会返回NAK握手包,让主机等下再重试
    若设备检测到数据没问题,但端点是挂起状态,就返回STALL
    若检测到有错误(CRC校验错误,位填充错误),设备就不响应,让主机等待超时

批量输入:

  1. 先发一个IN令牌包,包中包含设备地址、端点号,然后主机切到接收状态
  2. 若此设备检测到错误,那么不做任何回应,主机直接等待超时;
    若设备的地址和端点匹配,且没检测到错误,则回复数据,把一个数据包放到总线上;
    若设备没有数据要返回,直接用NAK握手包回复主机,主机稍后再试;
    若端点处于挂起状态,则返回STALL握手包;
  3. 若主机接收到数据并且解码正确,则用ACK握手包回复设备;
    若不正确,则不响应,设备检测到超时(USB协议规定不能用NAK握手包回复设备来拒接,主机没空间就不要发起请求)

USB2.0 新增的PING事务:
在USB2.0高速设备中增加了PING包,此包不发出数据,而只是等待设备的握手包,所以PING事务只有令牌包和握手包(无数据包)。

在这里插入图片描述
当数据出错时,无论主机还是设备都会不回应,进入空闲状态。
在这里插入图片描述
c. 同步传输(等时传输)
周期性
持续性的传输,用于传输与时效相关的信息,并在数据中保存时间戳的信息
等时传输使用等时事务(isochronous transaction)来传输数据。
用于数据量大,实时性高的场合,比如音视频设备,此类设备对数据延迟敏感,但对数据100%正确要求不高,所以数据错误时(由CRC校验确认,出错的数据如何处理,由软件确认),不进行重新传输,所以同步传输就没有应答包
数据包用的都是DATA0
在这里插入图片描述

d. 中断式传输
周期性,低频率,
允许有限延迟的通信
用于频率不高,对周期有一定要求的数据传输,具有保证的带宽,能在下个周期对先前的错误的传输进行重传
对于全速端点:间隔在1ms到255ms之间
对于低速端点:10ms到255ms之间
对于高速端点:2的0~15次方 x 125us
总是单向的
是一种保证查询频率的传输,中断的端点要在端点描述符中报告它的查询间隔,主机会保证在小于这个时间间隔的范围内安排一次传输(注:USB传输的中断与单片机上的中断是不一样的,USB的中断是由主机去查询),适用于数据量不大,但时间要求严格的设备,比如HID设备:鼠标,键盘等。

还可以用来不断检测某个状态,当条件满足后用批量传输传大的数据。
除了没有PING和NYET两种包,中断传输和批量传输的结构基本一样
在这里插入图片描述

在这里插入图片描述

USB设备上电枚举过程

[ ] 1. 插入USB设备

  • 2. 主机的集线器识别到有USB连接,然后通过中断的方式提醒主机,有USB设备接入

  • 3. 主机像集线器发送GetPortStatus请求,读取下行端口的状态及状态变化

  • 4. 主机等待100ms左右,消除拔插抖动

  • 5. 主机向集线器发出SetPortStatus请求,复位这个USB设备,在设备复位的过程中,集线器读取USB设备传输速度的类型

  • 6. 10ms复位等待,复位成功后设备会进入缺省状态,主机与设备通过端点0进行通讯(主要是枚举过程的通讯)

  • 7. 主机通过端点0发出Get_Descriptor(Device)请求,这个请求只读取设备描述符的前8个字节(用来确定端点0能传输的最大数据包长度,其实也可以通过直接告诉主机长度值来取消这一步的执行)
    a. 主机发送获取描述符GET_DESCRIPTOR (0x01获取设备描述符)请求,传来的数据一共18字节,请求如下图:
    在这里插入图片描述

  • 8. 设备复位

  • 8. 主机通过端点0发出Set_Address请求,为设备分配一个唯一的设备地址。之后设备将通过这个地址与主机进行数据的传递,除非USB拔除或者主机断电这个地址才会被擦除。
    注:在这个主机分配设备新地址之前,USB HOUND是捕捉不到信息的

  • 9. 主机向新的设备地址发出Get_Descriptor(Device)请求,读取设备描述符的全部信息。
    a. 主机发送获取描述符GET_DESCRIPTOR (0x01获取设备描述符)请求,传来的数据一共18字节,如下图:
    在这里插入图片描述

  • 10. 主机循环发送Get_Descriptor(0x02 Configuration)请求,读取USB设备的全部配置信息。一般主机第一次发送这个Get_Descriptor(Configuration)请求只读取前9个字节,主要是为了获取该配置描述符的全部长度。
    a. 主机发送获取描述符 GET_CONFIGURATION (Get_Descriptor 0x02获取配置描述符)请求,传来的数据一共9字节,如下图:
    在这里插入图片描述
    回复示例如下:
    在这里插入图片描述
    b. 主机发送获取描述符 GET_CONFIGURATION (Get_Descriptor 0x02获取配置描述符)请求,传来的数据一共32字节(一次性返回),包括设备配置描述符,所有的接口描述符,(类描述符,比如HID),以及所有的端点描述符
    在这里插入图片描述
    回复示例如下:
    在这里插入图片描述
    c. 获取字符串描述符(在前面索引值不为0的情况下),语言ID是对应的索引值
    (1) 先获取语言类型
    I. 先获取字符串语言类型的数据长度
    在这里插入图片描述
    II. 再获取语言类型
    在这里插入图片描述

    (2) 然后通过语言ID,获取对应字符串
    I. 先获取输入字符串数据的长度,下图0x22为34字节
    在这里插入图片描述
    II. 再获取对应的字符串
    在这里插入图片描述

  • 11. 主机根据usb设备的信息选择一个合适的USB设备驱动

  • 12. 加载驱动后,主机发送Set_Configuration(x)请求,为USB设备选择一个合适的配置,并要求设备按这个配置进行设置自己。

    a. 设为对应的配置(Set Configuration请求)
    在这里插入图片描述

    b. 设为对应的接口(Set Interface请求)
    在这里插入图片描述
    (以上步骤完成了USB主机与设备直接连接的过程,接下来则可以正常进行数据的传输)

  • 13. 主机与设备通过七种(IN事务、OUT事务、PING事务、SETUP事务、SOF事务、SPLIT事务、PRE事务)不同类型的传输事务来与不同传输速度的USB设备进行数据的交换。其中每一次完整的数据传输都可以看做是一次事务。
    以后便是类请求,比如USB MASS STORAGE类

  • 14. 如果设备与主机太长时间不进行数据传输,主机发送SetPortFeature挂起命令让设备将进入挂起状态。

  • 15. USB设备拔出。首先集线器会禁止USB使用的端口,然后通过中断告诉主机端口状态有变化,然后主机发送GetPortStatus请求来了解情况,如果是拔出则进行断开操作,并释放USB设备驱动以及其所占用的资源。

USB枚举整个过程的举例:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

USB标准设备请求

1)获取描述符:
先获取设备描述符,然后依次是 配置描述符, 接口描述符,类特殊描述符(若有),端点描述符。对于字符串描述符是单独获取的
2)设备描述符:
一个USB设备只有一个,决定设备有多少种配置
记录的信息有: 设备使用的USB协议版本号、设备类型、端点0的最大包大小、厂商ID(VID)和产品ID(PID)、设备版本号、厂商字符串索引、产品字符串索引、设备序列号索引、可能的配置参数等
3)配置描述符:
每种配置描述符中又定义了该配置有多少个接口,配置和接口以及端点的关系就像,把学校的课室作为平时用(非期末考试时),这叫做一种配置,然后把课室分成 老师办公室 和 学生课室 两类,这里的类便是接口,所以此时有两个接口(若只分成考试课室,就只有一种接口),老师办公室 和 学生课室 对应的一个个课室,就相当于一个个端点。
配置所包含的接口数,配置的编号、供电方式、是否支持远程唤醒、电流需求量等,同一时刻只有一个配置有效,不同的功能使用不同的配置
4)接口描述符:
每个接口都有一个接口描述符,定义该接口有多少个端点,
记录了接口的编号、接口的端点数、接口使用的类、子类、协议等
5)端点描述符:
每个端点都有一个端点描述符,定义了端点的大小、类型
6)字符串描述符:
非必需,提供一些方便人阅读的信息

USB标准设备请求数据格式

主机发送八个字节的标准设备请求,向默认控制端点0获取描述符,信息包含了传输方向、长度和数据类型等,任何一个设备都要有接收8字节标准请求的能力

在这里插入图片描述
上图为标准请求的数据结构,bmRequestType和bRequest下面会介绍,但不同的请求对于其接收者,wValue 和 wIndex各个字段的意义是不同的
(USB协议中使用的是小端模式,即低字节在先!!)

I. bmRequestType
在这里插入图片描述
各个请求的接受者不同,有的只能发到设备,有的可以发到设备、接口和端点

II. bRequest
在这里插入图片描述
上图为bmRequestType的 D6-5位为00的请求(标准请求),11个标准请求(bRequest)的名字和请求代码。
III. wValue
IV. wIndex

但不同的请求对于其接收者,wValue 和 wIndex各个字段的意义是不同的(USB协议中使用的是小端模式,即低字节在先)
主机重试几次操作后,读不到数据的话,只好放弃操作

A. GET_DESCRIPTOR (获取描述符)请求

在这里插入图片描述
上图为此请求的结构
主机通过发送获取描述符请求读取设备的各种描述符,从而得知设备类型,端点情况等。从bmRequestType的第七位看出,传输方向是从设备到主机的
(三)wValue
两个字节,
低字节(第一字节):表示的是索引号,用来选择字符串描述符或者配置描述符
高字节(第二字节):表描述符的类型编号,如下图所示
对于全速和低速模式,获取描述符的标准请求只有3种:
a. 获取设备描述符
b. 获取配置描述符
c. 获取字符串描述符
另外的 接口 和 端点 描述符是跟随 配置描述符 一并返回的(若单独返回,主机无法确认属于哪个配置)
在这里插入图片描述
(四)wIndex
两个字节,只在获取字符串描述符有用,表示字符串的语言ID号(获取除字符串描述符外的其他描述符时,wIndex的值为0)
(五)wLength
两个字节,请求设备返回数据的字节数(设备返回的实际字节数可比指定的字节数少)
设备描述符请求举例:
在这里插入图片描述
A+. 设备描述符的实现
在这里插入图片描述
在这里插入图片描述
(一)bLength
长度1字节,表示描述符的长度,设备描述符的长度是18字节,即0x12.
(二)bDescriptionType
长度1字节,描述符的类型,根据图3.5.5所示,设备描述符(DEVICE)编号为1
(三)bcdUSB
长度是2字节,使用USB协议的版本,可取2.0 或者 1.1。 注意:此处是用BCD码来表示的(用四位2进制数表示0~9),比如USB2.0 就是 0x0200,USB 1.1 是 0x0110,实际传输的时候,由于是低位在先,所以USB 2.0 拆成两个字节就是 0x00,0x20;USB 1.1 就是 0x10,0x01。
(四)bDeviceClass
长度1字节,设备的类代码,由USB协会规定,具体查阅USB相关文档,大多数设备此字段通常设置为0,当bDeviceClass为0时,下面的bDeviceSubClass也必须为0 。若此字段使用0xFF时,表厂商自定义的设备类型
(五)bDeviceSubClass
长度1字节,即设备所使用的子类代码,和上面一样同样使用0
(六)bDeviceProtocol
长度为1字节,使用0,当该字段是0xff时,表示设备使用厂商自定的协议,因为该字段要结合四和五字段使用才有意义,所以当bDeviceClass为0 时,此字段也为0
(七)bMaxPackSize
长度为1字节,为端点0的最大包长,取值可为 8、16、32、64
(八)idVender
长度为2字节,是厂商的ID号,有USB协会分配,必须使用公司自己的ID(学习没关系),主机通常是通过idVender,idProduct,iSerialNumber来安装加载驱动的,所以使用他人的ID号,可能会导致驱动安装或加载错误,致设备无法工作,同样要注意小端结构
(九)idProduct
长度为2字节,是产品的ID号,可以厂商自己编号,但不同产品要使用不同ID,否则会导致加载旧的驱动,从而工作不正常
(十)bcdDevice
长度为2字节,是设备的版本号,比如同个产品升级固件后,可以通过修改设备的版本号来区别
(十一)iManufacturer
长度1字节,是描述厂商的字符串的索引值(字符串索引值不要用相同的值!!!),当此值为0时,表示没厂商字符串。主机获取设备描述符时,会把索引值放在wValue的第一字节,以此选择不同的字符串。
(十二)iProduct
长度1字节,是描述产品的字符串的索引值,为0时表示没有产品字符串。第一次插上USB时,桌面右下角弹出的对话框,里面的字符就是从这获取的
(十三)iSerialNumber
长度1字节,是设备序列号字符串索引值,为0时表示没有序列号字符串,注:同时连接多个具有相同VID/PID和设备序列号的设备,可能导致设备无法识别
(十四)bNumConfigurations
长度1字节,表示这个设备有多少种配置,大部分USB设备只有一个配置,所以为1,每种配置都会有配置描述符,主机通过发送设置配置来选择某种配置

B. SET_ADDRESS(设置地址)请求
在这里插入图片描述
此请求是主机请求设备使用指定地址的请求
(三)wValue
指定的地址包含在此字段,每个链接在同一个主控制器的USB设备都需要有一个唯一的设备地址,以此区分不同设备
其余字段都为0,数据过程也为0
当设备收到设置地址的请求后,就直接进入状态过程,等待主机读取0长度的状态数据包。主机读取成功后,设备将使用新的地址。以后,主机都会用新的地址和设备通信。

C. SET_CONFIGURATION(设置配置)请求
在这里插入图片描述
和设置地址请求类似,区别在wValue的意义
(三)wValue
第一字节为配置的值,当这个值和某配置描述符的配置编号一致时,表示选中对应的配置。通常这个值是1,因为大多数设备只有一种配置,编号为1。若配置的值为0,会让设备进入设置地址状态,只有在收到非0的配置值后,设备才能启用它的非0端点
在这里插入图片描述
Class Descriptor: HID描述符就是一种类描述符

D. GET_CONFIGURATION(获取设置配置)请求

  1. 先获取配置描述符
    在这里插入图片描述

  2. 再获取配置描述符,接口描述符,类描述符,端点描述符
    在这里插入图片描述
    D1.设备配置描述符
    在这里插入图片描述

在这里插入图片描述
(一)bLength
大小为1字节,表示该描述符的长度。标准描述符长度为9字节
(二)bDescriptorType
大小为1字节,表示描述符的类型,配置描述符的类型为0x02.
(三)wTotalLength
大小为2字节, 表示整个配置描述符集合的总长度,包括配置描述符、接口描述符、(类特殊描述符,若有的话)、端点描述符。低字节在先,主机再由这个长度获取剩下的数据
(四)bNumInterfaces
大小为1字节,为配置所支持的接口数量。通常功能单一的设备只有一个接口(鼠标),复合设备有多个接口
(五)bConfiguration
大小为1字节,表该配置的值,通常一个USB设备支持多个配置,该字段就是每个配置的标识。设置配置请求的时候,发送的配置值若和这个bConfiguration匹配,就表示这个配置被激活
(六)iConfiguration
大小为1字节,为描述该配置的字符串的索引值,若为0,则表示没字符串。
(七)bmAttributes
大小为1字节,描述设备的特性
D7:1(保留位)
D6:0总线供电(VBUS 5V),1自供电
D5:0不支持远程唤醒,1支持远程唤醒
D4~D0:为0(保留位)
(八)bMaxPower
大小1字节,表示设备要从总线获取的最大电流量,单位为2mA。例如果需要500mA的最大电流,则改字节值为250。
低功耗总线供电设备:最大不超过100mA
高功耗总线供电设备:枚举时最大功耗不过100mA,枚举完成配置结束后不超过500mA

D2. 接口描述符
在这里插入图片描述
在这里插入图片描述
(一)bLength
大小为1字节,标准的USB接口描述符长度为9字节
(二)bDescriptorType
大小为1字节,为描述符的类型,接口描述符的类型编码是0x04
(三)bInterfaceNumber
大小为1字节,表接口的编号,当一个配置有多个接口时,每个接口的编号都不同,编号从0开始一次递增
(四)bAlternateSetting
大小为1字节,是该接口的备用编号,设置为0。编号规则和bInterfaceNumber一样,很少会使用该字段。
(五)bNumEndpoints
大小为1字节,为该接口的端点数(不包括端点0),若这个字段是0,表没有非0端点,使用的是默认的控制端点。
(六)bInterfaceClass
在这里插入图片描述
HID类的编码是0x03
(七)bInterfaceSubClass
在HID 1.1协议中,只规定了一种子类:支持 BIOS引导启动的子类,USB键盘,鼠标属于这个子类,子类代码为0x01,SCSI透明命令集的子类代码为0x06
(八)bInterfaceProtocol
分别是接口所用的类、子类和协议,由USB协会定义,和设备描述符中的意义类似。通常使用0,如果子类为支持引导启动的子类,则协议可选择鼠标和键盘,键盘代码: 0x01,鼠标为 0x02,在SCSI透明指令集中,批量传输的代码为0x50
(九)iConfiguration
大小为1字节,是描述该接口的字符串索引值,若这个值为0,则表示没字符串

D3. 端点描述符
在这里插入图片描述
(一)bLength
大小为1字节,表示该描述符的长度。标准的USB 端点描述符长度为7字节
(二)bDescriptorType
大小为1字节,表示描述符的类型,端点描述符类型编码是 0x05
(三)bEndpointAddress
大小为1字节, 表示这个端点的地址
D7:1为输入(Input),0为输出(Output)
D6~D4:都为0(保留)
D3~D0:端点号
(四)bmAttributes
大小为1字节,为此端点的属性
D1~D0:0控制传输,1等时传输,2批量传输,3中断传输
D7~D2:(非等时传输则设为0)
若该端点是等时传输,
D7~D6:保留
D5~D4:表用途,0为数据端点,1为反馈端点,2为暗含反馈的数据端点
D3~D2:同步的类型,0无同步,1为异步,2为适配,3为同步
(五)wMaxPackSize
大小2字节,是端点支持最大包长度(低字节在先)
对于全速和低速模式:
D10~D0 为端点的最大包长,其他位保留为0
对于高速模式:
D12~D11:为每帧附加的传输次数,具体参看USB 2.0协议
(六)bInterval
大小为1字节,表查询该端点的时间。对于中断端点,表查询的帧间隔数,
对于等时传输,高速模式的中断、批量传输,该字段意义参考USB2.0协议

D4. HID描述符
在这里插入图片描述
(一)bLength
大小为1字节,为该描述符的总长度,其大小和改描述符中下级描述符的个数有关。比如有两个下级描述符,总长度为 1+1+2+1+2+1+2+1+2 = 13 字节
(二)bDescriptorType
大小为1字节,是此描述符的编号,HID的描述符编号为0x21
(三)bcdHID
大小为2字节,为设备使用HID协议的版本号,为USB1.1的话则为0x0110
(四)bCountyCode
大小为1字节,是设备适用的国家,通常我们的键盘是美式键盘,代码为33(0x21)
(五)bNumDescriptors
大小为1字节,是下级描述符的数量,这个值至少等于1,下级描述符可以是报告描述符 或 物理描述符
(六)bDescriptorType
大小为1字节,是下级描述符的类型。
HID描述符是 0x21
报告描述符为 0x22
物理描述符为 0x23
(七)bDescriptorLength
大小为2字节,是下级描述符的长度。

当有多个下级描述符时,(六)bDescriptorType(七)bDescriptorLength会交替重复下去

E. 字符串 和 语言ID请求的实现
当某个描述符中的字符串索引值非0时,就表示它具有哪个字符串描述符(索引值不能重复!!!),USB主机通过获取 字符串描述符 和 索引值 来获取对应的字符串
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

大容量存储设备

接入后通讯过程:
前面枚举的过程不计
1) 主机发送GET MAX LUN请求,设备回复单字节0
2) 主机发送INQUIRY信号(0x12命令块封包CBW),设备回复对应信息,并且由设备回复命令状态封包CSW
3) 主机发送READ FORMAT CAPACITIES 读格式化容量命令(0x23命令块封包CBW),设备回复对应信息,并且由设备回复命令状态封包CSW
4)主机发送READ CAPACITY读容量命令(0x25命令块封包CBW),设备回复对应信息,并且由设备回复命令状态封包CSW
5)主机发送READ(10)命令(0x28命令块封包CBW),设备回复对应信息,并且由设备回复命令状态封包CSW
6)主机发送MODE SENSE 06命令(0x1a 命令块封包CBW),设备回复对应信息,并且由设备回复命令状态封包CSW
7)主机发送REQUEST SENSE命令(0x03 Device to Host)(命令块封包CBW),设备回复对应信息,并且由设备回复命令状态封包CSW
8)主机发送READ CAPACITY读容量命令(0x25命令块封包CBW),设备回复对应信息,并且由设备回复命令状态封包CSW
9)主机发送READ(10)命令(0x28命令块封包CBW),设备回复对应信息,并且由设备回复命令状态封包CSW
10)当测试单元准备好时,主机发送 TEST UNIT READY命令( 0x00 Host to Device)(0x00命令块封包CBW),设备回复状态bCSWStatus = 0(0x00表命令成功执行)的CSW包

(接口类代码)bInterfaceClass字段为 0x08,
(接口子类代码)bInterfaceSubClass字段为0x06,即SCSI通明命令集。
(协议代码)bInterfaceProtocol字段有三种:0x00,0x01,0x50,前两种使用中断传输,最后一种使用批量传输(bulk only transport)

SCSI命令集要求序列号最后至少有12位十六进制的数据位
接口描述符举例:

//------------------------------------------------------------------------------------------------
// INTERFACE DESCRIPTOR
//------------------------------------------------------------------------------------------------
                         0x09,              // bLength: Size of descriptor
                         0x04,              // bDescriptorType: Interface
                         0x00,              // bInterfaceNumber: #1
                         0x00,              // bAlternateSetting: #0
                         0x02,              // bNumEndpoints: 2
                         0x08,              // mass storage class
                         0x06,              // SCSI transparent
                         0x50,              // Bulk Only Transfer
                         0x00,              // iInterface: none

在USB大容量存储设备的BULK ONLY TRANSPORT协议中,规定了两个类特殊请求:

  1. Bulk-Only Mass Storage Reset,复位到命令状态的请求
  2. Get Max LUN,获取最大的逻辑单元请求

a. Get Max LUN请求
在这里插入图片描述
**bmRequestType:**发送到接口的类输入请求
bRequest: 0xFE
wValue: 0
wIndex: 为请求的接口号
wLength: 传输的数据长度为1字节,所以设备在数据过程中返回1字节的数据。
Data: 表示有多少个逻辑单元,0时有一个逻辑单元,1时有两个,最大可以取15

b. Bulk-Only Mass Storage Reset请求
用来通知设备接下来的批量端点输出数据为命令封包 CBW(Command Block Wrapper),在这个请求的处理中,把状态设置到CBW状态就可以了,并且返回一个0长度的状态数据包。
在这里插入图片描述
在这里插入图片描述

例如:
在这里插入图片描述

c. 批量端点传输数据
分三个阶段:

  1. 命令阶段,由主机通过批量端点发送一个CBW的结构,其中定义了要操作的命令,以及数据传输的方向和数量。
  2. 数据阶段,传输方向由命令阶段决定
  3. 状态阶段,总是由设备返回该命令完成的状态

I. 命令块封包CBW的结构
在这里插入图片描述

dCBWSignature: 这个字段是CBW的标志,是字符串USBC(USB Command)的缩写,用ASCII码表示:0x55,0x53,0x42,0x43,以小端模式表示就是0x43 42 53 55

dCBWTag: CBW的标签,由主机分配的,设备返回命令要求的状态时,要在CSW(Command Status Wrapper)中的 dCSWTag字段填入命令的dCBWTag

dCBWDataTransferLength: 四个字节,表示需要数据阶段传输的数据的字节数,小端结构!

bmCBWFlags: CBW的标志,最高为D7是数据传输的方向,0表示输出数据,1表示输入数据,其余位是0

bCBWLUN: 表目标逻辑单元的编号,当有多个逻辑单元时,用这个字段来区分不同的目标单元,这个字段只用低四位,高四位为保留值0。

bCBWCBLength: CBWCB的长度,这个字段只用5位,有效取值范围是1~16,不同的命令(由CBWCB决定),长度可能是不同的,如果命令的长度不够16字节,则后面的部分值为0

CBWCB: 需要执行的命令,由选择的子类决定使用哪些命令。

II. 命令状态封包CSW的结构
在这里插入图片描述

dCSWSignature : 这个字段是CSW的标志,是字符串 USBS(USB State)。用ASCII码表示: 0x55, 0x53,0x42,0x53,用小端模式的四字节证书表示,就是0x53425355

dCSWTag: 为命令状态封包的标签,值是CBW(Command Block Wrapper )的dCBWTag,响应哪一个CBW,就设置为哪个的CBW的 dCBWTag

dCSWDataResidue: 这个命令完成时,数据传输剩余的字节数,表示实际完成的传输的字节数 与 主机 在CBW中设置的长度dCBWDataTransferLength之间的差值。

bCSWStatus: 命令执行的状态。0x00表命令成功执行,0x01表执行失败,0x02表阶段错误。其他值是保留值。

通常命令都能成功执行, 前面两个字段为相应的值,后面两个字段设成0就好。

UFI(USB Floppy Interface)

是结合SCSI-2 和 SFF-8070i命令集抽取出来的命令,
揣想你在CBW的CBWCB字段,最长为16字节(若不足16字节,多余的将被忽略,通常设置为0),CBWCB的第一字节为操作代码。

a. INQUIRY 查询命令
操作码为 0x12
在这里插入图片描述
在这里插入图片描述
EVPD字段:
页码字段:
这两字段只支持0
在这里插入图片描述
a+. 命令返回数据的格式(36字节)
在这里插入图片描述
RMB位: 表示存储媒介是否可移除,0为不可移除,1为可移除
ISO,ECMA,ANSI: 为0
响应数据格式: 为0x01
附加数据长度: 31字节
厂商信息:
产品信息:
产品版本信息:
可以根据自己需要设置
例如,

unsigned char InquiryData[36] =
{
    0x00,                                           // Device class
    0x80,                                           // RMB bit is set by inquiry data
    0x02,                                           // Version
    0x01,                                           // Data format = 1
    0x00, 0x00, 0x00, 0x00,
    'A', 'D', 'M', 'I', 'R', 'A', 'L', ' ', // Manufacturer
    'M', 'a', 's', 's', ' ', 'S', 't', 'o', // Product(Zip 100)
    'r', 'a', 'g', 'e', ' ', ' ', ' ', ' ',
    '1', '.', '0', '0'                          // Revision
};
某U盘的数据:

在这里插入图片描述
b. READ FORMAT CAPACITIES 读格式化容量命令
操作代码为0x23
让主机读取设备各种可能的格式化容量的列表(设备所支持最大的容量),若设备中没有存储媒介,则返回最大能够支持的格式化容量。
在这里插入图片描述

b+. 命令返回数据的格式
在这里插入图片描述
容量列表长度为 8 字节(即4~11字节),
容量的计算方法为:容量 = 块数 x 每块字节数
详见UFI协议文档
**每块字节数:**通常为512字节(0x200)
例如,

unsigned char ReadFromatCapa[12] =
{
    0x00,0x00,0x00,0x08,  0x00,0x00,0x40,0x00,0x02,0x00,0x02,0x00
};      // 8M

c. READ CAPACITY读容量命令
让主机读取到当前存储媒介的容量(读到是实际的磁盘容量),操作代码为0x25,除操作代码外,其他字段都为0
在这里插入图片描述
在这里插入图片描述

c+. 命令返回数据的格式
在这里插入图片描述
最后逻辑块地址: 为存储媒介能够被访问的最大逻辑块地址
块大小(逻辑块): 为每个逻辑块的字节数,一般每块为512字节,磁盘总容量的计算方法为:
磁盘容量(字节数) = (最大逻辑块地址 + 1)x 块大小 (因为逻辑块地址是从0开始的,所以要加1)

unsigned char ReadCapa[8] = {0,}; 

ReadCapa[7] =  s_sector_size & 0xff;
ReadCapa[6] = (s_sector_size>>8) & 0xff;
ReadCapa[5] = (s_sector_size>>16) & 0xff;
ReadCapa[4] = (s_sector_size>>24) & 0xff;

ReadCapa[3] = fatpagecnt & 0xff;
ReadCapa[2] = (fatpagecnt>>8) & 0xff;
ReadCapa[1] = (fatpagecnt>>16) & 0xff;
ReadCapa[0] = (fatpagecnt>>24) & 0xff;

d. READ(10)命令
用来读取实际的磁盘数据,操作代码为 0x28。设备根据命令中指定的逻辑块地址,从存储媒介中读取数据并通过批量端点返回。当全部数据都返回后,再返回命令执行状态(CSW)
另外还有一个READ(12)命令,操作代码为 0xA8,格式和 READ(10)命令差不多,仅传输长度字段不一样。其6~9字节为传输长度,而READ(10)命令只有字节7 ~ 8 为传输长度。
在这里插入图片描述
DPO、FUA、RelAdr等字段为0值
逻辑块地址: 值为需要读取数据的起始块地址。(磁盘设备中,读写是按块操作的,一般来说,一个逻辑块是一个扇区,大小为 512字节,光盘是2048字节)
传输长度: 需要传输的逻辑块的数量,实际传输字节数为 传输长度 x 每块大小

union byte2
{
    unsigned short bw;

    struct
    {
        unsigned char bl;
        unsigned char bh;
    } wordsp;
};

union BYTE4
{
    unsigned long b4;

    struct
    {
        unsigned char ll;
        unsigned char lh;
        unsigned char hl;
        unsigned char hh;
    } longsp;
};
//==========以下为赋值给各个位==========
 	cmd = bulk_buf[0xf];					//第16位是命令操作代码
    TransLen.wordsp.bl=bulk_buf[0x17];		//第23位,传输时低位在后
    TransLen.wordsp.bh=bulk_buf[0x16];		//第22位,传输时高位在前
    BlkAddr.longsp.ll=bulk_buf[0x14];		//第20位,同样传输时低位在后
    BlkAddr.longsp.lh=bulk_buf[0x13];		//第19位
    BlkAddr.longsp.hl=bulk_buf[0x12];		//第18位
    BlkAddr.longsp.hh=bulk_buf[0x11];		//第17位,同样传输时高位在前

主机发送CBW包范例:
在这里插入图片描述

e. WRITE(10)命令
主机通常使用WRITE(10)命令往设备写入实际的数据,操作代码为 0x2a。
另外还有一个WRITE(12)命令,操作代码为0xAA,格式跟命令WRITE(10)差不多,仅传输长度字段不一样,其6~9字节为传输长度,而WRITE(10)命令只有7 ~ 8字节为传输长度
主机在发送此命令后,接着就会发送要传送的数据,设备在收到全部数据后,返回命令执行情况CSW
在这里插入图片描述

f. REQUEST SENSE命令(Device to Host)
命令代码为 0x03。
用来探测上一个命令执行失败的原因,主机可在每个命令之后使用该命令来读取命令执行的情况。
在这里插入图片描述

f+. 命令返回数据的格式
在这里插入图片描述

Valid: 为1 时表示信息字段符合UFI规范

Sense Key:
Additional Sense Code(ASC):
Additional Sense Code Qualifier(ASCQ):
上述三者为出错的代码,可在UFI协议的最后找到
信息: 返回哪个逻辑块地址出现了错误
无效的命令操作码: Sense Key 为 0x05, ASC 为 0x20,ASCQ 为 0。

unsigned char SenseData[18] =
{
    0xf0,0x00,0x05,0x00,
    0x00,0x00,0x00,0x0b,
    0x00,0x00,0x00,0x00,
    0x24,0x00,0x00,0x00,
    0x00,0x00
};

g. TEST UNIT READY命令(Host to Device)
用来测试设备的某个逻辑单元是否已准备好,操作代码为 0x00,
若设备已经准备好,则在状态阶段返回 命令执行成功,否则返回命令执行失败
在这里插入图片描述

g. MODE SENSE 06
操作代码为 0x1A

g+. 命令返回数据的格式
在这里插入图片描述

**MODE DATA LENGTH:**表示接下来有几个字段,为3
DEVICE-SPECIFIC PARAMETER:
在这里插入图片描述
WP为0表示 可读写,1表示只读,其他填0

uint8  BulkBuf[ ] ={

      [ 0] = 0x03,

      [ 1] = 0x00,

      [ 2] = 0x80, 

      [ 3] = 0x00,

      };   

HID设备报告描述符

输入报告:设备返回给主句的信息,主机通过获取描述报告符请求来获取报告符。
输出报告:主机发给设备,用来控制设备上的LED灯,大写锁定灯,主机可以发送给端点0 或者 中断输出端点
由item的形式排列组合成,无固定长度
类型分:
I. main
在这里插入图片描述
OUTPUT, INPUT, FEATURE后数据字节的说明
在这里插入图片描述

COLLECTION后的数据字节说明
在这里插入图片描述
input item tag: 指的是从设备的一个或多个类似控制管道得到的数据
output item tag: 指的是发送给一个或多个类似控制管道的数据
feature item tag: 表示设备的输入输出不面向最终用户
collection item tag: 一个有意义的input,output和feature的组合项目
end collection item tag: 指定一个collectionitem的终止

II. global
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

III. local
在这里插入图片描述
在这里插入图片描述

code char KeyBoardReportDescriptor[63] = {   
    //表示用途页为通用桌面设备   
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)   
  
    //表示用途为键盘   
    0x09, 0x06,                    // USAGE (Keyboard)   
       
    //表示应用集合,必须要以END_COLLECTION来结束它,见最后的END_COLLECTION   
    0xa1, 0x01,                    // COLLECTION (Application)   
       
    //表示用途页为按键   
    0x05, 0x07,                    //   USAGE_PAGE (Keyboard)   
  
    //用途最小值,这里为左ctrl键   
    0x19, 0xe0,                    //   USAGE_MINIMUM (Keyboard LeftControl)   
    //用途最大值,这里为右GUI键,即window键   
    0x29, 0xe7,                    //   USAGE_MAXIMUM (Keyboard Right GUI)   
    //逻辑最小值为0   
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)   
    //逻辑最大值为1   
    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)   
    //报告大小(即这个字段的宽度)为1bit,所以前面的逻辑最小值为0,逻辑最大值为1   
    0x75, 0x01,                    //   REPORT_SIZE (1)   
    //报告的个数为8,即总共有8个bits   
    0x95, 0x08,                    //   REPORT_COUNT (8)   
    //输入用,变量,值,绝对值。像键盘这类一般报告绝对值,   
    //而鼠标移动这样的则报告相对值,表示鼠标移动多少   
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)   
    //上面这这几项描述了一个输入用的字段,总共为8个bits,每个bit表示一个按键   
    //分别从左ctrl键到右GUI键。这8个bits刚好构成一个字节,它位于报告的第一个字节。   
    //它的最低位,即bit-0对应着左ctrl键,如果返回的数据该位为1,则表示左ctrl键被按下,   
    //否则,左ctrl键没有按下。最高位,即bit-7表示右GUI键的按下情况。中间的几个位,   
    //需要根据HID协议中规定的用途页表(HID Usage Tables)来确定。这里通常用来表示   
    //特殊键,例如ctrl,shift,del键等   
  
    
  
    //这样的数据段个数为1   
    0x95, 0x01,                    //   REPORT_COUNT (1)   
    //每个段长度为8bits   
    0x75, 0x08,                    //   REPORT_SIZE (8)   
    //输入用,常量,值,绝对值   
    0x81, 0x03,                    //   INPUT (Cnst,Var,Abs)   
       
    //上面这8个bit是常量,设备必须返回0   
  
  
    //这样的数据段个数为5   
    0x95, 0x05,                    //   REPORT_COUNT (5)   
    //每个段大小为1bit   
    0x75, 0x01,                    //   REPORT_SIZE (1)   
    //用途是LED,即用来控制键盘上的LED用的,因此下面会说明它是输出用   
    0x05, 0x08,                    //   USAGE_PAGE (LEDs)   
    //用途最小值是Num Lock,即数字键锁定灯   
    0x19, 0x01,                    //   USAGE_MINIMUM (Num Lock)   
    //用途最大值是Kana,这个是什么灯我也不清楚^_^   
    0x29, 0x05,                    //   USAGE_MAXIMUM (Kana)   
    //如前面所说,这个字段是输出用的,用来控制LED。变量,值,绝对值。   
    //1表示灯亮,0表示灯灭   
    0x91, 0x02,                    //   OUTPUT (Data,Var,Abs)   
  
    //这样的数据段个数为1   
    0x95, 0x01,                    //   REPORT_COUNT (1)   
    //每个段大小为3bits   
    0x75, 0x03,                    //   REPORT_SIZE (3)   
    //输出用,常量,值,绝对   
    0x91, 0x03,                    //   OUTPUT (Cnst,Var,Abs)       
    //由于要按字节对齐,而前面控制LED的只用了5个bit,   
    //所以后面需要附加3个不用bit,设置为常量。   
  
    
  
    //报告个数为6   
    0x95, 0x06,                    //   REPORT_COUNT (6)   
    //每个段大小为8bits   
    0x75, 0x08,                    //   REPORT_SIZE (8)   
    //逻辑最小值0   
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)   
    //逻辑最大值255   
    0x25, 0xFF,                    //   LOGICAL_MAXIMUM (255)   
    //用途页为按键   
    0x05, 0x07,                    //   USAGE_PAGE (Keyboard)   
    //使用最小值为0   
    0x19, 0x00,                    //   USAGE_MINIMUM (Reserved (no event indicated))   
    //使用最大值为0x65   
    0x29, 0x65,                    //   USAGE_MAXIMUM (Keyboard Application)   
    //输入用,变量,数组,绝对值   
    0x81, 0x00,                    //   INPUT (Data,Ary,Abs)   
    //以上定义了6个8bit宽的数组,每个8bit(即一个字节)用来表示一个按键,所以可以同时   
    //有6个按键按下。没有按键按下时,全部返回0。如果按下的键太多,导致键盘扫描系统   
    //无法区分按键时,则全部返回0x01,即6个0x01。如果有一个键按下,则这6个字节中的第一   
    //个字节为相应的键值(具体的值参看HID Usage Tables),如果两个键按下,则第1、2两个   
    //字节分别为相应的键值,以次类推。   
  
  
    //关集合,跟上面的对应   
    0xc0                           // END_COLLECTION   
};   
 char MouseReportDescriptor[52] = {   
    //通用桌面设备   
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)   
    //鼠标   
    0x09, 0x02,                    // USAGE (Mouse)   
    //集合   
    0xa1, 0x01,                    // COLLECTION (Application)   
    //指针设备   
    0x09, 0x01,                    //   USAGE (Pointer)   
    //集合   
    0xa1, 0x00,                    //   COLLECTION (Physical)   
    //按键   
    0x05, 0x09,                    //     USAGE_PAGE (Button)   
    //使用最小值1   
    0x19, 0x01,                    //     USAGE_MINIMUM (Button 1)   
    //使用最大值3。1表示左键,2表示右键,3表示中键   
    0x29, 0x03,                    //     USAGE_MAXIMUM (Button 3)   
    //逻辑最小值0   
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)   
    //逻辑最大值1   
    0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)   
    //数量为3   
    0x95, 0x03,                    //     REPORT_COUNT (3)   
    //大小为1bit   
    0x75, 0x01,                    //     REPORT_SIZE (1)   
    //输入,变量,数值,绝对值   
    //以上3个bit分别表示鼠标的三个按键情况,最低位(bit-0)为左键   
    //bit-1为右键,bit-2为中键,按下时对应的位值为1,释放时对应的值为0   
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)   
  
    //填充5个bit,补足一个字节   
    0x95, 0x01,                    //     REPORT_COUNT (1)   
    0x75, 0x05,                    //     REPORT_SIZE (5)   
    0x81, 0x03,                    //     INPUT (Cnst,Var,Abs)   
  
    //用途页为通用桌面   
    0x05, 0x01,                    //     USAGE_PAGE (Generic Desktop)   
    //用途为X   
    0x09, 0x30,                    //     USAGE (X)   
    //用途为Y   
    0x09, 0x31,                    //     USAGE (Y)   
    //用途为滚轮   
    0x09, 0x38,                    //     USAGE (Wheel)   
    //逻辑最小值为-127   
    0x15, 0x81,                    //     LOGICAL_MINIMUM (-127)   
    //逻辑最大值为+127   
    0x25, 0x7f,                    //     LOGICAL_MAXIMUM (127)   
    //大小为8个bits   
    0x75, 0x08,                    //     REPORT_SIZE (8)   
    //数量为3个,即分别代表x,y,滚轮   
    0x95, 0x03,                    //     REPORT_COUNT (3)   
    //输入,变量,值,相对值   
    0x81, 0x06,                    //     INPUT (Data,Var,Rel)   
  
    //关集合   
    0xc0,                          //   END_COLLECTION   
    0xc0                           // END_COLLECTION   
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值