☰
GD32F303 USB HID鼠标例程实战:从官方库到完整枚举
2026/9/26 17:28:46 网站建设 项目流程

简介:面向嵌入式开发者的GD32 USB鼠标例程,解决了在GD32上通过USB OTG与电容式触摸传感器构建触控鼠标的关键问题。压缩包共182个文件,大小约979KB,以80个H头文件和78个C源文件为主体,H文件承载寄存器定义与接口声明,C文件实现USB协议处理和触摸事件转换;另附Keil工程配置、hex固件、脚本及说明文档,便于直接编译、烧录与二次开发。代码覆盖USB设备枚举、描述符配置、端点管理、中断处理、触摸数据采集、坐标映射以及连接断开逻辑。目前已有1593人学习下载,适合正在研究USB协议栈或规划触控交互产品的工程师,无论用于课程设计还是产品预研,都能快速定位所需模块。例程完整走通了从初始化USB控制器、配置触摸传感器,到注册鼠标事件处理并发送USB中断报告的流程,同时对枚举过程、错误处理和兼容性测试给出了可参考的代码路径;读者既可以按模块逐段阅读,也能在现有工程上直接验证,扩展双击、滚轮或手势识别,对深入理解GD32外设驱动与USB设备开发很有帮助。

一句话定位:如果你手里正好有一块GD32开发板,想快速跑通USB HID设备(比如鼠标)的完整流程,同时又不想被官方库的层层封装绕晕,这篇实战笔记应该能帮你省下至少两天的摸索时间。本文基于GD32F303系列(其他型号思路通用),从硬件准备、官方例程结构、代码拆解、枚举失败排查到玩法扩展,逐层讲透。

1. 这套例程水有多深?先看清官方打包了哪些东西

很多人拿到GD32的USB鼠标例程,第一反应是打开工程直接编译下载,结果鼠标不动就懵了。其实这套例程真正的复杂度不在"鼠标怎么动",而在USB协议栈的完整链路。GD32官方库(GD32F30x_Firmware_Library)里的USB鼠标例程,默认放在Examples/USB/USB_Mouse目录下,它不是一个孤立的main.c文件,而是一整套USB设备协议栈的骨架,包含:

  • USB设备核心层:负责枚举、标准请求处理、端点调度。
  • HID类处理层:负责HID描述符解析、报告收发、鼠标按键与位移的编码。
  • 硬件抽象层:USB D+ / D- 引脚配置、中断处理、端点寄存器读写。
  • 应用层:main函数里初始化时钟、配置GPIO、启动USB,然后每隔一段时间模拟鼠标移动或按键。

拿到例程后,第一件事不是编译,而是先看usbd_hid_core.c和usbd_hw.c这两个文件的代码量——前者是HID协议实现,后者是硬件底层。如果这两个文件的结构你能说出个大概,后面排查问题就有方向了。

官方例程默认的鼠标行为是:上电后USB枚举成功,然后每500ms左右向右移动一段像素,同时周期性触发左键点击。这个行为被封装在usbd_hid_mouse_process这类函数里。也就是说,你只要编译烧录成功,插上电脑,不需要任何驱动,鼠标指针就会自己动起来。如果没动,问题一定出在枚举链路或端点配置上。

注意:GD32和STM32的USB外设虽然都是USB 2.0 FS设备,但寄存器映射、描述符缓冲区的访问方式、端点号分配逻辑有差异,不能直接拿STM32的HAL库代码套。这也是为什么很多人用网上淘来的STM32鼠标例程改GD32,折腾半天也枚举不了。

2. 硬件准备与开发环境里那些容易埋雷的细节

2.1 开发板选型与USB接口电路

GD32支持USB的型号很多,以最常见的GD32F303系列为例,它内置了USB 2.0 FS设备控制器,不需要外部PHY芯片,只要在D+引脚上接一个1.5kΩ上拉电阻到3.3V(部分核心板已集成),D-和D+走差分线,就能直接连电脑USB口。

选板子时务必确认三点:

  • 板子上的USB座是不是直接连到MCU的PA11(D-)和PA12(D+)。有些板子这两个引脚被复用到别的功能(比如USBDM和USBDP),需要跳线或改配置。
  • 供电是否稳定。USB枚举瞬间电流较大,如果板子靠USB口供电且板上还有其他负载(比如OLED屏、蜂鸣器),可能导致电压跌落导致枚举失败。
  • 晶体频率。GD32F303的USB要求USB时钟必须是48MHz,而USB时钟来源于CK_PLL。如果外部晶振是8MHz,PLL倍频必须配置成48MHz,或者使用内部IRC48M。这一条是很多人忽略的重灾区——用25M晶振的板子直接跑例程,USB时钟根本不对,枚举必然失败。

