USB的语言ID描述符和字符串描述符

这篇文章主要说下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》这本书中,作者写成了数组的形式,如下:

微信图片编辑_20200320115602.jpg

懒得手打了,手机拍一个。

然后在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,以此类推~~~

不是很难理解吧,打完收工!

发表评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据