news 2026/4/23 2:21:44

STM32平台usb通信实现HID鼠标项目应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32平台usb通信实现HID鼠标项目应用

手把手教你用STM32实现一个USB虚拟鼠标:从协议到代码的完整实践

你有没有想过,一块小小的STM32开发板,也能变成一只即插即用的USB鼠标?不需要驱动、不依赖操作系统,插上电脑就能控制光标移动和点击——这并不是什么黑科技,而是每一个嵌入式工程师都应该掌握的基础能力。

在工业自动化测试、辅助设备开发甚至安全研究领域,这种“伪装成输入设备”的嵌入式方案正变得越来越重要。而它的核心技术,就是我们今天要深入剖析的:如何在STM32平台上通过USB通信实现HID类鼠标功能

别被“协议”“枚举”这些术语吓退。接下来我会像带徒弟一样,带你一步步揭开USB HID背后的神秘面纱,从硬件配置到报告描述符,再到实际代码实现,全部讲透。


为什么选USB?为什么是HID?

先问个问题:如果你要做一个能控制电脑光标的设备,你会选哪种方式?

蓝牙?串口转虚拟输入?还是直接走USB?

答案很明确:USB + HID 类是最优解。

免驱才是王道

想想看,用户买了一个新鼠标,插上去要装驱动吗?基本不用。因为Windows、Linux、macOS都内置了对标准HID设备的支持。只要你遵循规范,系统就会自动识别为“通用USB输入设备”,立刻可用。

这就是HID的最大优势——跨平台免驱兼容性。相比之下,UART或自定义USB类设备往往需要安装专用驱动,部署成本陡增。

延迟够低,响应才快

鼠标这类输入设备最怕什么?延迟高、操作卡顿。

USB全速模式(Full Speed)提供12 Mbps的带宽,虽然比不上高速USB,但对于每次只传几个字节的鼠标数据来说绰绰有余。配合合理的轮询间隔(通常1~10ms),完全可以做到毫秒级响应。

📌小知识:Windows默认每8ms轮询一次HID设备,也就是说你的鼠标状态最多延迟8ms就能被主机读取到。

不止于“鼠标的形状”

HID的本质是一种数据描述机制,它不限定物理形态。你可以用陀螺仪做空中鼠标,用压力传感器做脚踏开关,甚至用脑电波信号生成点击事件——只要数据格式符合HID报告描述符定义,系统就认你是“鼠标”。

这也正是嵌入式开发者最看重的一点:高度可定制化


USB通信是怎么跑起来的?别再只会喊“插上去就能用”了

很多人以为USB就是“插上线,配个库,调个函数”,但实际上整个过程远比想象中精密。我们得搞清楚:当你把STM32开发板插入电脑时,背后到底发生了什么。

主机说了算:USB是典型的主从架构

所有USB通信都由主机(PC)发起,设备只能被动响应。这意味着:

  • 设备不能主动发数据给PC;
  • 每次传输前,必须等主机先发一个“令牌包(Token Packet)”;
  • 数据是否送达,也由主机确认。

所以你看,所谓的“发送鼠标数据”,其实是:
主机定时问:“有新动作吗?” → 我们答:“有,X轴动了+5”

这个交互过程叫做中断传输(Interrupt Transfer),专为低延迟周期性通信设计,正是HID设备的核心传输类型。

枚举:设备的“自我介绍大会”

刚接上电,STM32还什么都不是。只有完成“枚举”流程,PC才会知道它是谁、能干什么。

整个过程就像一场面试:

  1. 主机问:“你是啥设备?”
  2. 我们回:“我是USB设备,支持1种配置。”(返回设备描述符)
  3. 主机再问:“具体有哪些功能?”
  4. 我们交简历:“我有一个接口,属于HID类,版本1.11。”(返回配置描述符 + HID描述符)
  5. 主机追问:“你的数据长什么样?”
  6. 我们亮出结构图:“第一个字节是按键,第二字节X位移,第三字节Y位移……”(上传报告描述符)

一旦这套“对话”顺利完成,操作系统就会加载HID驱动,建立中断通道,设备正式上岗。

✅ 关键提示:任何一个描述符出错,枚举就会失败,设备显示为“未知USB设备”。这是新手最常见的坑!


HID报告描述符:你写的不是代码,是“设备说明书”

如果说USB协议是高速公路,那报告描述符(Report Descriptor)就是告诉交警“这辆车拉的是什么货”的清单。

它用一种紧凑的二进制语言(叫“Item Format”)来定义数据字段的意义。比如下面这段看似天书的东西:

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 (Buttons) 0x19, 0x01, // Usage Minimum (1) 0x29, 0x03, // Usage Maximum (3) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x01, // Logical Maximum (1) 0x95, 0x03, // Report Count (3) —— 三个按键 0x75, 0x01, // Report Size (1) —— 每个按键占1位 0x81, 0x02, // Input (Data,Var,Abs) —— 输入数据 0x95, 0x01, // Report Count (1) —— 填充5位 0x75, 0x05, 0x81, 0x01, // Input (Constant) —— 固定值,占位 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 bits) 0x95, 0x02, // Report Count (2) —— X和Y各1字节 0x81, 0x06, // Input (Data,Var,Rel) —— 相对坐标输入 0xC0, // End Collection 0xC0 // End Collection

