STM32 USB-HID下位机设计与实现

一、系统概述

USB-HID(Human Interface Device) 是USB协议中用于人机交互的设备类,常见如键盘、鼠标、游戏手柄等。本设计基于STM32F103C8T6(Cortex-M3内核,72MHz)实现USB-HID下位机,模拟自定义HID设备(如4按键键盘+2轴摇杆),通过USB与PC通信,支持输入报告(下位机→PC) 和输出报告(PC→下位机),可用于工业控制、数据采集、自定义输入设备等场景。

二、硬件设计

2.1 核心组件

模块 型号/参数 功能说明
主控 STM32F103C8T6(64KB Flash,20KB RAM) USB HID协议处理、数据收发、外设控制
USB接口 Mini USB(DP/DM引脚) 连接PC,实现USB 2.0全速通信(12Mbps)
输入设备 4×轻触开关(GPIO输入) 模拟HID按键(如K1-K4)
输出设备 2×LED(GPIO输出) 显示HID输出报告状态(如PC控制LED)
电源 5V USB供电(或3.3V LDO) 为STM32和USB模块供电

2.2 硬件连接

模块 引脚(STM32F103C8T6) 说明
USB PA11(DM) USB数据负线(D-)
PA12(DP) USB数据正线(D+)
按键 PA0-PA3(K1-K4) 上拉输入,按下接地(低电平有效)
LED PB0-PB1(LED1-LED2) 推挽输出,高电平点亮

三、软件设计(STM32标准库3.5)

3.1 系统架构

USB HID
输入报告
输出报告
PC上位机
STM32 USB OTG
按键输入
LED输出
HID报告处理

3.2 核心原理

  1. USB HID协议 :通过报告描述符定义设备功能(如按键、摇杆),上位机根据描述符解析数据。

  2. 端点配置

    • 端点1(IN,地址0x81):下位机→PC,发送输入报告(如按键状态)。

    • 端点2(OUT,地址0x02):PC→下位机,接收输出报告(如控制LED)。

  3. 数据格式:输入/输出报告为二进制数组,按报告描述符定义的格式打包(如1字节按键状态+2字节摇杆数据)。

3.3 开发步骤

3.3.1 STM32CubeMX配置(关键步骤)
  1. 时钟配置:USB时钟需48MHz(STM32F103通过PLL分频:72MHz主频→PLLP=9→8MHz×6=48MHz)。

  2. USB配置

    • 选择"Connectivity→USB_OTG_FS",模式设为"Device Only"。

    • 勾选"Device→HID",参数设置:

      • Vendor ID:0x0483(ST官方示例,可自定义)

      • Product ID:0x5710(自定义)

      • Report Descriptor:见下文"3.3.2 报告描述符"。

  3. GPIO配置:PA0-PA3(按键,上拉输入),PB0-PB1(LED,推挽输出)。

3.3.2 HID报告描述符(自定义设备)

定义4按键+2轴摇杆 的输入报告(8字节)和2LED控制的输出报告(1字节):

