这篇文章主要说下USB里面的语言ID描述符和字符串描述符,这两个描述符既简单又麻烦。说它简单,是因为好理解。语言ID,就是用什么语言和USB设备通信。说它麻烦,是因为USB官网在2019年下半年做了一次更新,原本和语言ID相关的一个文件:USB_LANGIDs.pdf,现在官网上找不到了。
还好影响不大,我们可以在网上找到一些别人分享的信息。
首先,在USB协议中,语言ID描述符和字符串描述符是配套出现的。因为只有你确定了用什么语言,才能读懂后面的字符串。
其次,字符串描述符在USB协议中是可选的。前面在说设备描述符的时候,提到过几个字符串索引,对应的就是这里生成的字符串,如图:
这三个索引,我们当时分别设置了1、2、3.
USB主机通过GET_DESCRIPTOR请求来获取字符串描述符和语言ID。如果请求里的索引值是0,表示获取语言ID;如果是非零值,表示获取该值对应的字符串。
先看语言ID描述符,结构如下:
偏移量 |
名称 |
大小 |
说明 |
0 |
bLength |
1 |
描述符长度(不定) |
1 |
bDescriptorType |
1 |
描述符类型(字符串描述符为0x03) |
2 |
wLANGID[0] |
2 |
语言ID号0 |
… |
… |
… |
… |
2*n+2 |
wLANGID[n] |
2 |
语言ID号n |
我们依次看下。
bLength,描述符长度,不解释。唯一需要注意的一点就是长度和HID描述符一样,不确定。
bDescriptorType,描述符类型。看代码:
#define USB_DESC_TYPE_DEVICE 0x01U
#define USB_DESC_TYPE_CONFIGURATION 0x02U
#define USB_DESC_TYPE_STRING 0x03U
#define USB_DESC_TYPE_INTERFACE 0x04U
#define USB_DESC_TYPE_ENDPOINT 0x05U
#define USB_DESC_TYPE_DEVICE_QUALIFIER 0x06U
#define USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION 0x07U
#define USB_DESC_TYPE_BOS 0x0FU
所以,从类型我们也能看出,语言ID描述符也属于字符串描述符。
wLANGID[0],语言ID号。这就是前面提到的,比较麻烦的一个地方。原本所有国家的语言ID号都能从USB_LANGIDs.pdf这个文档中查询,但后来USB官网搞了一次更新,现在找不到这个文档了。
通过百度的方式,我们可以得知:
中文的是0x1404,美式英语是0x0409.但是书中电脑圈圈测试中文,发现没有效果,所以最终使用的还是英文。
所以,语言ID描述符可以写成(以下代码来自STM32CUBE自动生成的代码):
#define USBD_LANGID_STRING 1033
/** USB lang indentifier descriptor. */
__ALIGN_BEGIN uint8_t USBD_LangIDDesc[USB_LEN_LANGID_STR_DESC] __ALIGN_END =
{
USB_LEN_LANGID_STR_DESC,
USB_DESC_TYPE_STRING,
LOBYTE(USBD_LANGID_STRING),
HIBYTE(USBD_LANGID_STRING)
};
然后是字符串描述符,表格如下:
偏移量 |
名称 |
大小 |
说明 |
0 |
bLength |
1 |
描述符长度(不定) |
1 |
bDescriptorType |
1 |
描述符类型(字符串描述符为0x03) |
2 |
bString |
N |
UNICODE编码的字符串 |
表格的前两项都很好理解,咱们就不解释了。看第三项(也就是2),UNICODE编码。在《圈圈教你玩USB》这本书中,作者写成了数组的形式,如下:
懒得手打了,手机拍一个。
然后在STM32自动生成的代码中,它们换了一种方式:
#define USBD_MANUFACTURER_STRING "stm32_game"
/**
* @brief Return the manufacturer string descriptor
* @param speed : Current device speed
* @param length : Pointer to data length variable
* @retval Pointer to descriptor buffer
*/
uint8_t * USBD_FS_ManufacturerStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length)
{
UNUSED(speed);
USBD_GetString((uint8_t *)USBD_MANUFACTURER_STRING, USBD_StrDesc, length);
return USBD_StrDesc;
}
/**
* @brief USBD_GetString
* Convert Ascii string into unicode one
* @param desc : descriptor buffer
* @param unicode : Formatted string buffer (unicode)
* @param len : descriptor length
* @retval None
*/
void USBD_GetString(uint8_t *desc, uint8_t *unicode, uint16_t *len)
{
uint8_t idx = 0U;
if (desc != NULL)
{
*len = (uint16_t)USBD_GetLen(desc) * 2U + 2U;
unicode[idx++] = *(uint8_t *)(void *)len;
unicode[idx++] = USB_DESC_TYPE_STRING;
while (*desc != '\0')
{
unicode[idx++] = *desc++;
unicode[idx++] = 0U;
}
}
}
使用函数直接生成相应的UNICODE码,很巧妙。
所以,当USB主机发送GET_DESCRIPTOR请求来获取字符串描述符时,如果索引是0,则发送语言ID,如果索引是1,发送厂商字符串,如果是2,以此类推~~~
不是很难理解吧,打完收工!