USB/HID设备报告描述符详解
概述:
报告在这里意思是数据传输(data transfer),而报告描述符是对这些传输的数据作用途(usage)上的说明。
USB通讯协议的规范是以1ms产生一个USB帧(frame),USB设备可以每一个帧中发送和接收一个交换(transaction)。交换是由几个封包(packet)组成,而传输是由一个或几个交换来完成传送一口中有效的数据。在这里,传输和报告的意思相类似。传输方式有四种,初始学一般只要了解控制型传输(control transfer)和中断型传输(interrupt transfer)即可。控制型传输是当需要时才执行传输要求,是最一般的传输方式,组态、命令和状态的通讯都可以使用控制型传输;控制型传输主要用于消息型数据(message-type data)。中断型传输目的在做重复的数据更新(recurring data)传输,精确一点而言,即是在每个有限周期内(bounded period)作至少一次的小量数据发送或接收;所以适用于流动型数据(stream-type data),注意这里所谓的周期时间就是在端点描述符中的轮询间隔时间。报告有三种:input、output和Feature。后面将作进一步介绍。中断型输入管线(interrupt in pipe)仅可以传送input报告;中断型输出管线(interrupt out pipe)仅可以传送output报告;但是控制型管线(control pipe)可以传送input、output和feature报告。端点描述符有声明所使用的端点为何种管线。
数据本身没有任何意义,要赋于用途才能明确其为控制什么(control);例如设备上的按钮指示灯和X与Y轴的位移等都通称控制,数据则为按钮和指示灯的开关状态或X与Y轴的位移量。为了这个目的应运而生报告描述符,其将数据的操控与它的用途作一对一的对应,所以解读报告后就可以知道每个数据作何种操作。所以“传输的数据”和“操作”只是一事件的两种描述方式。用途是以一个32位卷标(称作usage tag)来表示,高16位称作usage page(用途类页),低16位称为usage ID(用途识别名):Usage = (usage page:usage ID)
举例说明:二个字节分别为x和y轴的位移数据,因此第一个字节的usage= (generic desktop:X),而第二个字节的usage = (generic desktop:Y),其中generic desktop为用途的大类别(称作用途类页)之一,x和y轴的操作用途属于此用途类页。文件universal serial Bus HID Usage Table完整列出所有的usage pages(用途类页)和usage ID(用途识别名),使用者必须遵照文件的规范来声明操作的用途。该文件的附录A有十多个报告描述符的范例,值得研究下。
表1、报告描述符的标签
主项目Main Item 标签 Input Output Feature Collection End Collection 代码 全域项目Global Item 标签 代码 0x0? 0x1? 0x2? 0x3? 0x4? 0x5? 0x6? 0x7? 0x8? 0x9? 0xA4 0xB4 Usage Usage Minimum Usage Maximum Designator Minimum Designator Minimum Designator Maximum String Sreing Minimum String Maximum Delimiter 区域项目Local Item 标签 代码 0x0? 0x1? 0x2? 0x3? 0x4? 0x5? 0x7? 0x8? 0x9? 0xA? 0x8? Usage Page 0x9? Logical Minimum 0xB? Logical Maximum Physical Minimum 0xA1 Physical Maximum 0xc0 Unit Exponent Unit Report Size Report ID Report Coumt Push Pop 标签:
用途卷标只是报告描述符诸多标签的一个。表1列出所有的卷标,利用这些卷标取可以清楚完整的描述符操作的用途。报告描述符的语法不同于USB标准描述符,它是以项目(items)方式排列而成,无一定的长度;项目有一个前辍 (prefix),然后跟着一个括号,内为该项目的数据:item = prefix(data)。
项目分成三种类别:主项目,全局项目,区域项目。主项目中的 input,ouput,feature三个卷标用来表示报告中数据的种类,这些是报告描述符中最主要的项目,其他项目都是用来修饰这三种项目。主要项目中其他二个卷标后面再作详细的介绍。
>> Input 项:表示设备操作输入到主机的数据模式。这个数据格式就形成一个输入报告,虽然输入报告可以用控制型管线以get report(input)来传输,但是通常用中断型输入管线来传输以确保在每一固定周期内都能将更新的输入报告传给主机。
>> Output 项:表示由主机输出到装置操作的数据格式。这个数据格式就形成一个输出报告。输出报告通常不适用轮询的方式来传送给设备,而是由应用软件依实际需求以传令方式要求送出输出报告,所以大多用控制型管线以set report(output)指令来将报告送到设备。当然也可以选择用中断型输出管线来传送,只是通常不建议这样用。
>> Feature 项:表示由主机送到设备的组态所需数据的数据格式。这个数据模式就形成一个特征报告。特征报告只能用控制型管线以get report(feature)和set report(feature)指令分别来取得和设定设备的特征值。
>> 范例:考虑一个2X16字的显示装置,它的列数、行数、字宽、和字高为固定值属于feature报告;显示状态例如“就绪”和“输入字错误”则属于 input报告;光标位置和显示的字需可读写,所以属于另一个feature报告;更新显示的字则为output报告。为了区别两个features,要用到全局项目中的report ID,每个feature报告有一个不同的report ID,因而主机请求指令要加上report ID的值:get report(feature,report ID)和Set report(feature,report ID)。
主项目用来定义报告中数据的种类和格式,而说明主项目之意义与用途为全局项目和区域项目。顾名思义,区域性项目只能适用于列于其下的第一个主项目,不适用于其他主项目,若一个主项目之上有几个不同的卷标的区域性项目,则这些区域性项目皆适用于描述该主项目。相反,全局性项目适用于其下方的所有主项目,除非另一个相同卷标的全局性项目出现。为了清楚说明报告描述符,将使用“项目状 态表”(item state table)用来表示在某位址处适用的全局性项目的组合。图1显示全局性项目和区域性项目与所描述的主项目之对应关系。
区域性项目卷标:
简单地说,区域性项目(见表1)只是说明用途而已。Designator是要搭配实体描述符使用的,这里不对实体描述符进行介绍,所以略过这些designator标签。
标签Usage 实际上应该称作Usage ID,它搭配全域项目的Usage Page 卷标才形成前文所定义的用途{usage}﹔但是报告描述符允许在区域项目的Usage 卷标直接用32位的方式来指定用途,这种方式称作扩充式用途指定法(extended usage)以示区别。例如:Usage(Generic
Desktop:Mouse),Usage Minimum(Keyboard:0),和Usage Maximum(Keyboard:101)。很明显的,扩充式用途指定法会取代『项目状态表』中的Usage Page。还有,使用扩充式用途指定法时,数据的高16 个位为用途类页Usage Page,低16 个位则为用途识别名Usage ID。往往一个报告数据会对应到几个操作,因而会有几个用途,例如101 按键的键盘利用不同代码代表不同的键,每一个键是一个操作,有自己的用途,要将所有Usage ID 列出不太现实,所以就需要Usage Minimum 和Usage Maximum 二个标签。以键盘为例,主项目之上只要二个区域项目:Usage Minimum (0), Usage Maximum (101)。如此一来,则无键按下(Usage ID 为0)和101 键中任一
键被按下(Usage ID 为1 至101)的用途都被赋于到一个报告数据上,后面会有一个范例进一步解说。
卷标String Index 类似卷标Usage,而卷标String Minimum 和String Maximum 则类似标签Usage Minimum 和Usage Maximum。如果希望某个操作对应到一个字串,则用String Index 来描述该操控的报告数据,这个字符串在字符串描述符中,StringIndex (data)项目中的data 是这个字符串在字符串描述符中的位置索引。如果需要用到几个字符串,则可以使用String Minimum 来指向字符串描述符中被用到字符串的最先位置索引,和String Maximum 来指向最后位置索引。
标签Delimiter 很少用到,请参考Universal Serial Bus HID Usage Tables 文件中Appendix B 的范例详细说明。
全局项目卷标
全局项目的卷标事实上只要Usage Page,Logical Minimum,Logical Maximum, Report Size,Report ID,Report Count 就足够了。表2 列了二个音量操作的例子(音量增减键和音量旋钮)将用来辅助说明这些卷标,不过主项目括号内的数据会在后文中再做说明。
表2、音量操作举例 音量减键 Usage Page(consumer) Usage(Volume) Logical Minimum(-1) Logical Maximum(1) Report Size(2) Report Count(1) 音量旋钮 Usage Page(Consumer) Usage(Volume) Logical Minimum(0) Logical Maximum(100) Report Size(7) Report Count(1) Relative) Input(Data,Variable,Relative) Input(Data,Variable,Absolute,No Wrap,Linear,No
查阅Universal Serial Bus HID Usage Tables 文档,这两个例子的用途需要令为(Consumer: Volume)。Usage Page 前面已经介绍过了。Report Size 用来设定主项目(Input,Output,Feature)的报告字段大小,它的单位是位。主项目会对每个操作产生一个报告字段,字段大小则由 Report Size 决定。而Report Count 用来设定主项目之报告字段的数目,其等于操作的数目。音量增减键的例子中ReportCount (1)表示主项目Input 只产生一个字段,所以可知只有一个音量增减键﹔而Report Size (2)表示这个字段为2 位。另一个音量旋钮例子也是只有一个旋钮,所以用Report Count (1)﹔但是因为Report Size (7),所以该旋钮的数据字段为7位,可以表示0到127之数值。再举一例,如果是鼠标的三个按键,每个按键占用一个一位的字段,则Report Size (1), Report Count (3)﹔那么这个报告长度为三个位,可以同时呈现出三个按键的状态(原状或被按下)。
Logical Minimum 和Logical Maximum 在说明每个报告字段的数值范围,这是纯数值所以称为逻辑数值(logical value)。音量增减键的例子中Logical Minimum (-1),Logical Maximum (1)表示只会出现-1, 0, 1 三种数值,所以用到二位(即ReportSize(2)),0b11 代表-1,0b00 代表0,0b01 代表1。在音量旋钮例子中,虽然用7 位作一字段,但是旋钮仅会产生0 到100 的数值,因为Logical Minimum (0)和Logical Maximum (100)。假如实体程序错误产生超出逻辑数值的范围,则主机将会忽略该数值,这种数值称作null value。