MADL!AR
Code is cheap, show me the PPT!
首页
分类
Fragment
关于
使用STM32 H7实现HID组合设备(键盘、鼠标)
分类:
硬件
发布于: 2023-08-27
网上的stm32 usb教程,大多数都是基于F1系列或者是单设备的。这里寻找了一个方法实现组合设备,在一个usb device上模拟键盘和鼠标,同时可以与主机传输数据。此例定义了Custom HID设备,包括3个接口,第一个接口为自定义的HID输入输出设备,包含两个端点。另外两个接口分别为键盘和鼠标,分别包含1个端点。如果移除第一个接口,根据实际测试,需要对默认的`CUSTOMHIDInEpAdd`等配置做较多配置,较为麻烦,因此不再讨论。 此文仅用作备忘录,不介绍原理。 ## 硬件 使用wio lite ai,基于stm32H725AE,内置USB OTG HS,GPIO映射如下: ``` USB PA11, PA12 USART3 // 板子丝印为TX、RX RX -> PD9 TX -> PD8 LED RED -> PC13 ORANGE -> PF0 KEY PF1 ``` ## 创建工程 配置以下内容: * LED * KEY 设置为中断触发 * USART3,设置为中断触发 * 外部晶振 * 单线调试 然后配置USB_HS_OTG。在“Connectivity”组中,选择内部phy,"Device_Only"组中保持默认参数,中断不用勾选。然后在“Middleware”组中,配置“USB_DEVICE”,选择“Custom Human Interface ...(HID)”。关键配置项如下: ``` // 报告描述符大小,在这里暂不修改。等在代码中编写完成之后,替换为实际大小 USBD_CUSTOM_HID_REPORT_DESC_SIZE // 输出端点的报告buff大小,设置为64字节 USBD_CUSTOMHID_OUTREPORT_BUF_SIZE // 接口数量,这里设置为3,第一个为默认的HID设备,另外两个分别为键盘、鼠标 USBD_MAX_NUM_INTERFACES // 设备描述中,修改VID/PID,厂商描述符等。不影响功能 ``` 最后配置时钟,修改project中堆和栈的大小(可选)。 ## 编写配置描述符 配置描述符的位置在 `Middleware->ST->STM32_USB..Library->Class->CustomHID->Src`下的`usbd_customhid.c`文件里,关键字`USBD_CUSTOM_HID_CfgDesc`。这个数组下面,每一组第一个字节为该组配置的大小,单位为字节;第二个字节为类型;3、4个字节为配置描述大小。其他数值参照注释。 在这里需要修改接口数量,将其改为3。电源部分采用总线供电,支持唤醒,因此设置为0xA0. 具体内容: ```C++ __ALIGN_BEGIN static uint8_t USBD_CUSTOM_HID_CfgDesc[USB_CUSTOM_HID_CONFIG_DESC_SIZ] __ALIGN_END = { 0x09, /* bLength: Configuration Descriptor size */ USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */ LOBYTE(USB_CUSTOM_HID_CONFIG_DESC_SIZ), /* wTotalLength: Bytes returned */ HIBYTE(USB_CUSTOM_HID_CONFIG_DESC_SIZ), 0x03, // 3个接口 0x01, /* bConfigurationValue: Configuration value */ 0x00, /* iConfiguration: Index of string descriptor describing the configuration */ 0xA0, // 总线供电,支持唤醒 USBD_MAX_POWER, /* MaxPower (mA) */ // ==================== 第0个接口 (默认) ====================// /************** Descriptor of CUSTOM HID interface ****************/ 0x09, USB_DESC_TYPE_INTERFACE, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, /******************** Descriptor of CUSTOM_HID *************************/ 0x09, CUSTOM_HID_DESCRIPTOR_TYPE, 0x11, 0x01, 0x00, 0x01, 0x22, LOBYTE(USBD_CUSTOM_HID_REPORT_DESC_SIZE), HIBYTE(USBD_CUSTOM_HID_REPORT_DESC_SIZE), /******************** Descriptor of Custom HID endpoints ********************/ 0x07, USB_DESC_TYPE_ENDPOINT, CUSTOM_HID_EPIN_ADDR, 0x03, LOBYTE(CUSTOM_HID_EPIN_SIZE), HIBYTE(CUSTOM_HID_EPIN_SIZE), CUSTOM_HID_FS_BINTERVAL, 0x07, USB_DESC_TYPE_ENDPOINT, CUSTOM_HID_EPOUT_ADDR, 0x03, LOBYTE(CUSTOM_HID_EPOUT_SIZE), HIBYTE(CUSTOM_HID_EPOUT_SIZE), CUSTOM_HID_FS_BINTERVAL, /* 9 + 9 + 9 + 7 + 7 = 41 */ // ==================== 第1个接口 (鼠标) ====================// /************** Descriptor of CUSTOM HID interface ****************/ 0x09, USB_DESC_TYPE_INTERFACE, 0x01, // 接口编号,按顺序指定Interface */ 0x00, /* bAlternateSetting: Alternate setting */ 0x01, // 端点个数 0x03, /* bInterfaceClass: CUSTOM_HID */ 0x00, /* bInterfaceSubClass : 1=BOOT, 0=no boot */ 0x02, /* nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse */ 0x00, /* iInterface: Index of string descriptor */ /* 50 */ /******************** Descriptor of CUSTOM_HID *************************/ 0x09, CUSTOM_HID_DESCRIPTOR_TYPE, 0x11, 0x01, 0x00, // HID为键盘时设置国家, 0x21为美国。鼠标不用修改 0x00, 0x01, /* bNumDescriptors: Number of CUSTOM_HID class descriptors to follow */ 0x22, // 描述符类型: 报告描述符 LOBYTE(USBD_CUSTOM_HID_MOUSE_REPORT_DESC_SIZE), // 报告描述符的大小 HIBYTE(USBD_CUSTOM_HID_MOUSE_REPORT_DESC_SIZE), /* 59 */ /******************** Descriptor of Custom HID endpoints ********************/ 0x07, USB_DESC_TYPE_ENDPOINT, // CUSTOM_HID_EPIN_ADDR D0~D3端点编号,D4~D6保留。D7为传输方向,主机为out时为0, 主机为in时为1,即键盘鼠标类是以0b1xxx_xxxx开头。 // 这里使用2号 端点,MOUSE_EP 应为0x82。 MOUSE_EP, 0x03, // 4种控制方式,11: 中断; 10: 批量; 01: 同步; 00: 控制。批量需要自己编写驱动 LOBYTE(MOUSE_EP_SIZE), HIBYTE(MOUSE_EP_SIZE), CUSTOM_HID_FS_BINTERVAL, /* 66 */ // ==================== 第2个接口 (键盘) ====================// /************** Descriptor of CUSTOM HID interface ****************/ 0x09, USB_DESC_TYPE_INTERFACE, 0x02, // modified: 接口编号,前面为1,这里用2 0x00, /* bAlternateSetting: Alternate setting */ 0x01, // 端点个数,只有一个输入端点 0x03, /* bInterfaceClass: CUSTOM_HID */ 0x00, /* bInterfaceSubClass : 1=BOOT, 0=no boot */ 0x01, /* nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse */ 0x00, /* iInterface: Index of string descriptor */ /* 75 */ /******************** Descriptor of CUSTOM_HID *************************/ 0x09, CUSTOM_HID_DESCRIPTOR_TYPE, 0x11, /* bCUSTOM_HIDUSTOM_HID: CUSTOM_HID Class Spec release number */ 0x01, 0x21, // 键盘设置国家, 0x21为美国 0x01, /* bNumDescriptors: Number of CUSTOM_HID class descriptors to follow */ 0x22, // 0x22: 报告描述符 LOBYTE(USBD_CUSTOM_HID_KEYBOARD_REPORT_DESC_SIZE), // 报告描述符的大小 HIBYTE(USBD_CUSTOM_HID_KEYBOARD_REPORT_DESC_SIZE), /* 84 */ /******************** Descriptor of Custom HID endpoints ********************/ 0x07, USB_DESC_TYPE_ENDPOINT, KEYBOARD_EP, // CUSTOM_HID_EPIN_ADDR, 上述已使用了2号端点,KEYBOARD_EP 应该为0x83 0x03, // modified: 4种控制方式,11: 中断; 10: 批量; 01: 同步; 00: 控制 LOBYTE(KEYBOARD_EP_SIZE), HIBYTE(KEYBOARD_EP_SIZE), CUSTOM_HID_FS_BINTERVAL /* 91 */ }; ``` 修改 `USB_CUSTOM_HID_CONFIG_DESC_SIZ`为91,并在对应头文件中定义: ``` #define MOUSE_EP 0x82U #define MOUSE_EP_SIZE 0x0AU #define KEYBOARD_EP 0x83U #define KEYBOARD_EP_SIZE 0x0AU ``` ## 编写报告描述符 在生成的代码当中,有一个默认的报告描述符,在`USB_DEVICE->App->usbd_custom_hid_if.c`中: ``` __ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_HS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END = { /* USER CODE BEGIN 1 */ 0x00, /* USER CODE END 1 */ 0xC0 /* END_COLLECTION */ }; ``` 将函数体内的数组改为下述值(给默认的HID设备使用): ``` 0x06, 0x00, 0xFF, 0x09, 0x01, 0xA1, 0x01, 0x09, 0x01, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x40, 0x81, 0x02, 0x09, 0x01, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x40, 0x91, 0x02, 0xC0 ``` 并将`USBD_CUSTOM_HID_REPORT_DESC_SIZE`修改为34。 然后定义鼠标和键盘的描述符。将上述函数拷贝到配置描述符的`usbd_customhid.c`文件中`@defgroup USBD_CUSTOM_HID_Private_Variables`下(可选其他位置),并替换为键盘和鼠标的报告描述符: ``` __ALIGN_BEGIN static uint8_t CUSTOM_HID_MouseReportDesc_HS[USBD_CUSTOM_HID_MOUSE_REPORT_DESC_SIZE] __ALIGN_END = { /* USER CODE BEGIN 1 */ 0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x02, // Usage (Mouse) 0xA1, 0x01, // Collection (Application) 0x09, 0x01, // Usage (Pointer) 0xA1, 0x00, // Collection (Physical) 0x05, 0x09, // Usage Page (Button) 0x19, 0x01, // Usage Minimum (Button 1) 0x29, 0x03, // Usage Maximum (Button 3) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x01, // Logical Maximum (1) 0x95, 0x03, // Report Count (3) 0x75, 0x01, // Report Size (1) 0x81, 0x02, // Input (Data, Variable, Absolute) 0x95, 0x01, // Report Count (1) 0x75, 0x05, // Report Size (5) 0x81, 0x03, // Input (Constant, Var, abs) 0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x30, // Usage (X) 0x09, 0x31, // Usage (Y) 0x09, 0x38, // Usage (wheel) 0x15, 0x81, // Logical Minimum (-127) 0x25, 0x7F, // Logical Maximum (127) 0x75, 0x08, // Report Size (8) 0x95, 0x03, // Report Count (3) 0x81, 0x06, // Input (Data, Variable, Relative) 0xC0, // 需注意,此处的0xC0不是END_COLLECTION 0xC0 /* END_COLLECTION */ }; __ALIGN_BEGIN static uint8_t CUSTOM_HID_KeyboardReportDesc_HS[USBD_CUSTOM_HID_KEYBOARD_REPORT_DESC_SIZE] __ALIGN_END = { 0x05, 0x01, 0x09, 0x06, 0xA1, 0x01, 0x05, 0x07, 0x19, 0xE0, 0x29, 0xE7, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08, 0x81, 0x01, 0x95, 0x05, 0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x05, 0x91, 0x02, 0x95, 0x01, 0x75, 0x03, 0x91, 0x01, 0x95, 0x06, 0x75, 0x08, 0x15, 0x00, 0x25, 0x65, 0x05, 0x07, 0x19, 0x00, 0x29, 0x65, 0x81, 0x00, 0xC0 }; ``` 并在对应头文件添加上报告描述符的长度: ``` #define USBD_CUSTOM_HID_MOUSE_REPORT_DESC_SIZE 52U #define USBD_CUSTOM_HID_KEYBOARD_REPORT_DESC_SIZE 63U ``` ## 修改HID初始化函数 在`usbd_customhid.c`中,HID初始化通过`USBD_CUSTOM_HID_Init`完成,在其中添加 ``` ... /* Open EP IN */ (void)USBD_LL_OpenEP(pdev, CUSTOMHIDInEpAdd, USBD_EP_TYPE_INTR, CUSTOM_HID_EPIN_SIZE); pdev->ep_in[CUSTOMHIDInEpAdd & 0xFU].is_used = 1U; // 以下是添加自定义的EP (void)USBD_LL_OpenEP(pdev, MOUSE_EP, USBD_EP_TYPE_INTR, MOUSE_EP_SIZE); pdev->ep_in[MOUSE_EP & 0xFU].is_used = 1U; (void)USBD_LL_OpenEP(pdev, KEYBOARD_EP, USBD_EP_TYPE_INTR, KEYBOARD_EP_SIZE); pdev->ep_in[KEYBOARD_EP & 0xFU].is_used = 1U; ... ``` 同理,在`USBD_CUSTOM_HID_DeInit`中也添加上关闭IN端口的逻辑: ``` /* Close CUSTOM_HID EP IN */ (void)USBD_LL_CloseEP(pdev, CUSTOMHIDInEpAdd); pdev->ep_in[CUSTOMHIDInEpAdd & 0xFU].is_used = 0U; pdev->ep_in[CUSTOMHIDInEpAdd & 0xFU].bInterval = 0U; // 以下关闭自定义的端点 (void)USBD_LL_CloseEP(pdev, MOUSE_EP); pdev->ep_in[MOUSE_EP & 0xFU].is_used = 0U; pdev->ep_in[MOUSE_EP & 0xFU].bInterval = 0U; (void)USBD_LL_CloseEP(pdev, KEYBOARD_EP); pdev->ep_in[KEYBOARD_EP & 0xFU].is_used = 0U; pdev->ep_in[KEYBOARD_EP & 0xFU].bInterval = 0U; ``` 最后,在`USBD_CUSTOM_HID_Setup`中寻找到`case USB_REQ_GET_DESCRIPTOR`,在判断条件`if ((req->wValue >> 8) == CUSTOM_HID_REPORT_DESC)`的代码块中,修改为根据端点序号返回描述: ``` // 以下判断自定义的端点,进行setup if (req->wIndex == 0) { len = MIN(USBD_CUSTOM_HID_REPORT_DESC_SIZE, req->wLength); pbuf = ((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData[pdev->classId])->pReport; } else if (req->wIndex == 1) { len = MIN(USBD_CUSTOM_HID_MOUSE_REPORT_DESC_SIZE, req->wLength); pbuf = CUSTOM_HID_MouseReportDesc_HS; } else if (req->wIndex == 2) { len = MIN(USBD_CUSTOM_HID_KEYBOARD_REPORT_DESC_SIZE, req->wLength); pbuf = CUSTOM_HID_KeyboardReportDesc_HS; } ``` ## 修改发送缓冲 在`USB_DEVICE->Target->usbd_conf.c`中,`USBD_LL_Init`函数最后几行用于配置fifo。 ``` /* USER CODE BEGIN TxRx_HS_Configuration */ HAL_PCDEx_SetRxFiFo(&hpcd_USB_OTG_HS, 0x200); HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_HS, 0, 0x80); HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_HS, 1, 0x174); // 以下为端点2、3设置TxFIFO。由于H7有足够大的内存,地址直接加0x100 HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_HS, 2, 0x274); HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_HS, 3, 0x374); ``` ## 添加发送函数 在`usbd_customhid.c`中,参照`USBD_CUSTOM_HID_SendReport`函数编写键盘和鼠标的发送函数: ``` // ========================= Send report =================================== uint8_t USBD_KEYBOADR_HID_SendReport(USBD_HandleTypeDef *pdev, uint8_t *report, uint16_t len) { USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)pdev->pClassData; if (pdev->dev_state == USBD_STATE_CONFIGURED) { if (hhid->state == CUSTOM_HID_IDLE) { hhid->state = CUSTOM_HID_BUSY; USBD_LL_Transmit(pdev, KEYBOARD_EP, report, len); } else { return USBD_BUSY; } } return USBD_OK; } // ========================= Send report =================================== uint8_t USBD_MOUSE_HID_SendReport(USBD_HandleTypeDef *pdev, uint8_t *report, uint16_t len) { USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)pdev->pClassData; if (pdev->dev_state == USBD_STATE_CONFIGURED) { if (hhid->state == CUSTOM_HID_IDLE) { hhid->state = CUSTOM_HID_BUSY; USBD_LL_Transmit(pdev, MOUSE_EP, report, len); } else { return USBD_BUSY; } } return USBD_OK; } ``` 最后,需要把函数定义放在头文件中。 ## main函数中的测试 在main.c中,需要import USB实例所在的头文件,定义键鼠缓冲,操作如下: ``` #include "usb_device.h" extern USBD_HandleTypeDef hUsbDeviceHS; uint8_t MouseBuff[4] = {0, 10, 10, 0}; uint8_t KeyboardBuff[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // 在main loop中: ... // 鼠标往右下移10个单位 USBD_MOUSE_HID_SendReport(&hUsbDeviceHS, MouseBuff, 4); HAL_Delay(20); // 发送 z KeyboardBuff[2] = 0x1D; USBD_KEYBOADR_HID_SendReport(&hUsbDeviceHS, KeyboardBuff, 8); HAL_Delay(20); // 抬起按键 KeyboardBuff[2] = 0x00; USBD_KEYBOADR_HID_SendReport(&hUsbDeviceHS, KeyboardBuff, 8); // 休眠4秒 HAL_Delay(4000); ... ``` 烧录到设备,即可观察到屏幕中发送“z”并伴随鼠标移动。 --- 参考资料: 1. [花椰菜杀手-基于STM32复合USB设备教程](https://www.bilibili.com/video/BV15W4y1f7E5) * [北风神企鹅-stm32 USB系列-组合设备](https://www.bilibili.com/video/BV1eu411i7zd) * [STM32配置USB组合设备- 灰信网](https://www.freesion.com/article/15721446304/)