如遇開發技術問題,歡迎前往開發者社區,極海技術團隊將在線為您解答~
極海官方開發者社區?https://community.geehy.cn/
《APM32芯得》系列內容為用戶使用APM32系列產品的經驗總結,均轉載自21ic論壇極海半導體專區,全文未作任何修改,未經原文作者授權禁止轉載。
目錄
1、USB設備描述符
2、USB配置描述符
3、USB HID報告描述符
4、修改端點緩存地址
? ? 最近在編寫DMA_ADC例程的過程中出現了一個中斷配置的問題,在ADC采集過程中,結合手冊進行ADC連續轉換模式配置采集,手冊上給出需要進行中斷配置的信息,但是真實情況不需要進行中斷配置也可以進行ADC連續轉換采集,因此,我沒過濾掉ADC采集中開啟中斷配置的信息,開啟了ADC中斷采集,因此這次以APM32F411官方例程中的DMA_ADC例程,復刻了此次出現的問題。
APM32F103具有USB全速接口,可以做USB從機的多種功能。USB協議中為了提供對多樣設備的支持,定義了許多外部設備子類,常見的包括:
-
人機交互類設備HID(Human Interface Device)
-
通信類設備CDC(Communicate Device Class)
-
大容量存儲設備MSC(Mass Storage Class)
-
視頻類設備UVC(USB Video Class)
-
音頻類設備UAC(USB Audio Class)
HID (Human Interface Device)?是一種用于連接人機交互設備的USB設備類別。它定義了一組通用的協議和規范,用于支持鍵盤、鼠標、游戲控制器等各種輸入設備的連接和交互。
CDC(Communication Device Class)是USB組織定義的一類專門給各種通信設備(電信通信設備和中速網絡通訊設備)使用的USB子類,常用于虛擬串口。
USB大容量存儲設備類(The USB mass storage device class)是一種計算機和移動設備之間的傳輸協議,它允許一個通用串行總線(USB)設備來訪問主機的計算設備,使兩者之間進行文件傳輸,常用于存儲器讀寫和模擬U盤。
SDK中提供的例程就有虛擬串口CDC、鼠標HID、模擬U盤MSC的功能。
除了這些USB做單項功能的,USB還可以配置復合設備,能同時集成了多個不同類型的外設,可以實現多個功能的同時使用
接下來就嘗試在極海SDK中增加HID KeyBoard+CDC 虛擬串口的復合設備配置,修改就基于極海APM32F10x_SDK_V1.8中已有的USB_CDC_VirtualCOMPort例程,就不用重新配置虛擬串口部分了。
實現要對USB的描述符進行修改,能讓電腦識別出來是個什么設備。
1、USB設備描述符
重點是bDeviceClass改成0xEF,告訴電腦這是個復合設備。
const uint8_t g_usbDeviceDescriptor[USB_DEVICE_DESCRIPTOR_SIZE] ={0x12, ?/*bLength:長度,設備描述符的長度為18字節*/USBD_DESC_DEVICE, /*bDescriptorType*/0x00,0x02, ? ? ? ? ? ? ? ? ? ? /*bcdUSB = 2.00 */0xEF, ? ? ? ? ? ? ? ? ? ? ? /*bDeviceClass*/0x02, ? ? ? ? ? ? ? ? ? ? ? /*bDeviceSubClass*/0x01, ? ? ? ? ? ? ? ? ? ? ? /*bDeviceProtocol*/0x40, ? ? ? ? ? ? ? ? ? ? ? /*bMaxPacketSize40---------------------------*/0x83,0x05, ? ? ? ? ? ? ? ? ?/*idVendor (0x0583)*/0x50,0x57, ? ? ? ? ? ? ? ? ?/*idProduct = 0x5750*/0x00, ? ? ? ? ? ? ? ? ? ? ? /*bcdDevice rel. 2.00*/0x02,1, ? ? ? ? ? ? ? ? ? ? ? ? ?/*Index of string descriptor describingmanufacturer */2, ? ? ? ? ? ? ? ? ? ? ? ? ?/*Index of string descriptor describingproduct*/3, ? ? ? ? ? ? ? ? ? ? ? ? ?/*Index of string descriptor describing thedevice serial number */0x01 ? ? ? ? ? ? ? ? ? ? ? ?/*bNumConfigurations*/};
2、USB配置描述符
這里配置USB的配置描述符、接口描述符和端點描述符。
HID鍵盤使用兩個端點,端點4(IN)和端點4(OUT)
VCP虛擬串口使用三個端點,端點1(IN)、端點1(OUT)和端點2(IN)
const uint8_t g_usbConfigDescriptor[USB_CONFIG_DESCRIPTOR_SIZE] ={0x09, /* bLength: Configuration Descriptor size */USBD_DESC_CONFIGURATION, /* bDescriptorType: Configuration */USB_CONFIG_DESCRIPTOR_SIZE,/* wTotalLength: Bytes returned */0x00,0x03, ? ? ? ? /* bNumInterfaces: 1 interface */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 */USBD_DESC_INTERFACE,/* bDescriptorType: Interface descriptor type */0x00, ? ? ? ? /* bInterfaceNumber: Number of Interface */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 */0x21, /* 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 */KEYBOARD_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 */USBD_DESC_ENDPOINT, /* 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 */USBD_DESC_ENDPOINT, ? ? ? ?/* bDescriptorType: *//* ? ? ? ?Endpoint descriptor type */0x04, ? ? ? ?/* bEndpointAddress: *//* ? ? ? ?Endpoint Address (OUT) */0x03, ? ? ? ?/* bmAttributes: Interrupt endpoint */0x01, ? ? ? ?/* wMaxPacketSize: 2 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 */USBD_DESC_INTERFACE, ?/* 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 */USBD_DESC_ENDPOINT, ? /* 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 */USBD_DESC_INTERFACE, ?/* bDescriptorType: */0x02, ? /* bInterfaceNumber: Number of Interface */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 */USBD_DESC_ENDPOINT, ? /* bDescriptorType: Endpoint */0x01, ? /* bEndpointAddress: (OUT1) */0x02, ? /* bmAttributes: Bulk */VIRTUAL_COM_PORT_DATA_SIZE, ? ? ? ? ? ? /* wMaxPacketSize: */0x00,0x00, ? /* bInterval: ignore for Bulk transfer *//*Endpoint 1 Descriptor 端點描述符*/0x07, ? /* bLength: Endpoint Descriptor size */USBD_DESC_ENDPOINT, ? /* bDescriptorType: Endpoint */0x81, ? /* bEndpointAddress: (IN1) */0x02, ? /* bmAttributes: Bulk */VIRTUAL_COM_PORT_DATA_SIZE, ? ? ? ? ? ? /* wMaxPacketSize: */0x00,0x00 ? ?/* bInterval */};
3、USB HID報告描述符
const uint8_t s_hidKeyboardReportDescriptor[HID_REPORT_DESCRIPTOR_SIZE] ={/*short Item ? D7~D4:bTag;D3~D2:bType;D1~D0:bSize**bTag ---主條目 ? ? ? ? ?1000:輸入(Input) 1001:輸出(Output) 1011:特性(Feature) ? ? ? ?1010:集合(Collection) 1100:關集合(End Collection)?** ? ? ? ? ? ? ? ? ?全局條目 ? ? ? ? 0000:用途頁(Usage Page) 0001:邏輯最小值(Logical Minimum) 0010:邏輯最大值(Logical Maximum) 0011:物理最小值(Physical Minimum)** ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?0100:物理最大值(Physical Maximum) 0101:單元指數(Unit Exponet) 0110:單元(Unit) 0111:數據域大小(Report Size)** ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?1000:報告ID(Report ID) 1001:數據域數量(Report Count) 1010:壓棧(Push) 1011:出棧(Pop) 1100~1111:保留(Reserved)** ? ? ? ? ? ? ? ? ?局部條目 ? ? ? ?0000:用途(Usage) 0001:用途最小值(Usage Minimum) 0010:用途最大值(Usage Maximum) 0011:標識符索引(Designator Index)** ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?0100:標識符最小值(Designator Minimum) 0101:標識符最大值(Designator Maximum) 0111:字符串索引(String Index) 1000:字符串最小值(String Minimum) ??** ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?1001:字符串最大值(String Maximum) 1010:分隔符(Delimiter) 其他:保留(Reserved)**bType---00:主條目(main) ?01:全局條目(globle) ?10:局部條目(local) ?11:保留(reserved)**bSize---00:0字節 ?01:1字節 ?10:2字節 ?11:4字節*///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};
4、修改端點緩存地址
在 usb_config.h中,調整每個USB端點分配的地址。
#define ENDP0_RXADDR ? ? ? ?(0x40)#define ENDP0_TXADDR ? ? ? ?(0x80)#define ENDP1_TXADDR ? ? ? ?(0xC0)#define ENDP1_RXADDR ? ? ? ?(0x0F0)#define ENDP2_TXADDR ? ? ? ?(0x110)#define ENDP4_RXADDR ? ? ? ?(0x150)#define ENDP4_TXADDR ? ? ? ?(0x190)
這時候就完成基本的配置,可以讓電腦識別到鍵盤和虛擬串口的設備,但不能正常使用。
接下來就是代碼初始化部分的修改。
基于本來虛擬串口的初始化增加HID配置
void CDC_Init(void){USBD_InitParam_T usbParam;USBD_InitParamStructInit(&usbParam);usbParam.classReqHandler = USBD_ClassHandler;usbParam.stdReqExceptionHandler = KeyBoard_ReportDescriptor;//增加HID配置usbParam.resetHandler = VCP_Reset;usbParam.inEpHandler = USBD_VCP_InEpCallback;usbParam.outEpHandler = USBD_VCP_OutEpCallback;usbParam.pDeviceDesc = (USBD_Descriptor_T *)&g_deviceDescriptor;usbParam.pConfigurationDesc = (USBD_Descriptor_T *)&g_configDescriptor;usbParam.pStringDesc = (USBD_Descriptor_T *)g_stringDescriptor;usbParam.pStdReqCallback = &stdReqCallback;USBD_Init(&usbParam);}void KeyBoard_ReportDescriptor(USBD_DevReqData_T *reqData){uint8_t len;if((reqData->byte.bRequest == USBD_GET_DESCRIPTOR) &&(reqData->byte.bmRequestType.bit.recipient == USBD_RECIPIENT_INTERFACE) &&(reqData->byte.bmRequestType.bit.type == USBD_REQ_TYPE_STANDARD)){if(reqData->byte.wValue[1] == 0x21){len = USB_MIN(reqData->byte.wLength[0], 9);USBD_CtrlInData((uint8_t *)&g_configDescriptor.pDesc[0x12], len);}else if(reqData->byte.wValue[1] == 0x22){len = USB_MIN(reqData->byte.wLength[0], g_ReportDescriptor.size);USBD_CtrlInData((uint8_t *)g_ReportDescriptor.pDesc, len);}}else{USBD_SetEPTxRxStatus(USBD_EP_0, USBD_EP_STATUS_STALL, USBD_EP_STATUS_STALL);}}
到此配置部分基本完成,接下來到main中增加段測試代碼,驗證USB配置是否正確。
配置虛擬串口一直打印dataBuf[5]= {0xaa,0xbb,0xcc,0xdd,0xff};
HID鍵盤根據鍵值表輸出數字1
?
void Delay(uint32_t i){while(i--);}unsigned char dataBuf[5]= {0xaa,0xbb,0xcc,0xdd,0xff};uint8_t Keyboad_Buf[8]={0,0,0x1E,0,0,0,0,0};int main(void){CDC_Init();while(1){USBD_TxData(USBD_EP_1, dataBuf, 5);USBD_TxData(USBD_EP_4, Keyboad_Buf, 8);Delay(0xFFFFFF);}}
燒錄代碼到開發板,接上USB線到電腦,可以看到Bushound能看到配置的復合設備。
使用串口助手能看到虛擬串口能正確打印我們的數據,鍵盤也正常在發送數字1,證明代碼配置沒問題,兩個功能使用一個USB接口實現了。SDK的虛擬串口也有做接收回傳的功能,可以直接使用。
到此鍵盤+虛擬串口的例程就簡單修改完成了。
注:文章作者在原帖中提供了例程文件,有需要請至原文21ic論壇下載
原文地址:https://bbs.21ic.com/icview-3321226-1-1.html