在 Linux 系统中,与 USB HID(Human Interface Device,如键盘、鼠标、游戏手柄等)设备进行通信通常涉及以下几个步骤:
lsusb
查看连接的 USB 设备首先,使用 lsusb
命令列出所有连接的 USB 设备:
lsusb
示例输出:
Bus 001 Device 002: ID 1234:5678 Vendor Name Product Name
lsinput
或 evtest
查看 HID 输入设备HID 设备通常作为输入设备出现在 /dev/input/
目录下。可以使用以下命令查看:
lsinput
(需要安装 input-utils
)sudo apt-get install input-utils # Debian/Ubuntu
sudo yum install input-utils # CentOS/RHEL
lsinput
evtest
(需要安装 evtest
)sudo apt-get install evtest # Debian/Ubuntu
sudo yum install evtest # CentOS/RHEL
sudo evtest
evtest
会列出所有输入设备,并允许你选择设备进行事件监控。
dmesg
查看内核日志插入 USB HID 设备后,使用 dmesg
查看内核日志,确认设备是否被正确识别:
dmesg | grep usb
HID 设备通过报告描述符定义其数据格式。理解报告描述符对于自定义通信至关重要。
hidrd
解析报告描述符hidrd
是一个工具,可以将二进制报告描述符转换为人类可读的格式。
hidrd
sudo apt-get install hid-tools # 包含 hidrd
首先,找到设备的路径。假设设备在 /dev/hidraw0
:
sudo hidrd-convert /dev/hidraw0
或者,如果设备支持,可以通过 usbhid-dump
获取:
sudo apt-get install usbhid-dump # 需要安装
sudo usbhid-dump
注意:并非所有设备都允许直接读取报告描述符,某些设备可能需要特定的驱动程序或权限。
一个简单的 HID 报告描述符可能如下:
USAGE_PAGE (Generic Desktop)
USAGE (Keyboard)
COLLECTION (Application)
USAGE_PAGE (Keyboard/Keypad)
USAGE_MINIMUM (Keyboard LeftControl)
USAGE_MAXIMUM (Keyboard Right GUI)
LOGICAL_MINIMUM (0)
LOGICAL_MAXIMUM (101)
REPORT_SIZE (1)
REPORT_COUNT (8)
INPUT (Data,Var,Abs)
...
END_COLLECTION
此描述符定义了一个键盘设备,包含 8 个按键的状态。
Linux 提供了一些标准工具和接口,用于与 HID 设备进行通信。
libusb
进行低级 USB 通信libusb
是一个用户空间的 USB 库,允许应用程序直接与 USB 设备通信。
libusb
sudo apt-get install libusb-1.0-0-dev # Debian/Ubuntu
sudo yum install libusb-devel # CentOS/RHEL
以下是一个简单的 C 程序,使用 libusb
枚举 USB 设备:
#include <stdio.h>
#include <libusb-1.0/libusb.h>
int main() {
libusb_context *ctx = NULL;
libusb_device **devs;
ssize_t cnt;
// 初始化 libusb
if (libusb_init(&ctx) < 0) {
fprintf(stderr, "初始化 libusb 失败\n");
return 1;
}
// 获取设备列表
cnt = libusb_get_device_list(ctx, &devs);
if (cnt < 0) {
fprintf(stderr, "获取设备列表失败\n");
libusb_exit(ctx);
return 1;
}
printf("找到 %ld 个 USB 设备\n", cnt);
// 遍历设备
for (ssize_t i = 0; i < cnt; i++) {
libusb_device *dev = devs[i];
struct libusb_device_descriptor desc;
if (libusb_get_device_descriptor(dev, &desc) == 0) {
printf("设备 %04x:%04x\n", desc.idVendor, desc.idProduct);
}
}
// 释放设备列表
libusb_free_device_list(devs, 1);
libusb_exit(ctx);
return 0;
}
gcc -o list_usb list_usb.c -lusb-1.0
sudo ./list_usb
注意:访问 USB 设备通常需要超级用户权限,因此使用
sudo
运行程序。
hidapi
进行 HID 设备通信hidapi
是一个跨平台的库,简化了与 HID 设备的通信。
hidapi
sudo apt-get install libhidapi-dev # Debian/Ubuntu
# 对于 CentOS/RHEL,可能需要从源码编译
以下是一个简单的 C 程序,使用 hidapi
读取 HID 设备的数据:
#include <stdio.h>
#include <stdlib.h>
#include <hidapi/hidapi.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("用法: %s <设备路径>\n", argv[0]);
return 1;
}
const char *device_path = argv[1];
hid_device *handle;
// 初始化 hidapi
if (hid_init() != 0) {
fprintf(stderr, "初始化 hidapi 失败\n");
return 1;
}
// 打开设备
handle = hid_open_path(device_path);
if (!handle) {
fprintf(stderr, "无法打开设备: %s\n", device_path);
hid_exit();
return 1;
}
// 读取数据
unsigned char buf[64];
int res = hid_read(handle, buf, sizeof(buf));
if (res > 0) {
printf("读取到 %d 字节数据:\n", res);
for (int i = 0; i < res; i++) {
printf("%02x ", buf[i]);
}
printf("\n");
} else if (res == -1) {
fprintf(stderr, "读取失败\n");
} else {
printf("没有数据可读\n");
}
// 关闭设备并退出
hid_close(handle);
hid_exit();
return 0;
}
gcc -o read_hid read_hid.c -lhidapi-hidraw
sudo ./read_hid /dev/hidraw0
/dev/hidraw0
)。对于更复杂的 HID 设备,可能需要编写自定义的驱动程序或用户空间程序,直接与内核交互。
libusb
进行自定义通信以下是一个使用 libusb
发送和接收数据的示例:
#include <stdio.h>
#include <stdlib.h>
#include <libusb-1.0/libusb.h>
#define VENDOR_ID 0x1234 // 替换为你的设备的 VID
#define PRODUCT_ID 0x5678 // 替换为你的设备的 PID
int main() {
libusb_context *ctx = NULL;
libusb_device_handle *dev_handle = NULL;
int r; // 返回值
unsigned char data[64]; // 数据缓冲区
// 初始化 libusb
r = libusb_init(&ctx);
if (r < 0) {
fprintf(stderr, "初始化 libusb 失败\n");
return 1;
}
// 打开设备
dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID);
if (dev_handle == NULL) {
fprintf(stderr, "无法打开设备\n");
libusb_exit(ctx);
return 1;
}
// 断开内核驱动(如果已附加)
if (libusb_kernel_driver_active(dev_handle, 0)) {
libusb_detach_kernel_driver(dev_handle, 0);
}
// 声明接口
r = libusb_claim_interface(dev_handle, 0);
if (r < 0) {
fprintf(stderr, "无法声明接口\n");
libusb_close(dev_handle);
libusb_exit(ctx);
return 1;
}
// 发送数据示例
data[0] = 0x01; // 示例数据
int actual_length;
r = libusb_bulk_transfer(dev_handle, 0x01 /* 端点地址 */, data, sizeof(data), &actual_length, 5000);
if (r == 0 && actual_length > 0) {
printf("发送 %d 字节数据\n", actual_length);
} else {
fprintf(stderr, "发送失败: %d\n", r);
}
// 接收数据示例
memset(data, 0, sizeof(data));
r = libusb_bulk_transfer(dev_handle, 0x81 /* 端点地址 */, data, sizeof(data), &actual_length, 5000);
if (r == 0 && actual_length > 0) {
printf("接收 %d 字节数据:\n", actual_length);
for (int i = 0; i < actual_length; i++) {
printf("%02x ", data[i]);
}
printf("\n");
} else {
fprintf(stderr, "接收失败: %d\n", r);
}
// 释放接口并关闭设备
libusb_release_interface(dev_handle, 0);
libusb_close(dev_handle);
libusb_exit(ctx);
return 0;
}
gcc -o usb_comm usb_comm.c -lusb-1.0
sudo ./usb_comm
VENDOR_ID
和 PRODUCT_ID
为你的设备的实际值。0x01
和 0x81
)需要根据设备的通信协议确定。hidraw
进行自定义通信如果设备通过 hidraw
接口暴露,可以直接读写 /dev/hidrawX
设备文件。
/dev/hidrawX
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#define DEVICE_PATH "/dev/hidraw0" // 替换为你的设备路径
int main() {
int fd;
unsigned char buf[64];
// 打开设备
fd = open(DEVICE_PATH, O_RDWR | O_NONBLOCK);
if (fd == -1) {
fprintf(stderr, "无法打开设备 %s: %s\n", DEVICE_PATH, strerror(errno));
return 1;
}
// 发送数据示例
memset(buf, 0, sizeof(buf));
buf[0] = 0x01; // 示例数据
ssize_t bytes_written = write(fd, buf, 64);
if (bytes_written < 0) {
fprintf(stderr, "写入失败: %s\n", strerror(errno));
} else {
printf("写入 %zd 字节数据\n", bytes_written);
}
// 接收数据示例
memset(buf, 0, sizeof(buf));
ssize_t bytes_read = read(fd, buf, sizeof(buf));
if (bytes_read < 0) {
fprintf(stderr, "读取失败: %s\n", strerror(errno));
} else if (bytes_read > 0) {
printf("读取 %zd 字节数据:\n", bytes_read);
for (int i = 0; i < bytes_read; i++) {
printf("%02x ", buf[i]);
}
printf("\n");
} else {
printf("没有数据可读\n");
}
// 关闭设备
close(fd);
return 0;
}
gcc -o hidraw_comm hidraw_comm.c
sudo ./hidraw_comm
/dev/hidraw0
)。访问 /dev/hidrawX
或进行 USB 通信通常需要超级用户权限。如果遇到权限错误,可以:
sudo
运行程序。plugdev
或 dialout
组(取决于发行版):sudo usermod -aG plugdev $USER sudo usermod -aG dialout $USER
然后重新登录。确保设备已正确连接并被系统识别:
lsusb
ls /dev/hidraw*
某些 HID 设备可能需要特定的内核模块。可以使用 lsmod
查看已加载的模块,并根据需要加载:
lsmod | grep hid
sudo modprobe hid-generic
usbmon
:用于捕获 USB 流量。
sudo modprobe usbmon sudo wireshark # 需要安装 Wireshark 并配置 usbmon 接口dmesg
:查看内核日志,排查设备识别和通信问题。
dmesg | tail -n 50Python 提供了 hidapi
的绑定,可以简化 HID 设备的通信。
pyhidapi
pip install hidapi
import hid
# 替换为你的设备的 VID 和 PID
VENDOR_ID = 0x1234
PRODUCT_ID = 0x5678
try:
# 打开设备
device = hid.device()
device.open(VENDOR_ID, PRODUCT_ID)
# 设置非阻塞模式(可选)
device.set_nonblocking(1)
# 读取数据
while True:
data = device.read(64) # 读取最多64字节
if data:
print("接收到数据:", [hex(x) for x in data])
else:
# 没有数据可读时,可以进行其他操作
pass
except IOError as ex:
print(f"无法打开设备: {ex}")
finally:
if 'device' in locals():
device.close()
libhidapi
库。如果需要开发自定义 HID 设备,可能需要设计自定义的报告描述符。这通常涉及:
hidrd
)解析和生成报告描述符。对于自定义 HID 设备,可能需要开发固件。常用的微控制器(如 Arduino、ESP32、STM32)都有支持 HID 的库和示例代码。
#include <Keyboard.h>
void setup() {
// 初始化键盘
Keyboard.begin();
delay(1000);
// 发送按键
Keyboard.press(KEY_LEFT_GUI); // Windows 键
Keyboard.press('r');
delay(100);
Keyboard.releaseAll();
delay(100);
Keyboard.print("notepad");
delay(500);
Keyboard.press(KEY_RETURN);
Keyboard.releaseAll();
}
void loop() {
// 主循环
}
注意:此示例适用于支持 HID 的 Arduino 板(如 Leonardo、Micro)。
领取专属 10元无门槛券
手把手带您无忧上云