分类: LINUX
2011-01-04 17:14:42
现在非常流行USB设备,时时刻刻都在我们身边,比如使用U盘,使用MP3,使用手机,都需要与PC的USB通讯。通过USB的接口,使用我们的生
活非常方便了,想什么时候听歌,就可以插入MP3到PC机那里,然后再从PC上下载MP3。这个过程在用户看来是非常的简单,不需要安装驱动程序,不需要
断掉PC
机的电源。真正体现“科技以人为本”的思想。使用这么方便的U盘或者MP3,那么我们又需要去问个为什么了,为什么会这么方便呢?往往方便的背后,就意味
着需要工程师做大量的工作,才会让大家使用USB这么方便。下面就来详细地分析USB的协议。
USB通讯是非常复杂的,刚刚协议定义就厚厚的一本,要完全地去看完,并且理解它,是很费时间的事情。希望本文可以提供给你一个非常好的指导,让你深入地
理解USB
的协议。USB的协议是主从协议,在所有通讯里,只有一个主控器,其它都是从设备。最多能接127个从设备,因为协议里只保留了7bit作为设备地址。所
有的USB数据交流都是由主控器发起,其它从设备进行响应。现在就以WINDOWS上的USB通讯来学习一下USB的通讯协议,后面所有提到的数据,都是
WINDOWS上的USB驱动程序发下来的数据。
为了把所有通讯数据都显示出来,我找到了一个USB的ARM开发板,通过这个开发板,就可以把主控器所有数据打印出来,并作相应的分析。同时,使用这个开发板,也可以用来调试龙芯的主控器驱动程序的调试。
当USB的开发板加电时,就会先初始化USB的连接,但没有插入PC的连接线,这时开发板就会从串口输出下面的字符:
USB Suspend
USB Resume
从上面看到,从设备的USB一直不断地挂起和唤醒,直到插入PC的连接线。当插入连接线到PC时,就会收到主控器发来的信息。下面的数据,就是开发板与USB主控器交流的数据。
1.收到主控器的获取设备描述符配置包。
Setup m=0,n=0,val=37
80 06 00 01 00 00 40 00
这是主控器发来第一个配置包数据。由于主控器不知道USB设备设备描述符有多长,所以包的最后里的长度是0x0040,也就是64个字节长度。
REQUEST_STANDARD=0x6
USB_DEVICE_DESCRIPTOR_TYPE(0)
根据USB的协议,分析上面的数据,就知道它是获取设备描述符。为了实现即插即用,就需要对插入来的设备进行获取描述信息,才知道这个USB的设备是什么样的设备,是U盘,还是HID的键盘。因此,USB设备就返回下面的数据给主控器:
USB_DataInStage,cnt=18,EP0Data.Count=18
12 01 10 01 00 00 00 40 00 80 00 80 00 01 04 2C 4A 01
这条数据,就是USB的设备描述符,描述了这个设备使用什么USB的协议版本,这里是1.1的版本,还有厂家标识、产品标识,以及厂家、产品和序列号等字符串的偏移地址。
USB_EVT_IN,USB_DataInStage
USB_DataInStage,cnt=0,EP0Data.Count=0
USB_EVT_OUT
通过设备描述符,就可以让主控器知道这个设备是使用什么版本的USB协议,是高速的设备,还是低速的设备,是谁产生的,是什么产品,然后操作系统就可以通
过这些信息去找到相应的驱动程序,如果操作系统没有找到相应的驱动程序,就会提示用户插入光盘,或者其它方来安装相应的驱动程序。
2.收到主控器的设置设备地址配置包。
Setup m=0,n=0,val=37
00 05 01 00 00 00 00 00
这条数据,根据USB的协议,就可以知道它是设置USB设备的地址配置包。它的作用,就是分配USB设备的地址,由于USB总线上可以有127个设备,那
么每个设备都需要分配一个唯一的地址才通讯,这跟网卡的MAC地址的作用是一样的。其实,就像分配门牌号,让大家看到那个门牌就知道什么房了。
从下面的数据分析来看,是分配地址为1.
REQUEST_STANDARD=0x5
USB_DeviceAddress=129
USB_EVT_IN,USB_SetAddress(1)
3.收到主控器的获取设备描述符配置包。
Setup m=0,n=0,val=37
80 06 00 01 00 00 12 00
再次收到获取设备描述符的配置包,由于第一次不知道设备描述符有多大,因而总是发送一个最大数据的包,就是64个字节大,现在知道描述符的大小为0x0012个大小了,就把它发送下来了。所以USB设备再次回应设备描述符就可以了。
REQUEST_STANDARD=0x6
USB_DEVICE_DESCRIPTOR_TYPE(1)
USB_DataInStage,cnt=18,EP0Data.Count=18
12 01 10 01 00 00 00 40 00 80 00 80 00 01 04 2C 4A 01
在这里再次回应设备描述符。
USB_EVT_IN,USB_DataInStage
USB_DataInStage,cnt=0,EP0Data.Count=0
4.收到配置描述符包
Setup m=0,n=0,val=45
80 06 00 02 00 00 09 00
根据USB协议可以知道,这是一个配置描述符的包,也就是让USB设备发送本设备有多少个配置方式给主控器。由于USB的设备是多种多样,满足不同的用户
需要的。比如鼠标和键盘,就是不同的设备了。还有MP3播放器,还有各种数码相机等等,都是不一样的设备了。就可以通过下面的方式来说明这个配置有多少种
方式,主要是通讯的方式。
REQUEST_STANDARD=0x6
USB_CONFIGURATION_DESCRIPTOR_TYPE(2)(Offset=0x0)
USB_DataInStage,cnt=9,EP0Data.Count=9
09 02 22 00 01 01 00 01 32
这里就是USB设备返回配置描述符给主控器的,它主要说明了这个设备有多少个配置,比如定义端点的类型,端点的传送方式,还有这个设备使用USB总线的电源多少。
USB_EVT_IN,USB_DataInStage
USB_DataInStage,cnt=0,EP0Data.Count=0
5.收到字符串描述符包
Setup m=0,n=0,val=45
80 06 00 03 00 00 FF 00
这里收到获取字符串描述符。由于在设备描述符里已经说明字符串描述符在那里,主要是偏移地址,比如04就是厂家的描述符。
REQUEST_STANDARD=0x6
USB_STRING_DESCRIPTOR_TYPE(3)(Offset=0x0)
USB_DataInStage,cnt=4,EP0Data.Count=4
04 03 09 04
这里返回偏移地址为0的字符串描述,其实那里是保存字符串描述符的语言描述标识,这里英语的标识,0X0409。
USB_EVT_IN,USB_DataInStage
USB_DataInStage,cnt=0,EP0Data.Count=0
6.收到字符串描述符包
Setup m=0,n=0,val=45
80 06 4A 03 09 04 FF 00
这时收到获取字符串描述符,根据偏移地址,就知道它是想返回0x4A的字符串,也就是字符串描述符里的偏移地址。
REQUEST_STANDARD=0x6
USB_STRING_DESCRIPTOR_TYPE(4)(Offset=0x4A)
USB_DataInStage,cnt=36,EP0Data.Count=36
24 03 43 00 41 00 49 00 32 00 30 00 30 00 37 00 30 00 33 00 32 00 35 00 20 00 31 00 2E 00 30 00 2E 00 30 00
USB设备返回0x4A的字符串给PC。这里是我放置的字符串:
CAI20070325 1.0.0
它作为产品的序列号。由于采用UNICODE编码,所有高位字节全是0。由于USB协议是使用小端格式来发送数据,所以都低位在前,高位在后。这样在PC那里就可以看到USB设备的产品序列号了。又前进了一步。
USB_EVT_IN,USB_DataInStage
USB_DataInStage,cnt=0,EP0Data.Count=0
7.收到第二个配置描述符包
Setup m=0,n=0,val=45
80 06 00 02 00 00 FF 00
这里收到是第二个配置描述符包,与第一个的区别是返回长度不同。
第一个配置包返回的长度是9个字节,而这里的长度是255。
REQUEST_STANDARD=0x6
USB_CONFIGURATION_DESCRIPTOR_TYPE(5)(Offset=0x0)
USB_DataInStage,cnt=34,EP0Data.Count=34
09 02 22 00 01 01 00 01 32 09 04 00 00 01 03 00 00 6E 09 21 00 01 00 01 22 24 00 07 05 81 03 40 00 20
在这里返回全部配置描述给PC,让PC知道USB设备所有的配置。在这里包括设备配置,接口配置,端点配置,还有设备特别配置信息。
USB_EVT_IN,USB_DataInStage
USB_DataInStage,cnt=0,EP0Data.Count=0
8.收到字符串描述符包
Setup m=0,n=0,val=45
80 06 00 03 00 00 FF 00
收到PC的字符串描述符,后面设备就返回。
REQUEST_STANDARD=0x6
USB_STRING_DESCRIPTOR_TYPE(6)(Offset=0x0)
USB_DataInStage,cnt=4,EP0Data.Count=4
04 03 09 04
返回设备描述符的语言定义。
USB_EVT_IN,USB_DataInStage
USB_DataInStage,cnt=0,EP0Data.Count=0
9.收到字符串描述符包
Setup m=0,n=0,val=45
80 06 2C 03 09 04 FF 00
收到PC需要产品字符串。
REQUEST_STANDARD=0x6
USB_STRING_DESCRIPTOR_TYPE(7)(Offset=0x2C)
USB_DataInStage,cnt=30,EP0Data.Count=30
1E 03 42 00 69 00 67 00 53 00 6C 00 6F 00 70 00 65 00 33 00 44 00 20 00 48 00 49 00 44 00
这里USB设备返回产品字符串给PC了。
USB_EVT_IN,USB_DataInStage
USB_DataInStage,cnt=0,EP0Data.Count=0
10.收到字符串描述符包
Setup m=0,n=0,val=45
80 06 00 03 00 00 FF 00
这里收到字符串描述符。
REQUEST_STANDARD=0x6
USB_STRING_DESCRIPTOR_TYPE(8)(Offset=0x0)
USB_DataInStage,cnt=4,EP0Data.Count=4
04 03 09 04
返回语言标识。
USB_EVT_IN,USB_DataInStage
USB_DataInStage,cnt=0,EP0Data.Count=0
11.收到字符串描述符包
Setup m=0,n=0,val=45
80 06 2C 03 09 04 FF 00
收到PC需要产品字符串。
REQUEST_STANDARD=0x6
USB_STRING_DESCRIPTOR_TYPE(9)(Offset=0x2C)
USB_DataInStage,cnt=30,EP0Data.Count=30
1E 03 42 00 69 00 67 00 53 00 6C 00 6F 00 70 00 65 00 33 00 44 00 20 00 48 00 49 00 44 00
这里USB设备返回产品字符串给PC了。
USB_EVT_IN,USB_DataInStage
USB_DataInStage,cnt=0,EP0Data.Count=0
12.收到字符串描述符包
USB_EVT_OUT
Setup m=0,n=0,val=37
80 06 00 03 00 00 FF 00
这里收到字符串描述符。
REQUEST_STANDARD=0x6
USB_STRING_DESCRIPTOR_TYPE(10)(Offset=0x0)
USB_DataInStage,cnt=4,EP0Data.Count=4
04 03 09 04
返回语言标识。
USB_EVT_IN,USB_DataInStage
USB_DataInStage,cnt=0,EP0Data.Count=0
×××××××××××××××××××××××××××××××××××××××××××××
13.收到字符串描述符包
Setup m=0,n=0,val=45
80 06 2C 03 09 04 FF 00
收到PC需要产品字符串。
REQUEST_STANDARD=0x6
USB_STRING_DESCRIPTOR_TYPE(11)(Offset=0x2C)
USB_DataInStage,cnt=30,EP0Data.Count=30
1E 03 42 00 69 00 67 00 53 00 6C 00 6F 00 70 00 65 00 33 00 44 00 20 00 48 00 49 00 44 00
这里USB设备返回产品字符串给PC了
USB_EVT_IN,USB_DataInStage
USB_DataInStage,cnt=0,EP0Data.Count=0
14.收到字符串描述符包
Setup m=0,n=0,val=45
80 06 00 03 00 00 FF 00
REQUEST_STANDARD=0x6
USB_STRING_DESCRIPTOR_TYPE(12)(Offset=0x0)
USB_DataInStage,cnt=4,EP0Data.Count=4
04 03 09 04
USB_EVT_IN,USB_DataInStage
USB_DataInStage,cnt=0,EP0Data.Count=0
Setup m=0,n=0,val=45
80 06 2C 03 09 04 FF 00
REQUEST_STANDARD=0x6
USB_STRING_DESCRIPTOR_TYPE(13)(Offset=0x2C)
USB_DataInStage,cnt=30,EP0Data.Count=30
1E 03 42 00 69 00 67 00 53 00 6C 00 6F 00 70 00 65 00 33 00 44 00 20 00 48 00 49 00 44 00
USB_EVT_IN,USB_DataInStage
USB_DataInStage,cnt=0,EP0Data.Count=0
15 收到字符串描述符包
USB_EVT_OUT
Setup m=0,n=0,val=45
80 06 00 01 00 00 12 00
这里收到需要返回厂商字符串的请求。
REQUEST_STANDARD=0x6
USB_DEVICE_DESCRIPTOR_TYPE(14)
USB_DataInStage,cnt=18,EP0Data.Count=18
12 01 10 01 00 00 00 40 00 80 00 80 00 01 04 2C 4A 01
在这里返回设备的厂商字符串给PC。
USB_EVT_IN,USB_DataInStage
USB_DataInStage,cnt=0,EP0Data.Count=0
16.收到第三个配置描述符包
Setup m=0,n=0,val=45
80 06 00 02 00 00 09 00
REQUEST_STANDARD=0x6
USB_CONFIGURATION_DESCRIPTOR_TYPE(15)(Offset=0x0)
USB_DataInStage,cnt=9,EP0Data.Count=9
09 02 22 00 01 01 00 01 32
USB_EVT_IN,USB_DataInStage
USB_DataInStage,cnt=0,EP0Data.Count=0
17. 收到第四个配置描述符包
Setup m=0,n=0,val=45
80 06 00 02 00 00 22 00
REQUEST_STANDARD=0x6
USB_CONFIGURATION_DESCRIPTOR_TYPE(16)(Offset=0x0)
USB_DataInStage,cnt=34,EP0Data.Count=34
09 02 22 00 01 01 00 01 32 09 04 00 00 01 03 00 00 6E 09 21 00 01 00 01 22 24 00 07 05 81 03 40 00 20
根据长度返回不同的数据。
USB_EVT_IN,USB_DataInStage
USB_DataInStage,cnt=0,EP0Data.Count=0
18.收到设置描述符包
Setup m=0,n=0,val=45
00 09 01 00 00 00 00 00
REQUEST_STANDARD=0x9
USB_SetConfiguration WB.L =1
USB_Configure(TRUE)
USB_SetConfiguration true
由上面可以知道经过这么多次来回后,主控器已经配置完成,对这个设备可以使用了。这时,如果在WINDOWS里就会看到可以设备安装完成,可以使用了。
19.收到设置空闲描述符包
Setup m=0,n=0,val=37
21 0A 00 00 00 00 00 00
收到这个描述符,就表明设备在空闲状态。
20.收到HID的报告描述符包
Setup m=0,n=0,val=37
81 06 00 22 00 00 64 00
由于在配置描述符里,我把这个设备描述成HID的设备,所以会收到HID的报告描述符。
REQUEST_STANDARD=0x6
REQUEST_TO_INTERFACE (0x22)
HID_REPORT_DESCRIPTOR_TYPE
USB_DataInStage,cnt=36,EP0Data.Count=36
06 00 FF 09 01 A1 01 19 01 29 08 15 00 25 FF 95 3F 75 08 81 02 19 01 29 08 15 00 25 FF 95 3F 75 08 91 02 C0
这里就返回报告描述的类型,说明每次发送数据报告的大小,还有数据的格式。这里是返回63个字节输出,63个输入的描述符。
USB_EVT_IN,USB_DataInStage
USB_DataInStage,cnt=0,EP0Data.Count=0
USB_EVT_OUT
到这里就把USB设备初始化完成了。从上面可以知道,要想配置一个USB设备,需要经过20个来回才能完成配置,这个过程是非常多的。如果在调试过 程中,只要任何一个地方出错,都不会配置成功的。如果再加上硬件的出错,就需要花费更长的时间了。可见,USB的设备虽然非常方便使用,但是花费了工程师 大量的精力和相当多的时间。因而USB是一个非常值钱的设备。目前USB设备已经非常流行,今后PC机与外设的通讯,大部份都是使用USB设备来完成的。 完全会取代串口、并口的通讯。我看到有一个厂家开发的USB设备,就200多种,从USB风扇到USB电话,从USB网络到USB装饰品,比如USB接口 的彩灯。还有通过USB控制的按摩器等健身器材等等。希望你看到本文之后,又可以开发一款更好的USB设备到来了。
设备描述符配置包
在USB通讯里,从主控器发出来的第一个配置包就是设备描述符配置包,目的只有一个,就是获取插入的USB属性,以便加载合适的驱动程序。现在就来详细地分析一下设备描述符包的定义。
在USB2.0的协议里找到9.3 USB Device Requests里就找到这个结构的定义,这里我使用C的定义结构如下:
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 06 00 01 00 00 40 00
这里的80,就是D7位为1。
D6-D5位是请求主分类型
0 是表示标准的请求。
1 是表示类别的请求。
2 是表示厂商的请求。
3 是保留。
D4-D0位是表求接收这个包的接口。
0 是表示USB设备接收。
1 是表示接口接收。
2 是表示端点接收。
3 是表示其它接收,不知道的。
4-31是保留。
bRequest是本描述符的请求类型,也就是后面发送的数据是什么样的东西。由于USB里有很多配置信息,比如获取设备描述符,又有设置USB地址等等,就是通过这个字节来区分的。
从USB协议里查找表9-4,就可看到如下的编码:
GET_STATUS 0
CLEAR_FEATURE 1
Reserved for future use 2
SET_FEATURE 3
Reserved for future use 4
SET_ADDRESS 5
GET_DESCRIPTOR 6
SET_DESCRIPTOR 7
GET_CONFIGURATION 8
SET_CONFIGURATION 9
GET_INTERFACE 10
SET_INTERFACE 11
SYNCH_FRAME 12
在上面的数据包里,看到它的内容是06,那么它就是GET_DESCRIPTOR类型。也就是主控器想读取USB设备的描述符,到这里就已经分析出来的意思,就是主控器想读取USB描述符,但还不知道是什么描述符的内容。
wValue是根据不同的请求而设置不同的值。一般就是传送参数给设备标明这是什么请求。在上面GET_DESCRIPTOR获取设备描述符里,它
的值是00 01。在GET_DESCRIPTOR里这个字段的低字节表示描述符的索引,高字节表示描述符的类型。高字节的类型如下:
DEVICE 1
CONFIGURATION 2
STRING 3
INTERFACE 4
ENDPOINT 5
DEVICE_QUALIFIER 6
OTHER_SPEED_CONFIGURATION 7
INTERFACE_POWER1 8
wValue值在这里的高字节是01,那么它就是设备描述符了。低字节是00,那么它就是表示从偏移地址0开始读取设备描述符。由于在配置描述符里有很多配置,所以低字节在那里就可以用来识别获取同样类型的描述符不同的配置。
wIndex是根据不同的请求而设置不同的值。一般用来说明端点号或者说明接口标识。在获取描述符里,设置为0,或者是语言ID。在这个发送的描述符里,它是设置为00 00。
wLength是根据请求来决定下一阶段发送数据的长度。前面请求第一个字节里,已经说明下一阶段数据传送的方向,这里说明了传送数据的长度。不管是发送
数据,还是接收数据,都不要超过这个数据长度,否则主机会出问题,或者设备有问题。在这个获取设备描述里,它的长度是40
00,按小端格式去解释,就是64个字节。
到这里,就把主控器发下来的数据解释完成了,知道去做什么的事情和回应。下一次就去分析怎么样返回设备描述符。
回应设备描述符
上一次已经介绍怎么样收到主控器的获取设备描述符的数据,这里就解释怎么样发送回应数据给主控器。
先从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设备里,我把它填写如下的数据:
12 01 10 01 00 00 00 40 00 80 00 80 00 01 04 2C 4A 01
看到这串数据是不明白是什么东西的,现在就来仔细地分析它的具体定义。下面就按着一个字段一个字段地分析它。
bLength是本结构的数据长度,这样可以方便以后兼容不同的版本协议。因为不同的结构是不同的长度,这样就可以区分不同的协议了。比如有一天想添加一
个字段,那么它的长度就会改变,这时就可以根据不同的长度进行解释不同的协议了。这次返回的结构长度是0x12,也就是18个字节,它的长度是从
bLength长度开始,也就是说是完全整个结构的长度。
bDescriptorType是描述符的类型。它的定义跟主控器发下来描述符的类型是一样的,如下:
DEVICE 1
CONFIGURATION 2
STRING 3
INTERFACE 4
ENDPOINT 5
DEVICE_QUALIFIER 6
OTHER_SPEED_CONFIGURATION 7
INTERFACE_POWER1 8
由于返回的是设备描述符,所以就选择了1,也就是包里显示的第二个字节01。用这个类型来区分不同的描述符。
USB协议深入分析 返回设备描述符
bcdUSB是USB发布的协议版本。也就是本设备能适用于那种协议,目前USB主要有两个版本,一个是1.10,一个是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,16,32,64中的任何一个大小。在本设备里是设置为64个字节大小。所以看
到这个字段是40 的大小。12 01 10 01 00 00 00 40 00 80 00 80 00 01 04 2C 4A 01
idVendor是厂商标识。由USB-IF分配的编码。在这里使用0x8000。
idProduct是厂商定义的产品标识。由厂家和产品标识,就可以让操作系统加载不同的驱动程序。如下:12 01 10 01 00 00 00
40 00 80 00 80 00 01 04 2C 4A 01 bcdDevice是用BCD表示的设备发布的版本号。这里是1.00。12 01
10 01 00 00 00 40 00 80 00 80 00 01 04 2C 4A 01
iManufacturer是厂商字符串的偏移值。这值主要说明了它在字符串描述符里的偏移位置。如果它设置为0,表示没有厂商字符串。在这里是
0x04,就是从字符串描述符开始位置算起第4个字节位置读取字符串。
iProduct是产品字符串的偏移值。这值主要说明了它在字符串描述符里的偏移位置。如果它设置为0,表示没有产品字符串。在这里是0x2C,就是从字
符串描述符开始位置算起第2C个字节位置读取字符串。
iSerialNumber是序列号字符串的偏移值。这值主要说明了它在字符串描述符里的偏移位置。如果它设置为0,表示没有序列号字符串。在这里是
0x4A,就是从字符串描述符开始位置算起第4A个字节位置读取字符串。所有字符串,都是采有UNICODE编码。
bNumConfigurations是配置描述符的个数。在这里只使用了一个配置,所以设置为1。
前面已经解释主控器怎么样发送设备描述符下来,然后设备返回相应的设备描述符。下一步主控器的动作是做什么呢?由于在USB总线上的设备有很多,为
了区分不同的设备通讯,就需要给每个设备分配一个地址,这跟网络中的IP地址是一样的,或者跟MAC地址也是一样的。因而,接着下来就是主控器分配地址给
设备,USB的设备地址是从1开始到127。下面就是接收到主控器发下来的数据包:00 05 01 00 00 00 00 00
由USB_SETUP_PACKET定义具体地分析这个数据,就可以知道应做什么样的响应了。下面就来解释这个操作。先取得bmRequestType的
类型,也就是第一个字节,它是00。从USB协议里查看,它的方向位是主控器发送给设备,由D6D5位就知道它是USB协议里定义的标准请求,由D4-
D0位知道它是USB设备接收这个包数据。bRequest是05,从前面已经介绍的类型,就知道它是设置地址,如
下:SET_ADDRESS 5所以这个包需要按设置地址的格式去解释后面的数据。
由于USB协议可以知道,USB的设备地址放在字段wValue里,因它的值是01
00,按小端格式解释就是0x0001了。其它相应的字段wIndex和wLength应都是0,如果是其它非0的数据,是没有定义的。
USB的串行引擎通过这个地址来判断是否接收总线上的数据,如果发送的地址跟它一致,就会接收主控器发过来的数据,当然从这个设备发送出去的数据也带有这
个地址,因此就可以让主控器识别不同的USB设备数据了。
前面已经介绍设置USB的设备地址,接着下来是做什么呢?其实有了设备地址后,主控器还会再次发送获取上面已经读取的设备描述符下来,如下:
80 06 00 01 00 00 12 00
然后USB设备也再次回应它,但这次发送的长度是0x0012了,不再是第一次64个字节长度了。接着USB设备就返回下面的描述符给主控器,也就是第一
次已经发送的设备描述符,如下:12 01 10 01 00 00 00 40 00 80 00 80 00 01 04 2C 4A 01
这样分配地址之后,再次获取设备描述符成功了,接着下来就是主控器获取配置描述符。下面就是收到的配置描述符数据:80 06 00 02 00 00
09 00
分析上面的数据如下:bmRequestType是80,表示方向USB设备发送给主控器,接收设备是USB设备。bRequest是06,表示这是获取
描述符。GET_DESCRIPTOR 6wValue是00
02。低字节表示偏移地址00,高字节表示描述符的类型。如下:CONFIGURATION
2所以这里的返回的设备描述符是配置描述符。wIndex是00 00。wLength是09
00。它表示返回描述符的长度。这里是9个字节。接着下来,就是设备返回配置描述符给主控器,发送的数据如下:09 02 22 00 01 01 00
01 32 发送的数据是按下面的结构来定义,这也是在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
00,也就是0x0022个字节。bNumInterfaces是接口个数,这里一个。bConfigurationValue是配置的个数,当设置配置
时发送的值。这时设置为1个配置。iConfiguration是说明配置的字符的偏移值。这里是0。bmAttributes是配置特性,D7位保留。
D6位是说明是否自供电。D5位是否支持远程唤醒。D4—D0是保留,设置为0。MaxPower是使用的功率,它采用电流来表示。每2mA为单位,比如
它的值是50时就表示是100mA的电流消耗。通过这样说明,主控器就知道这个设备是什么样的设备,有多少功能。 USB协议深入分析
字符串描述符上一次说到把配置描述符返回给主控器那里了,现在接着下来,就会收到主控器发来字符串描述符。如果在设备描述符那里指定没有字符串描述的话,
在这里是不会收到字符串描述符的。由于我在设备描述符里指定有字符串描述符的偏移地址,因此,就收到主控器发出请求字符串描述符。收到的数据如下:
80 06 00 03 00 00 FF 00
bmRequestType是80,表示方向USB设备发送给主控器,接收设备是USB设备。
bRequest是06,表示这是获取描述符。GET_DESCRIPTOR 6
wValue是00 03。低字节表示偏移地址00,高字节表示描述符的类型,如下:
STRING 3
wIndex是00 00。
wLength是FF 00。它表示返回描述符的长度。这里是256个字节。
因此,这个获取字符串描述符,就是从字符串描述内存里,0偏移地址开始的位置读取第一个字符串描述符返回给主控器。接着就返回下面的数据给主控器:
04 03 09 04
上面的数据是按字符串描述符来组织的,它的结构,我定义结构如下:
typedef struct _USB_STRING_DESCRIPTOR {
BYTE bLength;
BYTE bDescriptorType;
WORD bString/*[]*/;
} USB_STRING_DESCRIPTOR;
bLength是所有数据的长度。在这里是4。
bDescriptorType是描述类型,这里字符串描述符,所以它是3。
bString是可变的字符数组。不超过254个应都可以的,并且它是使用UNICODE编码的字符串。在这里是09 04,这是美国英语的标识,0x0409。如果想输入中文的标识,只要改为0x0804就可以了。
通过这个字符串描述符,主控器就知道字符串描述符是使用什么语言说明的了,这样就可以支持全世界的语言标识。