STM32配置USB组合设备
有个比喻描述的特别好
说到USB复合设备就不得不说说USB的枚举过程,也就是USB插入后主机的一系列动作。当然我们这里是不会用很专业的术语来说明的,相信如果你是开发者,必然已经了如指掌,如果你是使用者,更是不愿看到太多的专业术语的。
我把USB的枚举比作一个自我介绍,在插入后主机会询问USB设备的属性,而设备则像自我介绍一样告诉主机自己的“名字”,“年龄”,“证件号”,“母语”等等。主机也是根据这些来区分插入的设备的。
因此,我们可以做个假设是不是修改相应的身份信息(也就是描述符)就可以实现复合设备了呢?答案是肯定的,但是具体要修改那些描述符就需要来分析一下了。
因此想要实现USB复合,需要解决的最大的一个问题就是USB描述符!
1. 修改USB组合设备描述符之“设备描述符”
- 组合设备需要对设备描述符进行修改,见下图
修改设备描述符,描述设备为综合设备,对应修改usbd_desc.c
文件内的
/** USB standard device descriptor. USB设备描述符 */
__ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END =
{
0x12, /*bLength */
USB_DESC_TYPE_DEVICE, /*bDescriptorType 描述符编号0x01 */
0x00, /*bcdUSB 版本2.0 */
0x02,
0xEF, /*bDeviceClass 综合设备 */
0x02, /*bDeviceSubClass*/
0x01, /*bDeviceProtocol*/
USB_MAX_EP0_SIZE, /*bMaxPacketSize 端点0大小为64字节 */
LOBYTE(USBD_VID), /*idVendor 厂家ID */
HIBYTE(USBD_VID), /*idVendor*/
LOBYTE(USBD_PID_FS), /*idProduct 产品ID */
HIBYTE(USBD_PID_FS), /*idProduct*/
0x00, /*bcdDevice rel. 2.00版本*/
0x02,
USBD_IDX_MFC_STR, /*Index of manufacturer string 厂商字符串索引值 0x01 */
USBD_IDX_PRODUCT_STR, /*Index of product string 产品字符串索引值 0x02 */
USBD_IDX_SERIAL_STR, /*Index of serial number string 设备序列号字符串索引值 0x03 */
USBD_MAX_NUM_CONFIGURATION /*bNumConfigurations 该设备所具有的配置数 0x01 */
};
2. 修改USB组合设备描述符之“配置描述符、接口描述符、端点描述符”
就是把多个设备的配置描述符、接口描述符、端点描述符拼接在一起,设备之间加入一个IAD描述符分隔开来,同时每个接口描述符的接口编号需要修改,下面是我弄的HID+CDC的配置描述符、接口描述符、端点描述符,仅供参考
/* USB composite device Configuration Descriptor */
/* All Descriptors (Configuration, Interface, Endpoint, Class, Vendor */
__ALIGN_BEGIN uint8_t USBD_Composite_CfgFSDesc[USBD_COMPOSITE_DESC_SIZE] __ALIGN_END =
{
/* 配置描述符 */
0x09, /* bLength: Configuation Descriptor size */
USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */
USBD_COMPOSITE_DESC_SIZE,
0x00,
USBD_MAX_NUM_INTERFACES , /* bNumInterfaces: */
0x01, /* bConfigurationValue: 0 配置的值 */
0x00, /* iConfiguration: 00 字符串索引 */
0x80, /* bmAttributes:no-bus powered and Dissupport Remote Wake-up*/
0x32, /* MaxPower 100 mA */
/****************************HID************************************/
/* Interface Association Descriptor */
USBD_IAD_DESC_SIZE, // bLength IAD描述符大小
USBD_IAD_DESCRIPTOR_TYPE, // bDescriptorType IAD描述符类型
0x00, // bFirstInterface 接口描述符是在总的配置描述符中的第几个从0开始数
0x01, // bInterfaceCount 接口描述符数量
0x03, // bFunctionClass 设备符中的bDeviceClass
0x00, // bFunctionSubClass 设备符中的bDeviceSubClass
0x00, // bInterfaceProtocol 设备符中的bDeviceProtocol
0x00,
/******************** HID interface ********************/
/************** Descriptor of Custom HID interface ****************/
/* 09 */
0x09, /*bLength: Interface Descriptor size*/
USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/
0x00, /*bInterfaceNumber: Number of Interface 接口编号 0 */
0x00, /*bAlternateSetting: Alternate setting 备用接口 */
0x01, /*bNumEndpoints 使用的端点数 1 */
0x03, /*bInterfaceClass: HID*/
0x00, /*bInterfaceSubClass : 1=BOOT, 0=no boot*/
0x00, /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
0, /*iInterface: Index of string descriptor*/
/******************** Descriptor of Custom HID ********************/
/* 18 */
0x09, /*bLength: HID Descriptor size*/
HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/
0x00, /*bcdHID: HID Class Spec release number*/
0x01,
0x00, /*bCountryCode: Hardware target country*/
0x01, /*bNumDescriptors: Number of HID class descriptors to follow*/
0x22, /*bDescriptorType*/
HID_MOUSE_REPORT_DESC_SIZE,/*wItemLength: Total length of Report descriptor*/
0x00,
/******************** Descriptor of TouchScreen endpoint ********************/
/* 27 */
0x07, /*bLength: Endpoint Descriptor size*/
USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/
HID_EPIN_ADDR, /*bEndpointAddress: Endpoint Address (IN)*/
0x03, /*bmAttributes: Interrupt endpoint*/
HID_EPIN_SIZE, /*wMaxPacketSize: 16 Byte max */
0x00,
HID_FS_BINTERVAL, /*bInterval: Polling Interval */
/* 34 */
/****************************CDC************************************/
/* IAD描述符 */
/* Interface Association Descriptor */
USBD_IAD_DESC_SIZE, // bLength
USBD_IAD_DESCRIPTOR_TYPE, // bDescriptorType
0x01, // bFirstInterface 接口描述符是在总的配置描述符中的第几个从0开始数 1
0x02, // bInterfaceCount 接口描述符数量 2
0x02, // bFunctionClass CDC Control
0x02, // bFunctionSubClass Abstract Control Model
0x01, // bInterfaceProtocol AT Commands: V.250 etc
0x00, // iFunction
/* CDC命令接口描述符 */
/*Interface Descriptor */
0x09, /* bLength: Interface Descriptor size 长度 */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: Interface 接口编号0x04 */
/* Interface descriptor type */
0x01, /* bInterfaceNumber: Number of Interface 接口编号,第一个接口编号为1 */
0x00, /* bAlternateSetting: Alternate setting 接口备用编号 0 */
0x01, /* bNumEndpoints: One endpoints used 非0端点的数目 1 cdc接口只使用了一个中断输入端点 */
0x02, /* bInterfaceClass: Communication Interface Class 接口所使用的类0x02 */
0x02, /* bInterfaceSubClass: Abstract Control Model 接口所使用的子类0x02 */
0x01, /* bInterfaceProtocol: Common AT commands 使用AT命令协议 */
0x00, /* iInterface: 接口字符串索引值 0表示没有 */
/* 类特殊接口描述符--功能描述符 用来描述接口的功能 */
/*Header Functional Descriptor*/
0x05, /* bLength: Endpoint Descriptor size 描述符长度为5字节 */
0x24, /* bDescriptorType: CS_INTERFACE 描述符类型为类特殊接口CS_INTERFACE*/
0x00, /* bDescriptorSubtype: Header Func Desc 子类为 Header Func Desc,编号0x00 */
0x10, /* bcdCDC: spec release number CDC版本 */
0x01,
/*Call Management Functional Descriptor*/
0x05, /* bFunctionLength */
0x24, /* bDescriptorType: CS_INTERFACE 描述符类型为类特殊接口CS_INTERFACE*/
0x01, /* bDescriptorSubtype: Call Management Func Desc 子类为Call Management Func Desc 编号0x01*/
0x00, /* bmCapabilities: D0+D1 设备自己不管理call management */
0x01, /* bDataInterface: 1 有一个数据类接口用作call management */
/*ACM Functional Descriptor*/
0x04, /* bFunctionLength */
0x24, /* bDescriptorType: CS_INTERFACE 描述符类型为类特殊接口CS_INTERFACE*/
0x02, /* bDescriptorSubtype: Abstract Control Management desc 子类为Abstract Control Management desc编号0x02*/
0x02, /* bmCapabilities 支持Set_Control_Line_State、Get_Line_Coding请求和Serial_State通知*/
/*Union Functional Descriptor*/
0x05, /* bFunctionLength */
0x24, /* bDescriptorType: CS_INTERFACE 描述符类型为类特殊接口CS_INTERFACE */
0x06, /* bDescriptorSubtype: Union func desc 子类为Union func desc 编号0x06*/
0x01, /* bMasterInterface: Communication class interface 编号为1的CDC接口 */
0x02, /* bSlaveInterface0: Data Class Interface 编号为2的数据类接口 */
/*Endpoint 2 Descriptor*/
0x07, /* bLength: Endpoint Descriptor size */
USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */
CDC_CMD_EP, /* bEndpointAddress */
0x03, /* bmAttributes: Interrupt */
LOBYTE(CDC_CMD_PACKET_SIZE), /* wMaxPacketSize: */
HIBYTE(CDC_CMD_PACKET_SIZE),
CDC_FS_BINTERVAL, /* bInterval: */
/*---------------------------------------------------------------------------*/
/* 数据类接口的接口描述符 */
/*Data class interface descriptor*/
0x09, /* bLength: Endpoint Descriptor size 接口描述符长度9字节*/
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: 接口描述符的编号0x04*/
0x02, /* bInterfaceNumber: Number of Interface 接口的编号为2*/
0x00, /* bAlternateSetting: Alternate setting 该接口的备用编号为0 */
0x02, /* bNumEndpoints: Two endpoints used 非0端点的数据 设备需要使用一对批量端点,设置为2*/
0x0A, /* bInterfaceClass: CDC 该接口所使用的类 数据类接口代码为0x0A */
0x00, /* bInterfaceSubClass: 接口所使用的子类为0*/
0x00, /* bInterfaceProtocol: 接口所使用的协议为0*/
0x00, /* iInterface: 接口的字符串索引值,0表示没有*/
/* 输出端点的端点描述符 */
/*Endpoint OUT Descriptor*/
0x07, /* bLength: Endpoint Descriptor size 端点描述符长度7字节 */
USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint 端点描述符编号为0x05 */
CDC_OUT_EP, /* bEndpointAddress 端点的地址0x02 D7为方向*/
0x02, /* bmAttributes: Bulk 批量传输*/
LOBYTE(CDC_DATA_HS_MAX_PACKET_SIZE), /* wMaxPacketSize: 端点的最大包长 512字节*/
HIBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),
0x00, /* bInterval: ignore for Bulk transfer 端点查询时间,对批量端点无效 */
/* 输入端点的端点描述符 */
/*Endpoint IN Descriptor*/
0x07, /* bLength: Endpoint Descriptor size */
USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint 端点描述符编号为0x05*/
CDC_IN_EP, /* bEndpointAddress 端点的地址0x82 D7为方向*/
0x02, /* bmAttributes: Bulk 批量传输*/
LOBYTE(CDC_DATA_HS_MAX_PACKET_SIZE), /* wMaxPacketSize: 端点的最大包长 512字节*/
HIBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),
0x00 /* bInterval: ignore for Bulk transfer 端点查询时间,对批量端点无效*/
};
3. 修改USB外设所支持的最大接口数
因为组合设备肯定是使用的多个接口,肯定不止1个,所以需要配置stm32硬件所支持的接口数大于或等于实际使用的接口数,对应修改usbd_conf.h
内的#define USBD_MAX_NUM_INTERFACES 3
4. 修改每个端点的地址
组合设备如何区分数据是发给谁的呢,肯定是不同的设备有不同的接口了,因此我们需要整理每一个设备的端点地址,不能冲突!!!注意的是,端点地址的第七位表示的是方向!
5. 修改PID和VID
为什么要改,请看下面我踩过的坑!
USB组合设备之大坑
1号大坑
- 确认描述符没有问题,但是USB插上电脑之后依旧只能识别一个设备,记得修改VID和PID,卸载驱动,重新插拔,由于USB是通过PID和VID来加载驱动的,如果我们继续使用stm32虚拟串口对应的VID1155和PID22336,则会自动加载虚拟串口的驱动,使得电脑根据驱动,只会去加载一个设备,修改VID为1156,PID为22339之后重新卸载驱动,重新插拔,之后便可以识别到两个设备!!!
2号大坑
- 修改了VID和PID之后,电脑往往不能自动加载我们想让他加载的驱动了,所以可以看到一个黄色感叹号在设备管理器的设备上,提示驱动安装失败什么的,这个时候需要我们手动添加驱动,之前的办法是修改.inf文件(驱动程序描述文件)里面的VID和PID与程序里面的对应,结果指定文件夹搜索驱动压根搜不到,之后一顿离奇操作发现了新大陆!(以下仅供CDC虚拟串口参考,其他设备驱动不一定有效)
- 进入设备管理器选择无法识别的驱动,选择更新驱动
- 按图操作
- 3号大坑
- 每次更换了PID和VID之后一定要卸载驱动,重新插拔再看看,不然电脑会按照上一次的VID、PID记录,就比如我刚刚烧了一个可以识别两个设备的,然后换成了另外一个其实只能生成一个设备的程序,但是没有跟换PID和VID,然后电脑依旧显示找到了两个设备,自以为配置成功了,兴奋的跳起之后,点击了下卸载驱动,重新插拔,结果露出正面目,其实还是只有一个设备,好吧,回去继续改代码!