当我们需要了解一家超市时,首先获取的应该是其名称、地址、面积大小、负责人、营业执照等相关信息,这是关于该超市的一般信息。对于每家超市,这些一般信息通常是唯一的,并且不涉及超市内部具体的货架配置、顾客流量和其年销量等情况。
USB设备描述符给出关于USB设备的一般信息,并且 每一个USB设备必须有且仅有一个设备描述符 ,它也是主机与设备连接时读取到的第一个描述符,其总长度为18字节,共包含了14个字段,相应的结构如表13.1所示。
表13.1 设备描述符的结构
表13.1中的偏移量(Offset)明确给出了设备描述符中各字段的排列顺序,大小(Size)表示各字段占用的字节数。USB规范给每个字段取了一个名称,通常使用一个“暗示字段类型”的前缀字符,如表13.2所示。
表13.2 字段类型的前缀字符
使用C语言结构体来表达设备描述符,能够更清晰地理解表13.1所示结构的具体含义了,如清单13.1所示。
清单13.1 C语言结构体描述的USB设备描述符
设备描述符中的第1个字段(bLength)代表设备描述符的长度,固定为18字节(0x12)。所有标准描述符的第1个字节都代表相应描述符的长度信息。第2个字段(bDescriptorType)表示描述符的类型。我们前面提到过,USB设备的描述符分为很多种类,USB主机就是通过该字段进行描述符类型的区分的。在设备描述符中,bDescriptorType字段的值为0x01,具体如表13.3所示( 接口电源描述符 不像其他标准描述符那样被定义在USB 2.0规范中,而是在《USB接口电源管理规范》( USB Interface Power Management Specification )中被定义,由于应用很少,本书不涉及)。
表13.3 USB描述符的类型值
第3个字段(bcdUSB)表示USB设备所遵循的USB规范版本号,其长度为2字节,并且以BCD码的形式给出,相应的格式为0xJJMN。其中,JJ为主版本号(Major Version Number),M为次版本号(Minor Version Number),N为子次版本号(Sub-minor Version Number)。例如,USB的版本号为2.0,则其相应的字段为0x0200。需要特别注意的是: 该字段值的低字节在前面(偏移量小),高字节在后面 ,也就是我们常说的小端模式(Little-endian)。
bcdUSB字段容易被读者忽略,甚至可能会随意设置,但是不少规范(USB规范只是其中之一,很多设备类规范也有版本号字段)在最初发布时可能考虑得不是很周全,其局限性会随着时代的发展而凸显,因此需要在随后的版本中进行更新,这可能导致很多信息(例如,描述符各字段代表的含义)是不一样的。举个简单的例子,你在bcdUSB字段填入的USB版本号为A,但进行固件开发时仍然按USB版本号B来设计,这 可能 会导致设备无法正常工作。由于有些规范是向后兼容的,如果开发固件时没有涉及“不同版本规范之间有差异”方面的编程,还不会出现明显的问题,但这就是后续固件工作开发过程中的“雷区”。简单地说,bcdUSB字段选择了哪个版本,固件开发时就应该参考相应版本的规范进行开发,对于其他设备类规范版本号的选择也是如此,后续在分析应用实例时再涉及。
第4个字段(bDeviceClass)表示该USB设备所属的设备类;刚刚就提到了设备类规范,那到底什么是设备类呢?当USB在全球范围内被广泛使用后,越来越多的产品会选择使用USB。为了方便进行设备的管理,USB-IF组织将设备划分为不同的类型,每种类型的设备具有相同或相似的通信方式,它们都各自对应另一个设备类定义规范(不在USB 2.0规范中定义)。例如,USB鼠标、USB键盘、USB游戏操纵杆、USB轨迹球等都属于人机接口设备(Human Interface Device, HID)类,对应《HID设备类定义》( Device Class Definition for HID )规范。硬盘、U盘、数码相机等可以归属为大容量存储类(Mass Storage Class, MSC),对应也有《USB大容量存储类》规范。电话、调制解调器(Modem)则属于通信设备类(Communication Device Class, CDC)。
当bDeviceClass字段值为0xFF时,表示其是由厂商自定义的设备类;当bDeviceClass字段值为0时,表示USB设备的各个接口互相独立,分别属于不同的设备类。也就是说,你的设备并不一定只能使用人机接口类、大容量存储类或其他设备类之一,不同的接口可以使用 相同或不同 的设备类,我们称为复合设备(Composite Device),这取决于你的设备定义,而具体的接口功能则在接口描述符中进一步定义。
当该字段的值为0x01~0xFE时,则表示设备在不同接口上支持不同的类,并且这些接口可能无法独立工作,同时该字段则会指出这些集体接口的类定义(USB 2.0规范原文如下:the device supports different class specifications on different interfaces and the interfaces may not operate independently. This value identifies the class definition used for the aggregate interface)。简单地说,此时设备的各个接口只能属于 相同的 设备类。USB规范定义的设备类代码如表13.4所示。
表13.4 USB规范定义的设备类代码
续表
第5个字段(bDeviceSubClass)表示USB设备所属的设备子类,主要用于进一步定义bDeviceClass代表的USB设备类。如果bDeviceClass为0,则该字段也必须设置为0;如果bDeviceClass为非0xFF,则该字段的所有值保留作为USB-IF组织去分配。
第6个字段(bDeviceProtocol)表示USB设备所使用的设备类协议,也被定义在相应的设备类规范中,其值与bDeviceClass与bDeviceSubClass有关。也就是说,这3个字段共同标识设备的功能,并且提供加载对应设备驱动的依据。当bDeviceProtocol字段值为0时,表示不使用设备类协议。如果该USB设备属于某个设备类和设备子类,则应该继续指明所采用的设备类协议。当该字段为0xFF时,表示设备类协议由厂商自定义。
第7个字段(bMaxPacketSize0)表示USB设备的端点0所支持的最大数据包的长度(以字节为单位)。低速设备为8字节,全速设备可以为8字节、16字节、32字节、64字节,高速设备为64字节。
第8个字段(idVendor)表示USB设备的厂商标识符(VID),第9个字段(idProduct)表示产品标识符(PID),图8.9所示对话框中的VID(0483)与PID(5710)就是设备描述符中的这两个字段信息。第10个字段(bcdDevice)表示USB设备的版本号(通常由厂商指定)。主机通过idVendor、idProduct、bcdDevice字段区别不同的产品,在USB设备上电时也可以帮助USB主机选择并加载合适的驱动程序。
值得一提的是,USB规范规定所有USB设备都应该有相应的VID与PID。其中,VID由供应商向USB-IF申请(需要交费),每个供应商的VID是唯一的,而PID则由供应商自行决定。换句话说,不同的产品、相同产品的不同型号以及相同型号但设计不同的产品最好采用不同的PID,以便区别于相同厂家的不同设备。但是即便VID与PID重复,也并不会对产品的使用带来很大的影响,很多USB设备生产商为了方便,并不会向USB-IF申请自己的VID,而是依然沿用主控生产商的VID,甚至随意向产品写入VID和PID。
第11个字段(iManufacturer)与第12个字段(iProduct)分别表示厂商与产品字符串描述符的索引值。字符串具体的内容在 字符串描述符 中定义(后述)。如果没有供应商字符串,可以置为0。第13个字段(iSerialNumber)表示设备序列号字符串描述的索引值(如果没有,则可以置为0),它可以用来区分多个相同设备。例如,同一个USB系统连接了多个同一厂商生产的同一型号设备,这些设备的idVendor、idProduct、bcdDevice字段值都是一样的,该怎么区分呢?厂商在iSerialNumber字段中针对每一个产品设置不同的序列号即可,JoystickMouse例程也是这样做的,它根据单片机的唯一96位ID来设置iSerialNumber字段。如果一个设备有序列号,当用户将其连接到一台PC的不同端口时,操作系统就不需要再重新载入该设备的驱动。第18个字段bNumConfigurations表示该USB设备所支持的配置数量,因为我们前面提过,一个设备可以包含一个或多个配置。
概括来说, 如果想使用自己的开发板进行相应的设备开发,则需要查看相应的规范来设置(所有)描述符字段的值。 例如,如果使用开发板模拟USB鼠标,则需要查看USB鼠标相关的分规范;如果要设计与音频相关的设备,则需要查看音频类分规范。 我们根据这些规范修改描述符的目的,就是使主机能够将我们的开发板枚举成为需要的设备类 。
好的,我们再回过头来分析清单12.2中的设备描述符,它对应的数组为Joystick_DeviceDescriptor,其长度为JOYSTICK_SIZ_DEVICE_DESC(18),相应地,定义在usb_desc.h头文件中。设备描述符的第1个字段代表设备描述符的长度,所以数组的第1个字节也是18(0x12)。第2个字段USB_DEVICE_DESCRIPTOR_TYPE表示描述符对应的类型值,应为0x01(见表13.3),第3、4个字段表示USB的版本号为2.0,第5~7个字段均为0x00,表示具体的接口功能在接口描述符中进一步定义,第8个字段表示端点0支持的最大数据包的长度为64(0x40)字节。第9、10个字段表示VID(0x0483),第11、12个字段表示PID(0x5710),第13、14个字段表示USB设备的版本号为2.00,而第18个字段表示该设备只有一个配置。
网络上有不少可以在PC上查询“成功完成枚举的设备”的描述符信息。USB游戏操纵杆对应的设备描述符信息如表13.5所示,读者可自行对应代码查看。
表13.5 USB游戏操纵杆对应的设备描述符信息
有人可能会问:怎么跳过了设备描述符中的第15~17个字段?从表13.5来看,它们应该分别代表STMicroelectronics、STM32 Joystick、498232883238,但为什么相应的字段值分别为1、2、3呢?Language 0x0409又代表什么呢?请参见后文。