【STM32 HAL】Keil5 + CubeMX 开发 USB HID 键盘教程本(附带工程下载)
本文介绍如何使用 STM32CubeMX + Keil5 + HAL 库 开发一个简单的 USB HID 键盘。
程序功能示例:
-
PC15 按键触发,电脑输入
C -
PC14 按键触发,电脑输入
V -
PC13 接 LED,用于状态指示
-
PA11 / PA12 连接 USB
-
PA13 / PA14 作为 SWD 下载调试接口
本文以 STM32F103 系列为例,外部晶振为 8 MHz。
一、新建工程
新建 STM32 HAL 工程的步骤可以参考:
这里不再重复 CubeMX 新建工程的基础流程,重点说明 USB HID 键盘相关配置。
二、CubeMX 工程配置
1. 开启 USB 外设
在 CubeMX 左侧选择:
Connectivity -> USB
将 USB 配置为:
Device (FS)
STM32F103 的 USB 引脚通常为:
PA11 -> USB_DM
PA12 -> USB_DP


2. 开启 USB Device HID 中间件
继续选择:
Middleware -> USB_DEVICE
将 USB 设备类型配置为:
Human Interface Device Class / HID

开启后,CubeMX 会生成 USB Device 相关文件,例如:
usb_device.c
usb_device.h
usbd_desc.c
usbd_conf.c
usbd_hid.c
usbd_hid.h
注意:
CubeMX 默认生成的 HID 模板通常是 鼠标 HID,而不是键盘 HID。
因此后面需要手动修改 HID 描述符。
3. GPIO 配置
本文示例硬件连接如下:
PC13 -> LED
PC14 -> 按键 V
PC15 -> 按键 C
按键推荐接法:
GPIO ---- 按键 ---- GND
因此 GPIO 配置为:
PC14 -> GPIO_Input,Pull-up
PC15 -> GPIO_Input,Pull-up
PC13 -> GPIO_Output
也就是说:
按键松开:GPIO 读取高电平
按键按下:GPIO 读取低电平
4. 时钟配置
如果外部晶振是 8 MHz,可以配置为:
HSE = 8 MHz
PLL = HSE x 9 = 72 MHz
USB Clock = 72 MHz / 1.5 = 48 MHz
USB FS 必须使用 48 MHz 时钟,否则电脑可能无法正确识别设备。
三、修改 HID 为键盘设备
CubeMX 默认生成的 HID 通常是鼠标 HID,因此需要修改两个文件:
usbd_hid.h
usbd_hid.c
四、修改 usbd_hid.h
在 Keil5 中打开:
Middlewares/ST/STM32_USB_Device_Library/Class/HID/Inc/usbd_hid.h

找到:
#define HID_EPIN_SIZE 0x04U
#define HID_MOUSE_REPORT_DESC_SIZE 74U

修改为:
#define HID_EPIN_SIZE 0x08U
#define HID_MOUSE_REPORT_DESC_SIZE 63U

说明:
-
鼠标 HID 默认 Report 通常是 4 字节
-
标准键盘 HID Report 是 8 字节
-
本文使用的键盘描述符长度是 63 字节
虽然宏名仍然叫 HID_MOUSE_REPORT_DESC_SIZE,但是内容已经会被我们改成键盘 HID 描述符。为了减少对 ST 库文件的改动,宏名可以先不改。
五、修改 usbd_hid.c 的 HID 描述符
打开:
Middlewares/ST/STM32_USB_Device_Library/Class/HID/Src/usbd_hid.c
找到:
__ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE] __ALIGN_END =
{
...
};

