尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
STM32F103C8T6实现USB复合HID:键盘+鼠标一体化设计
简介本资源是一份基于STM32F103C8T6的USB复合HID设备完整工程实现面向嵌入式初学者与STM32进阶开发者解决USB协议下键盘鼠标双设备协同枚举与数据上报的核心难点。项目采用STM32CubeMX图形化配置生成HAL底层框架涵盖时钟树设置、USB OTG FS设备模式启用、双HID报告描述符定制、中断端点分配及固件级数据响应逻辑适用于人机交互类毕业设计、USB协议学习与低成本HID设备原型开发。压缩包含626个文件7.58MB以345个C源文件和114个头文件构成主程序逻辑43个汇编启动文件与28个IAR链接脚本支撑多工具链适配另含Keil工程文件uvprojx/uvoptx、调试符号axf/map及HAL库依赖模块目录结构按外设驱动、USB核心、HID报告层分层组织便于理解USB设备栈构建流程。目前已有3418人学习下载可直接编译烧录验证复合设备识别效果并为自定义HID功能扩展提供可复用的代码骨架与配置范式。1. 一个 USB 接口同时当键盘又当鼠标STM32F103C8T6 上跑通复合 HID 设备的真实路径你手头那块蓝 pillSTM32F103C8T6开发板USB 口一直只接个串口调试器别浪费了——它完全能在一个物理 USB 插头上同时暴露「标准键盘」和「标准鼠标」两个 HID 设备。Windows/macOS/Linux 都认即插即用不用装驱动。这不是理论是实测可运行的完整工程按下 PA0 按键PC 端立刻收到 CtrlAltDel 组合键旋转编码器光标就平滑移动长按 PB1 还能模拟鼠标中键滚轮。整个项目基于 STM32CubeMX 图形化配置生成初始化代码HID 报文结构、描述符定义、端点管理、状态同步全部封装在 C 文件里没有 HAL 黑匣子陷阱也没有 USB 协议栈玄学。适合想把单片机真正变成“人机交互终端”的嵌入式开发者——比如做定制化控制面板、带按键旋钮的工业 HMI、或给树莓派/PC 加个硬件级快捷键层。不是 demo是能焊进产品里的最小可行 HID 复合设备。2. 工程骨架与 CubeMX 配置从空芯片到双设备枚举成功的 7 步关键操作2.1 创建工程前必须确认的 3 个硬件事实STM32F103C8T6 的 USB 是Full-speed Device-only不支持 Host且仅支持 USB Device 模式没有 OTG 功能。这意味着必须使用PA11 (DM) 和 PA12 (DP)作为 USB 差分信号线不能改脚板载 USB 口必须直连 PA11/PA12且DP 线需接 1.5kΩ 上拉电阻到 3.3V这是主机识别设备速度的关键芯片内部无 USB PHY依赖外部信号完整性——若枚举失败90% 是 DP/DM 走线过长、未包地、或上拉电阻值偏差实测 1.4–1.6kΩ 可用2.2kΩ 就大概率失败。提示很多蓝 pill 板已内置 1.5kΩ 上拉但部分山寨板用的是 10kΩ务必用万用表量 PA12 对 3.3V 电阻值。这是后续所有调试的起点别跳过。2.2 CubeMX 中 USB Device 的 4 项强制配置打开 STM32CubeMX建议 v6.12低版本对复合设备支持不全选择 STM32F103C8T6按顺序操作Enable USB Device在 Pinout 视图中找到 USB_DEVICE → Mode → SelectDevice OnlyClock Configuration在 Clock Configuration 标签页确保USBCLK 48MHz由 PLL 输出分频得到必须勾选USB在 RCC → USB clock sourceUSB Device Class在 Middleware → USB_DEVICE → Class For Port 1 → 选择Custom Class⚠️ 不要选 HID Keyboard 或 HID Mouse 单一类复合设备必须自定义Descriptor Endpoint点击 USB_DEVICE → Configuration → Descriptor Settings →Device DescriptorbNumConfigurations 1复合设备仍为单配置Configuration DescriptorbNumInterfaces 2核心键盘 鼠标 2 个接口Interface Descriptor添加两个 Interface分别设置Interface 0bInterfaceClass 0x03HIDbInterfaceSubClass 0x01No SubclassbInterfaceProtocol 0x01KeyboardInterface 1bInterfaceClass 0x03bInterfaceSubClass 0x01bInterfaceProtocol 0x02Mouse生成代码后usbd_conf.c中会自动创建USBD_CUSTOM_HID_Desc数组但此时它是空的——下一步要手动填描述符。2.3 复合 HID 描述符的手动注入为什么不能靠 CubeMX 自动生成CubeMX 的 Custom Class 仅生成框架真正的 HID Report Descriptor 和 Interface Association DescriptorIAD必须手写。原因IAD 是 USB 2.0 复合设备识别多接口的必需结构而 CubeMX v6.12 仍未原生支持 IAD 生成。你需要在Core/Inc/usbd_custom_hid.h中补充// IAD: 关联 Interface 0 和 Interface 1 为同一设备 #define CUSTOM_HID_IAD_DESCRIPTOR_SIZE 8 extern uint8_t CUSTOM_HID_IAD_Desc[CUSTOM_HID_IAD_DESCRIPTOR_SIZE];并在Core/Src/usbd_custom_hid.c中实现// IAD 描述符告诉主机这两个接口属于同一个逻辑设备 uint8_t CUSTOM_HID_IAD_Desc[CUSTOM_HID_IAD_DESCRIPTOR_SIZE] { 0x08, // bLength 0x0B, // bDescriptorType IAD 0x00, 0x01, // bFirstInterface 0, bInterfaceCount 2 0x03, // bFunctionClass 0x03 (HID) 0x00, // bFunctionSubClass 0 0x00, // bFunctionProtocol 0 0x00 // iFunction 0 (no string) };注意IAD 必须放在 Configuration Descriptor 之后、第一个 Interface Descriptor 之前。否则 Windows 设备管理器会显示“未知 USB 设备”且无法枚举出两个 HID 子设备。2.4 生成最终 USB 描述符链顺序决定成败完整的描述符链顺序必须严格为Device Desc→Configuration Desc→IAD Desc→Interface 0 Desc→HID Desc (Interface 0)→Endpoint Desc (IN for Keyboard)→Interface 1 Desc→HID Desc (Interface 1)→Endpoint Desc (IN for Mouse)在usbd_custom_hid.c的USBD_CUSTOM_HID_Desc数组中按此顺序拼接所有描述符段。其中键盘和鼠标的 Report Descriptor 必须符合 HID Usage Tables v1.12 规范键盘 Report Descriptor简化版支持 6 键 rollover modifier0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x06, // USAGE (Keyboard) 0xA1, 0x01, // COLLECTION (Application) 0x05, 0x07, // USAGE_PAGE (Keyboard/Keypad) 0x19, 0xE0, // USAGE_MINIMUM (Keyboard LeftControl) 0x29, 0xE7, // USAGE_MAXIMUM (Keyboard Right GUI) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x01, // LOGICAL_MAXIMUM (1) 0x75, 0x01, // REPORT_SIZE (1) 0x95, 0x08, // REPORT_COUNT (8) 0x81, 0x02, // INPUT (Data,Var,Abs) 0x95, 0x01, // REPORT_COUNT (1) 0x75, 0x08, // REPORT_SIZE (8) 0x81, 0x03, // INPUT (Cnst,Var,Abs) 0x95, 0x06, // REPORT_COUNT (6) 0x75, 0x08, // REPORT_SIZE (8) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0xFF, // LOGICAL_MAXIMUM (255) 0x05, 0x07, // USAGE_PAGE (Keyboard/Keypad) 0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated)) 0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application) 0x81, 0x00, // INPUT (Data,Ary,Abs) 0xC0 // END_COLLECTION鼠标 Report Descriptor支持 X/Y 移动 3 键 滚轮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,Var,Abs) 0x95, 0x01, // REPORT_COUNT (1) 0x75, 0x05, // REPORT_SIZE (5) 0x81, 0x03, // INPUT (Cnst,Var,Abs) 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x30, // USAGE (X) 0x09, 0x31, // USAGE (Y) 0x15, 0x81, // LOGICAL_MINIMUM (-127) 0x25, 0x7F, // LOGICAL_MAXIMUM (127) 0x75, 0x08, // REPORT_SIZE (8) 0x95, 0x02, // REPORT_COUNT (2) 0x81, 0x06, // INPUT (Data,Var,Rel) 0x09, 0x38, // USAGE (Wheel) 0x15, 0x81, // LOGICAL_MINIMUM (-127) 0x25, 0x7F, // LOGICAL_MAXIMUM (127) 0x75, 0x08, // REPORT_SIZE (8) 0x95, 0x01, // REPORT_COUNT (1) 0x81, 0x06, // INPUT (Data,Var,Rel) 0xC0, // END_COLLECTION 0xC0 // END_COLLECTION关键参数说明REPORT_SIZE和REPORT_COUNT决定每个 HID 报文的字节数。键盘报文固定 8 字节1 字节 modifier 1 字节 reserved 6 字节 key codes鼠标报文固定 4 字节3 字节 buttonXY 1 字节 wheel。USB 端点最大包长必须 ≥ 这个值此处设为 64。2.5 USB 端点分配与中断处理逻辑绑定在usbd_custom_hid.c的USBD_CUSTOM_HID_Init()中必须显式启用两个 IN 端点// 键盘使用 EP1 IN鼠标使用 EP2 IN避免冲突 USBD_LL_OpenEP(pdev, CUSTOM_HID_EPIN_ADDR_KEYBOARD, USBD_EP_TYPE_INTR, 0x40); // 64-byte max packet USBD_LL_OpenEP(pdev, CUSTOM_HID_EPIN_ADDR_MOUSE, USBD_EP_TYPE_INTR, 0x40);对应地在USBD_CUSTOM_HID_DataIn()回调中根据epnum区分发送static uint8_t USBD_CUSTOM_HID_DataIn(USBD_HandleTypeDef *pdev, uint8_t epnum) { if (epnum CUSTOM_HID_EPNUM_KEYBOARD) { // 填充键盘 report buffer调用 USBD_LL_Transmit() } else if (epnum CUSTOM_HID_EPNUM_MOUSE) { // 填充鼠标 report buffer调用 USBD_LL_Transmit() } return USBD_OK; }血泪经验HAL 库默认只开一个 IN 端点。若忘记为鼠标单独开 EP2Windows 会枚举出键盘但鼠标设备永远显示“正在安装驱动”然后失败——因为主机发了 SET_INTERFACE 请求但设备没响应。2.6 主循环中的 HID 状态同步非阻塞扫描与双缓冲机制在main.c的while(1)循环中不能直接调用USBD_CUSTOM_HID_SendReport()——它底层调用USBD_LL_Transmit()而该函数在 USB 总线忙时会阻塞。正确做法是定义两个全局 report bufferuint8_t keyboard_report[8] {0}; // modifier reserved 6 keys uint8_t mouse_report[4] {0}; // buttons X Y wheel每 10ms 扫描一次 GPIO/编码器更新 buffer 内容注意原子性// 使用 HAL_GetTick() 实现软定时器 if ((HAL_GetTick() - last_scan_tick) 10) { last_scan_tick HAL_GetTick(); // 更新 keyboard_report[0]modifier和 keyboard_report[2..7]key codes // 更新 mouse_report[0]buttons、mouse_report[1]X、mouse_report[2]Y、mouse_report[3]wheel }在USBD_CUSTOM_HID_DataIn()回调中将 buffer 拷贝到 USB 传输缓冲区并触发发送if (epnum CUSTOM_HID_EPNUM_KEYBOARD) { memcpy(hid-Report_buf, keyboard_report, 8); USBD_LL_Transmit(pdev, CUSTOM_HID_EPIN_ADDR_KEYBOARD, hid-Report_buf, 8); } else if (epnum CUSTOM_HID_EPNUM_MOUSE) { memcpy(hid-Report_buf, mouse_report, 4); USBD_LL_Transmit(pdev, CUSTOM_HID_EPIN_ADDR_MOUSE, hid-Report_buf, 4); }这样设计保证了即使 USB 总线暂时繁忙按键/鼠标事件也不会丢失只是延迟 1 帧约 10ms发送。比轮询USBD_CUSTOM_HID_IsBusy()更可靠。3. 避坑指南5 个让开发者反复重启烧录器的致命细节3.1 现象设备管理器显示“未知 USB 设备”右键更新驱动无效原因IAD 描述符缺失或位置错误。Windows 10 强制要求复合设备提供 IAD否则拒绝枚举第二接口。解决检查USBD_CUSTOM_HID_Desc数组中 IAD 是否紧接在 Configuration Descriptor 之后且bFirstInterface0、bInterfaceCount2。用 USBlyzer 或 Wireshark USBPcap 抓包确认主机是否收到 IAD类型 0x0B。3.2 现象键盘能用鼠标在设备管理器中显示为“HID-compliant mouse”但光标不动原因鼠标 Report Descriptor 中LOGICAL_MINIMUM/MAXIMUM值错误或USBD_CUSTOM_HID_DataIn()中发送了错误长度的数据如发了 8 字节给鼠标端点。解决用hid-report-descriptor在线工具校验鼠标描述符在USBD_CUSTOM_HID_DataIn()中加if(epnum2) printf(Mouse send %d bytes\n, len);日志确保USBD_LL_Transmit()第三个参数传4鼠标报文长度。3.3 现象按下按键后 PC 端收到乱码字符如、~而非预期的CtrlAltDel原因键盘 Report Descriptor 中USAGE_MINIMUM/MAXIMUM范围与实际发送的 keycode 不匹配或 modifier 字节report[0]位定义错误。解决对照 HID Usage Tables v1.12确认 keycode0x29是Keyboard Application而0xE0是Left Controlmodifier 字节 bit0Left Ctrl, bit1Left Shift, bit2Left Alt, bit3Left GUI, bit4Right Ctrl... 发送0x08表示只按 Left Alt。3.4 现象首次插入 USB 后设备正常拔插几次后 Windows 提示“设备无法启动代码 10”原因USB 复位后HAL 库未正确重置端点状态导致USBD_LL_Transmit()返回USBD_BUSY后卡死。解决在USBD_CUSTOM_HID_DeInit()中显式关闭端点USBD_LL_CloseEP(pdev, CUSTOM_HID_EPIN_ADDR_KEYBOARD); USBD_LL_CloseEP(pdev, CUSTOM_HID_EPIN_ADDR_MOUSE);并在USBD_CUSTOM_HID_Init()中重新OpenEP。3.5 现象使用 ST-Link V2 烧录后USB 无法识别但 SWD 调试正常原因ST-Link V2 的 3.3V 输出能力不足仅 50mA而 USB 枚举初期需要 100mA 维持 PHY 稳定。解决改用外部 3.3V 电源供电如 AMS1117-3.3 模块或在main()开头加HAL_Delay(100)让电源稳定后再初始化 USB。4. 核心 HID 数据流拆解从 GPIO 按下到 PC 光标移动的 11 个关键节点4.1 GPIO 输入捕获消抖与状态机设计PA0 按键连接到 GND采用上拉输入。HAL 库默认无硬件消抖必须软件实现// 在 main.c 全局定义 static uint8_t key_state 0; // 0idle, 1pressed, 2released static uint32_t key_down_time 0; // 在 10ms 扫描中 if (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) GPIO_PIN_SET) { // 按键释放 if (key_state 1) { key_state 2; key_down_time HAL_GetTick(); } } else { // 按键按下 if (key_state 0) { key_state 1; } else if (key_state 2 (HAL_GetTick() - key_down_time) 500) { // 长按 500ms 触发特殊功能如鼠标中键 keyboard_report[0] | 0x04; // Left Alt } }注意不要用HAL_GPIO_EXTI_Callback()做按键中断——USB 中断优先级更高EXTI 可能被屏蔽导致丢键。4.2 键盘 Report Buffer 填充逻辑modifier 与 keycode 的协同键盘报文格式8 字节Byte用途示例值0Modifier bits0x08 Left Alt1Reserved (always 0)0x002-7Keycode array (最多 6 键)0x29, 0x00, 0x00, 0x00, 0x00, 0x00Del填充代码// 清空 memset(keyboard_report, 0, sizeof(keyboard_report)); // 设置 modifierCtrlAltDel 0x01 | 0x04 0x05 keyboard_report[0] 0x05; // 设置 keycodeDel 的 usage code 是 0x29 keyboard_report[2] 0x29;关键keyboard_report[1]必须为0x00否则 Windows 解析失败。很多教程漏写这行导致键盘“有按键声但无输出”。4.3 鼠标移动数据生成编码器旋转到 ΔX/ΔY 的映射PB2/PB3 接增量编码器 A/B 相。使用 HAL 的HAL_TIM_Encoder_Start()获取计数值// 初始化 TIM2 为编码器模式 htim2.Instance TIM2; htim2.Init.Prescaler 0; htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.Period 0xFFFF; htim2.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; htim2.Encoder.Init.EncoderMode TIM_ENCODERMODE_TI12; htim2.Encoder.Init.IC1Polarity TIM_ICPOLARITY_RISING; htim2.Encoder.Init.IC2Polarity TIM_ICPOLARITY_RISING; htim2.Encoder.Init.IC1Selection TIM_ICSELECTION_DIRECTTI; htim2.Encoder.Init.IC2Selection TIM_ICSELECTION_DIRECTTI; HAL_TIM_Encoder_Start(htim2, TIM_CHANNEL_ALL); // 在 10ms 扫描中 int16_t delta __HAL_TIM_GET_COUNTER(htim2) - last_encoder_count; last_encoder_count delta; // 映射1 编码器脉冲 ≈ 1 像素移动可调 mouse_report[1] (delta 127) ? 127 : (delta -127) ? -127 : delta; // X mouse_report[2] 0; // Y 暂不使用注意__HAL_TIM_GET_COUNTER()返回 int32_t但鼠标 report 只接受 int8_t。必须截断否则高位溢出导致光标狂奔。4.4 USB IN 端点传输触发USBD_LL_Transmit 的隐藏约束USBD_LL_Transmit()并非立即发送而是将数据放入 FIFO由 USB ISR 触发 DMA 传输。其成功前提端点必须已OpenEP且未被Stallpdev-pClass-DataIn回调必须已注册CubeMX 自动生成USBD_CUSTOM_HID_DataIn()中不能修改hid-Report_buf地址它是静态 buffer。典型错误写法// ❌ 错误动态分配 bufferISR 中访问野指针 uint8_t* temp_buf malloc(4); memcpy(temp_buf, mouse_report, 4); USBD_LL_Transmit(pdev, EP2_IN, temp_buf, 4); // 危险正确写法// ✅ 正确使用预分配的 hid-Report_buf memcpy(hid-Report_buf, mouse_report, 4); USBD_LL_Transmit(pdev, EP2_IN, hid-Report_buf, 4);4.5 主机端接收解析Windows HID Class Driver 如何拆包Windows 收到 IN 包后按以下流程处理根据bInterfaceNumber将数据路由到对应 HID minidriverkbdclass.sys 或 mouclass.sys用 Report Descriptor 中的USAGE_PAGE/USAGE查找字段含义将keyboard_report[0]解析为 modifier 键状态将keyboard_report[2]解析为 keycode查表转为 Unicode 字符需配合当前键盘布局将mouse_report[1]作为相对 X 位移叠加到当前光标坐标。验证方法在 Windows 设备管理器 → 键盘/鼠标属性 → “详细信息” → 选择“硬件 ID”确认VID_XXXXPID_YYYY与usbd_desc.c中定义一致用hid-nuget工具读取设备 Report Descriptor比对是否与源码一致。4.6 复合设备的电源管理Suspend/Resume 事件处理USB 主机可能在空闲时发送SET_FEATURE DEVICE_SUSPEND。若设备不响应会被强制断开。必须实现// 在 usbd_custom_hid.c 中添加 static uint8_t USBD_CUSTOM_HID_Setup(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req) { switch (req-bmRequest 0x1F) { case USB_REQ_SET_FEATURE: if (req-wValue 0x0002) { // DEVICE_SUSPEND // 进入低功耗关闭 GPIO 时钟、降低系统时钟 __HAL_RCC_GPIOA_CLK_DISABLE(); HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); } break; } return USBD_OK; }注意唤醒后需重新初始化所有外设时钟否则 GPIO 无法读取。这是最易忽略的电源管理坑。5. 二次开发实战从双设备扩展到三设备键盘鼠标游戏手柄的改造清单5.1 新增 Gamepad 接口的 Descriptor 修改清单要在现有双设备基础上增加标准游戏手柄HID Joystick需修改三处Configuration DescriptorbNumInterfaces从2改为3IAD DescriptorbInterfaceCount从2改为3新增 Interface 2bInterfaceClass 0x03,bInterfaceSubClass 0x00,bInterfaceProtocol 0x00Joystick 无协议对应 Report Descriptor支持 32 按键 6 轴 2 个 POV 开关0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x04, // USAGE (Joystick) 0xA1, 0x01, // COLLECTION (Application) 0x05, 0x09, // USAGE_PAGE (Button) 0x19, 0x01, // USAGE_MINIMUM (Button 1) 0x29, 0x20, // USAGE_MAXIMUM (Button 32) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x01, // LOGICAL_MAXIMUM (1) 0x75, 0x01, // REPORT_SIZE (1) 0x95, 0x20, // REPORT_COUNT (32) 0x81, 0x02, // INPUT (Data,Var,Abs) 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x30, // USAGE (X) 0x09, 0x31, // USAGE (Y) 0x09, 0x32, // USAGE (Z) 0x09, 0x33, // USAGE (Rx) 0x09, 0x34, // USAGE (Ry) 0x09, 0x35, // USAGE (Rz) 0x15, 0x81, // LOGICAL_MINIMUM (-127) 0x25, 0x7F, // LOGICAL_MAXIMUM (127) 0x75, 0x08, // REPORT_SIZE (8) 0x95, 0x06, // REPORT_COUNT (6) 0x81, 0x02, // INPUT (Data,Var,Abs) 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x39, // USAGE (Hat switch) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x07, // LOGICAL_MAXIMUM (7) 0x35, 0x00, // PHYSICAL_MINIMUM (0) 0x45, 0x07, // PHYSICAL_MAXIMUM (7) 0x75, 0x04, // REPORT_SIZE (4) 0x95, 0x01, // REPORT_COUNT (1) 0x81, 0x02, // INPUT (Data,Var,Abs) 0xC0 // END_COLLECTION关键变化游戏手柄 Report Descriptor 长度为 52 字节因此需为 EP3 IN 分配更大 buffer如 64 字节并在USBD_CUSTOM_HID_Init()中OpenEP。5.2 新增端点与传输通道的代码补丁在usbd_custom_hid.h中定义新端点#define CUSTOM_HID_EPIN_ADDR_JOYSTICK 0x83 // EP3 IN #define CUSTOM_HID_EPNUM_JOYSTICK 0x03在USBD_CUSTOM_HID_Init()中USBD_LL_OpenEP(pdev, CUSTOM_HID_EPIN_ADDR_JOYSTICK, USBD_EP_TYPE_INTR, 0x40);在USBD_CUSTOM_HID_DataIn()中增加分支else if (epnum CUSTOM_HID_EPNUM_JOYSTICK) { memcpy(hid-Report_buf, joystick_report, 12); // 游戏手柄报文实际用 12 字节 USBD_LL_Transmit(pdev, CUSTOM_HID_EPIN_ADDR_JOYSTICK, hid-Report_buf, 12); }注意joystick_report需定义为uint8_t joystick_report[12]其中 byte0-3 为 32 个按钮bit0-bit31byte4-9 为 6 轴各 1 字节byte10-11 为 POV4-bit each。5.3 主循环中游戏手柄状态采集ADC 模拟摇杆接入PA4/PA5 接电位器模拟 X/Y 轴。使用 HAL ADC 多通道扫描// 初始化 ADC1通道 IN4(IN5)12-bit右对齐 hadc1.Instance ADC1; hadc1.Init.ClockPrescaler ADC_CLOCK_SYNC_PCLK_DIV2; hadc1.Init.Resolution ADC_RESOLUTION_12B; hadc1.Init.ScanConvMode ENABLE; hadc1.Init.ContinuousConvMode DISABLE; hadc1.Init.DiscontinuousConvMode DISABLE; hadc1.Init.ExternalTrigConvEdge ADC_EXTERNALTRIGCONVEDGE_NONE; hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion 2; hadc1.AdvancedInit.AutoDelay DISABLE; hadc1.AdvancedInit.Overrun ADC_OVR_DATA_OVERWRITTEN; HAL_ADC_Init(hadc1); // 配置通道 sConfig.Channel ADC_CHANNEL_4; sConfig.Rank 1; sConfig.SamplingTime ADC_SAMPLETIME_15CYCLES; HAL_ADC_ConfigChannel(hadc1, sConfig); sConfig.Channel ADC_CHANNEL_5; sConfig.Rank 2; HAL_ADC_ConfigChannel(hadc1, sConfig); // 在 10ms 扫描中 HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 10); uint32_t x_raw HAL_ADC_GetValue(hadc1); HAL_ADC_Start(hadc1); // 重新启动获取第二个通道 HAL_ADC_PollForConversion(hadc1, 10); uint32_t y_raw HAL_ADC_GetValue(hadc1); // 映射到 -127 ~ 127 joystick_report[4] (x_raw 2048) ? (x_raw - 2048) / 16 : -(2048 - x_raw) / 16; joystick_report[5] (y_raw 2048) ? (y_raw - 2048) / 16 : -(2048 - y_raw) / 16;血泪经验ADC 采样必须用HAL_ADC_PollForConversion()不能用中断——USB ISR 优先级高于 ADC IRQ中断方式必然丢数据。5.4 Windows 设备识别验证三设备共存的注册表证据成功枚举后在 Windows 注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB下会看到三个子项VID_XXXXPID_YYYYMI_00→ 键盘Interface 0VID_XXXXPID_YYYYMI_01→ 鼠标Interface 1本文还有配套的精品资源点击获取
RELATED