这段代码定义了一个标准三键鼠标,包含:

  • 左、中、右三个按钮(bit0~bit2)
  • X/Y轴相对位移(signed 8-bit,范围±127)
  • 总共占用3字节数据

当你调用USBD_HID_SendReport()发送[0x01, 5, -3]这样的数据包时,PC就知道:“哦,左键按下,向右移动5格,向下移动3格”。

🔧调试建议:可以用 HID Descriptor Tool 在线解析你的描述符,确保格式正确。


STM32实战:让F103C8T6真正“动起来”

现在进入重头戏。我们以最常见的STM32F103C8T6(Blue Pill板)为例,手把手实现一个基础HID鼠标。

硬件准备

  • STM32F103C8T6 最小系统板
  • Micro USB线(用于供电和通信)
  • 可选:一个按键(模拟触发事件)

注意:F1系列没有专用USB引脚,但PA11(D-) 和 PA12(D+) 支持复用为USB通信脚。


第一步:搞定48MHz时钟

USB通信对时钟精度要求极高(±0.25%),F1系列没有外部晶振时可用内部HSI48MHz作为USB时钟源。

使用CubeMX配置如下:

  • SYS → Debug: Serial Wire
  • RCC → High Speed Clock: Crystal/Ceramic Resonator
  • RCC → Clock Security System Enable ✅
  • RCC → HSI48 Clock Enabled ✅
  • Clock Configuration:
  • 设置SYSCLK = 72MHz
  • USB时钟分频为1(即直接使用HSI48)

生成代码后,HAL会自动启用__HAL_RCC_HSI48_ENABLE()并配置PLL。


第二步:初始化USB外设

CubeMX中打开USB模块,选择Device FS模式,并添加中间件USB Device → HID

生成的工程会自动包含:

  • usbd_core.h/c
  • usbd_hid.h/c
  • usbd_desc.h/c(设备描述符)
  • usbd_conf.h/c

无需手动编写底层寄存器操作。


第三步:定义鼠标数据结构

// mouse_report.h typedef struct { uint8_t buttons; // bit0: left, bit1: right, bit2: middle int8_t x; // X轴相对位移 (-127 ~ +127) int8_t y; // Y轴相对位移 int8_t wheel; // 滚轮(本例暂不用) } Mouse_Report_TypeDef;

这个结构体必须与报告描述符严格对应!否则主机无法解析。


第四步:封装发送函数

// usb_mouse.c #include "usbd_hid.h" extern USBD_HandleTypeDef hUsbDeviceFS; void USB_SendMouseReport(uint8_t btn, int8_t x, int8_t y) { Mouse_Report_TypeDef report; report.buttons = btn; report.x = x; report.y = y; report.wheel = 0; USBD_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&report, sizeof(report)); }

⚠️ 注意:不要频繁调用此函数!两次发送之间要有足够时间让主机完成轮询,否则可能丢包。


第五步:主循环触发动作

假设我们接了一个按键在PA0,按下时模拟鼠标向右移动并左击:

// main.c int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USB_PCD_Init(); USBD_Init(&hUsbDeviceFS, &FS_PCD_Desc, DEVICE_FS); USBD_RegisterClass(&hUsbDeviceFS, &USBD_HID); USBD_Start(&hUsbDeviceFS); while (1) { if (HAL_GPIO_ReadPin(USER_BUTTON_GPIO_Port, USER_BUTTON_Pin) == GPIO_PIN_SET) { USB_SendMouseReport(0x01, 10, 0); // 按下左键,右移10 HAL_Delay(50); USB_SendMouseReport(0x00, 0, 0); // 释放按键 HAL_Delay(100); } HAL_Delay(10); // 防抖 } }

烧录程序,插上电脑——恭喜你,你的STM32已经是一只真正的USB鼠标了!


踩过的坑我都帮你记下来了

别以为写完代码就能成功。我在第一次调试时也遇到了一堆问题,这里总结几个高频“翻车点”:

❌ 枚举失败:Unknown USB Device

常见原因:

  • 时钟不准:没启用HSI48或外部晶振频率偏差大。
  • DP/DM接反:D+要接1.5kΩ上拉电阻才能识别为全速设备。
  • 描述符错误:报告长度与实际发送不符。

✅ 解决方法:
- 用示波器测D+/D-是否有差分信号;
- 使用 USBlyzer 或 Wireshark 抓包分析枚举过程;
- 核对hid_descriptor.bNumDescriptors是否指向正确的报告大小。

❌ 数据发不出去:SendReport总是失败

USBD_HID_SendReport()返回值非USBD_OK

