网上的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
配置以下内容:
然后配置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. 具体内容:
__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
在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.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”并伴随鼠标移动。
参考资料: