本文介绍如何使用 STM32CubeMX + Keil5 + HAL 库 开发一个简单的 USB HID 键盘。
程序功能示例:

  • PC15 按键触发,电脑输入 C

  • PC14 按键触发,电脑输入 V

  • PC13 接 LED,用于状态指示

  • PA11 / PA12 连接 USB

  • PA13 / PA14 作为 SWD 下载调试接口

本文以 STM32F103 系列为例,外部晶振为 8 MHz。


一、新建工程

新建 STM32 HAL 工程的步骤可以参考:


【STM32-HAL库】STM32F系列新建工程并点灯教程(小白向)-CSDN博客文章浏览阅读1.9k次,点赞6次,收藏16次。本帖是STM32HAL库系列新建工程并点灯系列合集,目前包含了:嘉立创梁山派天空星版(GD32F407VET6)、嘉立创梁山派(GD32F470ZGT6)、STM32F103C8T6 https://blog.csdn.net/qq_39150957/article/details/143087351

这里不再重复 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 仍然显示旧名称,可能是系统缓存导致的。可以尝试:

  1. 拔掉 USB

  2. 在设备管理器中卸载旧设备

  3. 重新插入 USB

  4. 或者修改 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);
  }
}

注意:
上面发送的是键盘上的 CV 按键。
如果电脑没有按下 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 键盘的基本实现流程:

  1. CubeMX 开启 USB Device FS

  2. 开启 USB_DEVICE HID 中间件

  3. 修改 HID Report Descriptor 为键盘描述符

  4. 修改 HID 接口协议为 Keyboard

  5. 修改 USB 设备名称

  6. 在 main.c 中扫描按键并发送键盘 Report

完成后,STM32 插入电脑后会被识别为 USB HID 键盘。
按下 PC15 对应按键,电脑输入 C 键;按下 PC14 对应按键,电脑输入 V 键。

十二、工程下载

工程下载链接,提取码: 6666https://pan.baidu.com/s/1ZbnQ9B75G7LyAhE-1ITupQ?pwd=6666

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