You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过PC端HIDAPI控制RP2040键鼠复合设备并接收指令

问题描述

参考Adafruit_TinyUSB_Arduino的HID复合设备示例,计划通过PC端Python的hidapi发送指令,让RP2040实现键鼠操作。使用Raspberry Pi Pico Arduino core v4.7.1,设备能被PC正确识别为键鼠,但RP2040无法接收PC发送的指令。


现有代码

Python发送端代码

import hid
import time

# 打开HID设备(基于设备的VID和PID)
VID = 0x239A  # 设置对应的厂商ID
PID = 0xCAFE  # 设置对应的产品ID

# 打开HID设备
device = hid.device()
device.open(VID, PID)


# 发送鼠标移动报告
def send_mouse_move(dx, dy):
    # 限制dx和dy在-127到127范围内
    dx = max(min(dx, 127), -127)
    dy = max(min(dy, 127), -127)

    # 转换为无符号8位整数(0-255)
    dx = (dx + 128) & 0xFF
    dy = (dy + 128) & 0xFF

    # 构造鼠标报告
    report = [0x00, dx, dy, 0x00, 0x00, 0x00, 0x00, 0x00]  # 鼠标报告格式
    print("发送鼠标报告:", report)
    device.write(report)
    time.sleep(0.1)


# 发送键盘按键报告
def send_key_press(keycode):
    report = [
        keycode,
        0x00,
        0x00,
        0x00,
        0x00,
        0x00,
        0x00,
        0x00,
    ]  # 键盘报告格式
    print("发送键盘报告:", report)
    device.write(report)
    time.sleep(0.1)

    # 发送释放报告
    report = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
    print("发送键盘释放报告:", report)
    device.write(report)


# 控制指令映射
def send_command(command):
    if command == "MOVE_LEFT":
        send_mouse_move(-10, 0)
    elif command == "MOVE_RIGHT":
        send_mouse_move(10, 0)
    elif command == "KEY_A":
        send_key_press(0x04)  # 按下A键
    elif command == "KEY_ENTER":
        send_key_press(0x28)  # 按下回车键


# 发送测试指令
commands = ["MOVE_LEFT", "MOVE_RIGHT", "KEY_A", "KEY_ENTER"]
for command in commands:
    send_command(command)
    time.sleep(1)

device.close()

RP2040接收端代码

#include "Adafruit_TinyUSB.h"

// 定义报告ID
enum {
  RID_KEYBOARD = 1,
  RID_MOUSE,
};

// HID报告描述符
uint8_t const desc_hid_report[] = {
    TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(RID_KEYBOARD)),
    TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(RID_MOUSE)),
};

// USB HID对象
Adafruit_USBD_HID usb_hid;

// 处理接收的HID报告(Get Report回调)
uint16_t get_report(uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t bufsize) {
  Serial.print("收到Get Report: ");
  for (uint16_t i = 0; i < bufsize; i++) {
    Serial.print(buffer[i], HEX);
    Serial.print(" ");
  }
  Serial.println();

  return bufsize;
}

// 设置报告回调(Set Report回调)
void set_report(uint8_t report_id, hid_report_type_t report_type, const uint8_t *buffer, uint16_t bufsize) {
  Serial.print("收到Set Report: ");
  for (uint16_t i = 0; i < bufsize; i++) {
    Serial.print(buffer[i], HEX);
    Serial.print(" ");
  }
  Serial.println();
}

void setup() {
  // 初始化串口调试
  Serial.begin(115200);
  while (!Serial);

  // 初始化TinyUSB
  if (!TinyUSBDevice.isInitialized()) {
    TinyUSBDevice.begin(0);
  }

  // 配置HID设备
  usb_hid.setReportDescriptor(desc_hid_report, sizeof(desc_hid_report));
  usb_hid.setPollInterval(2);
  usb_hid.setStringDescriptor("TinyUSB HID Composite");

  // 设置报告回调函数
  usb_hid.setReportCallback(get_report, set_report);
  usb_hid.begin();

  // 确保设备正确挂载
  if (TinyUSBDevice.mounted()) {
    TinyUSBDevice.detach();
    delay(10);
    TinyUSBDevice.attach();
  }

  Serial.println("Adafruit TinyUSB HID复合设备示例启动");
}

void loop() {
  #ifdef TINYUSB_NEED_POLLING_TASK
  // 运行TinyUSB任务处理
  TinyUSBDevice.task();
  #endif

  if (!TinyUSBDevice.mounted()) {
    return;
  }
}

问题排查与修复方案

1. 报告ID不匹配

Arduino端的HID描述符定义了带报告ID的键鼠报告(RID_KEYBOARD=1、RID_MOUSE=2),但Python代码发送的报告未携带报告ID,导致TinyUSB无法识别并触发回调。

修复Python代码:

  • 鼠标报告添加第一个字节0x02(对应RID_MOUSE)
  • 键盘报告添加第一个字节0x01(对应RID_KEYBOARD)

修改后的鼠标移动函数:

def send_mouse_move(dx, dy):
    dx = max(min(dx, 127), -127)
    dy = max(min(dy, 127), -127)
    dx = (dx + 128) & 0xFF
    dy = (dy + 128) & 0xFF
    # 第一个字节为报告ID(0x02=鼠标)
    report = [0x02, dx, dy, 0x00, 0x00, 0x00, 0x00, 0x00]
    print("发送鼠标报告:", report)
    device.write(report)
    time.sleep(0.1)

修改后的键盘按键函数:

def send_key_press(keycode):
    # 标准带报告ID的键盘报告格式:[报告ID, 修饰键, 保留, 按键1, 按键2, ..., 按键6]
    report = [
        0x01,  # 报告ID(0x01=键盘)
        0x00,  # 修饰键(无)
        0x00,  # 保留位
        keycode,
        0x00,
        0x00,
        0x00,
        0x00,
        0x00
    ]
    print("发送键盘报告:", report)
    device.write(report)
    time.sleep(0.1)

    # 释放报告
    report = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
    print("发送键盘释放报告:", report)
    device.write(report)

2. 确保打开正确的HID接口

部分复合设备会有多个HID接口,需确认Python代码打开的是支持输出报告的接口。可通过枚举设备接口验证:

devices = hid.enumerate(VID, PID)
for d in devices:
    print(f"接口路径: {d['path']}, Usage Page: {d['usage_page']}, Usage: {d['usage']}")
# 选择Usage Page=1(通用桌面设备)、Usage=6(键盘)或Usage=2(鼠标)的接口
device.open_path(d['path'])

3. 验证回调触发

Python的device.write()发送的是输出报告,会触发Arduino端的set_report回调。修复报告ID后,打开RP2040的串口监视器(115200波特率),运行Python代码即可看到接收的报告数据。


内容的提问来源于stack exchange,提问作者kongbai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 12:05:53