1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > STM32 USB复合设备(VCP虚拟串口+HID键盘)详解

STM32 USB复合设备(VCP虚拟串口+HID键盘)详解

时间:2020-10-10 17:31:10

相关推荐

STM32 USB复合设备(VCP虚拟串口+HID键盘)详解

USB复合设备

介绍USB复合设备与组合设备区别USB描述符修改修改CustomHID_Reset修改CustomHID_Data_Setup

介绍

本次使用的是Keil 5+STM32F103ZE(正点原子的板子)+STD标准库+USB的官方例程修改的。

因为新入职了一家公司,需要使用USB复合设备,不然每次使用都需要切换来切换去的很是麻烦,所以开始学习USB的复合设备相关知识。

本文章需要对事先了解一些USB的基础知识,不然在后面的修改中很容易卡住。

USB设备描述符介绍(转帖):/lifexy/p/7634511.html?tdsourcetag=s_pctim_aiomsg

IAD描述符介绍(转帖):/shangdawei/p/4712305.html?tdsourcetag=s_pctim_aiomsg

PS:这个IAD描述符是用来连接多个接口描述符用的,请务必认真阅读。

USB复合设备与组合设备区别

其实多个接口组合在一起有2种情况

第一种叫做USB复合设备(Compound Device):

就是此类设备内部会集成一个HUD,然后多个USB连接到此HUD上,然后每个USB都会有自己独立的PID,VID。

第二种叫做USB组合设备(Composite Device):

此类设备则内部只有一个PID,VID,通过定义多个接口描述符来实现多个功能的组合。

在USB官方例程中,例程名字就是叫做Composite_Example,其实这次的教程其实应该叫做组合设备的,只是大多数人认识叫做复合设备,所以我才写成复合设备,希望大家注意一下。

USB描述符修改

如果修改USB描述符成功,电脑能识别出来其实就已经成功了一半了

1.USB设备描述符

因为用到IAP描述符(上面的介绍IAD的地址)重点是bDeviceClass,bDeviceSubClass,bDeviceProtocol这3个值一定要改为如下图。

修改后的USB设备描述符如下所示:

/* USB Standard Device Descriptor */const uint8_t Composite_DeviceDescriptor[Composite_DEVICE_DESC] ={0x12, /* 设备描述符的长度bLength */USB_DEVICE_DESCRIPTOR_TYPE,/* 此描述符类型,这里是USB设备描述符类型bDescriptorType */0x10,0x01, /* bcdUSB = 2.00 */0xEF, /* bDeviceClass 设备类 复合设备 */0x02, /* bDeviceSubClass 设备子类*/0x01, /* bDeviceProtocol 设备协议*/0x40, /* bMaxPacketSize0 端点0对应包的的最大包的大小*/0x83,0x04, /* idVendor = 0x0483 */0x40,0x57, /* idProduct = 0x5740 */0x00,0x02, /* bcdDevice = 2.00 */1, /* Index of string descriptor describing manufacturer */2, /* Index of string descriptor describing product */3, /* Index of string descriptor describing the device's serial number */0x01 /* bNumConfigurations 配置描述符个数*/}

2.USB配置描述符

USB描述符主要有:USB设备描述符USB配置描述符接口描述符端点描述符报告描述符(在HID类中采用,在VCP虚拟串口中没有这个描述符)。

但是在USB代码中,将USB配置描述符接口描述符端点描述符这3个整为一个USB描述符,然后还是叫做USB配置描述符,关系如下图

