STM32F723 USB HID缓冲透传实现及头文件报错问题咨询
一、"HID already been declared"编译报错解决
报错原因是ST官方USB Host库的usbh_hid.h和USB Device库的usbd_hid.h存在大量重名的宏、枚举类型定义(比如HID类型枚举、HID子类宏等),直接在同一个编译单元(同一个.c文件)中包含两个头文件必然触发重定义错误。
最稳妥的解决方法是隔离两个USB栈的头文件作用域,不需要修改官方库源码:
- 新建独立的桥接模块文件
usbd_hid_bridge.c和usbd_hid_bridge.h - 仅在
usbd_hid_bridge.c中包含usbd_hid.h,所有和USB Device HID相关的操作都封装成对外接口函数,在usbd_hid_bridge.h中只声明这些接口,不引入任何usbd_hid的类型定义 - main.c等其他业务文件只需要包含
usbd_hid_bridge.h,不需要直接引入usbd_hid.h,从根源上避免重名冲突
如果确实需要在同一个.c文件中引用两个头文件,可以在引入第二个头文件前#undef所有冲突的宏定义,这种方法容易漏项,不推荐:
#include "usbh_hid.h" // 先取消Host侧HID头文件定义的重名宏 #undef HID #undef HID_MOUSE #undef HID_KEYBOARD #undef HID_BOOT // 再引入Device侧头文件 #include "usbd_hid.h"
二、HID缓冲设备功能实现
核心设计原则:USB Device侧的HID设备从上电完成枚举后始终保持连接状态,完全不随Host侧外接鼠标的拔插改变枚举状态,这样PC端永远识别到HID鼠标处于已连接状态,不会感知到外接鼠标的断开事件。
1. 描述符配置(优先选固定标准描述符方案)
如果你不需要兼容带自定义按键/功能的非标准鼠标,直接将USB Device侧的HID报告描述符配置为标准4字节Boot鼠标报告描述符即可,兼容市面上绝大多数普通鼠标,不需要动态读取外接鼠标的描述符,也不会触发PC端的设备重枚举,完全满足无缝缓冲的需求:
标准Boot鼠标报告格式:
- Byte0:按键状态,bit0=左键、bit1=右键、bit2=中键,其余位填0
- Byte1:X轴相对位移,int8_t类型
- Byte2:Y轴相对位移,int8_t类型
- Byte3:滚轮相对位移,int8_t类型
直接替换CubeMX生成的usbd_hid.c里的HID_MOUSE_ReportDesc数组为标准鼠标报告描述符即可。
如果你确实需要适配自定义HID鼠标的特殊功能,可在Host侧第一次枚举到外接鼠标时,从Host HID栈的句柄中读取到外接鼠标的报告描述符,替换Device侧的HID报告描述符后触发一次Device端USB重枚举(拉低USB DP引脚10ms再释放),注意这一步仅在第一次识别到新型号鼠标时执行一次,后续外接鼠标拔插时绝对不能再次触发重枚举,否则PC端会识别到设备断开。
2. 代码实现步骤
- 桥接层接口封装(
usbd_hid_bridge.h):#ifndef __USBD_HID_BRIDGE_H #define __USBD_HID_BRIDGE_H #include <stdint.h> // 设置外接鼠标连接状态 void HID_Bridge_SetExtMouseState(uint8_t connected); // 发送鼠标报告给PC uint8_t HID_Bridge_SendMouseReport(int8_t x, int8_t y, int8_t wheel, uint8_t btn_state); // 发送空报告(鼠标断开时调用) uint8_t HID_Bridge_SendNullReport(void); #endif - 桥接层实现(
usbd_hid_bridge.c):#include "usbd_hid_bridge.h" #include "usbd_hid.h" #include "usb_device.h" #include <string.h> static uint8_t ext_mouse_connected = 0; // 标准鼠标报告缓冲区 static uint8_t mouse_report[4] = {0}; void HID_Bridge_SetExtMouseState(uint8_t connected) { ext_mouse_connected = connected; if (!connected) { // 鼠标断开时立刻发一次空报告,避免PC端停留在最后一次按键/位移状态 HID_Bridge_SendNullReport(); } } uint8_t HID_Bridge_SendMouseReport(int8_t x, int8_t y, int8_t wheel, uint8_t btn_state) { if (!ext_mouse_connected) return USBD_FAIL; mouse_report[0] = btn_state; mouse_report[1] = (uint8_t)x; mouse_report[2] = (uint8_t)y; mouse_report[3] = (uint8_t)wheel; return USBD_HID_SendReport(&hUsbDeviceFS, mouse_report, sizeof(mouse_report)); } uint8_t HID_Bridge_SendNullReport(void) { memset(mouse_report, 0, sizeof(mouse_report)); return USBD_HID_SendReport(&hUsbDeviceFS, mouse_report, sizeof(mouse_report)); } - 主逻辑和回调修改:
- 在USB Host的用户回调
USBH_UserProcess中监听设备连接/断开事件:// 找到HOST_USER_CLASS_ACTIVE和HOST_USER_DISCONNECTION事件分支 case HOST_USER_CLASS_ACTIVE: if (USBH_HID_GetDeviceType(&hUsbHostFS) == HID_MOUSE) { HID_Bridge_SetExtMouseState(1); } break; case HOST_USER_DISCONNECTION: HID_Bridge_SetExtMouseState(0); break; - 修改现有的HID数据接收回调,不要直接透传整个
HID_MOUSE_Info_TypeDef结构体(这个结构体内部有指针,长度和格式都不符合标准鼠标报告要求),提取有效字段后调用桥接接口发送:void USBH_HID_EventCallback(USBH_HandleTypeDef *phost) { if (USBH_HID_GetDeviceType(phost) == HID_MOUSE) { HID_MOUSE_Info_TypeDef *Mouse_Info = USBH_HID_GetMouseInfo(phost); if (Mouse_Info == NULL) return; HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET); // 组装按键状态:bit0左键、bit1右键、bit2中键 uint8_t btn_state = (Mouse_Info->buttons[0] ? 0x01 : 0) | (Mouse_Info->buttons[1] ? 0x02 : 0) | (Mouse_Info->buttons[2] ? 0x04 : 0); // 调用桥接接口发报告,ST库已经将x/y/wheel解析为int8_t相对位移,直接强转即可 HID_Bridge_SendMouseReport((int8_t)Mouse_Info->x, (int8_t)Mouse_Info->y, (int8_t)Mouse_Info->wheel, btn_state); // 原有串口打印逻辑,注意补全缺失的分号 int len = sprintf(TxBuff, "X=%d ,Y=%d, Button1=%d, Button2=%d, Button3=%d\n", (int8_t)Mouse_Info->x, (int8_t)Mouse_Info->y, Mouse_Info->buttons[0], Mouse_Info->buttons[1], Mouse_Info->buttons[2]); HAL_UART_Transmit(&huart7, (uint8_t*)TxBuff, len, 1000); } }
- 在USB Host的用户回调
3. 注意事项
- 不要在任何业务逻辑中调用
USBD_Stop、USBD_DeInit等会终止USB Device设备的函数,保证Device侧从初始化完成后始终处于配置完成状态 - 外接鼠标断开后不需要做任何Device侧的状态修改,只需要停止透传真实鼠标数据,按需发送空报告即可,PC端只会认为鼠标没有输入操作,不会检测到设备断开
- 现有代码中
HAL_UART_Transmit行末尾缺失分号,需要补上避免编译报错
内容的提问来源于stack exchange,提问作者Ilia Hadzhiev