检查:

  • 是否已成功枚举?
  • 上一次传输是否已完成?HID不支持连续快速发送。
  • 缓冲区是否被占用?避免在中断中调用。

推荐做法:加一个状态标志位,等待上次传输完成后再发下一次。


进阶玩法:不只是“移动+点击”

你以为这就完了?远远不止。有了这个基础框架,你可以轻松扩展更多功能:

添加滚轮支持

修改报告描述符,在最后加上:

0x09, 0x38, // Usage (Wheel) 0x15, 0x81, 0x25, 0x7F, 0x75, 0x08, 0x95, 0x01, 0x81, 0x06, // Input (Relative)

然后发送时填入wheel = +1-1即可滚动一页。

实现空中鼠标

接一个MPU6050陀螺仪,读取角速度积分成位移:

float gyro_x, gyro_y; int8_t dx = (int8_t)(gyro_x * sensitivity); int8_t dy = (int8_t)(gyro_y * sensitivity); USB_SendMouseReport(0, dx, dy);

摇一摇就能控制光标,妥妥的DIY体感鼠标。

自动化脚本执行器

类似Digispark的Rubber Ducky,预存一系列鼠标动作序列:

const Mouse_Action_t script[] = { {0x01, 10, 0}, {0x00, 0, 0}, // 左键单击 {0x00, 50, 0}, // 右移50 {0x02, 0, 0}, {0x00, 0, 0}, // 右键单击 };

可用于无人值守测试、演示自动化等场景。


写在最后:学会的不仅是技术,更是思维方式

当我们完成这个项目时,收获的绝不仅仅是一个能动的鼠标。

你学会了:

  • 如何理解一个复杂协议的分层结构;
  • 如何将抽象规范转化为具体代码;
  • 如何阅读芯片手册和标准文档;
  • 如何排查软硬件协同中的疑难杂症。

这才是嵌入式开发的魅力所在。

下次当你看到某个设备时,不妨多问一句:“它是怎么工作的?”
也许答案,就在你手边这块STM32上。

如果你也在做类似的项目,或者遇到了其他USB HID的问题,欢迎留言交流。我们可以一起探讨更复杂的用法,比如多报告ID切换、复合设备(HID+MSC)、固件升级机制等等。

毕竟,真正的工程师,永远在路上。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/22 18:07:34

如何在PowerPoint中轻松使用LaTeX公式:终极完整教程

想要在PowerPoint中创建专业美观的数学公式吗?latex-ppt插件让你能够直接在演示文稿中使用熟悉的LaTeX语法,轻松应对复杂的数学表达式和科学公式。这款免费工具将彻底改变你的PPT制作体验。 【免费下载链接】latex-ppt Use LaTeX in PowerPoint 项目地…

作者头像 李华
网站建设 2026/4/23 11:19:00

UNT403A盒子Armbian系统完整部署手册:从零打造高性能服务器

UNT403A盒子Armbian系统完整部署手册:从零打造高性能服务器 【免费下载链接】amlogic-s9xxx-armbian amlogic-s9xxx-armbian: 该项目提供了为Amlogic、Rockchip和Allwinner盒子构建的Armbian系统镜像,支持多种设备,允许用户将安卓TV系统更换为…

作者头像 李华
网站建设 2026/4/23 10:41:37

终极解决方案:快速修复Horos在macOS上的版本兼容性问题

终极解决方案:快速修复Horos在macOS上的版本兼容性问题 【免费下载链接】horos Horos™ is a free, open source medical image viewer. The goal of the Horos Project is to develop a fully functional, 64-bit medical image viewer for OS X. Horos is based u…

作者头像 李华
网站建设 2026/4/23 16:15:26

AcFun视频下载完整指南:2025年最实用的免费A站下载工具

AcFun视频下载完整指南:2025年最实用的免费A站下载工具 【免费下载链接】AcFunDown 包含PC端UI界面的A站 视频下载器。支持收藏夹、UP主视频批量下载 😳仅供交流学习使用喔 项目地址: https://gitcode.com/gh_mirrors/ac/AcFunDown 还在为无法离线…

作者头像 李华
网站建设 2026/4/23 13:17:12

基于STM32的FDCAN灵活速率配置示例

从“卡顿”到“丝滑”:STM32上手FDCAN灵活速率实战全解析你有没有遇到过这样的场景?在调试一台多轴伺服系统时,主控MCU刚发出一条运动指令,反馈数据却要等几个毫秒才陆续回传;又或者,在电池管理系统&#x…

作者头像 李华
网站建设 2026/4/23 12:59:05

32种语言OCR识别!Qwen3-VL扩展文字识别覆盖低光模糊场景

Qwen3-VL:让机器真正“读懂”图像中的文字与世界 在智能手机随手一拍就能生成文档摘要的今天,我们对AI“看图识字”的期待早已不止于清晰扫描件上的工整印刷体。现实场景复杂得多——昏暗灯光下的会议纪要、倾斜拍摄的手写笔记、混杂中英日韩的跨境电商…

作者头像 李华