当前位置: 首页 > news >正文

从零到实战:基于CH582和CherryUSB打造一个自定义HID设备(键盘/鼠标)

从零到实战基于CH582和CherryUSB打造自定义HID设备在创客和嵌入式开发领域自定义HID人机接口设备的需求日益增长。无论是打造一款带有宏功能的机械键盘还是开发一个基于手势识别的空气鼠标CH582微控制器配合CherryUSB协议栈的组合为开发者提供了快速实现创意的可能。本文将带你从零开始完成一个完整HID设备的开发流程。1. 开发环境搭建与硬件准备CH582是沁恒微电子推出的一款集成USB功能的ARM Cortex-M0微控制器内置蓝牙5.3和丰富的外设资源。要开始我们的HID设备开发之旅首先需要准备好以下硬件和软件环境硬件清单CH582开发板如CH582F评估板USB Type-C或Micro USB连接线按键模块用于键盘功能测试加速度传感器模块可选用于鼠标功能软件工具准备Keil MDK或IAR Embedded Workbench开发环境WCH官方提供的CH582 SDK包CherryUSB协议栈可从GitHub获取最新版本USB分析工具如Wireshark或USBlyzer安装完开发环境后我们需要在项目中集成CherryUSB协议栈。这里有一个简单的目录结构示例project_root/ ├── CH582_SDK/ # WCH官方SDK ├── CherryUSB/ # 协议栈核心代码 ├── Drivers/ # 硬件驱动 ├── Inc/ # 项目头文件 └── Src/ # 项目源文件提示确保在项目设置中正确包含所有必要的头文件路径特别是CherryUSB的核心头文件和CH582的USB驱动头文件。2. CherryUSB协议栈基础配置CherryUSB是一个轻量级的USB协议栈特别适合资源有限的嵌入式设备。要将其适配到CH582平台需要进行一些基础配置。首先我们需要修改usb_config.h文件启用HID类支持#define CONFIG_USB_DEVICE_HID #define CONFIG_USB_DEVICE_HID_NUM 1 // 我们只需要一个HID接口接下来配置USB设备描述符。在usbd_desc.c中定义设备的基本信息const uint8_t usbd_device_descriptor[] { 0x12, // bLength 0x01, // bDescriptorType (Device) 0x0110, // bcdUSB (USB 1.1) 0x00, // bDeviceClass 0x00, // bDeviceSubClass 0x00, // bDeviceProtocol 0x40, // bMaxPacketSize0 (64 bytes) 0x0483, // idVendor (示例VID) 0x5750, // idProduct (示例PID) 0x0100, // bcdDevice 0x01, // iManufacturer 0x02, // iProduct 0x03, // iSerialNumber 0x01 // bNumConfigurations };对于HID设备最关键的是HID描述符和报告描述符的配置。下面是一个简单的键盘HID描述符示例const uint8_t hid_keyboard_descriptor[] { // HID描述符 0x09, // bLength 0x21, // bDescriptorType (HID) 0x0110, // bcdHID 0x00, // bCountryCode 0x01, // bNumDescriptors 0x22, // bDescriptorType (Report) sizeof(hid_keyboard_report_descriptor), 0x00, // wDescriptorLength // 报告描述符 0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x06, // Usage (Keyboard) 0xA1, 0x01, // Collection (Application) // ... 更多报告描述符内容 };3. HID报告描述符深度解析报告描述符是HID设备与主机通信的核心它定义了设备能够发送和接收的数据格式。让我们深入解析一个完整的键盘报告描述符const uint8_t hid_keyboard_report_descriptor[] { 0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x06, // Usage (Keyboard) 0xA1, 0x01, // Collection (Application) // 修饰键 (Ctrl, Shift, Alt等) 0x05, 0x07, // Usage Page (Key Codes) 0x19, 0xE0, // Usage Minimum (Left Control) 0x29, 0xE7, // Usage Maximum (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, 0x06, // Report Count (6) 0x75, 0x08, // Report Size (8) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x65, // Logical Maximum (101) 0x05, 0x07, // Usage Page (Key Codes) 0x19, 0x00, // Usage Minimum (0) 0x29, 0x65, // Usage Maximum (101) 0x81, 0x00, // Input (Data, Array) 0xC0 // End Collection };这个描述符定义了一个标准的键盘报告格式包含8个1位的修饰键Ctrl、Shift、Alt等1个8位的保留字段6个8位的按键码最多可同时报告6个按键对于鼠标设备报告描述符会有所不同。下面是一个简单的鼠标报告描述符示例const uint8_t hid_mouse_report_descriptor[] { 0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x02, // Usage (Mouse) 0xA1, 0x01, // Collection (Application) // 按键 0x05, 0x09, // Usage Page (Button) 0x19, 0x01, // Usage Minimum (Button 1) 0x29, 0x03, // Usage Maximum (Button 3) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x01, // Logical Maximum (1) 0x95, 0x03, // Report Count (3) 0x75, 0x01, // Report Size (1) 0x81, 0x02, // Input (Data, Variable, Absolute) // 填充5位 0x95, 0x01, // Report Count (1) 0x75, 0x05, // Report Size (5) 0x81, 0x01, // Input (Constant) // X/Y轴移动 0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x30, // Usage (X) 0x09, 0x31, // Usage (Y) 0x15, 0x81, // Logical Minimum (-127) 0x25, 0x7F, // Logical Maximum (127) 0x75, 0x08, // Report Size (8) 0x95, 0x02, // Report Count (2) 0x81, 0x06, // Input (Data, Variable, Relative) 0xC0 // End Collection };4. CH582 USB硬件层配置CH582的USB外设需要正确初始化才能与CherryUSB协议栈协同工作。以下是关键的初始化步骤时钟配置void USB_Clock_Init(void) { R8_USB_CTRL RB_UC_CLR_ALL; R8_USB_CTRL RB_UC_RESET_SIE | RB_UC_CLR_ALL; DelayMs(10); R8_USB_CTRL 0; // 配置USB时钟 R8_CK32K_CONFIG | RB_CLK_USB_EN | RB_CLK_OSC32K_EN; R8_USB_CTRL | RB_UC_DEV_PU_EN; }端点配置void USB_EP_Init(void) { // 配置端点0控制端点 R8_UEP0_CTRL UEP_R_RES_ACK | UEP_T_RES_NAK; R16_UEP0_DMA (uint16_t)(uint32_t)Ep0Buffer; R8_UEP0_T_LEN 0; // 配置HID中断端点端点1输入 R8_UEP1_CTRL UEP_R_RES_ACK | UEP_T_RES_NAK; R16_UEP1_DMA (uint16_t)(uint32_t)HIDReportBuffer; R8_UEP1_T_LEN sizeof(HIDReportBuffer); // 启用端点中断 R8_USB_INT_EN | RB_UIE_SETUP_ACT | RB_UIE_TRANSFER; }中断处理void USB_IRQHandler(void) { uint8_t int_status R8_USB_INT_ST; if(int_status RB_UIS_SETUP_ACT) { // 处理SETUP包 USB_HandleSetupPacket(); R8_USB_INT_FG RB_UIS_SETUP_ACT; } if(int_status RB_UIS_TRANSFER) { uint8_t ep_status R8_USB_INT_ST MASK_UIS_TOKEN; switch(ep_status) { case UIS_TOKEN_IN: // 处理IN令牌 if(R8_USB_INT_ST RB_UIS_TOG_OK) { USB_HandleInToken(); } break; case UIS_TOKEN_OUT: // 处理OUT令牌 if(R8_USB_INT_ST RB_UIS_TOG_OK) { USB_HandleOutToken(); } break; } R8_USB_INT_FG RB_UIS_TRANSFER; } }5. 自定义HID设备实现现在我们将实现一个具体的HID设备示例——一个带有额外功能键的宏键盘。这个键盘除了标准按键外还有三个可编程的宏键。键盘功能实现步骤定义报告数据结构typedef struct { uint8_t modifiers; // 修饰键 uint8_t reserved; // 保留字节 uint8_t keys[6]; // 按键码 uint8_t macro_keys; // 我们的自定义宏键3位 } KeyboardReport;修改报告描述符以包含宏键const uint8_t hid_keyboard_report_descriptor[] { // ... 标准键盘描述符部分 // 添加宏键 0x05, 0x0C, // Usage Page (Consumer) 0x19, 0x01, // Usage Minimum (Consumer Control) 0x29, 0x03, // Usage Maximum (3个自定义功能) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x01, // Logical Maximum (1) 0x75, 0x01, // Report Size (1) 0x95, 0x03, // Report Count (3) 0x81, 0x02, // Input (Data, Variable, Absolute) // 填充5位 0x95, 0x01, // Report Count (1) 0x75, 0x05, // Report Size (5) 0x81, 0x01, // Input (Constant) // ... 其余部分 };实现按键扫描和报告生成void GenerateKeyboardReport(void) { static KeyboardReport report {0}; // 扫描物理按键 report.modifiers ScanModifierKeys(); ScanNormalKeys(report.keys); // 扫描宏键 report.macro_keys 0; if(IsMacroKey1Pressed()) report.macro_keys | 0x01; if(IsMacroKey2Pressed()) report.macro_keys | 0x02; if(IsMacroKey3Pressed()) report.macro_keys | 0x04; // 发送报告 USBD_HID_SendReport(report, sizeof(report)); }对于空气鼠标的实现我们可以使用CH582内置的ADC读取加速度传感器数据void GenerateMouseReport(void) { int8_t x_movement 0, y_movement 0; uint8_t buttons 0; // 读取加速度传感器数据 int16_t x_accel ReadAccelerometerX(); int16_t y_accel ReadAccelerometerY(); // 转换为鼠标移动量 x_movement (int8_t)(x_accel / 64); // 适当缩放 y_movement (int8_t)(y_accel / 64); // 限制范围 x_movement (x_movement -127) ? -127 : (x_movement 127) ? 127 : x_movement; y_movement (y_movement -127) ? -127 : (y_movement 127) ? 127 : y_movement; // 读取按键状态 if(IsLeftButtonPressed()) buttons | 0x01; if(IsRightButtonPressed()) buttons | 0x02; // 构造报告 uint8_t report[4] {buttons, x_movement, y_movement, 0}; // 发送报告 USBD_HID_SendReport(report, sizeof(report)); }6. 调试与优化技巧开发HID设备时调试是一个重要环节。以下是一些实用的调试技巧常见问题排查表问题现象可能原因解决方案设备无法被识别描述符错误使用USB分析工具检查描述符按键无响应报告格式不匹配检查报告描述符与实际发送的数据设备频繁断开电源不足检查供电必要时使用外部电源移动不流畅采样率太低提高报告发送频率性能优化建议报告频率优化// 设置10ms的报告间隔 #define HID_REPORT_INTERVAL 10 void TIM2_IRQHandler(void) { static uint32_t tick 0; if(tick HID_REPORT_INTERVAL) { tick 0; GenerateInputReport(); } }电源管理void EnterLowPowerMode(void) { // 当无用户交互时进入低功耗模式 if(!AnyKeyPressed()) { R8_SLP_POWER_CTRL | RB_SLP_USB_SUSPEND; R8_POWER_CTRL | RB_PWR_USB_SUSPEND; } }使用DMA提高效率void USB_DMA_Config(void) { // 配置USB端点DMA R16_UEP1_DMA (uint16_t)(uint32_t)HIDReportBuffer; R8_UEP1_T_LEN sizeof(HIDReportBuffer); R8_UEP1_CTRL UEP_R_RES_ACK | UEP_T_RES_NAK | UEP_T_TOG; // 启用DMA完成中断 R8_USB_INT_EN | RB_UIE_DMA_END; }在实际项目中我发现合理设置报告间隔对用户体验影响很大。对于键盘设备20-30ms的报告间隔通常足够而对于鼠标或游戏手柄建议将报告间隔缩短到10ms以内以获得更流畅的操作体验。
http://www.rkmt.cn/news/1411074.html

