查阅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。
当要将同一种报告分成数个部分,则每一个部分要给予一个识别值,这时就需用到卷标Report ID,其数据值必须从1起算,不可使用0。没有赋予Report ID 标签的报告,主机有可能会将其Report ID 视为0,所以Report ID (0)被要求不能使用。这个标签对控制型管线才有意义,因为它可以在请求报告时指定Report ID的值。对于中断型管线,其为周期性传输报告,所以每次都会将所属报告传完,没有仅传输部分之必要,所以Report ID 标签就无意义。
其它的全局项目卷标可分为辅助工具(Push 和Pop)和物理量说明(Physical Minimum,Physical Maximum,Unit Exponent,和Unit)。Push 卷标将『项目状态表』存放到缓存器(stack),而Pop 卷标反过来将缓存器最顶层的『项目状态表』取回来取代目前之状态表。这二个标签对很长的报告描述符才有用处,因为其可以节省多列一些全局项目。读者当要使用到时,参考Universal Serial Bus HIDUsage Tables 文件的Appendix A.7 节中范例则可获得正确使用方式。
不同厂家的鼠标有不同的分辨率,若要让主机知道鼠标的分辨率,就必须用到物理量的标签。不使用也不会影响到鼠标的功能,只是使用者无法由主机的驱动程式得知分辨率而已。但是量测装置(例如温度计)的应用程序必须知道物理量,则这些标签就必备了。分辨率r 的算法如下
r = ((lM-lm)/(PM-Pm))X10iUnit
其中lm = Logical Minimum,lM = Logical Maximum, pm = Physical Minimum,pM = Physical Maximum, i = Unit Exponent。以400-dpi 的鼠标为例如表3。
表3:解析度的范例
Logical Minimum(-127)
|
R = ((127-(-127))/(3175-(-3175))X10-4
= 400counts per inch
|
Logical Minimum(127)
|
Physical Minimum(-3175)
|
Physical Minimum(3175)
|
给定Logical值,计算出physical值:
((PM-Pm)/2)/10i=((127-(-127))/400)/2=0.3173
à|PM|=|Pm|=3175,i=4
|
Unit Exponent(-4)
|
Unit(inch)
|
注意,若是Unit Exponent 未定义,则视为i = 0﹔若是Physical Minimum和Physical Maximum有一个以上未定义,则视为PM=lM和pm=lm。所以标签Physical Minimum和Physical Maximum 一定要同时定义,否则无意义。这些卷标的括号内数字为有符号的整数,可以是一个字节或二至四个字节,字节数目会在卷标代码的最低二位定义,详情后文会叙述。卷标Unit 的括号内数据比较复杂,总共用了7 个四位(nibbles)来描述,各个四位之意义如表4,其中第8 个四位
未被使用到。
表4:标签Unit的信息格式
Nibbe
|
7
|
6
|
5
|
4
|
3
|
2
|
1
|
0
|
|
0
|
Luminous Intensity
|
Current
|
Temperature
|
Time
|
Mass
|
Length
|
System
|
HID 共享了四种单位系统,最低的四位就是决定使用的单位系统(System),不同的系统中当然物理量的单位也不一样。单位和系统间的对应关系如表5。
表5:物理量的单位之编码法
|
None
|
SI Linear
|
SI Rotation
|
English Linear
|
English Rotation
|
System
|
0x0
|
0x1
|
0x2
|
0x3
|
0x4
|
Length
|
None
|
公分
|
径度
|
英寸
|
角度
|
Mass
|
None
|
公克
|
公克
|
Slug
|
Slug
|
Time
|
None
|
秒
|
秒
|
秒
|
秒
|
Temperature
|
None
|
凯氏(绝对温度)
|
凯氏(绝对温度)
|
华氏
|
华氏
|
Current
|
None
|
安培
|
安培
|
安培
|
安培
|
Luminous intensity
|
None
|
Candela
|
Candela
|
Candela
|
Candela
|
除了最低四位的值用来选择单位系统外,其余每个四位皆表示该单位的幂次方,每个四位(nibble)都是有符号的整数,可表示的范围为-8 至+7:
-8
|
-7
|
-6
|
-5
|
-4
|
-3
|
-2
|
-1
|
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
08h
|
09h
|
0ah
|
0bh
|
0ch
|
0dh
|
0eh
|
0fh
|
00h
|
01h
|
02h
|
03h
|
04h
|
05h
|
06h
|
07h
|
因此长度的单位若为公分则Unit (data)中data 的码为0x11,若为英吋则为0x13,这二者中Length 的四位值皆为1 表示幕次方为1,即cm1 或in1 。质量单位为公克之码为0x0101,加速度单位为公分除以平方秒之码为0xE011,其中E 代表-2。所以力量单位为质量(公克)乘于加速度(公分/平方秒)的码为0xE111。能量单位焦尔为力量乘于长度之码为0xE121,其等义于s?2g cm2 和单位系统为SI Linear。
主项目
主项目中产生报告数据格式的三个卷标(Input,Output,和Feature)具有共通的数据定义,这些数据和其代码列于表6中。目前用到9个位来表示这些数据。如果第九位(bit 8)为0,则仅需用一个字节来表示该数据,即忽略第九位。如果第九位为1,就需用到二个字节来表示该数据。
表6:主项目的信息代码
Bit
|
8
|
7
|
6
|
5
|
4
|
3
|
2
|
1
|
0
|
0
|
Bit Field
|
Non Volatile
|
No Null Position
|
Preferred State
|
Linear
|
No Wrap
|
Absolute
|
Array
|
Data
|
1
|
Buffered Bytes
|
Volatile
|
Null State
|
No Preferred
|
Non Linear
|
Wrap
|
Relative
|
Variable
|
Constant
|
Data/Constant:主项目之数据为可变值(设为Data),或为固定不可变值(设为Constant)。Constant 都用于Feature 的报告,或是用于填充位(padding),使报告长度以字节为单位。
Array/Variable:主项目之数据的每个字段可以表示几个不同的操作的其中一个被触发(设为Array),或是每个字段仅表示一个操作(设为Variable)。如果是Variable,则Report Count 的数据值等于报告数据的字段数。若是Array,则Report Count 的数据值表示可以同时被触发的最多操作数目。后文中键盘之例会解说Array 的用法。
Absolute/Relative:主项目的数据是以相对于固定的基准点方式提供绝对数值(设为Absolute),或是提供相对于前次报告的相对值(设为Relative)。
【范例说明】前文中的音量操控范例,因为都是Data 和Variable,二者的操作值皆为变化值,且一个字段仅表示一个操作。但是音量增减键的例子为Relative,所以若报告值由0 变成+1,则音量增大一个刻度,反之由0 变作-1 则音量减小一个刻度,因而音量大小因输入值而作相对的变化。然而音量旋钮的例子为Absolute,当输入值为最小值0 时,为静音,而输入值为最大值100 时,为最大音量,其余值作百分比的音量调整,输入值和音量成绝对关系。
No Wrap/Wrap:主项目的数据值达到极值后会转为极低值,反之亦同,称作卷绕(设为Wrap)。例如一个转钮可以做360°旋转,输出值从0 至10,若设定为Wrap,则值达10 后,在同方向旋转则值变为0,反之若达到0,再转就得到10。
Linear/Nonlinear:主项目的数据与操作刻度为线性关系(设为Linear),或为非线性(设为Nonlinear)。
Preferred State/No Preferred:主项目对应的操作再不被触发时会自动恢复到初始状态(设为Preferred State),或是不会恢复原状(设为No Preferred)。例如键盘的按键和会自动置中(self-centering)的游戏杆,皆为Preferred State。
【范例说明】再以音量操作为例,音量增减键的例子都没标注No Wrap,Linear,Preferred State,但是没有标注即认定其属于默认值,所以等同于是这些设定,只是这些设定对此例的操作无意义,所以不标出。音量旋钮的例子明确指出其为No Wrap, Linear, No Preferred,可见旋钮不是循环旋转,输出值与旋转角呈线性关系,旋钮释放开时会停留在释放前位置(因为No Preferred)。
No Null Position/Null State:主项目对应的操和有一个状态,其不会送出有意义的数据,即数据将不在Logical Minimum 和Logical Maximum 之间,这种操控要标注Null State,否则为No Null Position。例如几个按键,而无键被按下的用途没有声明在Usage 之列,则可以在主项目的数据中设Null State,将无键被按下的状态排除在Logical Minimum 和Logical Maximum区间之外,进一步请参看Universal Serial Bus HID Usage Tables 文件的Appendix A.3 节中范例。
Non Volatile/Volatile:主项目Feature 的数据不允许被主机改变(设为Non Volatile),或是允许被主机改变(设为Volatile)。注意主项目Input 和Output,此标注设定无意义,所以bit 7 的代码必须为0。
Bit Field/Buffered Bytes:主项目的数据格式要以字节为单位,不足构成字节时自动填充成字节则设Buffered Bytes。
最后来谈谈主项目的其它二个卷标:Collection 和End Collection。以鼠标而言,在实体上是一个指针(pointer),只是应用为计算机鼠标﹔而这个指针含有三个按键和二个平移轴X 和Y。所以指针的报告是由不同格式的数据所构成,因而需要用到Collection 和End Collection 将几个Input 项目集结成一组,其用途为指针,再用Collection 和End Collection 将指针括起来说明其应用为鼠标。
卷标End Collection 没有跟随任何资料。但是卷标Collection 跟随一个字节的数据,例如指针的数据名为Physical,而鼠标的为Application。所有Collection的数据名称与代码如表7:
表7:报告集合的名称与代码
|
Physical
|
Application
|
Logical
|
Report
|
Named Arrary
|
Usage Modifier
|
Usage Switch
|
Reserved
|
Vendor-defined
|
代码
|
0x00
|
0x01
|
0x02
|
0x03
|
0x04
|
0x05
|
0x06
|
0x07-0x7f
|
0x80-0xff
|
用途
|
CP
|
CA
|
CL
|
|
Nary
|
US
|
UM
|
|
|
Collection 的数据名称很难有一个准则来给定,Universal Serial Bus HID Usage Tables文档中将各种用途的用途种类(usage type)列出,使用者必须依据用途种类来指定Collection 的数据名称,例如鼠标,键盘和游戏杆的用途种类为CA,所以要用Collection (Application),而指针为CP,所以用Collection (Physical)。
编码
报告描述符的项目编码有二种:短项目和长项目。长项目仅是保留给未来使用,所以不作介绍。短项目的编码形式如下:
Bits 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
[data]
|
[data]
|
bTag
|
bType
|
bSize
|
Bytes 2 1 0
最低字节分别标注项目大小(bSize),项目类别(bType),和项目卷标(bTag)﹔
其中bTag 占4个位,其余二者各占2个位。BSize 用来指出项目的数据所需字节的数目,该数目仅可以为0(当bSize=0),1(当bSize=1),2(当bSize=2),和4(当bSize=3)﹔注意不可以为3个字节。大部分的卷标仅需一个字节的数据﹔全局项目的卷标Unit 比较特殊有可能最多用到4 个字节来表示其资料。
标签代码bTag 已经于前章的表1 中描述,例如Input 的标签代码『0x8?』中8 即为bTag 之值﹔再如标签Feature 之bTag=11,而Unit 之bTag=6。主项目之bType=0,全局项目之bType=1,而区域项目之bType= 2。所以在前章的表1 中的主项目卷标代码中的『?』可以改为『00nnB』,全局项目的可以改为『01nnB』,而区域项目的可以改为『10nnB』,其中nn 代表bSize。
实际范例
这里举一个Device Class Definition for Human Interface Device 文件的附录E 中的整合鼠标的键盘装置的范例。这个装置只有一个组态描述符,但是这个组态具有二个接口,一个为键盘接口(接口编号为0x00),另一个为鼠标接口(接口编号为0x01)。每一个接口都有一个自己的中断型输入端点,输出则都靠内定的控制型端点0。这个整合鼠标的键盘装置的标准描述符,请参考附件中的『USB 标准描述符之技巧』文件。在该文中所使用的范例即为整合鼠标的键盘装置,只是仅列出一个接口描述符(即编号为0x00 的键盘接口),另一个编号为0x01 的鼠标接口在该文中没有列出,读者可以自行参考本文所附的描述符程序代码descriptor.asm(即在标记为interface_descriptor01,hid_descriptor01,和endpoint_descriptor01 处)。
表8:范例的输入报告格式
键盘(输入报告)
|
鼠标(输入报告)
|
Byte
|
7
|
6
|
5
|
4
|
3
|
2
|
1
|
0
|
Byte
|
7
|
6
|
5
|
4
|
3
|
2
|
1
|
0
|
0
|
Modifier keys
|
0
|
Pad
|
Buttons
|
1
|
Reserved
|
1
|
X displacement
|
2
|
Keycode 1
|
2
|
Y displacement
|
3
|
Keycode 2
|
|
|
4
|
Keycode 3
|
|
|
5
|
Keycode 4
|
|
|
6
|
Keycode 5
|
|
|
7
|
Keycode 6
|
|
|
表9:范例的输出报告格式
键盘输出报告
|
Byte
|
7
|
6
|
5
|
4
|
3
|
2
|
1
|
0
|
0
|
Pad
|
LED’s
|
这个范例有输入报告和输出报告,其中输入报告有二组,一组属于键盘接口,另一组属于鼠标接口。表8 列出输入报告的数据格式。而输出报告只有键盘接口需要,表9 为输出报告的数据格式。因为有二个接口,所以有二个报告描述符,分属于不同的界面,二个报告描述元都列于表10 中。键盘的报告描述元中整个报告集合的用途为(Generic Desktop: Keyboard),由于键盘用途属于应用性,所以标签Collection 的资料名为Application。由于单独键本身的用途类页不再是Generic Destop,而是Keyboard(注意Keyboard 也可为用途类页),所以在项目Collection(Application)下重新声明用途页Usage Page (Keyboard)。根据Universal Serial Bus HID Usage Tables 文件,鼠标是指针的一种,只是应用为计算机的鼠标,所以报告的内层集合的用途为(Generic Desktop: Pointer),外层的应用性集合的用途为(Generic Desktop: Mouse)。注意鼠标的按钮和位移轴又分属不同的用途类页,所以在内层集合中还要重新声明用途类页。按钮的用途类业为Buttons,而二个位移轴所属的用途类业为Generic Desktop。
表10:报告描述符范例
键 盘
|
鼠 标
|
项 目
|
编 码
|
项 目
|
编 码
|
Usage Page (Generic Desktop),
|
0x0105
|
Usage Page (Generic Desktop),
|
0x0105
|
Usage (Keyboard),
|
0x0609
|
Usage (Mouse),
|
0x0209
|
Collection (Application),
|
0x01A1
|
Collection (Application),
|
0x01A1
|
Usage Page (Keyboard),
|
0x0705
|
Usage (Pointer),
|
0x0109
|
Usage Minimum (224),
|
0xE019
|
Collection (Physical),
|
0x00A1
|
Usage Maximum (231),
|
0xE729
|
Usage Page (Buttons),
|
0x0905
|
Logical Minimum (0),
|
0x0015
|
Usage Minimum (1),
|
0x0119
|
Logical Maximum (1),
|
0x0125
|
Usage Maximum (3),
|
0x0329
|
Report Size (1),
|
0x0175
|
Logical Minimum (0),
|
0x0015
|
Report Count (8),
|
0x0895
|
Logical Maximum (1),
|
0x0125
|
Input (Data, Variable, Absolute),
|
0x0281
|
Report Size (1),
|
0x0175
|
Report Size (8),
|
0x0875
|
Report Count (3),
|
0x0395
|
Report Count (1),
|
0x0195
|
Input (Data, Variable, bsolute),
|
0x0281
|
Input (Constant),
|
0x0181
|
Report Size (5),
|
0x0575
|
Usage Minimum (0),
|
0x0019
|
Report Count (1),
|
0x0195
|
Usage Maximum (101),
|
0x6529
|
Input (Constant),
|
0x0181
|
Logical Minimum (0),
|
0x0015
|
Usage Page (Generic Desktop),
|
0x0105
|
Logical Maximum (101),
|
0x6525
|
Usage (X),
|
0x3009
|
Report Size (8),
|
0x0875
|
Usage (Y),
|
0x3109
|
Report Count (6),
|
0x0695
|
Logical Minimum (-127),
|
0x8115
|
Input (Data, Array),
|
0x0081
|
Logical Maximum (127),
|
0x7F25
|
Usage Page (LEDs),
|
0x0805
|
Report Size (8),
|
0x0875
|
Usage Minimum (1),
|
0x0119
|
Report Count (2),
|
0x0295
|
Usage Maximum (5),
|
0x0529
|
Input (Data, Variable, Relative),
|
0x0681
|
Logical Minimum (0),
|
0x0015
|
End Collection,
|
0xC0
|
Logical Maximum (1),
|
0x0125
|
End Collection
|
0xC0
|
Report Size (1),
|
0x0175
|
|
|
Report Count (5),
|
0x0595
|
|
|
Output (Data, Variable,Absolute),
|
0x0291
|
|
|
Report Size (3),
|
0x0375
|
|
|
Report Count (1),
|
0x0195
|
|
|
Output (Constant),
|
0x0191
|
|
|
End Collection
|
0xC0
|
|
|
从表8 看出,键盘的输入报告中最低的8位分别代表键盘上的8个修饰键(亦即左和右边的Control 键、Shift 键、Alt 键、和Windows 键),平常每位的值为0,当对应的修饰键被压下时则位值为1。键盘报告描述符中第一个Input 项目必须声明这8位的格式。这8个修饰键为用途类页Key Codes 中的第224 个键到第231 键,所以用Usage Minimum (224)和Usage Maximum (231)来声明。每一个按键的逻辑值不是0 就是1,所以用Logical Minimum(0)和Logical Maximum (1)
来声明。很显然的,每一个键占用一个数据位,而共需8个位,因此ReportSize ( 1),而Report Count (8)。请特别注意,最低位对应到Usage Minimum 的声明,而最高位所对应的为Usage Maximum 的数据内容。这8 个位值是可变的数据,每一个位是独立的变量,提供的值不须与前次的值有相对关系。总结而言,该8位的主项目必须为Input (Data, Variable, Absolute)。
键盘的输入报告中次高的字节被保留,该字节的值无意义,也不需更新,所以用Input (Constant)来填充(padding)。而最高的6 个字节则是最近同时被压下的6 个按键之代码。这个键盘装置有101 个键,而报告格式的最高的6 个位组中任何一个字节都可以代表101 个键之任一键,所以这101 键再加上无键被压下状态(代码为0x00)构成一组操作数组,这个装置允许同时压下6个键。
键盘报告描述符中Input (Data, Array)即在声明这6个字节的数据格式,注意这个数据格式的逻辑值声明和用途代码声明具有相同的数据值(即0 和101)。
键盘有一个输出报告,长度为1个字节,但是只用到最低5个位来代表五个LED 的操控,所以最高的3个位需要用Output (Constant)项目来填充。输出报告的用途类页不再是Key Codes,而是Page of LEDs,所以要重新声明Usage Page,而主项目为Output (Data, Variable, Absolute)。这个项目的数据内容如同输入报告的最低8位所声明的主项目之数据内容,不再作说明。因为键盘接口的端点描述符只有声明一个中断型输入端点,所以输出报告需要依赖内定控制型端点0来传送。输入报告由声明的输入端点作中断型输入传输,当然也可以依需要用内定控制型端点0来作控制型读入传输。
鼠标的报告描述符的输入数据格式中最低的一个字节只有最低3个位有意义,其分别对应到鼠标上的三个按钮,用途类页为Buttons。其它二个字节的用途为(Generic Desktop: X)和(Generic Desktop: X),分别对应到鼠标X 轴和Y 轴的位移操控。这二个位移值得逻辑范围为-127 到127,即一个字节可以表示最大范围。位移的数值是相对值,所以主项目为Input (Data, Variable, Relative)。
HID 描述符编辑工具
USB 协会提供了一个HID 描述符编辑工具称作HID Descriptor Tool,其执行程序为DT.exe。这个工具软件可以在USB 网站上取得。虽然称作HID 描述符工具,事实上,仅提供编撰报告描述符之用。执行DT.exe 后会出现如图2 之窗口,小内窗口HID Items 列出所有报告描述符的标签。以前面所举的实际范例中键盘的报告描述符为例,首先点选[USAGE_PAGE],后会出现一个次窗口列出所有的Usage Page 的选项,这个例子要选[Generic Desktop],按[OK]后则次窗口消失,DT 的主窗口中的右边小内窗口Report Descriptor 就出现Usage Page(Generic Desktop)并跟随着该项目的编码05 01(低字节在左边),也就是这个工具可以帮助作自动编码的工作。程序员只要输入项目的卷标和内容,则可以由这个工具软件提供报告描述符的程序代码。接着,当点选第二个项目[USAGE]时,DT 软件会根据前面的编签Usage Page 的内容Generic Desktop,而产生一个次窗口列出Generic Desktop 包含的所有Usage 选项。同样道理,当选完Usage Page
(Keyboard),再要编撰Usage Minimum 和Usage Maximum 时,所出现的次窗口则为Usage Page (Keyboard)所包含的全部Usage 选项,选第224 个为Left Control键当用途范围的最小者,再选第231 个为Right GUI 键当用途范围的最大者。其他项目的编撰以此类推。
在主窗口下,点选下拉选项[File]中的[Info],则会出现讯息窗口,告知编撰的描述符中项目的个数和描述符长度所需字节的数目。编撰报告描述符完成后,还要做语法检验,这时点选下拉选项[Parse Descriptor],则DT 软件会告知检查的结果,并提供错误原因与更改的建议。
总结
最后一个问题是如何将报告描述符加入微控器的汇编程序。对于任何一种描述符,都是以汇编语言中的一个标记来分辨,例如第一个接口的报告描述符的标记就称作hid_report_descriptor00;同样的,第二个界面的就称作hid_report_descriptor01。记得在报告描述符结束处也加上一个标记,如end_hid_report_descriptor00 和end_ hid_report_descriptor01。这个结束标记除了有助于阅读程序外,其最主要的用处是可以用来计算描述符的长度(即字节数)。例如使用dwl end_hid_report_descriptor00 - hid_report_descriptor00组译器就会自动算出第一个报告描述符的长度,这个长度以二个字节来记载。“dwl”为汇编语言的指示,在于储存二个字节的数据,储存的方式为little Endian。所谓little Endian 方法,就是将低字节的值存于低地址值的内存空间,高字节之值存于高地址值处。