USB HID键盘实现详解:从硬件扫描到USB通信的完整解决方案

发布于:2025-08-01 ⋅ 阅读:(18) ⋅ 点赞:(0)

USB HID键盘实现详解:从硬件扫描到USB通信的完整解决方案

前言

USB HID(Human Interface Device)是USB设备中最常见的设备类型之一,键盘、鼠标、游戏手柄等都属于HID设备。本文将详细介绍如何基于CH559单片机实现一个完整的USB HID键盘,包括按键扫描、USB描述符配置、HID报告描述符设计等核心技术。

1. 项目架构概述

1.1 系统整体架构

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   按键矩阵扫描   │───▶│   按键处理逻辑   │───▶│   USB HID通信   │
│   (KEY_Scan)    │    │ (Key_Process)   │    │    (USB)       │
└─────────────────┘    └─────────────────┘    └─────────────────┘
         │                       │                       │
         ▼                       ▼                       ▼
   硬件按键检测              按键码转换              发送到主机

1.2 核心文件结构

  • USB.H/USB.C: USB协议栈实现,包含设备描述符、配置描述符等
  • KEY_Scan.H: 按键扫描相关定义
  • Key_Process.H: 按键处理逻辑
  • USB_Keyboard.H: USB键盘特定功能

2. USB描述符详解

2.1 设备描述符(Device Descriptor)

设备描述符是USB设备的"身份证",告诉主机这是什么设备。

// 设备描述符 - 18字节固定长度
UINT8C DevDesc[18] = 
{
    0x12,       // bLength: 描述符长度为18字节
    0x01,       // bDescriptorType: 设备描述符类型
    0x00, 0x02, // bcdUSB: USB协议版本2.0
    0x00,       // bDeviceClass: 在接口描述符中定义设备类
    0x00,       // bDeviceSubClass: 在接口描述符中定义子类
    0x00,       // bDeviceProtocol: 在接口描述符中定义协议
    0x08,       // bMaxPacketSize0: 端点0最大包大小8字节
    0x89, 0x11, // idVendor: 厂商ID(自定义)
    0x90, 0x88, // idProduct: 产品ID(自定义)
    0x00, 0x01, // bcdDevice: 产品版本号
    0x01,       // iManufacturer: 厂商字符串索引
    0x02,       // iProduct: 产品字符串索引  
    0x03,       // iSerialNumber: 序列号字符串索引
    0x01        // bNumConfigurations: 配置数量为1
};

关键参数说明:

  • 厂商ID和产品ID: 用于系统识别设备,开发测试可使用自定义值
  • 端点0包大小: 控制传输的基础,通常为8、16、32或64字节
  • 字符串索引: 指向设备名称、厂商等描述信息

2.2 配置描述符(Configuration Descriptor)

配置描述符定义了设备的功能配置,包含接口描述符、HID描述符和端点描述符。

// 配置描述符 - 总长度66字节
UINT8C CfgDesc[66] =
{
    // 配置描述符头部 (9字节)
    0x09,       // bLength: 配置描述符长度
    0x02,       // bDescriptorType: 配置描述符类型
    0x42, 0x00, // wTotalLength: 整个配置描述符总长度66字节
    0x02,       // bNumInterfaces: 接口数量为2个
    0x01,       // bConfigurationValue: 配置值标识
    0x00,       // iConfiguration: 配置字符串索引
    0xA0,       // bmAttributes: 总线供电,支持远程唤醒
    0x32,       // bMaxPower: 最大功耗100mA (0x32*2mA)
    
    // 接口描述符 - 键盘接口 (9字节)
    0x09,       // bLength: 接口描述符长度
    0x04,       // bDescriptorType: 接口描述符类型
    0x00,       // bInterfaceNumber: 接口编号0
    0x00,       // bAlternateSetting: 备用接口编号
    0x01,       // bNumEndpoints: 端点数量1个
    0x03,       // bInterfaceClass: HID设备类
    0x01,       // bInterfaceSubClass: 启动接口子类
    0x01,       // bInterfaceProtocol: 键盘协议
    0x00,       // iInterface: 接口字符串索引
    
    // HID描述符 (9字节)
    0x09,       // bLength: HID描述符长度
    0x21,       // bDescriptorType: HID描述符类型
    0x00, 0x01, // bcdHID: HID版本1.0
    0x21,       // bCountryCode: 国家代码(中国)
    0x01,       // bNumDescriptors: 报告描述符数量
    0x22,       // bDescriptorType: 报告描述符类型
    sizeof(KeyRepDesc), 0x00, // wDescriptorLength: 报告描述符长度
    
    // 端点描述符 - 中断输入端点 (7字节)
    0x07,       // bLength: 端点描述符长度
    0x05,       // bDescriptorType: 端点描述符类型
    0x81,       // bEndpointAddress: 端点1,输入方向
    0x03,       // bmAttributes: 中断传输类型
    0x40, 0x00, // wMaxPacketSize: 最大包大小64字节
    0x0A,       // bInterval: 轮询间隔10ms
    
    // 第二个接口描述符 - 自定义HID接口
    // ... (类似结构,用于扩展功能)
};