相关推荐

程序设计与算法测验全解析:从基础语法到算法思维的完整训练路径

程序设计与算法测验全解析:从基础语法到算法思维的完整训练路径

1. 这套测验到底在考什么1.1 从标题拆出来的三层信息看到“程序设计与算法(一)测验汇总(2021夏季)”这个标题,很多人第一反应是“不就是一套题吗”。但如果你真的把这一整套测验从头到尾刷过一遍,就会发现它其实是一个被精心设计过…

📅 2026/10/20 5:24:28
选择排序详解:原理、代码实现、复杂度优化与面试题

选择排序详解:原理、代码实现、复杂度优化与面试题

说到数据结构里的排序算法,选择排序大概是不少人的入门第二课。学会冒泡排序之后,紧接着接触的往往就是它。名字听着简单——选择——但真让我说清楚它到底高效在哪、坑在哪,很多人反而一时答不上来。这篇文章把选择排序从原理、代码到优化、…

📅 2026/10/20 5:24:28
上下文工程实战:6个核心问题玩转Token预算与上下文管理

上下文工程实战:6个核心问题玩转Token预算与上下文管理

做提示工程这几年,我有个越来越深的体会:真正拉开AI应用体验差距的,往往不是模型选型,而是上下文怎么填、怎么管、怎么省。上下文工程(Context Engineering)这个说法出现得比提示工程晚,但它是提…