c 复制代码
// 输入报告(8字节):1字节按键状态 + 2字节X轴 + 2字节Y轴 + 3字节保留
const uint8_t HID_ReportDescriptor[] = {
  0x05, 0x01,        // USAGE_PAGE (Generic Desktop)
  0x09, 0x05,        // USAGE (Game Pad)
  0xA1, 0x01,        // COLLECTION (Application)
  
  // 按键(4个,1字节,bit0-bit3对应K1-K4)
  0x05, 0x09,        //   USAGE_PAGE (Button)
  0x19, 0x01,        //   USAGE_MINIMUM (Button 1)
  0x29, 0x04,        //   USAGE_MAXIMUM (Button 4)
  0x15, 0x00,        //   LOGICAL_MINIMUM (0)
  0x25, 0x01,        //   LOGICAL_MAXIMUM (1)
  0x75, 0x01,        //   REPORT_SIZE (1)
  0x95, 0x04,        //   REPORT_COUNT (4)
  0x81, 0x02,        //   INPUT (Data,Var,Abs)
  0x75, 0x04,        //   REPORT_SIZE (4)  // 保留4位
  0x95, 0x01,        //   REPORT_COUNT (1)
  0x81, 0x03,        //   INPUT (Cnst,Var,Abs)
  
  // 摇杆X轴(2字节,0-1023)
  0x05, 0x01,        //   USAGE_PAGE (Generic Desktop)
  0x09, 0x30,        //   USAGE (X)
  0x16, 0x00, 0x00,  //   LOGICAL_MINIMUM (0)
  0x26, 0xFF, 0x03,  //   LOGICAL_MAXIMUM (1023)
  0x75, 0x10,        //   REPORT_SIZE (16)
  0x95, 0x01,        //   REPORT_COUNT (1)
  0x81, 0x02,        //   INPUT (Data,Var,Abs)
  
  // 摇杆Y轴(2字节,0-1023)
  0x09, 0x31,        //   USAGE (Y)
  0x16, 0x00, 0x00,  //   LOGICAL_MINIMUM (0)
  0x26, 0xFF, 0x03,  //   LOGICAL_MAXIMUM (1023)
  0x75, 0x10,        //   REPORT_SIZE (16)
  0x95, 0x01,        //   REPORT_COUNT (1)
  0x81, 0x02,        //   INPUT (Data,Var,Abs)
  
  // 输出报告(1字节):2个LED控制(bit0-LED1,bit1-LED2)
  0x09, 0x00,        //   USAGE (Undefined)
  0x15, 0x00,        //   LOGICAL_MINIMUM (0)
  0x25, 0x01,        //   LOGICAL_MAXIMUM (1)
  0x75, 0x01,        //   REPORT_SIZE (1)
  0x95, 0x02,        //   REPORT_COUNT (2)
  0x91, 0x02,        //   OUTPUT (Data,Var,Abs)
  0xC0               // END_COLLECTION
};
3.3.3 核心代码实现
(1)USB HID初始化(标准库3.5)
c 复制代码
#include "usb_lib.h"
#include "hw_config.h"
#include "usb_desc.h"
#include "usb_pwr.h"

// USB HID设备初始化
void USB_HID_Init(void) {
  Set_System();       // 系统时钟、GPIO初始化
  Set_USBClock();     // 配置USB时钟(48MHz)
  USB_Interrupts_Config();  // 配置USB中断
  USB_Init();         // 初始化USB设备
  USB_SIL_Init();     // 初始化端点
}
(2)发送HID输入报告(按键+摇杆数据)
c 复制代码
// 输入报告结构(8字节)
typedef struct {
  uint8_t buttons;  // 按键状态(bit0:K1, bit1:K2, bit2:K3, bit3:K4)
  uint8_t reserved; // 保留字节
  uint16_t x_axis;  // X轴摇杆(0-1023)
  uint16_t y_axis;  // Y轴摇杆(0-1023)
} HID_InputReport;

// 发送输入报告
void HID_SendInputReport(HID_InputReport *report) {
  uint8_t buf[8];
  buf[0] = report->buttons;       // 按键状态(1字节)
  buf[1] = report->reserved;      // 保留(1字节)
  buf[2] = report->x_axis & 0xFF;  // X轴低8位
  buf[3] = report->x_axis >> 8;   // X轴高8位
  buf[4] = report->y_axis & 0xFF;  // Y轴低8位
  buf[5] = report->y_axis >> 8;   // Y轴高8位
  buf[6] = 0;                      // 保留
  buf[7] = 0;                      // 保留
  
  // 通过端点1发送(IN端点,地址0x81)
  UserToPMABufferCopy(buf, ENDP1_TXADDR, 8);
  SetEPTxCount(ENDP1, 8);
  SetEPTxValid(ENDP1);
}
(3)接收HID输出报告(控制LED)
c 复制代码
// 输出报告结构(1字节)
typedef struct {
  uint8_t leds;  // LED状态(bit0:LED1, bit1:LED2)
} HID_OutputReport;