配置要点:

  • 接口数量: 键盘通常需要1个接口,扩展功能可增加接口
  • 端点配置: 键盘至少需要1个中断输入端点
  • 轮询间隔: 10ms是键盘的标准轮询间隔

3. HID报告描述符深度解析

3.1 键盘报告描述符结构

HID报告描述符定义了设备发送给主机的数据格式,是HID设备的核心。

// 键盘HID报告描述符 - 65字节
UINT8C KeyRepDesc[65] =
{
    // 全局项:选择通用桌面页面
    0x05, 0x01, // USAGE_PAGE (Generic Desktop)
    
    // 局部项:指定为键盘设备
    0x09, 0x06, // USAGE (Keyboard)
    
    // 主要项:开始应用集合
    0xa1, 0x01, // COLLECTION (Application)
    
    // 修饰键部分(Ctrl、Alt、Shift等)
    0x05, 0x07, //     USAGE_PAGE (Keyboard/Keypad)
    0x19, 0xe0, //     USAGE_MINIMUM (Left Control)
    0x29, 0xe7, //     USAGE_MAXIMUM (Right GUI)
    0x15, 0x00, //     LOGICAL_MINIMUM (0)
    0x25, 0x01, //     LOGICAL_MAXIMUM (1)
    0x95, 0x08, //     REPORT_COUNT (8)
    0x75, 0x01, //     REPORT_SIZE (1)
    0x81, 0x02, //     INPUT (Data,Var,Abs)
    
    // 保留字节
    0x95, 0x01, //     REPORT_COUNT (1)
    0x75, 0x08, //     REPORT_SIZE (8)
    0x81, 0x03, //     INPUT (Const,Var,Abs)
    
    // 普通按键部分(6个按键码)
    0x95, 0x06, //   REPORT_COUNT (6)
    0x75, 0x08, //   REPORT_SIZE (8)
    0x15, 0x00, //   LOGICAL_MINIMUM (0)
    0x25, 0xFF, //   LOGICAL_MAXIMUM (255)
    0x05, 0x07, //   USAGE_PAGE (Keyboard/Keypad)
    0x19, 0x00, //   USAGE_MINIMUM (No Event)
    0x29, 0x65, //   USAGE_MAXIMUM (Keyboard Application)
    0x81, 0x00, //     INPUT (Data,Ary,Abs)
    
    // LED输出部分(Num Lock、Caps Lock等)
    0x25, 0x01, //     LOGICAL_MAXIMUM (1)
    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,Var,Abs)
    
    // LED填充位
    0x95, 0x01, //   REPORT_COUNT (1)
    0x75, 0x03, //   REPORT_SIZE (3)
    0x91, 0x03, //   OUTPUT (Const,Var,Abs)
    
    0xc0,        // END_COLLECTION
};

3.2 键盘数据包格式

根据报告描述符,键盘发送的数据包格式为8字节:

字节0: 修饰键状态 (Ctrl/Alt/Shift等)
字节1: 保留字节 (固定为0)
字节2-7: 普通按键码 (最多6个同时按下的键)

修饰键位定义:

// 修饰键位掩码定义
#define KEY_LEFT_CTRL   0x01  // 左Ctrl键
#define KEY_LEFT_SHIFT  0x02  // 左Shift键  
#define KEY_LEFT_ALT    0x04  // 左Alt键
#define KEY_LEFT_GUI    0x08  // 左Windows键
#define KEY_RIGHT_CTRL  0x10  // 右Ctrl键
#define KEY_RIGHT_SHIFT 0x20  // 右Shift键
#define KEY_RIGHT_ALT   0x40  // 右Alt键
#define KEY_RIGHT_GUI   0x80  // 右Windows键

4. 按键扫描系统设计

4.1 按键矩阵扫描原理

键盘通常采用矩阵扫描方式,通过行列扫描减少IO口使用。