将整个数组内容替换为键盘 HID 描述符:
__ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE] __ALIGN_END =
{
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x06, // Usage (Keyboard)
0xA1, 0x01, // Collection (Application)
0x05, 0x07, // Usage Page (Keyboard)
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, Variable, Absolute)
0x95, 0x01, // Report Count (1)
0x75, 0x08, // Report Size (8)
0x81, 0x01, // Input (Constant)
0x95, 0x05, // Report Count (5)
0x75, 0x01, // Report Size (1)
0x05, 0x08, // Usage Page (LEDs)
0x19, 0x01, // Usage Minimum (Num Lock)
0x29, 0x05, // Usage Maximum (Kana)
0x91, 0x02, // Output (Data, Variable, Absolute)
0x95, 0x01, // Report Count (1)
0x75, 0x03, // Report Size (3)
0x91, 0x01, // Output (Constant)
0x95, 0x06, // Report Count (6)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x65, // Logical Maximum (101)
0x05, 0x07, // Usage Page (Keyboard)
0x19, 0x00, // Usage Minimum (Reserved)
0x29, 0x65, // Usage Maximum (Keyboard Application)
0x81, 0x00, // Input (Data, Array)
0xC0 // End Collection
};

注意:
数组名虽然仍然是 HID_MOUSE_ReportDesc,但内容已经是标准键盘描述符。
六、修改 HID 接口协议
继续在 usbd_hid.c 中搜索:
0x02, /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
CubeMX 默认这里通常是 0x02,表示鼠标协议。
需要修改为 0x01,表示键盘协议:
0x01, /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
一般需要修改 3 处:
USBD_HID_CfgFSDesc
USBD_HID_CfgHSDesc
USBD_HID_OtherSpeedCfgDesc



也就是 Full Speed、High Speed、Other Speed 三个配置描述符中都要修改。
修改前:
0x02, /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
修改后:
0x01, /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
七、修改电脑端识别的设备名
如果想修改电脑设备管理器中显示的 USB 设备名称,需要修改:
USB_DEVICE/App/usbd_desc.c
找到类似下面的宏定义:
#define USBD_MANUFACTURER_STRING "STMicroelectronics"
#define USBD_PRODUCT_STRING_FS "STM32 Human interface"
#define USBD_CONFIGURATION_STRING_FS "HID Config"
#define USBD_INTERFACE_STRING_FS "HID Interface"
可以改成:
#define USBD_MANUFACTURER_STRING "MyKeyboard"
#define USBD_PRODUCT_STRING_FS "STM32 CV Keyboard"
#define USBD_CONFIGURATION_STRING_FS "Keyboard Config"
#define USBD_INTERFACE_STRING_FS "Keyboard Interface"
其中最关键的是:
#define USBD_PRODUCT_STRING_FS "STM32 CV Keyboard"