/* USB Configuration Descriptor *//* All Descriptors (配置描述符, 接口描述符, 端点描述符, 类描述符 */const uint8_t Composite_ConfigDescriptor[CUSTOMHID_SIZ_CONFIG_DESC] ={配置描述符 //Configuration Descriptor 只能有1个/*功能1——VCP虚拟串口接口*/IAD描述符 //复合设备才有 在单接口的设备这个可以不要接口1描述符 //Interface Descriptor类描述符 //Class Desdriptor端点描述符 //Endpoint Descriptor接口2描述符 //Interface Descriptor类描述符 //Class Desdriptor端点描述符 //Endpoint Descriptor端点描述符 //Endpoint Descriptor如果此接口有多个端点,可接着添加端点描述符/*如果有多个接口 下面还可以继续添加以下描述符*//*功能2——HID键盘*/IAD描述符 //复合设备才有 在单接口的设备这个可以不要接口描述符 //Interface Descriptor类描述符 //Class Desdriptor端点描述符 //Endpoint Descriptor/*如果有多个接口 下面还可以继续添加以下描述符*//*接口3 */...}

接下来分析实际的USB配置描述符,这里配置了:

1.HID键盘,一个接口,两个端点4(IN)和端点4(OUT)用于PC接收USB设备发送的键值和USB接收PC发送的数据(比如亮大小写和Num Lock的灯)。

2.VCP虚拟串口,两个接口,三个端点,端点1(IN),端点2(IN),端点3(OUT)。

从设备发送给PC是IN,PC发送给从设备是OUT。

还有需要注意的是端点号不能重复,且端点0不能使用。

/* USB Configuration Descriptor *//* All Descriptors (Configuration, Interface, Endpoint, Class, Vendor */const uint8_t Composite_ConfigDescriptor[CUSTOMHID_SIZ_CONFIG_DESC] ={0x09, /* bLength: Configuration Descriptor size */USB_CONFIGURATION_DESCRIPTOR_TYPE, /* bDescriptorType: Configuration */CUSTOMHID_SIZ_CONFIG_DESC,/* wTotalLength: Bytes returned */0x00,0x03, /* bNumInterfaces: 3 interface 共3个接口 CDC 2个 HID一个*/0x01, /* bConfigurationValue: Configuration value */0x00, /* iConfiguration: Index of string descriptor describingthe configuration*/0xC0, /* bmAttributes: Self powered */0x32, /* MaxPower 100 mA: this current is used for detecting Vbus *//*************************************功能1 HID键盘**************************************//*IAD描述符*/0x08, //bLength:IAD描述符大小 0x0B, //bDescriptorType:IAD描述符类型0x00, //bFirstInterface:功能1 HID键盘的第一个接口描述符是在总的配置描述符中的第几个从0开始数0x01, //bInferfaceCount:功能1 HID键盘有1个接口描述符0x03, //bFunctionClass:同单HID功能时,设备符中的bDeviceClass0x00, //bFunctionSubClass:同单HID功能时,设备符中的bDeviceSubClass0x01, //bFunctionProtocol:同单HID功能时,设备符中的bDeviceProtocol0x00, //iFunction:字符串描述中关于此设备的索引(个人理解是一个字符串描述符中有比如0~5是功能1的字符串,//6~10是功能2的字符串,如果是功能2的话,此值为6)/************** Descriptor of Custom HID interface ****************//* 09 */0x09, /* bLength: Interface Descriptor size */USB_INTERFACE_DESCRIPTOR_TYPE,/* bDescriptorType: Interface descriptor type */0x00, /* bInterfaceNumber: Number of Interface */ //<接口 0>0x00, /* bAlternateSetting: Alternate setting */0x02, /* bNumEndpoints */0x03, /* bInterfaceClass: HID */0x01, /* bInterfaceSubClass : 1=BOOT, 0=no boot */0x01, /* nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse */0, /* iInterface: Index of string descriptor *//******************** Descriptor of Custom HID HID ********************//* 18 */0x09, /* bLength: HID Descriptor size */HID_DESCRIPTOR_TYPE, /* bDescriptorType: HID */0x10, /* bcdHID: HID Class Spec release number */0x01,0x00, /* bCountryCode: Hardware target country */0x01, /* bNumDescriptors: Number of HID class descriptors to follow */0x22, /* bDescriptorType */CUSTOMHID_SIZ_REPORT_DESC,//KEYBOARD_SIZ_REPORT_DESC,/* wItemLength: Total length of Report descriptor */0x00,/******************** Descriptor of Custom HID endpoints ******************//* 27 */0x07,/* bLength: Endpoint Descriptor size */USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */0x84,/* bEndpointAddress: Endpoint Address (IN) */0x03,/* bmAttributes: Interrupt endpoint */0x08,/* wMaxPacketSize: 8 Bytes max */0x00,0x20,/* bInterval: Polling Interval (32 ms) *//* 34 */0x07,/* bLength: Endpoint Descriptor size */USB_ENDPOINT_DESCRIPTOR_TYPE,/* bDescriptorType: */0x04,/* bEndpointAddress:Endpoint Address (OUT) */0x03,/* bmAttributes: Interrupt endpoint */0x01,/* wMaxPacketSize: 1 Bytes max */0x00,0x20,/* bInterval: Polling Interval (20 ms) *//* 41 *//********************************功能2 VCP虚拟串口*****************************//*IAD描述符*//* Interface Association Descriptor(IAD Descriptor) */ 0x08, /* bLength */0x0B, /* bDescriptorType*/0x01, /* bFirstInterface*/0x02, /* bInterfaceCount*/0x02, /* bFunctionClass --CDC*/0x02, /* bFunctionSubClass*/0x01, /* bFunctionProtocoll*/0x00, /* iFunction *//**VCP虚拟串口**//*Interface Descriptor接口描述符*/0x09, /* bLength: Interface Descriptor size */USB_INTERFACE_DESCRIPTOR_TYPE, /* bDescriptorType: Interface *//* Interface descriptor type */0x01, /* bInterfaceNumber: Number of Interface */ //<接口 1>0x00, /* bAlternateSetting: Alternate setting */0x01, /* bNumEndpoints: One endpoints used 该接口非0端点数*/0x02, /* bInterfaceClass: Communication Interface Class */0x02, /* bInterfaceSubClass: Abstract Control Model */0x01, /* bInterfaceProtocol: Common AT commands */0x00, /* iInterface: *//*Header Functional Descriptor类描述符*/0x05, /* bLength: Endpoint Descriptor size */0x24, /* bDescriptorType: CS_INTERFACE */0x00, /* bDescriptorSubtype: Header Func Desc */0x10, /* bcdCDC: spec release number */0x01,/*Call Management Functional Descriptor*/0x05, /* bFunctionLength */0x24, /* bDescriptorType: CS_INTERFACE */0x01, /* bDescriptorSubtype: Call Management Func Desc */0x00, /* bmCapabilities: D0+D1 */0x01, /* bDataInterface: 1 *//*ACM Functional Descriptor*/0x04, /* bFunctionLength */0x24, /* bDescriptorType: CS_INTERFACE */0x02, /* bDescriptorSubtype: Abstract Control Management desc */0x02, /* bmCapabilities *//*Union Functional Descriptor*/0x05, /* bFunctionLength */0x24, /* bDescriptorType: CS_INTERFACE */0x06, /* bDescriptorSubtype: Union func desc */0x00, /* bMasterInterface: Communication class interface */0x01, /* bSlaveInterface0: Data Class Interface *//*Endpoint 2 Descriptor端点描述符*/0x07, /* bLength: Endpoint Descriptor size */USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: Endpoint */0x82, /* bEndpointAddress: (IN2) */0x03, /* bmAttributes: Interrupt */VIRTUAL_COM_PORT_INT_SIZE,/* wMaxPacketSize: */0x00,0xFF, /* bInterval: *//*Data class interface descriptor类描述符*/0x09, /* bLength: Endpoint Descriptor size */USB_INTERFACE_DESCRIPTOR_TYPE, /* bDescriptorType: */0x02, /* bInterfaceNumber: Number of Interface *///<接口 2>0x00, /* bAlternateSetting: Alternate setting */0x02, /* bNumEndpoints: Two endpoints used */0x0A, /* bInterfaceClass: CDC */0x00, /* bInterfaceSubClass: */0x00, /* bInterfaceProtocol: */0x00, /* iInterface: *//*Endpoint 3 Descriptor端点描述符*/0x07, /* bLength: Endpoint Descriptor size */USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: Endpoint */0x03, /* bEndpointAddress: (OUT3) */0x02, /* bmAttributes: Bulk */VIRTUAL_COM_PORT_DATA_SIZE, /* wMaxPacketSize: */0x00,0x00, /* bInterval: ignore for Bulk transfer *//*Endpoint 1 Descriptor 端点描述符*/0x07, /* bLength: Endpoint Descriptor size */USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: Endpoint */0x81, /* bEndpointAddress: (IN1) */0x02, /* bmAttributes: Bulk */VIRTUAL_COM_PORT_DATA_SIZE, /* wMaxPacketSize: */0x00,0x00 /* bInterval */}; /* CustomHID_ConfigDescriptor */

2.USB HID报告描述符

这里就不过多累赘了,报告描述符是用来描述HID类设备在收发数据的时候,该如何解释数据。比如有个报告描述符,描述了该键盘有8个字节的输入报告(该输入是针对于电脑的输入,设备是输出),发送给电脑,然后输出报告(这个不是必须的,可省略),用来发送给USB设备,控制CAPS LOCK和NUM LOCK灯的亮灭。鼠标只有1个字节的输入报告,用来描述鼠标的左键,右键,和鼠标的移动量。

const uint8_t CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] ={//0x05:0000 01 01 这是个全局条目,用途页选择为普通桌面页0x05, 0x01, // USAGE_PAGE (Generic Desktop)//0x09:0000 10 01 这是个全局条目,用途选择为键盘0x09, 0x06, // USAGE (Keyboard)//0xa1:1010 00 01 这是个主条目,选择为应用集合,0xa1, 0x01, // COLLECTION (Application)//0x05:0000 01 11 这是个全局条目,用途页选择为键盘/按键0x05, 0x07, // USAGE_PAGE (Keyboard/Keypad)//0x19:0001 10 01 这是个局部条目,用途的最小值为0xe0,对应键盘上的左ctrl键0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)//0x29:0010 10 01 这是个局部条目,用途的最大值为0xe7,对应键盘上的有GUI(WIN)键0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)//0x15:0001 01 01 这是个全局条目,说明数据的逻辑值最小值为00x15, 0x00, // LOGICAL_MINIMUM (0)//0x25:0010 01 01 这是个全局条目,说明数据的逻辑值最大值为10x25, 0x01, // LOGICAL_MAXIMUM (1)//0x95:1001 01 01 这是个全局条目,数据域的数量为8个0x95, 0x08, // REPORT_COUNT (8)//0x75:0111 01 01 这是个全局条目,每个数据域的长度为1位0x75, 0x01, // REPORT_SIZE (1) //0x81:1000 00 01 这是个主条目,有8*1bit数据域作为输入,属性为:Data,Var,Abs0x81, 0x02, // INPUT (Data,Var,Abs)//0x95:1001 01 01 这是个全局条目,数据域的数量为1个0x95, 0x01, // REPORT_COUNT (1)//0x75:0111 01 01 这是个全局条目,每个数据域的长度为8位0x75, 0x08, // REPORT_SIZE (8)//0x81:1000 00 01 这是个主条目,有1*8bit数据域作为输入,属性为:Cnst,Var,Abs0x81, 0x03, // INPUT (Cnst,Var,Abs)//0x95:1001 01 01 这是个全局条目,数据域的数量为6个0x95, 0x06, // REPORT_COUNT (6)//0x75:0111 01 01 这是个全局条目,每个数据域的长度为8位0x75, 0x08, // REPORT_SIZE (8)//0x25:0010 01 01 这是个全局条目,逻辑最大值为2550x25, 0xFF, // LOGICAL_MAXIMUM (255)//0x19:0001 10 01 这是个局部条目,用途的最小值为00x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))//0x29:0010 10 01 这是个局部条目,用途的最大值为0x650x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)//0x81:1000 00 01 这是个主条目,有6*8bit的数据域作为输入,属相为属性为:Data,Var,Abs0x81, 0x00, // INPUT (Data,Ary,Abs)//0x25:0010 01 01 这是个全局条目,逻辑的最大值为10x25, 0x01, // LOGICAL_MAXIMUM (1)//0x95:1001 01 01 这是个全局条目,数据域的数量为20x95, 0x02, // REPORT_COUNT (2)//0x75:0111 01 01 这是个全局条目,每个数据域的长度为1位0x75, 0x01, // REPORT_SIZE (1)//0x05:0000 01 01 这是个全局条目,用途页选择为LED页0x05, 0x08, // USAGE_PAGE (LEDs)//0x19:0001 10 01 这是个局部条目,用途的最小值为0x01,对应键盘上的Num Lock0x19, 0x01, // USAGE_MINIMUM (Num Lock)//0x29:0010 10 01 这是个局部条目,用途的最大值为0x02,对应键盘上的Caps Lock0x29, 0x02, // USAGE_MAXIMUM (Caps Lock)//0x91:1001 00 01 这是个主条目,有2*1bit的数据域作为输出,属性为:Data,Var,Abs0x91, 0x02, // OUTPUT (Data,Var,Abs)//0x95:1001 01 01 这是个全局条目,数据域的数量为1个0x95, 0x01, // REPORT_COUNT (1)//0x75:0111 01 01 这是个全局条目,每个数据域的长度为6bit,正好与前面的2bit组成1字节0x75, 0x06, // REPORT_SIZE (6)//0x91:1001 00 01 这是个主条目,有1*6bit数据域最为输出,属性为:Cnst,Var,Abs0x91, 0x03, // OUTPUT (Cnst,Var,Abs)0xc0 // END_COLLECTION}; /* CustomHID_ReportDescriptor */

3.端点缓存地址修改

在usb_conf.h文件中有关于USB端点的配置,我们需要来修改一下。

首先是端点数量,这里是使用的总端点数,我们共使用了端点0~4,共5个,修改如下。

#define EP_NUM(5)

接下来是关于端点的缓存区进行配置,配置每个端点使用的地址在内存中的偏移。在USB模块中有一个所有端点一起使用的RAM区(Packet Buffer)共512字节,其中有一个USB Description Table用于存储每个端点的在RAM区中的偏移和缓存大小。

每个端点都有2个方向(IN/OUT),共有8个端点,所有共有 8x2 = 16个方向,然后每个方向都有2个寄存器,每个寄存器有4字节,所以USB Description Table最大共占内存 16x4x2 = 128字节 = 0x80H(这个是最大值,有些实际未必会用到这么多端点)。

本例子中使用了:

EP0(IN)

EP0(OUT)

EP1(IN)

EP2(IN)

EP3(OUT)

EP4(IN)

EP4(OUT)

共7个端点,所以USB Description Table共占内存 7x4x2 = 56字节 = 0x38H,然后取个整数大于0x38即可,那就取0x40H。所以端点0的发送缓存地址起始地址0x40H。

/*-------------------------------------------------------------*//* -------------- Buffer Description Table -----------------*//*-------------------------------------------------------------*//* buffer table base address */#define BTABLE_ADDRESS(0x00)/* EP0 *//* rx/tx buffer base address */#define ENDP0_RXADDR (0x40)#define ENDP0_TXADDR (0x80)/* EP1 2 3 *//* tx buffer base address */#define ENDP1_TXADDR (0xC0)#define ENDP2_TXADDR (0x100)#define ENDP3_RXADDR (0x110)/* EP4 *//* rx/tx buffer base address */#define ENDP4_RXADDR (0x150)#define ENDP4_TXADDR (0x190)

修改CustomHID_Reset

端口的初始化是放在CustomHID_Reset中的,需要添加新加的端口初始化,我是基于HID的例程修改的,所以只需要添加虚拟串口的端点,并且将HID的端口号改为4。

void CustomHID_Reset(void){/* Set CustomHID_DEVICE as not configured */pInformation->Current_Configuration = 0;pInformation->Current_Interface = 0;/*the default Interface*//* Current Feature initialization */pInformation->Current_Feature = CustomHID_ConfigDescriptor[7];SetBTABLE(BTABLE_ADDRESS);/* Initialize Endpoint 0 */SetEPType(ENDP0, EP_CONTROL);SetEPTxStatus(ENDP0, EP_TX_STALL);SetEPRxAddr(ENDP0, ENDP0_RXADDR);SetEPTxAddr(ENDP0, ENDP0_TXADDR);Clear_Status_Out(ENDP0);SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);SetEPRxValid(ENDP0);/* Initialize Endpoint 1 */SetEPType(ENDP1, EP_BULK);SetEPTxAddr(ENDP1, ENDP1_TXADDR);SetEPTxStatus(ENDP1, EP_TX_NAK);SetEPRxStatus(ENDP1, EP_RX_DIS);/* Initialize Endpoint 2 */SetEPType(ENDP2, EP_INTERRUPT);SetEPTxAddr(ENDP2, ENDP2_TXADDR);SetEPRxStatus(ENDP2, EP_RX_DIS);SetEPTxStatus(ENDP2, EP_TX_NAK);/* Initialize Endpoint 3 */SetEPType(ENDP3, EP_BULK);SetEPRxAddr(ENDP3, ENDP3_RXADDR);SetEPRxCount(ENDP3, VIRTUAL_COM_PORT_DATA_SIZE);SetEPRxStatus(ENDP3, EP_RX_VALID);SetEPTxStatus(ENDP3, EP_TX_DIS);/* Initialize Endpoint 4 */SetEPType(ENDP4, EP_INTERRUPT);SetEPTxAddr(ENDP4, ENDP4_TXADDR);SetEPRxAddr(ENDP4, ENDP4_RXADDR);SetEPTxCount(ENDP4, 8);SetEPRxCount(ENDP4, 1);SetEPRxStatus(ENDP4, EP_RX_VALID);SetEPTxStatus(ENDP4, EP_TX_NAK);/* Set this device to response on default address */SetDeviceAddress(0);bDeviceState = ATTACHED;}

修改CustomHID_Data_Setup

只是将VCP例程中的Data_Setup和HID的Data_Setup整合在一起。

RESULT CustomHID_Data_Setup(uint8_t RequestNo){uint8_t *(*CopyRoutine)(uint16_t);// if (pInformation->USBwIndex != 0) ???????????????????????????????????????????????????// return USB_UNSUPPORT; CopyRoutine = NULL;if (RequestNo == GET_LINE_CODING){if (Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT)){CopyRoutine = Virtual_Com_Port_GetLineCoding;}}else if (RequestNo == SET_LINE_CODING){if (Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT)){CopyRoutine = Virtual_Com_Port_SetLineCoding;}Request = SET_LINE_CODING;}else if (RequestNo == GET_DESCRIPTOR){if(Type_Recipient == (STANDARD_REQUEST | INTERFACE_RECIPIENT)){if (pInformation->USBwValue1 == REPORT_DESCRIPTOR){CopyRoutine = CustomHID_GetReportDescriptor;}else if (pInformation->USBwValue1 == HID_DESCRIPTOR_TYPE){CopyRoutine = CustomHID_GetHIDDescriptor;}}}/*** GET_PROTOCOL, GET_REPORT, SET_REPORT ***/else if ( (Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT)) ){switch( RequestNo ){case GET_PROTOCOL:CopyRoutine = CustomHID_GetProtocolValue;break;case SET_REPORT:CopyRoutine = CustomHID_SetReport_Feature;Request = SET_REPORT;break;default:break;}}if (CopyRoutine == NULL){return USB_UNSUPPORT;}pInformation->Ctrl_Info.CopyData = CopyRoutine;pInformation->Ctrl_Info.Usb_wOffset = 0;(*CopyRoutine)(0);return USB_SUCCESS;}

其实此时电脑就已经能够同时识别出HID和CDC了,接下来就是发送和接收了,这部分有点USB基础的应该都能做出来,在这里就不过多累赘了。

以下是代码下载地址

USB复合设备代码

/download/juqi_/11220896

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。