2.2 IDE与固件库版本的选择

GD32官方支持Keil MDK、IAR、Eclipse(搭配编译工具链)。我日常用Keil MDK,因为GD32官方Demo工程默认就是Keil格式,双击.uvprojx就能打开,省去手动移植的功夫。

固件库版本建议使用最新版(GD32F30x_Firmware_Library_V2.x以上)。老版本(V1.x)的USB协议栈和描述符结构体定义差异较大,网上搜到的很多解决方案都是基于老版本的,直接照抄到新版本上会导致编译错误或逻辑错乱。如果你手里的例程是V2.x但报错提示找不到usbd_conf.h,多半是没有把Firmware_Library的USB_Device目录完整添加到工程Include路径。

2.3 烧录工具与调试方式

GD32支持通过串口ISP、SWD(J-Link/ST-Link/DAP-Link)等方式烧录。这里有个小提醒:不能用ST-Link直接连GD32F303的SWD接口刷固件,虽然芯片是Cortex-M4内核且SWD协议通用,但ST-Link的固件版本和驱动对GD32的支持时好时坏。我自己遇到过ST-Link能识别芯片、能擦除,但写入后校验失败的情况,换用DAP-Link或J-Link就正常了。

调试USB设备时,除了常规的单步调试,强烈建议准备一个USB抓包工具——如果手里没有逻辑分析仪,可以用Wireshark配合USBPcap(Windows平台)抓USB HID层的数据包。这在你后面排查枚举失败时会救命。

3. 核心代码逐段拆解:描述符、端点和HID报告的配合关系

3.1 设备描述符与配置描述符的结构

打开usbd_desc.c,你会看到一堆uint8_t数组。USB主机(你的PC)在枚举时就是靠这些数组识别设备是谁、要干什么。

