3. USB描述符设计与emUSB配置
USB设备与主机之间的“自我介绍”完全依赖描述符。主机上电枚举设备时,会依次读取设备描述符、配置描述符、接口描述符、端点描述符和字符串描述符,据此判断设备类型、供电需求、可用接口与传输能力,并加载匹配的驱动。emUSB-Device 把这些描述符抽象成 C 结构体与配置 API,开发者只需按 USB 规范填写字段,再由协议栈在枚举阶段按正确顺序返回给主机。理解描述符的层级与字段含义,是 emUSB 开发中最基础也最容易出错的一环。
描述符的层级关系
USB 描述符是树状结构:一个设备描述符下挂一个或多个配置描述符,每个配置描述符下挂一个或多个接口描述符,每个接口描述符下挂零个或多个端点描述符,字符串描述符则被其它描述符通过索引引用。主机按“设备→配置→接口→端点”的顺序逐层解析。下图展示了这一层级以及各描述符之间的引用关系。
设备描述符与配置描述符
设备描述符是主机读取的第一个描述符,长度固定 18 字节。关键字段包括 bcdUSB(协议版本,如 0x0200 表示 USB 2.0)、bDeviceClass(设备类,0 表示由接口描述符定义)、idVendor 与 idProduct(厂商与产品 ID)、bNumConfigurations(配置数量)。在 emUSB 中,这些字段通过 USB_DEVICE_DESCRIPTOR 结构体填写,例如:
static const USB_DEVICE_DESCRIPTOR deviceDesc = {
.bLength = sizeof(USB_DEVICE_DESCRIPTOR),
.bDescriptorType = USB_DEVICE_DESCRIPTOR_TYPE,
.bcdUSB = 0x0200,
.bDeviceClass = 0x00,
.bMaxPacketSize0 = 64,
.idVendor = 0x1234,
.idProduct = 0x5678,
.bcdDevice = 0x0100,
.iManufacturer = 1,
.iProduct = 2,
.iSerialNumber = 3,
.bNumConfigurations = 1
};
配置描述符描述设备的供电方式与接口数量。bmAttributes 的 bit7 保留为 1,bit6 表示自供电,bit5 表示支持远程唤醒;bMaxPower 以 2mA 为单位,例如 0x32 表示 100mA。emUSB 通常用 USB_CONFIGURATION_DESCRIPTOR 配合接口与端点描述符组成一个连续的配置块,通过 USB_AddConfiguration() 注册。
接口描述符与端点描述符
接口描述符决定主机加载哪类驱动。bInterfaceClass 取 0x03 表示 HID,0x08 表示 Mass Storage,0xFF 表示厂商自定义类。bNumEndpoints 必须与该接口下实际端点数量一致,否则枚举会失败。端点描述符中,bEndpointAddress 的 bit7 表示方向(1 为 IN,0 为 OUT),低 4 位为端点号;bmAttributes 指定传输类型(0 控制、1 同步、2 批量、3 中断);wMaxPacketSize 为最大包长,全速批量端点通常为 64 字节,高速为 512 字节;bInterval 仅对中断与同步端点有效,表示轮询间隔。
下面是一个厂商自定义接口加一对批量端点的配置示例,接口 0 使用 IN 端点 0x81 与 OUT 端点 0x01:
static const USB_ENDPOINT_DESCRIPTOR epIn = {
.bLength = sizeof(USB_ENDPOINT_DESCRIPTOR),
.bDescriptorType = USB_ENDPOINT_DESCRIPTOR_TYPE,
.bEndpointAddress = 0x81, /* IN, EP1 */
.bmAttributes = 0x02, /* 批量传输 */
.wMaxPacketSize = 64,
.bInterval = 0
};
static const USB_INTERFACE_DESCRIPTOR iface = {
.bLength = sizeof(USB_INTERFACE_DESCRIPTOR),
.bDescriptorType = USB_INTERFACE_DESCRIPTOR_TYPE,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 2,
.bInterfaceClass = 0xFF, /* 厂商自定义 */
.bInterfaceSubClass = 0x00,
.bInterfaceProtocol = 0x00,
.iInterface = 0
};
字符串描述符与编码
字符串描述符使用 UTF-16LE 编码,首字节为长度,第二字节为类型 0x03,其后每个字符占两字节。emUSB 提供 USB_AddString() 或静态字符串表来注册,索引 0 固定为语言 ID 数组(如 0x0409 表示美式英语)。设备描述符中的 iManufacturer、iProduct、iSerialNumber 即指向这些字符串索引。若某字段填 0,表示无对应字符串,主机不会请求。
使用emUSB配置工具与API
emUSB-Device 提供两种定制途径。其一是图形化配置工具(如 emUSB-Device Configurator),在界面中填写 VID、PID、接口类、端点方向与包长,工具自动生成描述符 C 文件与初始化代码,适合快速搭建原型。其二是直接调用 API,在 USB_Init() 之前通过 USB_AddConfiguration()、USB_AddInterface()、USB_AddEndpoint() 动态构建描述符树,适合需要运行时切换配置或多配置设备的场景。
bNumEndpoints、bNumInterfaces、bNumConfigurations 与实际注册数量严格一致;端点地址不能重复,IN 与 OUT 可共用端点号但方向位不同。常见错误与验证方法
描述符错误最典型的症状是主机反复枚举失败、设备管理器显示“未知 USB 设备”或驱动无法加载。常见原因包括:bMaxPacketSize0 与端点 0 实际能力不符;配置描述符总长度 wTotalLength 计算错误;字符串索引指向不存在的字符串;端点包长超过该传输类型上限。验证时可用 USB 协议分析仪抓取枚举过程,或在 emUSB 中开启调试输出,观察主机请求的描述符类型与返回长度是否匹配。若主机在 GET_DESCRIPTOR 配置阶段停止,通常说明配置块长度或接口数量有误。
掌握描述符的层级与字段后,emUSB 的配置工作就变成“填结构体 + 注册 + 校验”三步。下一阶段即可在此基础上绑定具体的类驱动或自定义端点处理函数,让设备真正开始收发数据。