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); // 按键运行服务
扫描流程:
- 行扫描: 依次将每行设为低电平,其他行设为高电平
- 列检测: 读取所有列的电平状态
- 按键判断: 低电平表示该位置有按键按下
- 消抖处理: 通过多次采样确认按键状态
- 状态更新: 更新按键状态矩阵
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); // 发送按键数据
处理步骤:
- 按键映射: 将物理按键位置映射为HID键码
- 组合键处理: 检测Ctrl、Alt、Shift等修饰键
- 数据包构建: 按照HID格式构建8字节数据包
- 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 常见问题排查
设备无法识别
- 检查设备描述符中的VID/PID
- 确认USB时钟配置正确
- 验证端点0响应是否正常
按键无响应
- 检查HID报告描述符格式
- 确认按键扫描时序
- 验证端点1数据发送
按键重复或丢失
- 调整消抖时间参数
- 检查扫描频率设置
- 优化中断处理时序
7.2 性能优化
扫描频率优化
- 键盘扫描频率建议1000Hz
- USB报告频率建议125Hz
- 平衡响应速度与功耗
内存使用优化
- 合理分配端点缓冲区
- 优化按键状态存储
- 减少不必要的数据复制
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通信的各个环节。关键技术点包括:
- USB描述符设计: 正确配置设备、配置、接口、HID和端点描述符
- HID报告格式: 理解并实现标准键盘HID报告描述符
- 按键扫描算法: 实现高效的矩阵扫描和消抖处理
- USB通信协议: 掌握HID设备的数据传输机制
通过本文的学习,读者可以完全理解USB HID键盘的工作原理,并能够独立开发出功能完整的USB键盘产品。在实际项目中,还可以根据需求扩展多媒体键、宏功能、RGB灯效等高级特性。
开发建议:
- 先实现基础功能,再逐步添加扩展特性
- 重视USB协议的标准化,确保兼容性
- 充分测试各种使用场景,保证产品稳定性
- 关注功耗优化,特别是无线键盘应用
希望本文能够帮助大家深入理解USB HID技术,在嵌入式开发道路上更进一步!