这个通常就是电脑端识别到的设备名称。
如果 Windows 仍然显示旧名称,可能是系统缓存导致的。可以尝试:
-
拔掉 USB
-
在设备管理器中卸载旧设备
-
重新插入 USB
-
或者修改 PID 后重新烧录
PID 通常也在 usbd_desc.c 中,例如:
#define USBD_PID_FS 22352
调试阶段可以临时修改 PID,让 Windows 重新识别为新设备。
正式产品不要随意使用未经授权的 VID/PID。
八、main.c 示例代码
下面是一个简单的按键触发键盘输入示例:
#include "main.h"
#include "usb_device.h"
#include "gpio.h"
#include "usbd_hid.h"
#include <string.h>
extern USBD_HandleTypeDef hUsbDeviceFS;
#define HID_KEY_C 0x06
#define HID_KEY_V 0x19
#define KEY_C_PORT GPIOC
#define KEY_C_PIN GPIO_PIN_15
#define KEY_V_PORT GPIOC
#define KEY_V_PIN GPIO_PIN_14
#define LED_PORT GPIOC
#define LED_PIN GPIO_PIN_13
#define KEY_ACTIVE GPIO_PIN_RESET
static uint8_t Key_IsPressed(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{
return HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == KEY_ACTIVE;
}
static void Keyboard_SendReport(uint8_t *report)
{
if (hUsbDeviceFS.dev_state != USBD_STATE_CONFIGURED)
{
return;
}
USBD_HID_SendReport(&hUsbDeviceFS, report, 8);
}
static void Keyboard_SendKey(uint8_t keycode)
{
uint8_t report[8] = {0};
// 按下按键
report[0] = 0x00; // modifier
report[1] = 0x00; // reserved
report[2] = keycode; // keycode
Keyboard_SendReport(report);
HAL_Delay(20);
// 释放按键
memset(report, 0, sizeof(report));
Keyboard_SendReport(report);
HAL_Delay(20);
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USB_DEVICE_Init();
uint8_t last_c = 0;
uint8_t last_v = 0;
while (1)
{
uint8_t now_c = Key_IsPressed(KEY_C_PORT, KEY_C_PIN);
uint8_t now_v = Key_IsPressed(KEY_V_PORT, KEY_V_PIN);
// PC15 按下,发送 C 键
if (now_c && !last_c)
{
HAL_Delay(20);
if (Key_IsPressed(KEY_C_PORT, KEY_C_PIN))
{
Keyboard_SendKey(HID_KEY_C);
HAL_GPIO_TogglePin(LED_PORT, LED_PIN);
}
}
// PC14 按下,发送 V 键
if (now_v && !last_v)
{
HAL_Delay(20);
if (Key_IsPressed(KEY_V_PORT, KEY_V_PIN))
{
Keyboard_SendKey(HID_KEY_V);
HAL_GPIO_TogglePin(LED_PORT, LED_PIN);
}
}
last_c = now_c;
last_v = now_v;
HAL_Delay(1);
}
}
注意:
上面发送的是键盘上的 C 和 V 按键。
如果电脑没有按下 Shift,实际输入通常是小写:
c
v
如果需要输入大写 C / V,需要同时发送 Shift 修饰键。
九、输入大写 C / V
新增一个带修饰键的发送函数:
static void Keyboard_SendKeyWithModifier(uint8_t modifier, uint8_t keycode)
{
uint8_t report[8] = {0};
report[0] = modifier;
report[1] = 0x00;
report[2] = keycode;
Keyboard_SendReport(report);
HAL_Delay(20);
memset(report, 0, sizeof(report));
Keyboard_SendReport(report);
HAL_Delay(20);
}
调用时使用:
Keyboard_SendKeyWithModifier(0x02, HID_KEY_C); // Shift + C
Keyboard_SendKeyWithModifier(0x02, HID_KEY_V); // Shift + V
其中:
0x02 = Left Shift
十、常见问题排查
1. 电脑无法识别设备
优先检查:
USB 时钟是否为 48 MHz
PA11 / PA12 是否接反
USB D+ 是否有正确上拉
是否调用了 MX_USB_DEVICE_Init()
是否启用了 USB_DEVICE HID 中间件
2. 电脑识别为鼠标,不是键盘
检查 usbd_hid.c 中是否把协议改成了键盘:
0x01, /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
如果还是 0x02,电脑会按鼠标协议识别。
3. 按键按下没有输入
检查:
PC14 / PC15 是否配置为 GPIO_Input
是否开启 Pull-up
按键是否一端接 GPIO,另一端接 GND
程序中 KEY_ACTIVE 是否为 GPIO_PIN_RESET
如果按键是按下接 3.3V,则需要改成下拉输入,并修改:
#define KEY_ACTIVE GPIO_PIN_SET
4. 只能输入一次,后续没有反应
可能是 HID 发送过快或释放包没有成功发送。
可以适当增大 HAL_Delay(20),例如改成:
HAL_Delay(30);
也可以修改 USBD_HID_SendReport(),让它在 USB 忙时返回 USBD_BUSY,主程序再进行重试。
十一、总结
本文完成了 STM32 HAL USB HID 键盘的基本实现流程:
-
CubeMX 开启 USB Device FS
-
开启 USB_DEVICE HID 中间件
-
修改 HID Report Descriptor 为键盘描述符
-
修改 HID 接口协议为 Keyboard
-
修改 USB 设备名称
-
在 main.c 中扫描按键并发送键盘 Report
完成后,STM32 插入电脑后会被识别为 USB HID 键盘。
按下 PC15 对应按键,电脑输入 C 键;按下 PC14 对应按键,电脑输入 V 键。
十二、工程下载
工程下载链接,提取码: 6666
https://pan.baidu.com/s/1ZbnQ9B75G7LyAhE-1ITupQ?pwd=6666
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)