相关文章:

  • Keil MDK与Arm DS在Cortex-R开发中的对比与选型
  • 别再手动调增益了!手把手教你用RFSoC的AGC功能搞定动态信号(附Vivado 2023.1工程)
  • 稀土化合物是什么?不是“稀有金属”这么简单
  • AI 超节点服务器开始疯狂爆发,128卡正在成为新标杆?从阿里云磐久到新华三 UniPoD,看懂 AI 数据中心为什么正在“巨型化”
  • 2026世界杯蒙特雷钢铁侠球场:工业之都的足球狂想曲
  • 可视化多智能体 LLM 交易研究平台 — 看见 Agent 怎么想、怎么辩、怎么决策,而不是只看最后一个 BUY/SELL。
  • 华为云码道实测报告,从安装配置到远程开发避坑全记录
  • 用ESP32-CAM做个寝室智能看宠摄像头:低成本、免公网、手机随时看
  • 从光纤卡顿到晶格禁带:用一维单原子链模型理解生活中的“色散”与“截止频率”
  • 水平越权 垂直越权-漏洞解析5
  • ESXi 7.0升级避坑指南:ThinkServer升级后Win2022虚拟机启动报错?安全引导惹的祸
  • 基于多智能体流水线的代码审查自动化实践与架构解析
  • 没想到!坚持用森优时铁锌维,白发居然悄悄转黑了 科学解读内调养发的真实逻辑
  • 27周洋鑫1000题|杨超三大计算资料
  • 为OpenClawAgent工作流配置Taotoken作为模型供应商的步骤
  • HIMA H7202 985030008 控制器模块
  • CAD依赖管理:从软件工程到机械设计的实践创新
  • TestNG 接口测试:提取返回值 + 数据库断言完整实战
  • MCB167评估板时钟频率配置与优化解析
  • 不只是出SQL和报表:离智能决策还有多远
  • 深挖.NET 11:.NET Aspire 在云原生应用韧性架构构建的探索与实践
  • React Grab工具详解:AI助力Vue3、Svelte和Solid前端元素调试
  • AI编码工作流优化:从代码生成到人机协同的范式转移
  • 双万兆加持!DXP4800GT 打造高效存储新范式
  • 保姆级教程:解决Ubuntu 20.04下U-Boot无法NFS挂载的TTT与cannot mount错误
  • 判断力:AI落地的最后一块拼图——为什么说它与Token、Transformer同等重要
  • 冰雪传奇官网下载:冰雪传奇手游最新官方下载渠道
  • 短波 / 超短波通吃!RM-1000 高性能无线电综合测试仪,现场检测可靠之选
  • Next.js项目国际化:从Day One开始的架构设计与实践指南
  • 别再死记硬背了!用卡诺图化简逻辑电路的保姆级指南(附常见错误分析)