/* usb协议分析仅供大家参考---设备描述符配置包,设备描述符, 地址设置, 配置描述符, 字符串描述符 */ /* -1- usb设备描述符配置包 */
typedef struct _USB_SETUP_PACKET
{
REQUEST_TYPE bmRequestType;
BYTE bRequest;
WORD_BYTE wValue;
WORD_BYTE wIndex;
WORD wLength;
} USB_SETUP_PACKET; .bmRequestType 是包含有下面几方面的内容:
D7 D6 D5 D4 D3 D2 D1 D0
在这一个字节里,又按位分为:
D7位是表示后面传送数据的方向位。
当D7等于0时,表示后面的数据是从主控器发送到USB设备。在PC里,就是从PC机发送到USB的设备。
当D7等于1时,表示后面的数据是从USB设备发送到主控器。在PC里,就是从USB设备发送到USB设备。
在上次里,我收到并显示出来的数据是80,就表示从USB设备里发送数据给PC。在这里再次给出上一次的包数据: 这里的80,就是D7位为1。 D6-D5位是请求主分类型
是表示标准的请求。
是表示类别的请求。
是表示厂商的请求。
是保留。 D4-D0位是表求接收这个包的接口。
是表示USB设备接收。
是表示接口接收。
是表示端点接收。
是表示其它接收,不知道的。
-31是保留。 .bRequest 是本描述符的请求类型,也就是后面发送的数据是什么样的东西。
由于USB里有很多配置信息,比如获取设备描述符,又有设置USB地址等等,就是通过这个字节来区分的。
从USB协议里查找表9-,就可看到如下的编码:
GET_STATUS
CLEAR_FEATURE
Reserved for future use
SET_FEATURE
Reserved for future use
SET_ADDRESS
GET_DESCRIPTOR
SET_DESCRIPTOR
GET_CONFIGURATION
SET_CONFIGURATION
GET_INTERFACE
SET_INTERFACE
SYNCH_FRAME
在上面的数据包里,看到它的内容是06,那么它就是GET_DESCRIPTOR类型。也就是主控器想读取USB设备的描述符,
到这里就已经分析出来的意思,就是主控器想读取USB描述符,但还不知道是什么描述符的内容。 .wValue是根据不同的请求而设置不同的值。一般就是传送参数给设备标明这是什么请求。在上面GET_DESCRIPTOR获取设备描述符里,它的值是00 。
在GET_DESCRIPTOR里这个字段的低字节表示描述符的索引,高字节表示描述符的类型。高字节的类型如下:
DEVICE
CONFIGURATION
STRING
INTERFACE
ENDPOINT
DEVICE_QUALIFIER
OTHER_SPEED_CONFIGURATION
INTERFACE_POWER1
wValue值在这里的高字节是01,那么它就是设备描述符了。低字节是00,那么它就是表示从偏移地址0开始读取设备描述符。
由于在配置描述符里有很多配置,所以低字节在那里就可以用来识别获取同样类型的描述符不同的配置。 .wIndex是根据不同的请求而设置不同的值。一般用来说明端点号或者说明接口标识。在获取描述符里,设置为0,或者是语言ID。
在这个发送的描述符里,它是设置为00 。 .wLength是根据请求来决定下一阶段发送数据的长度。前面请求第一个字节里,已经说明下一阶段数据传送的方向,
这里说明了传送数据的长度。不管是发送数据,还是接收数据,都不要超过这个数据长度,否则主机会出问题,或者设备有问题。
在这个获取设备描述里,它的长度是40 ,按小端格式去解释,就是64个字节。 到这里,就把主控器发下来的数据解释完成了,知道去做什么的事情和回应。下一次就去分析怎么样返回设备描述符。 /* ------------------------------------------------------------------------------------------------ */ /* -2- 回应设备描述符 */
上一次已经介绍怎么样收到主控器的获取设备描述符的数据,这里就解释怎么样发送回应数据给主控器。
先从USB协议里找到标准设备的定义,我把它用C语言定义如下:
typedef struct _USB_DEVICE_DESCRIPTOR {
BYTE bLength;
BYTE bDescriptorType;
WORD bcdUSB;
BYTE bDeviceClass;
BYTE bDeviceSubClass;
BYTE bDeviceProtocol;
BYTE bMaxPacketSize0;
WORD idVendor;
WORD idProduct;
WORD bcdDevice;
BYTE iManufacturer;
BYTE iProduct;
BYTE iSerialNumber;
BYTE bNumConfigurations;
} USB_DEVICE_DESCRIPTOR;
返回给主控器的数据结构就是上面的内容,只要把上面的结构填写合适的内容,就可以发送回去给主控器。
在我的USB设备里,我把它填写如下的数据:
2C 4A 看到这串数据是不明白是什么东西的,现在就来仔细地分析它的具体定义。下面就按着一个字段一个字段地分析它。
bLength是本结构的数据长度,这样可以方便以后兼容不同的版本协议。因为不同的结构是不同的长度,这样就可以区分不同的协议了。
比如有一天想添加一个字段,那么它的长度就会改变,这时就可以根据不同的长度进行解释不同的协议了。
这次返回的结构长度是0x12,也就是18个字节,它的长度是从bLength长度开始,也就是说是完全整个结构的长度。 bDescriptorType是描述符的类型。它的定义跟主控器发下来描述符的类型是一样的,如下:
DEVICE
CONFIGURATION
STRING
INTERFACE
ENDPOINT
DEVICE_QUALIFIER
OTHER_SPEED_CONFIGURATION
INTERFACE_POWER1
由于返回的是设备描述符,所以就选择了1,也就是包里显示的第二个字节01。用这个类型来区分不同的描述符。 bcdUSB是USB发布的协议版本。也就是本设备能适用于那种协议,目前USB主要有两个版本,一个是1.,一个是2.10版本。
在本设备里,采用了1.10的协议版本。由于这个字段是采用BCD编码,所以1.10的表示为0x0110的格式,按小端格式输出来,就变成10 01的显示了。 bDeviceClass是设备分类。当它的值是0时,表示所有接口在配置描述符里,并且所有接口是独立的。
当它的值是1到FEH时,表示不同的接口关联的。当它的值是FFH时,它是厂商自己定义的。在这个设备里,是定义为0。 bDeviceSubClass是设备子分类码。当前面的bDeviceClass值是0时,这里一定要设置为0。其它就跟据USB-IF组织定义的编码。 bDeviceProtocol是设备使用的协议。如果使用USB-IF组织定义的协议,就需要设置这里的值。如果不使用,就直接设置为0。
如果厂商自己定义的可以设置为FFH。
以上三个值,在本设备里全部设置为0。 bMaxPacketSize0是端点0收发最大的包大小。仅允许设置8,,,64中的任何一个大小。在本设备里是设置为64个字节大小。所以看到这个字段是40 的大小。
2C 4A idVendor是厂商标识。由USB-IF分配的编码。在这里使用0x8000。 idProduct是厂商定义的产品标识。由厂家和产品标识,就可以让操作系统加载不同的驱动程序。如下:
2C 4A bcdDevice是用BCD表示的设备发布的版本号。这里是1.。
2C 4A iManufacturer是厂商字符串的偏移值。这值主要说明了它在字符串描述符里的偏移位置。如果它设置为0,表示没有厂商字符串。在
这里是0x04,就是从字符串描述符开始位置算起第4个字节位置读取字符串。 iProduct是产品字符串的偏移值。这值主要说明了它在字符串描述符里的偏移位置。如果它设置为0,表示没有产品字符串。
在这里是0x2C,就是从字符串描述符开始位置算起第2C个字节位置读取字符串。 iSerialNumber是序列号字符串的偏移值。这值主要说明了它在字符串描述符里的偏移位置。如果它设置为0,表示没有序列号字符串。
在这里是0x4A,就是从字符串描述符开始位置算起第4A个字节位置读取字符串。
所有字符串,都是采有UNICODE编码。 bNumConfigurations是配置描述符的个数。在这里只使用了一个配置,所以设置为1。 /* ------------------------------------------------------------------------------------------------ */ /* -3- 设置USB地址 */
前面已经解释主控器怎么样发送设备描述符下来,然后设备返回相应的设备描述符。下一步主控器的动作是做什么呢?
由于在USB总线上的设备有很多,为了区分不同的设备通讯,就需要给每个设备分配一个地址,这跟网络中的IP地址是一样的,或者跟MAC地址也是一样的。
因而,接着下来就是主控器分配地址给设备,USB的设备地址是从1开始到127。下面就是接收到主控器发下来的数据包: 由USB_SETUP_PACKET定义具体地分析这个数据,就可以知道应做什么样的响应了。下面就来解释这个操作。
先取得bmRequestType的类型,也就是第一个字节,它是00。从USB协议里查看,它的方向位是主控器发送给设备,
由D6D5位就知道它是USB协议里定义的标准请求,由D4-D0位知道它是USB设备接收这个包数据。
bRequest是05,从前面已经介绍的类型,就知道它是设置地址,如下:
SET_ADDRESS
所以这个包需要按设置地址的格式去解释后面的数据。 由于USB协议可以知道,USB的设备地址放在字段wValue里,因它的值是01 ,按小端格式解释就是0x0001了。
其它相应的字段wIndex和wLength应都是0,如果是其它非0的数据,是没有定义的。 USB的串行引擎通过这个地址来判断是否接收总线上的数据,如果发送的地址跟它一致,就会接收主控器发过来的数据,
当然从这个设备发送出去的数据也带有这个地址,因此就可以让主控器识别不同的USB设备数据了。 /* ------------------------------------------------------------------------------------------------ */ /* -4- 配置描述符 */
前面已经介绍设置USB的设备地址,接着下来是做什么呢?其实有了设备地址后,主控器还会再次发送获取上面已经读取的设备描述符下来,如下: 然后USB设备也再次回应它,但这次发送的长度是0x0012了,不再是第一次64个字节长度了。
接着USB设备就返回下面的描述符给主控器,也就是第一次已经发送的设备描述符,如下:
2C 4A 这样分配地址之后,再次获取设备描述符成功了,接着下来就是主控器获取配置描述符。下面就是收到的配置描述符数据: 分析上面的数据如下:
bmRequestType是80,表示方向USB设备发送给主控器,接收设备是USB设备。
bRequest是06,表示这是获取描述符。GET_DESCRIPTOR
wValue是00 。低字节表示偏移地址00,高字节表示描述符的类型。如下:
CONFIGURATION
所以这里的返回的设备描述符是配置描述符。
wIndex是00 。
wLength是09 。它表示返回描述符的长度。这里是9个字节。
接着下来,就是设备返回配置描述符给主控器,发送的数据如下: 发送的数据是按下面的结构来定义,这也是在USB协议里定义的格式。如下:
typedef struct _USB_CONFIGURATION_DESCRIPTOR {
BYTE bLength;
BYTE bDescriptorType;
WORD wTotalLength;
BYTE bNumInterfaces;
BYTE bConfigurationValue;
BYTE iConfiguration;
BYTE bmAttributes;
BYTE MaxPower;
} USB_CONFIGURATION_DESCRIPTOR; bLength是配置的长度,也就是配置结构的整个长度。在这里9个字节。
bDescriptorType是描述符的类型,这里配置描述符,所以设置为02。
wTotalLength是所有配置设置的结构长度。包括配置描述符、接口描述符、HID或者其它描述符和端点描述符的长度。这里是22 ,也就是0x0022个字节。
bNumInterfaces是接口个数,这里一个。
bConfigurationValue是配置的个数,当设置配置时发送的值。这时设置为1个配置。
iConfiguration是说明配置的字符的偏移值。这里是0。
bmAttributes是配置特性,D7位保留。D6位是说明是否自供电。D5位是否支持远程唤醒。D4—D0是保留,设置为0。
MaxPower是使用的功率,它采用电流来表示。每2mA为单位,比如它的值是50时就表示是100mA的电流消耗。
通过这样说明,主控器就知道这个设备是什么样的设备,有多少功能。 /* ------------------------------------------------------------------------------------------------ */ /* -5- 字符串描述符 */
上一次说到把配置描述符返回给主控器那里了,现在接着下来,就会收到主控器发来字符串描述符。如果在设备描述符那里指定没有字符串描述的话,
在这里是不会收到字符串描述符的。由于我在设备描述符里指定有字符串描述符的偏移地址,因此,就收到主控器发出请求字符串描述符。收到的数据如下:
FF
bmRequestType是80,表示方向USB设备发送给主控器,接收设备是USB设备。
bRequest是06,表示这是获取描述符。GET_DESCRIPTOR
wValue是00 。低字节表示偏移地址00,高字节表示描述符的类型,如下:
STRING
wIndex是00 。
wLength是FF 。它表示返回描述符的长度。这里是256个字节。
因此,这个获取字符串描述符,就是从字符串描述内存里,0偏移地址开始的位置读取第一个字符串描述符返回给主控器。接着就返回下面的数据给主控器: 上面的数据是按字符串描述符来组织的,它的结构,我定义结构如下:
typedef struct _USB_STRING_DESCRIPTOR {
BYTE bLength;
BYTE bDescriptorType;
WORD bString/*[]*/;
} USB_STRING_DESCRIPTOR; bLength是所有数据的长度。在这里是4。
bDescriptorType是描述类型,这里字符串描述符,所以它是3。
bString是可变的字符数组。不超过254个应都可以的,并且它是使用UNICODE编码的字符串。
在这里是09 ,这是美国英语的标识,0x0409。如果想输入中文的标识,只要改为0x0804就可以了。
通过这个字符串描述符,主控器就知道字符串描述符是使用什么语言说明的了,这样就可以支持全世界的语言标识。