📅 2026/10/20 5:19:28
MORE NEWS

更多资讯

📰

无人机河道漂浮检测数据集构建实战:五类目标识别全流程解析

做无人机河道巡查的朋友应该都有同感:跑完几十公里航线,导出的素材动辄几十个G,熬夜看完视频,发现真正需要上报的漂浮物没几处,而水面上最扎眼的往往是乱七八糟的枯枝、白色垃圾、废旧船只。单纯靠人眼去盯&#xff0c…

📰

AI教材写作如何避开查重雷区?从底层原理到实操降重技巧

2. 为什么AI写教材容易“撞车”:查重系统的底层逻辑3.1 别急着怪AI,先搞清楚“查重”查的是什么“我明明是自己写的,为什么一查重就红?”这是我被问过最多的一句话。尤其是在AI工具刚普及那两年,很多人把大模型生成的内…

📰

国产AI会议助手深度拆解:语音识别、信创适配与私有化部署全解析

这两年因为工作关系,我接触了不少会议纪要工具。说实话,一开始我对“国产AI会议助手”这个说法是有点疑虑的,毕竟底层用的模型、算力、框架,很多都是国外的开源方案。但真正深入去部署、去调优、去做信创环境适配之后,…

📰

Ubuntu 20.04 Docker部署FATE联邦学习框架实操指南

1. 先回答问题:为什么在 Ubuntu 20.04 上要选择 Docker 部署 FATE1.1 FATE 联邦学习框架到底解决什么问题FATE 联邦学习框架的核心场景,是让多个参与方在不直接暴露原始数据的前提下,共同完成模型训练和评估。传统机器学习通常会先把各方数据…

📰

电力通信协议实战组合包:DL/T 698.45与Q/GDW 1376.1可执行解析工具

简介:本资源是面向电力系统自动化工程师、智能电表开发与测试人员的通信协议实践套件,聚焦DL/T 645、Q/GDW 1376.1–1376.3及Q/GDW 698等核心行业协议的解析、模拟与验证需求。资源包含89个文件,涵盖37个协议文本说明(txt&#xf…

📰

比 PyTorch 快 4 倍?map-anything-apache-fp16 精度与速度基准测试完整报告

【免费下载链接】map-anything-apache-fp16 项目地址: https://ai.gitcode.com/hf_mirrors/mlx-community/map-anything-apache-fp16 点击查看 免费下载 map-anything-apache-fp16 是 Meta 团队 MapAnything 3D 重建模型的 float16 精度 MLX 移植版,可在…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