/* USB Device Descriptor */ uint8_t usbd_dev_desc[] = { 0x12, // bLength 0x01, // bDescriptorType: Device 0x00, 0x02, // bcdUSB: 2.00 0x00, // bDeviceClass: Per interface ... 0x01, // iManufacturer 0x02, // iProduct 0x03, // iSerialNumber 0x01 // bNumConfigurations };

这里有几个坑点与业务直接相关:

  • bDeviceClass如果填0x00,表示设备类由各个接口的bInterfaceClass决定。HID鼠标通常每个接口的bInterfaceClass填0x03(HID类)。如果你在这里填了0x00以外其他值,Windows可能把它当复合设备或特定类设备处理,导致鼠标功能异常。
  • idVendor和idProduct如果沿用官方默认值(比如0x28E9是GD32的VID),系统会识别为GD32设备。如果你后续要量产,需要申请自己的VID/PID,否则在电脑的设备管理器里会有黄色感叹号提示“设备无法启动”的可能性(实际原因是PID冲突或驱动绑定错误)。
  • 字符串描述符(iManufacturer、iProduct、iSerialNumber)编码方式是UTF-16LE,即每个字符占2字节,且需要以0x03类型开头。很多新手直接改成纯ASCII数组,结果Windows上设备描述符字符串解析异常。

3.2 配置描述符集合的组成

配置描述符集合通常由以下部分串联:

描述符类型作用典型长度
配置描述符描述整个配置的总长度、接口数量、供电方式9字节
接口描述符描述一个接口的类、端点数等9字节
HID描述符描述HID版本、报告描述符长度9字节
端点描述符描述中断输入/输出端点属性7字节

在usbd_hid_core.c中,官方例程给了USBD_HID_config这个配置描述符集合。重点要看端点描述符里的bInterval(查询间隔)。对鼠标这类低带宽设备,bInterval设为1~10(单位ms)都可以。如果设得太小(比如1ms),数据量不大但会增加CPU中断频率;设得太大(比如100ms),鼠标会明显感到卡顿,指针移动不跟手。

3.3 HID报告描述符才是鼠标的灵魂

这是整套例程里最容易被忽略又最关键的部分。鼠标的按键状态和位移必须按照HID报告描述符定义的格式打包上传,Windows才认得出"光标向右移动了X像素"。

__ALIGN_BEGIN static uint8_t usbd_hid_mouse_report_desc[] = { 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 (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) 0x81, 0x02, // Input (Data, Variable, Absolute) ... 0xC0, // End Collection 0xC0 // End Collection };

这段描述符定义了一份4字节的HID报告:

  • 第1字节:按键状态,bit0表示左键,bit1表示右键,bit2表示中键。
  • 第2字节:X方向位移,有符号数(-127~127)。
  • 第3字节:Y方向位移,有符号数。
  • 第4字节:滚轮位移,有符号数。

实际发报告时,你只需要构造一个4字节数组并发送到中断端点即可。官方例程里的usbd_hid_mouse_send_report函数就是干这件事的。如果你改了报告描述符(比如增加了自定义按键、多媒体键),那么发送缓冲区长度和报告描述符的Report Count必须严格匹配,否则Windows会报HID设备描述符请求失败。

4. 从编译到枚举:鼠标不动时的完整排查链路

我拿到这块板子第一次跑例程,烧录完成后插上电脑,设备管理器里什么反应都没有。排查了整整一晚上,最终定位到问题。这里我分享一下完整的排查链路,按优先级从低级到高级排列。

4.1 第一步:时钟配置错误导致USB时钟不对

打开system_gd32f30x.c,确认系统时钟使用的是外部高速晶振(HXTAL)还是内部IRC。如果板子上的晶振是8MHz,那么必须确保PLL配置成PLL_MUL12(8MHz * 12 = 96MHz)然后USBCK = CK_PLL / 2 = 48MHz。如果板子上的晶振是25MHz,PLL倍频系数就要重新算。

一个非常隐蔽的点:GD32F303的USB时钟源选择和STM32不一样,STM32是USBCLK由PLL输出直接分频,GD32是USBCK = CK_PLL / 2。很多人按STM32的思维去改时钟,结果差了一倍,枚举直接失败。

检查方法:在调试器里给RCC_CFG寄存器打个断点,查看USBCK位的值;或者用逻辑分析仪抓D+引脚,如果枚举正常,D+上应该有一系列脉冲。没有脉冲就是USB时钟没起来。

4.2 第二步:上拉电阻没接好

D+上拉电阻是USB主机识别全速设备(12Mbps)的关键信号。FS设备通过把D+拉高来告诉主机"我在这里,我是全速设备"。如果这个1.5kΩ电阻没接或者虚焊,主机完全感知不到设备插入。

很多开发板为了节省一个电阻,直接用MCU的GPIO控制上拉。例程里会初始化一个GPIO输出高电平来接“模拟上拉”。此时要确认:

  • GPIO是否复用为USB功能;
  • 上拉动作是否在USB初始化之前完成;
  • 如果使用外部上拉电阻,检查其一端接D+,另一端接3.3V,不能接5V——接5V可能直接烧坏MCU的USB PHY。

4.3 第三步:枚举失败但时钟和上拉正常,这时候要抓包

一旦确认时钟和上拉没问题,但Windows仍然不识别,就要抓包看枚举到哪一步卡住了。我用Wireshark加USBPcap抓过几次,常见失败点有:

  • GET_DESCRIPTOR(Device)不响应:问题在设备描述符数组导入不正确,或者USB中断没开启。检查usbd_core.c中的usbd_core_init是否被调用,以及NVIC中USB中断优先级是否配置。
  • GET_DESCRIPTOR(Config)返回长度错误:配置描述符集合的总长度(wTotalLength)与实际数组长度不符。很多人在配置描述符里单独加了一个自定义端点,但忘记更新wTotalLength字段,导致主机在读取配置描述符集合时数据截断。
  • SET_CONFIGURATION后无响应:设置配置完成后,端点要立即激活。检查usbd_hid_core.c里usbd_hid_set_config的实现,确认端点的EPTYPE是否配置为中断传输、方向是否正确(IN方向是设备到主机)。

4.4 第四步:报告发送失败导致鼠标不动

枚举成功(设备管理器显示"HID-compliant mouse")但鼠标就是不动,问题大概率出在报告发送链路。常见原因:

  • 发送函数调用前没有检查USB状态是否为USBD_CONFIGURED。枚举结束后USB状态会切到CONFIGURED,但你在main循环里可能提前调用了发送函数,此时端点还没激活,数据直接丢弃。
  • 端点发送缓冲区忙。USB中断端点发送完毕后需要等待USBD_EPT_OK标志。如果你在紧接着的第二次发送时上一次发送还没完成,数据会覆盖导致偶发丢包。
  • 使用DMA传输时,缓冲区地址没有对齐。GD32的USB DMA要求缓冲区地址4字节对齐,局部变量uint8_t buffer[4]在栈上的地址是随机的,可能导致DMA传输异常。解决方法是定义成全局数组,或者用__ALIGN_BEGIN修饰符。

5. 从HID数据格式到上位机联动:再往深处玩的花样

5.1 模拟鼠标轨迹

很多人在热词里提到"模拟鼠标运动轨迹",这在GD32上其实可以做得非常平滑。HID报告里的X/Y位移是相对位移,即每次上报的是一个增量。Windows的指针移动是增量累加。所以如果想画一条直线,你可以在MCU里用Bresenham算法逐步生成位移坐标,每隔一小段时间发一次报告。

但是如果想实现"鼠标移到指定坐标点击",只靠相对位移是不行的——你没法知道当前指针的绝对坐标。这种情况要么在PC端配合上位机软件把绝对坐标换算成相对位移再下发(需要额外的串口或网络通道),要么使用HID绝对定位表(Digitizer风格),让上位机直接写屏。后者在Windows上同样不需要驱动,但需要修改HID报告描述符中的Usage (X)和Usage (Y),并设置Logical Minimum / Maximum为屏幕分辨率范围。

5.2 USB转串口的组合应用

GD32的USB鼠标例程可以和USB转串口(CDC类)组合,做一个"串口遥控鼠标":PC上位机通过虚拟串口发送坐标和点击指令给GD32,GD32收到后解析并模拟鼠标上报。这种方案在工业控制场景非常常见,比如自动化测试台、无人工位的电脑操作工具。

实现组合时需要注意:

  • 复合设备的配置描述符集合要包含两个接口描述符:一个HID接口(鼠标),一个CDC接口(虚拟串口)。Windows会把它识别为“USB输入设备 + 虚拟COM口”,这时鼠标功能仍然不需要驱动,CDC则需要系统自带驱动(usbser.sys)。
  • 两个接口共享同一套USB设备地址和电源配置,但端点号不能冲突。HID用IN端点1,CDC的发送和接收分别用IN端点2、OUT端点2。
  • 枚举时复合设备的bDeviceClass建议设为0xEF(Miscellaneous),bDeviceSubClass设为0x02,bDeviceProtocol设为0x01,这样Windows能正确拆分接口功能。

5.3 自制USB键鼠一体设备

官方还有USB键盘例程(Examples/USB/USB_Keyboard),两者可以合并成一个复合HID设备,同时上报鼠标和键盘事件。这个在电竞外设、快捷按键板、辅助输入设备等场景很实用。

合并时要把HID报告描述符改成一个总集合,包含键盘用到的按键数据段和鼠标用到的位移/按键数据段。此时Report ID就变得重要了——如果没有Report ID,两个功能的数据会混在一起;加了Report ID后(比如键盘Report ID = 1,鼠标Report ID = 2),PC就能区分包的类型。这个改动虽然不复杂,但涉及描述符结构大改,建议先跑通单独的鼠标例程再去改。

6. 调试经验总结:那些文档里不会写的事

最后分享几条实打实的经验:

  1. USB例程里所有带__ALIGN_BEGIN修饰的全局数组,不要轻易去掉对齐。GD32F303的DMA/描述符缓冲区要求4字节对齐,全局变量编译器默认4字节对齐,但局部变量不保证。如果你图省事把描述符数组改成局部变量,极大概率踩到对齐坑。

  2. USB中断优先级不是越高越好。在官方例程中,USB中断通常设置为NVIC_PRIGROUP_1或NVIC_PRIGROUP_2下的抢占优先级1或2。如果低于系统滴答定时器优先级,那么在高频中断场景下可能丢包。如果高于串口中断优先级,串口打印调试信息时会被USB打断导致打印乱序。建议USB抢占优先级设为2,子优先级设为0。

  3. 热插拔时的处理。Windows对USB设备热插拔非常敏感,如果你在调试时频繁拔插,建议使用带ESD保护的USB线/座,并且每次重新插拔后清空一下电脑的Device Manager缓存,不然偶尔会遇到"设备描述符请求失败"的假故障,实际是系统缓存了旧的配置信息。

  4. 用逻辑分析仪抓D+/D-信号时,采样率建议至少100MS/s,25MS/s也能看个大概,但波形细节不够。如果只是确认有没有枚举动作,25MS/s能凑合;要分析时序细节,上100MS/s。

  5. 最后,如果你把鼠标例程改成了键盘、游戏手柄甚至自定义HID设备,报告描述符每改一次,建议先用USBPcap抓一次包,核对主机端枚举时读到的报告描述符长度是否和你代码里定义的一致。这个习惯能省掉大量"看起来很对但不工作"的排查时间。

这套例程跑通之后,你会对GD32的USB外设架构有比较完整的认知,后面再接触CDC、MSC,或者做复合设备,都会顺畅得多。如果卡在某个具体报错上,欢迎带着寄存器截图或枚举抓包来交流。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询