// USB中断回调函数(接收输出报告)
void EP2_OUT_Callback(void) {
  uint8_t buf[1];
  HID_OutputReport report;
  
  // 从端点2(OUT端点,地址0x02)读取数据
  PMAToUserBufferCopy(buf, ENDP2_RXADDR, 1);
  report.leds = buf[0];
  
  // 控制LED
  GPIO_WriteBit(GPIOB, GPIO_Pin_0, (BitAction)(report.leds & 0x01));  // LED1
  GPIO_WriteBit(GPIOB, GPIO_Pin_1, (BitAction)((report.leds >> 1) & 0x01));  // LED2
  
  SetEPRxValid(ENDP2);  // 使能端点2接收
}
(4)主函数(按键扫描+数据发送)
c 复制代码
int main(void) {
  HID_InputReport input_report = {0};
  uint8_t key_state = 0;
  
  SystemInit();
  USB_HID_Init();
  GPIO_Configuration();  // 配置按键(PA0-PA3)和LED(PB0-PB1)
  
  while (1) {
    // 1. 扫描按键状态(K1-K4:PA0-PA3,按下为低电平)
    key_state = 0;
    if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 0) key_state |= 0x01;  // K1
    if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == 0) key_state |= 0x02;  // K2
    if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2) == 0) key_state |= 0x04;  // K3
    if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3) == 0) key_state |= 0x08;  // K4
    
    // 2. 模拟摇杆数据(固定值,可替换为ADC采集)
    input_report.buttons = key_state;
    input_report.x_axis = 512;  // 中间位置
    input_report.y_axis = 512;
    
    // 3. 发送HID输入报告(每10ms一次)
    HID_SendInputReport(&input_report);
    Delay_ms(10);
  }
}

参考代码 STM32实现的USB-HID下位机 www.youwenfan.com/contentcss/56294.html

四、上位机测试(Python示例)

使用pywinusb库接收HID数据,验证下位机通信:

python 复制代码
import pywinusb.hid as hid

def on_data(data):
    print(f"按键状态: {bin(data[0])}, X轴: {data[2]+data[3]*256}, Y轴: {data[4]+data[5]*256}")

# 查找HID设备(VID=0x0483, PID=0x5710)
filter = hid.HidDeviceFilter(vendor_id=0x0483, product_id=0x5710)
devices = filter.get_devices()

if devices:
    device = devices[0]
    device.open()
    device.set_raw_data_handler(on_data)
    print("设备已连接,按Ctrl+C退出...")
    import time
    while True: time.sleep(1)
else:
    print("未找到HID设备")

五、关键问题与解决方案

5.1 USB枚举失败

  • 原因:USB时钟未配置为48MHz,或报告描述符错误。

  • 解决

    • 用示波器测量USB DP/DM引脚,确保有48MHz时钟;

    • USBlyzer工具分析枚举过程,检查报告描述符是否符合HID规范。

5.2 数据发送卡顿

  • 原因:HID报告发送频率过高,或PC端处理不及时。

  • 解决

    • 降低发送频率(如10ms/次);

    • 使用双缓冲机制,避免数据覆盖。

5.3 按键抖动

  • 原因:机械按键按下时产生抖动,导致误报。

  • 解决:软件消抖(检测到按键变化后延时10ms再确认状态)。

六、总结

基于STM32F103实现了USB-HID下位机,通过自定义报告描述符支持4按键+2轴摇杆输入,2LED输出,可扩展为游戏手柄、工业控制器等。核心步骤包括:USB时钟配置、HID报告描述符定义、端点数据收发、按键/LED控制。

相关推荐
Winner13002 小时前
【单片机 控制小车】
单片机·嵌入式硬件
xingzhemengyou12 小时前
STM32 UART 通信详解
stm32·单片机·嵌入式硬件
LDR0062 小时前
乐得瑞 LDR6020P,重新定义 Type-C 多口方案
嵌入式硬件
苏灵凯2 小时前
智能环境监测终端全栈设计:从单片机到微信小程序,手把手搞定!
单片机·嵌入式硬件·mcu·物联网·微信小程序·小程序·蓝牙模块
济6172 小时前
STM32串口通信实战|从基础到实战(发送 + 接收控制 LED)---STM32 HAL库专栏
stm32·嵌入式·stm32hal库编程
福尔摩斯张2 小时前
一文搞懂74HC595芯片(附详细使用方法)
linux·服务器·网络·单片机·嵌入式硬件
LCG元2 小时前
串口屏快速开发:STM32 UART通信,复杂HMI界面调试技巧
stm32·单片机·嵌入式硬件
从零点3 小时前
如何在cmake中添加自己的项目文件夹文件
嵌入式硬件
广州灵眸科技有限公司3 小时前
瑞芯微(EASY EAI)RV1126B 人脸98关键点算法识别
开发语言·科技·嵌入式硬件·物联网·算法·php