说明:
- 此文档包括四部分:
- USB固件开发(通用部分)
- USB固件开发(HID设备)
- USB固件开发(Mass Storage设备)
- USB固件开发(复合设备:HID+Mass Storage)
- 由于不同的USB接口芯片在固件编写时会有不同的具体操作及特性,所以此文档不描述编程细节。
USB固件开发(通用部分)
1. 基本概念
1.1 工作机制
USB的通信是主从式通信。即USB主机发送请求,USB设备(从机)做出应答。设备无法主动请求主机。这样,设备的工作主要就是正确响应主机发来的请求。主机为了识别不同功能的设备,必然要求设备在投入具体使用前先报告一些特征数据给主机,这些特征数据的格式遵循着主机和设备之间制定的协议,所以如果设备正确返回了这些特征数据,那么主机就能够确定设备的功能。在此之后,主机就会按照设备的具体功能发出具体请求了,它们之间的正常工作由此展开。
以上表述中,所谓的“特征数据”就是“描述符”,主要包括设备描述符、配置描述符、接口描述符、端点描述符和字符串描述符,以及对应于设备具体功能的类描述符(譬如HID描述符)。
1.2 USB系统的层次结构
如下图所示,主机与设备的通信分为3层,一般的开发只关心上两层。具体来讲,设备固件的开发会关心上两层,而主机还有可能只关心第一层,即客户软件的开发。从图上也可以开出,对具体“应用”来说,对设备来说只有“接口”是可见的,对主机客户软件来说,只有“管道”(图中的“通道”)可见。
2. 主机对设备的识别
这里的识别仅指设备插入后所进行的软件识别,不包括主机对设备硬件插入的识别。
主机检测到有USB设备插入后,会依次进行如下动作:
<!--[if !supportLists]-->1、 <!--[endif]-->复位设备。
<!--[if !supportLists]-->2、 <!--[endif]-->以缺省地址0向设备发出读取设备描述符的请求,以取得缺省控制管道所支持的最大数据包长度,此时主机只会读取描述符的前8个字节。
<!--[if !supportLists]-->3、 <!--[endif]-->向设备发出SetAddress请求,为其分配一个唯一的设备地址。
<!--[if !supportLists]-->4、 <!--[endif]-->使用新地址向设备发出读取设备描述符的请求,与第2步不同,此时是读取整个描述符。
<!--[if !supportLists]-->5、 <!--[endif]-->向设备循环发出读取配置描述符的请求,以读取全部配置信息(一个USB设备可能有多个配置,由设备描述符相关字段指出)。值得注意的是,这里的配置信息包括了配置描述符本身、接口描述符和端点描述符,以及相关类描述符。所以,每读取一个配置,主机会分为两步:首先发出请求读取配置描述符本身(9字节),以得到配置信息的长度;然后再次发出请求读取整个配置信息。这两步中的请求都是“读取配置描述符”,只是指明读取的数据长度不同。
<!--[if !supportLists]-->6、 <!--[endif]-->发出SetConfiguration请求为设备选择合适的配置。
在实际的开发的开发中,程序员很有可能觉察不到有这么多的请求,因为USB接口芯片很可能封装了一些控制管道的标准请求如SET_ADDRESS等,对这些请求芯片会自动做出反应,不需程序员干预。这是对开发过程的一种简化。譬如Sunplus公司MCU上集成的USB接口,一般就只将读取/设定描述符的请求暴露给程序员,而将GET_STATUS,CLEAR_FEATURE,SET_FEATURE,SET_ADDRESS,GET_CONFIGURATION,SET_CONFIGURATION,GET_INTERFACE,SET_INTERFACE这些请求全部封装了。
在以上表述中,有几处提到主机在发送同一个请求时,要求读取的数据长度会不一样。如读取设备描述符时,是先读8字节,而后再读取整个;读取配置信息时,是先读取9字节,然后读取整个。所以,固件程序必须处理这些情况。有的时候,设备要求读取的数据长度比实际描述符的长度要长,例如读取字符串描述符时,它一律发出0xFF这样的长度,对这种情况固件也必须处理。一个通用的做法就是,对于所有的请求,都把描述符长度与主机请求读取的长度做个比较,取其中较小者为实际处理的长度。
3. 开发中的细节问题
3.1各描述符详解
各描述符字段的具体意义,可以参考USB规范第9章“设备架构”。这里主要是举例说明各描述符的用法和它们在实际编程中的表现形式,以及一些注意事项。
总的一点:不管是何种描述符,其前两个字节必定是指明该描述符的长度及描述符类型。
设备描述符:
_Device_Descriptor:
.dw _Device_Descriptor_End-_Device_Descriptor //bLength
.dw 0x01 //bDescriptorType : Device
.dw 0x10, 0x01 //bcdUSB : version 1.10
.dw 0x00 //bDeviceClass
.dw 0x00 //bDeviceSubClass
.dw 0x00 //bDeviceProtocol
.dw 0x08 //bMaxPacketSize0
.dw 0x55, 0x0f //idVendor : 0x0F55
.dw 0x8A, 0x02 //idProduct : 0x028A
.dw 0x00, 0x01 //bcdDevice
.dw 0x01 //iManufacturer
.dw 0x02 //iProduct
.dw 0x03 //iSerialNumber
.dw 0x01 //bNumConfigurations
_Device_Descriptor_End:
此设备描述符描述了一个支持USB1.1协议的,只有一个配置的设备,指出控制管道的最大数据包长度为8个字节。
注意:
字段bDeviceClass与bDeviceSubClass在USB规范中被定义为“指明该设备所属的USB设备类与设备子类”,但一般的应用都会将此两个字段置0,表示各个接口相互独立工作,其所属的设备类将在接口描述符中指出。这一点就暗示了主机端的客户软件是以接口为基本操作对象的,与1.2节中的USB层次结构描述相呼应。
配置信息描述符集合:
_Config_Descriptor:
.dw _Config_Descriptor_End-_Config_Descriptor //bLength: 0x09 byte
.dw 0x02 //bDescriptorType: CONFIGURATION
.dw _Config_Descriptor_Total-_Config_Descriptor //wTotalLength:
.dw 0x00
.dw 0x01 //bNumInterfaces: 1 interfaces
.dw 0x01 //bConfigurationValue: configuration 1
.dw 0x00 //iConfiguration: index of string
.dw 0xC0 //bmAttributes: self powered, Not Support Remote-Wakeup
.dw 0x32 //MaxPower: 100 mA
_Config_Descriptor_End:
_Interface_Descriptor:
.dw 0x09 //bLength: 0x09 byte
.dw 0x04 //bDescriptorType: INTERFACE
.dw 0x00 //bInterfaceNumber: interface 0
.dw 0x00 //bAlternateSetting: alternate setting 0
.dw 0x02 //bNumEndpoints: 3 endpoints(EP0,EP1,EP2)
.dw 0x08 //bInterfaceClass: Mass Storage Devices Class
.dw 0x06 //bInterfaceSubClass:
.dw 0x50 //bInterfaceProtocol
.dw 0x00 //iInterface: index of string
_Interface_Descriptor_End:
_Endpoint1:
.dw 0x07 //bLength: 0x07 byte
.dw 0x05 //bDescriptorType: ENDPOINT
.dw 0x81 //bEndpointAddress: IN endpoint 1
.dw 0x02 //bmAttributes: Bulk
.dw 0x40, 0x00 //wMaxPacketSize: 64 byte
.dw 0x00 //bInterval: ignored
_Endpoint2:
.dw 0x07 //bLength: 0x07 byte
.dw 0x05 //bDescriptorType: ENDPOINT
.dw 0x02 //bEndpointAddress: OUT endpoint 2
.dw 0x02 //bmAttributes: Bulk
.dw 0x40, 0x00 //wMaxPacketSize: 64 byte
.dw 0x00 //bInterval: ignored
_Config_Descriptor_Total:
配置信息包括其配置描述符本身,外加端点描述符、接口描述符以及可能存在的类描述符。
关于配置描述符:
<!--[if !supportLists]-->l <!--[endif]-->字段wTotalLength指明了配置信息的总长度。
<!--[if !supportLists]-->l <!--[endif]-->字段bNumInterfaces指明了接口数目,这里是1,代表此设备只有一个接口。实际应用中,很可能有两个以上的接口,譬如说两个接口,那么这里应填为2,并且在配置信息中要增加一个接口描述符及相配套的描述符,如端点描述符等。文档的第四部分“USB固件开发(复合设备:HID+Mass Storage)”将会举例说明。
<!--[if !supportLists]-->l <!--[endif]-->字段bConfigurationValue指明了配置号,主机在SetConfiguration请求时用此值来选定此配置。如果有两个以上的配置,那么配置号将各不相同。
<!--[if !supportLists]-->l <!--[endif]-->字段iConfiguration指明了描述该配置的字符串描述的索引,0代表不使用。
接口描述符紧接着配置描述符,描述具体的接口信息。接口指明了此设备具体有什么用途。
关于接口描述符:
<!--[if !supportLists]-->l <!--[endif]-->字段bInterfaceNumber指明接口号。如果有多个不同种类的接口,则每种接口的接口号各不相同。
<!--[if !supportLists]-->l <!--[endif]-->字段bAlternateSetting指明可选设置的索引值。意思是如果此设备只有一种接口,但是有两个可选设置,那么这两个设置的bInterfaceNumber值必须相同,且bAlternateSetting值必须不同。文档的第四部分“USB固件开发(复合设备:HID+Mass Storage)”将会举例说明。
<!--[if !supportLists]-->l <!--[endif]-->字段bNumEndpoints指明接口使用的端点数目,不包括端点0(端点0是必须有的且被用在缺省的控制管道上)。
<!--[if !supportLists]-->l <!--[endif]-->字段bInterfaceClass、bInterfaceSubClass和bInterfaceProtocol联合指明接口所属的设备类及协议,也就是接口的具体用途及使用方法。
<!--[if !supportLists]-->l <!--[endif]-->字段iInterface指明描述此接口的字符串描述符的索引,0代表不使用。
端点描述符紧接着接口描述符,描述接口所使用的端点信息。这里是指明了两个端点:Bulk OUT与Bulk IN端点。
关于端点描述符:
<!--[if !supportLists]-->l <!--[endif]-->字段bEndpointAddress指明了端点号与端点的传输方向。这必须与USB接口芯片中的端点信息一致。譬如说,_Endpoint1指明了以Bulk IN方式使用端点1,那么在固件编程的时候,也必须设置USB接口芯片的相关寄存器,指明端点1的使用方式为Bulk IN。
<!--[if !supportLists]-->l <!--[endif]-->字段wMaxPacketSize指明了端点所支持的最大数据包长度。这和USB接口芯片的特性及USB传输方式和传输速度有关。在块(bulk)传输方式下,如果是低速/全速传输,那最大只能为64字节;如果是高速传输,那么最大为512字节。如果接口芯片支持的最大值小于上述最大值,那么当然要以接口芯片的为准。
<!--[if !supportLists]-->l <!--[endif]-->字段bInterval只对用作中断传输的断点起作用,表述主机轮询此端点的最大时间间隔。
字符串描述符:
_String0_Descriptor:
.dw 0x04 //bLength
.dw 0x03 //bDescriptorType
.dw 0x09, 0x04 //bString
_String0_Descriptor_End:
_String1_Descriptor:
.dw _String1_Descriptor_End-_String1_Descriptor //bLength
.dw 0x03 //bDescriptorType
.dw 'S', 0x00 //bString
.dw 'T', 0x00
.dw 'R', 0x00
.dw 'I', 0x00
.dw 'N', 0x00
.dw 'G', 0x00
.dw '1', 0x00
_String1_Descriptor_End:
_String2_Descriptor:
.dw _String2_Descriptor_End-_String2_Descriptor //bLength
.dw 0x03 //bDescriptorType
.dw 'S', 0x00 //bString
.dw 'T', 0x00
.dw 'R', 0x00
.dw 'I', 0x00
.dw 'N', 0x00
.dw 'G', 0x00
.dw '2, 0x00
_String2_Descriptor_End:
_String3_Descriptor:
.dw _String3_Descriptor_End-_String3_Descriptor //bLength
.dw 0x03 //bDescriptorType
.dw 'S', 0x00 //bString
.dw 'T', 0x00
.dw 'R', 0x00
.dw 'I', 0x00
.dw 'N', 0x00
.dw 'G', 0x00
.dw '3', 0x00
_String3_Descriptor_End:
字符串描述符所遵循的字符编码标准是UNICODE,即使用两字节来表示一个字符。唯一要注意的是字符串0有着特殊意义:它返回字符的语言信息即LANGID给主机。此例中,LANGID为0x0409,表示在字符串描述符中字符的语言是US英语。其它3个字符串分别与设备描述符中/iManufacture、iProduct、iSerialNumber字段对应。