// 按键扫描相关定义
extern xdata UINT8 g_KEY_keydata[6][20]; // 6行20列按键矩阵

// 按键扫描服务函数
void KEY_TimerPlus_Service(void);  // 定时器扫描服务
void KEY_Init_Service(void);       // 按键初始化
void KEY_Run_Service(void);        // 按键运行服务

扫描流程:

  1. 行扫描: 依次将每行设为低电平,其他行设为高电平
  2. 列检测: 读取所有列的电平状态
  3. 按键判断: 低电平表示该位置有按键按下
  4. 消抖处理: 通过多次采样确认按键状态
  5. 状态更新: 更新按键状态矩阵

4.2 按键处理逻辑

// 按键处理相关定义
extern xdata UINT8 SendHIDKey[8];    // 发送HID按键缓冲区
extern xdata UINT8 ReleaseHIDKey[8]; // 释放按键缓冲区
extern UINT8 HIDKey[6][20][9];       // HID按键映射表

// 按键处理函数
void Init_HIDKey();                   // 初始化HID按键映射
void Key_Send_DataMode(UINT8 row, UINT8 col); // 发送按键数据

处理步骤:

  1. 按键映射: 将物理按键位置映射为HID键码
  2. 组合键处理: 检测Ctrl、Alt、Shift等修饰键
  3. 数据包构建: 按照HID格式构建8字节数据包
  4. USB发送: 通过中断端点发送给主机

5. USB通信实现

5.1 端点缓冲区配置

// USB端点缓冲区定义(指定内存地址)
UINT8X Ep0Buffer[THIS_ENDP0_SIZE] _at_ 0x0000; // 端点0缓冲区
UINT8X Ep1Buffer[MAX_PACKET_SIZE] _at_ 0x0008; // 端点1输入缓冲区  
UINT8X Ep2Buffer[2*MAX_PACKET_SIZE] _at_ 0x0050; // 端点2输出缓冲区

// USB状态变量
UINT8 SetupReq, SetupLen, Ready, Count, FLAGUpdata, UsbConfig;
PUINT8 pDescr;                    // USB描述符指针
USB_SETUP_REQ SetupReqBuf;        // Setup请求缓冲区

5.2 USB字符串描述符

// 语言描述符
UINT8C MyLangDescr[4] = { 0x04, 0x03, 0x09, 0x04 };

// 厂商信息字符串
UINT8C MyManuInfo[18] = { 
    0x12, 0x03, 
    'B', 0, 'i', 0, 'l', 0, 'i', 0, 
    'B', 0, 'i', 0, 'l', 0, 'i', 0 
};

// 产品信息字符串  
UINT8C MyProdInfo[12] = { 
    0x0C, 0x03, 
    'h', 0, 'o', 0, 'o', 0, 'p', 0, '0', 0 
};

// 产品版本信息字符串
UINT8C MyProductIDInfo[14] = {
    0x0E, 0x03,
    'k', 0, 'e', 0, 'y', 0, '5', 0, '5', 0, '9', 0
};

字符串格式说明:

  • 第1字节:字符串长度
  • 第2字节:描述符类型(0x03)
  • 后续字节:Unicode编码的字符串内容

6. 实际应用示例

6.1 发送按键数据

// 发送HID按键数据到主机
void enp1IntIn(PUINT8 HIDKeyBuf) 
{
    // 将按键数据复制到端点1缓冲区
    memcpy(Ep1Buffer, HIDKeyBuf, 8);
    
    // 设置端点1为准备发送状态
    UEP1_T_LEN = 8;           // 设置发送长度为8字节
    UEP1_CTRL = UEP1_CTRL & ~MASK_UEP_T_RES | UEP_T_RES_ACK;
}

6.2 按键扫描示例

// 按键扫描主循环
void KEY_Run_Service(void)
{
    static UINT8 scan_row = 0;
    
    // 设置当前扫描行为低电平
    set_row_low(scan_row);
    
    // 延时稳定
    mDelayuS(10);
    
    // 读取所有列状态
    for(UINT8 col = 0; col < 20; col++) {
        if(read_col_status(col) == 0) {
            // 检测到按键按下
            if(g_KEY_keydata[scan_row][col] == 0) {
                // 新按键按下,开始消抖
                g_KEY_keydata[scan_row][col] = 1;
            } else if(g_KEY_keydata[scan_row][col] < 255) {
                // 按键持续按下,增加计数
                g_KEY_keydata[scan_row][col]++;
                
                // 消抖完成,发送按键
                if(g_KEY_keydata[scan_row][col] == 3) {
                    Key_Send_DataMode(scan_row, col);
                }
            }
        } else {
            // 按键释放
            if(g_KEY_keydata[scan_row][col] > 0) {
                g_KEY_keydata[scan_row][col] = 0;
                // 发送按键释放
                Key_Release_DataMode(scan_row, col);
            }
        }
    }
    
    // 切换到下一行
    scan_row = (scan_row + 1) % 6;
}

