购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

第14章
字符串描述符:超市招牌信息

超市一般都会有一个招牌,设计足够好的招牌能够给顾客留下深刻的印象,从而达到期望的品牌推广效果。例如,我可以开一家名为“龙虎”的超市。但是在具体设计时,还要考虑到区域因素,如果在国外开超市,还应该取一个英文名字。同样的道理,字符串描述符相当于设备的招牌,当设备第一次连接主机时,招牌名称通常就会像图8.6那样弹出来,有所不同的是,字符串描述符是可选的。

我们可以总结出超市招牌的两个特点:其一,招牌肯定有一个主要名称;其二,不同招牌使用的语种可能会不一样。字符串描述符同样具备这两个特点,所以它对应有两个描述符(结构相同)。表示语种的字符串描述符如表14.1所示。

表14.1 表示语种的字符串描述符

字符串描述符的第1个字段仍然代表总的字节长度,第2个字段代表描述符的种类(此处应为0x03,见表13.3),接下来是表示语种的语言标识符(Language Identifier, LANGID),每种LANGID由两个字节构成,一个字符串描述符可以支持多个LANGID,所有可以使用的LANGID被定义在单独的LANGIDs文档中,已定义的部分LANGID如表14.2所示。

表14.2 已定义的部分LANGID

例如,0x0804代表 汉语-中华人民共和国 ,0x0409代表 英语-美国 。清单12.2中的Joystick_StringLangID数组就是代表语种的字符串描述符。很明显,定义的语言是“英语-美国(0x0409)”,这个16位二进制数字同样3次出现在表13.5中,也就意味着使用“英语-美国”语种来解析从设备获得的字符串流(Unicode编码),而具体的字符串流则被定义在另一个字符串描述符中,其结构如表14.3所示。

表14.3 字符串描述符结构

表14.3中的bString字段为设备实际返回的字符串流,用户可以自定义长度(取决于bLength字段),清单12.2中定义的Joystick_StringVendor、Joystick_StringProduct、Joystick_StringSerial数组分别保存了厂商、产品、设备序列号的字符串信息,所以表13.5中的iManufacturer与iProduct字段分别显示了“STMicroelectronics”与“STM32 Joystick”。

细心的读者可能会问,为什么iSerialNumber字段不显示“STM32”呢?问得好!前文已经提过,Joystick_StringSerial数组并没有被关键字const修饰,所以数组数据是可以被修改的。设备在上电启动时会首先调用Joystick_init函数进行初始化(现阶段只需要知道是这样就行了,后续还会全面梳理初始化流程),该函数被定义在usb_prop.c源文件中,如清单14.1所示。

清单14.1 usb_prop.c源文件(部分)

可以看到,在Joystick_init函数一开始就调用了Get_SerialNum函数,该函数定义在hw_config.c源文件中,如清单14.2所示。

清单14.2 hw_config.c源文件(部分)

每一片STM32单片机的唯一96位ID存放的地址为ID1(0x1FFFF7AC)、ID2(0x1FFFF7B0)、ID3(0x1FFFF7B4),每一个地址存放32位二进制数字。Get_SerialNum函数从中取出并计算出48位二进制(12位十六进制)数字后,将每一位十六进制数字转换为相应的Unicode并填充到Joystick_StringSerial数组,这是由IntToUnicode函数实现的。十六进制数字转换为ASCII的原理如图14.1所示(补0就是相应的Unicode),此处不再赘述。

图14.1 十六进制数字转换为ASCII

第一次调用IntToUnicode函数时将其中8位十六进制数字进行转换,它被填充到Joystick_StringSerial数组索引为2的位置(原来字符“S”对应的索引),由于每个Unicode对应两个字符(ASCII补0),所以剩下4位十六进制数字对应的Unicode被填充到Joystick_StringSerial数组索引为18(8×2+2)的位置。

那为什么将清单12.2中设备描述符中的iManufacturer、iProduct、iSerialNumber字段分别设置为1、2、3后,表13.5中就会出现对应的字符串呢?我们从清单14.1可以看到,其中定义了一个ONE_DESCRIPTOR类型的数组String_Descriptor,而ONE_DESCRIPTOR是一个定义在usb_core.h头文件中的结构体类型,如清单14.2所示。

清单14.3 ONE_DESCRIPTOR结构体

从清单14.3中可以看到,ONE_DESCRIPTOR结构体包含了指向描述符数组数据的指针(首地址)与描述符数据的字节长度,它们能够唯一确定字符串的来源,而数组String_Descriptor分别使用Joystick_StringLangID、Joystick_StringVendor、Joystick_StringProduct、Joystick_StringSerial字符串描述符数组的名称(首地址)与字节长度进行了初始化,我们在设备描述符中设置的iManufacturer、iProduct、iSerialNumer字段分别对应String_Descriptor数组中的索引。

在讨论总线枚举过程时提过,字符串描述符是可选的,如果设备没有字符串描述符,相应的索引值必须为0。主机通常分两个步骤获取字符串描述符,首先从设备获取索引值为0(String_Descriptor数组中LANGID的索引号)对应的字符串描述符,也就是获得设备支持的语种(可以是多个语种),然后主机再次向设备获取其他索引值不为0的字符串描述符,并使用选择的语种进行字符串流的解析。表14.4给出了游戏操纵杆设备的字符串描述符信息。

表14.4 游戏操纵杆设备的字符串描述符信息 hjPSQQz31vRUJtMjSa7wSaPnWtXOyPkc2L/L33oBKSqxrfVek7a5yClcC0o/IXB8

点击中间区域
呼出菜单
上一章
目录
下一章
×