7. 调试与优化建议

7.1 常见问题排查

  1. 设备无法识别

    • 检查设备描述符中的VID/PID
    • 确认USB时钟配置正确
    • 验证端点0响应是否正常
  2. 按键无响应

    • 检查HID报告描述符格式
    • 确认按键扫描时序
    • 验证端点1数据发送
  3. 按键重复或丢失

    • 调整消抖时间参数
    • 检查扫描频率设置
    • 优化中断处理时序

7.2 性能优化

  1. 扫描频率优化

    • 键盘扫描频率建议1000Hz
    • USB报告频率建议125Hz
    • 平衡响应速度与功耗
  2. 内存使用优化

    • 合理分配端点缓冲区
    • 优化按键状态存储
    • 减少不必要的数据复制

8. 扩展功能实现

8.1 多媒体键支持

// 多媒体键HID报告描述符
UINT8C MediaKeyRepDesc[] = {
    0x05, 0x0C,        // Usage Page (Consumer)
    0x09, 0x01,        // Usage (Consumer Control)
    0xA1, 0x01,        // Collection (Application)
    0x19, 0x00,        //   Usage Minimum (0)
    0x2A, 0x3C, 0x02,  //   Usage Maximum (572)
    0x15, 0x00,        //   Logical Minimum (0)
    0x26, 0x3C, 0x02,  //   Logical Maximum (572)
    0x95, 0x01,        //   Report Count (1)
    0x75, 0x10,        //   Report Size (16)
    0x81, 0x00,        //   Input (Data,Array,Abs)
    0xC0,              // End Collection
};

8.2 自定义功能键

通过第二个HID接口实现自定义功能:

// 自定义HID报告描述符(用于宏功能、RGB控制等)
UINT8C HIDRepDesc[33] = {
    0x05, 0x01, // USAGE_PAGE (Generic Desktop)     
    0x09, 0x00, // USAGE (Undefined) - 自定义用途
    0xa1, 0x01, // COLLECTION (Application)
    
    // 输入报告:8字节自定义数据
    0x15, 0x00, // LOGICAL_MINIMUM (0)
    0x25, 0xff, // LOGICAL_MAXIMUM (255)
    0x19, 0x01, // USAGE_MINIMUM (1)
    0x29, 0x08, // USAGE_MAXIMUM (8)
    0x95, 0x08, // REPORT_COUNT (8)
    0x75, 0x08, // REPORT_SIZE (8)
    0x81, 0x02, // INPUT (Data,Var,Abs)
    
    // 输出报告:64字节配置数据
    0x09, 0x02, // USAGE (2)
    0x15, 0x00, // LOGICAL_MINIMUM (0)
    0x25, 0xff, // LOGICAL_MAXIMUM (255)
    0x75, 0x08, // REPORT_SIZE (8)
    0x95, 0x40, // REPORT_COUNT (64)
    0x91, 0x06, // OUTPUT (Data,Var,Abs,Wrap)
    
    0xc0        // END_COLLECTION
};

9. 总结

本文详细介绍了USB HID键盘的完整实现方案,涵盖了从硬件按键扫描到USB通信的各个环节。关键技术点包括:

  1. USB描述符设计: 正确配置设备、配置、接口、HID和端点描述符
  2. HID报告格式: 理解并实现标准键盘HID报告描述符
  3. 按键扫描算法: 实现高效的矩阵扫描和消抖处理
  4. USB通信协议: 掌握HID设备的数据传输机制

通过本文的学习,读者可以完全理解USB HID键盘的工作原理,并能够独立开发出功能完整的USB键盘产品。在实际项目中,还可以根据需求扩展多媒体键、宏功能、RGB灯效等高级特性。

开发建议:

  • 先实现基础功能,再逐步添加扩展特性
  • 重视USB协议的标准化,确保兼容性
  • 充分测试各种使用场景,保证产品稳定性
  • 关注功耗优化,特别是无线键盘应用

希望本文能够帮助大家深入理解USB HID技术,在嵌入式开发道路上更进一步!


网站公告

今日签到

点亮在社区的每一天
去签到