USB子系统学习(四)用户态下使用libusb读取鼠标数据

news/2025/2/12 11:21:14/

文章目录

  • 1、声明
  • 2、HID协议
    • 2.1、描述符
    • 2.2、鼠标数据格式
  • 3、应用程序
  • 4、编译应用程序
  • 5、测试
  • 6、其它

1、声明

本文是在学习韦东山《驱动大全》USB子系统时,为梳理知识点和自己回看而记录,全部内容高度复制粘贴。

韦老师的《驱动大全》:商品详情

其对应的讲义资料:https://e.coding.net/weidongshan/linux/doc_and_source_for_drivers.git

libusb api:https://libusb.sourceforge.io/api-1.0/libusb_api.html

2、HID协议

HID:Human Interface Devices, 人类用来跟计算机交互的设备。就是鼠标、键盘、游戏手柄等设备。对于USB接口的HID设备,有一套协议。

2.1、描述符

HID设备有如下描述符:

  • HID设备的"设备描述符"并无实际意义,没有使用"设备描述符"来表示自己是HID设备。
  • HID设备只有一个配置,所以只有一个配置描述符。
  • 接口描述符:
    • bInterfaceClass为3,表示它是HID设备。
    • bInterfaceSubClass是0或1,1表示它支持"Boot Interface"(表示PC的BIOS能识别、使用它),0表示必须等操作系统启动后通过驱动程序来使用它。
    • bInterfaceProtocol:0-None, 1-键盘, 2-鼠标。
  • 端点描述符:HID设备有一个控制端点、一个中断端点。

对于鼠标,HOST可以通过中断端点读到数据。

2.2、鼠标数据格式

通过中断传输可以读到鼠标数据,它是8字节的数据,格式如下:

偏移大小描述
01字节
11字节按键状态
22字节X位移
42字节Y位移
61字节或2字节滚轮

按键状态里,每一位对应鼠标的一个按键,等1时表示对应按键被点击了,格式如下:

长度描述
01鼠标的左键
11鼠标的右键
21鼠标的中间键
35保留,设备自己定义bit3: 鼠标的侧边按键bit4:

X位移、Y位移都是8位的有符号数。对于X位移,负数表示鼠标向左移动,正数表示鼠标向右移动,移动的幅度就使用这个8位数据表示。对于Y位移,负数表示鼠标向上移动,正数表示鼠标向下移动,移动的幅度就使用这个8位数据表示。

3、应用程序

本次应用程序使用的是同步接口读取鼠标数据。

#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <libusb-1.0/libusb.h>int main(int argc, char **argv)
{int err;libusb_device *dev, **devs;int num_devices;int endpoint;int interface_num;int transferred;int count = 0;unsigned char buffer[8];struct libusb_config_descriptor *config_desc;struct libusb_device_handle *dev_handle = NULL;int found = 0;/* libusb init */err = libusb_init(NULL);if (err < 0) {fprintf(stderr, "failed to initialise libusb %d - %s\n", err, libusb_strerror(err));exit(1);}/* get device list */if ((num_devices = libusb_get_device_list(NULL, &devs)) < 0)    // 获取设备描述符列表(函数返回设备描述符数量){fprintf(stderr, "libusb_get_device_list() failed\n");libusb_exit(NULL);exit(1);}   fprintf(stdout, "libusb_get_device_list() ok\n");/* for each device, get config descriptor */for (int i = 0; i < num_devices; i++){dev = devs[i];err = libusb_get_config_descriptor(dev, 0, &config_desc);   // 获取配置描述符if (err) {fprintf(stderr, "could not get configuration descriptor\n");continue;}fprintf(stdout, "libusb_get_config_descriptor() ok\n");/* parse interface descriptor, find usb mouse */for (int interface = 0; interface < config_desc->bNumInterfaces; interface++)   // 枚举所有接口描述符{const struct libusb_interface_descriptor *intf_desc = &config_desc->interface[interface].altsetting[0];     // 获取配置描述符里的第一个接口描述符interface_num = intf_desc->bInterfaceNumber;        // 记录该接口描述符的编号(编号是从0开始)if (intf_desc->bInterfaceClass != 3 || intf_desc->bInterfaceProtocol != 2)  // 判断是否是HID设备和是否是鼠标协议continue;/* 找到了USB鼠标 */fprintf(stdout, "find usb mouse ok\n");for (int ep = 0; ep < intf_desc->bNumEndpoints; ep++)   // 枚举所有端点描述符{// 判断是否是中断传输,是否是输入端点(输入输出都是以USB Host来讨论,所以该端点是USB Device输出到USB Host)if ((intf_desc->endpoint[ep].bmAttributes & 3) == LIBUSB_TRANSFER_TYPE_INTERRUPT || (intf_desc->endpoint[ep].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_IN){/* 找到了输入的中断端点 */fprintf(stdout, "find in int endpoint\n");endpoint = intf_desc->endpoint[ep].bEndpointAddress;found = 1;break;}}if (found)break;}libusb_free_config_descriptor(config_desc);if (found)break; }if (!found){/* free device list */libusb_free_device_list(devs, 1);libusb_exit(NULL);exit(1);}/* libusb open */if (found){err = libusb_open(dev, &dev_handle);if (err){fprintf(stderr, "failed to open usb mouse\n");exit(1);}fprintf(stdout, "libusb_open ok\n");}/* free device list */libusb_free_device_list(devs, 1);/* claim interface */libusb_set_auto_detach_kernel_driver(dev_handle, 1);  err = libusb_claim_interface(dev_handle, interface_num);if (err){fprintf(stderr, "failed to libusb_claim_interface\n");exit(1);}fprintf(stdout, "libusb_claim_interface ok\n");/* libusb interrupt transfer */while (1){err = libusb_interrupt_transfer(dev_handle, endpoint, buffer, 8, &transferred, 5000);		// 发起中断传输,阻塞等待,5s超时时间if (!err) {/* parser data */printf("%04d datas: ", count++);printf("recv datas len = %d\n", transferred);for (int i = 0; i < transferred; i++){printf("%02x ", buffer[i]);}printf("\n");} else if (err == LIBUSB_ERROR_TIMEOUT){fprintf(stderr, "libusb_interrupt_transfer timout\n");} else {const char *errname = libusb_error_name(err);fprintf(stderr, "libusb_interrupt_transfer err : %d, %s\n", err, errname);//exit(1);}}/* libusb close */libusb_release_interface(dev_handle, interface_num);libusb_close(dev_handle);libusb_exit(NULL);
}

4、编译应用程序

假设你的开发板是ubuntu系统:

# 安装libusb
$ sudo apt install libusb-1.0-0-dev# 编译程序
$ gcc -o readmouse readmouse.c -lusb-1.0

5、测试

usb鼠标插入开发板:

执行程序:

$ sudo ./readmouse

移动鼠标:

滚轮滑动:

按键状态:

另外,每个鼠标的数据格式是不一样的。以上测试结果只是我使用的鼠标。

6、其它

以下是使用异步接口读取鼠标数据的测试程序。

#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <libusb-1.0/libusb.h>struct usb_mouse {struct libusb_device_handle *handle;int interface;int endpoint;unsigned char buf[16];int transferred;struct libusb_transfer *transfer;struct usb_mouse *next;
};static struct usb_mouse *usb_mouse_list;void free_usb_mouses(struct usb_mouse *usb_mouse_list)
{struct usb_mouse *pnext;while (usb_mouse_list){pnext = usb_mouse_list->next;free(usb_mouse_list);usb_mouse_list = pnext;}
}int get_usb_mouse(libusb_device **devs, int num_devices, struct usb_mouse **usb_mouse_list)
{int err;libusb_device *dev;int endpoint;int interface_num;struct libusb_config_descriptor *config_desc;struct libusb_device_handle *dev_handle = NULL;struct usb_mouse *pmouse;struct usb_mouse *list = NULL;int mouse_cnt = 0;/* for each device, get config descriptor */for (int i = 0; i < num_devices; i++) {dev = devs[i];/* parse interface descriptor, find usb mouse */        err = libusb_get_config_descriptor(dev, 0, &config_desc);       // 获取配置描述符if (err) {fprintf(stderr, "could not get configuration descriptor\n");continue;}fprintf(stdout, "libusb_get_config_descriptor() ok\n");for (int interface = 0; interface < config_desc->bNumInterfaces; interface++) {     // 枚举所有接口描述符const struct libusb_interface_descriptor *intf_desc = &config_desc->interface[interface].altsetting[0];interface_num = intf_desc->bInterfaceNumber;        // 记录该接口描述符的编号(编号是从0开始)if (intf_desc->bInterfaceClass != 3 || intf_desc->bInterfaceProtocol != 2)      // 判断是否是HID设备和是否是鼠标协议    continue;else{/* 找到了USB鼠标 */fprintf(stdout, "find usb mouse ok\n");for (int ep = 0; ep < intf_desc->bNumEndpoints; ep++)   // 枚举所有端点描述符{// 判断是否是中断传输,是否是输入端点(输入输出都是以USB Host来讨论,所以该端点是USB Device输出到USB Host)if ((intf_desc->endpoint[ep].bmAttributes & 3) == LIBUSB_TRANSFER_TYPE_INTERRUPT ||(intf_desc->endpoint[ep].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_IN) {/* 找到了输入的中断端点 */fprintf(stdout, "find in int endpoint\n");endpoint = intf_desc->endpoint[ep].bEndpointAddress;/* libusb_open */err = libusb_open(dev, &dev_handle);if (err){fprintf(stderr, "failed to open usb mouse\n");return -1;}fprintf(stdout, "libusb_open ok\n");/* 记录下来: 放入链表 */pmouse = malloc(sizeof(struct usb_mouse));if (!pmouse){fprintf(stderr, "can not malloc\n");return -1;}pmouse->endpoint  = endpoint;pmouse->interface = interface_num;pmouse->handle    = dev_handle;pmouse->next      = NULL;if (!list)list = pmouse;else{pmouse->next = list;list = pmouse;}mouse_cnt++;break;}}}}libusb_free_config_descriptor(config_desc);}*usb_mouse_list = list;return mouse_cnt;
}static void mouse_irq(struct libusb_transfer *transfer)
{static int count = 0;if (transfer->status == LIBUSB_TRANSFER_COMPLETED){/* parser data */printf("%04d datas: ", count++);for (int i = 0; i < transfer->actual_length; i++){printf("%02x ", transfer->buffer[i]);}printf("\n");}if (libusb_submit_transfer(transfer) < 0){fprintf(stderr, "libusb_submit_transfer err\n");}
}int main(int argc, char **argv)
{int err;libusb_device **devs;int num_devices, num_mouse;struct usb_mouse *pmouse;/* libusb init */err = libusb_init(NULL);if (err < 0) {fprintf(stderr, "failed to initialise libusb %d - %s\n", err, libusb_strerror(err));exit(1);}/* get device list */if ((num_devices = libusb_get_device_list(NULL, &devs)) < 0)    // 获取设备描述符列表(函数返回设备描述符数量){fprintf(stderr, "libusb_get_device_list() failed\n");libusb_exit(NULL);exit(1);}   fprintf(stdout, "libusb_get_device_list() ok\n");/* get usb mouse */num_mouse = get_usb_mouse(devs, num_devices, &usb_mouse_list);if (num_mouse <= 0){/* free device list */libusb_free_device_list(devs, 1);libusb_exit(NULL);exit(1);}fprintf(stdout, "get %d mouses\n", num_mouse);/* free device list */libusb_free_device_list(devs, 1);/* claim interface */pmouse = usb_mouse_list;while (pmouse){libusb_set_auto_detach_kernel_driver(pmouse->handle, 1);  err = libusb_claim_interface(pmouse->handle, pmouse->interface);if (err){fprintf(stderr, "failed to libusb_claim_interface\n");exit(1);}pmouse = pmouse->next;}fprintf(stdout, "libusb_claim_interface ok\n");/* for each mouse, alloc transfer, fill transfer, submit transfer */pmouse = usb_mouse_list;while (pmouse){/* alloc transfer */pmouse->transfer = libusb_alloc_transfer(0);/* fill transfer */libusb_fill_interrupt_transfer(pmouse->transfer, pmouse->handle, pmouse->endpoint, pmouse->buf,sizeof(pmouse->buf), mouse_irq, pmouse, 0);/* submit transfer */libusb_submit_transfer(pmouse->transfer);pmouse = pmouse->next;}/* handle events */while (1) {struct timeval tv = { 5, 0 };int r;r = libusb_handle_events_timeout(NULL, &tv);if (r < 0) {fprintf(stderr, "libusb_handle_events_timeout err\n");break;}}/* libusb_close */pmouse = usb_mouse_list;while (pmouse){libusb_release_interface(pmouse->handle, pmouse->interface);libusb_close(pmouse->handle);        pmouse = pmouse->next;}free_usb_mouses(usb_mouse_list);libusb_exit(NULL);}

运行程序前,先把多个鼠标插入开发板,然后运行测试程序,移动鼠标,查看打印结果。


http://www.ppmy.cn/news/1571406.html

相关文章

MyBatis-Plus-Join (MPJ) 框架介绍

MyBatis-Plus-Join (MPJ) 框架介绍 MyBatis-Plus-Join (MPJ) 是一个基于 MyBatis-Plus 的多表关联查询框架&#xff0c;它提供了一种优雅的方式来处理复杂的表关联查询。 主要特性 Lambda 式查询 wrapper.selectAs(Teacher::getId, TeacherLeaveVO::getTeacherId).leftJoin(C…

4.python+flask+SQLAlchemy+达梦数据库

前提 1.liunx Centos7上通过docker部署了达梦数据库。从达梦官网下载的docker镜像。(可以参考前面的博文) 2.windows上通过下载x86,win64位的达梦数据库,只安装客户端,不安装服务端。从达梦官网下载达梦数据库windows版。(可以参考前面的博文) 这样就可以用windows的达…

cuda学习资料汇总

一 gemm 1.1 ampere 1.2 hopper 1.2.1 github中的使用cutlass和wgmma cutlass/examples/cute/tutorial/wgmma_sm90.cu at main NVIDIA/cutlass GitHub 1.2.2 TMA (1)TMA cutlass

二、Golang Channel通信和控制题目

要求 采用Golang语言中channel实现协程间的通讯&#xff0c;并通过控制channel控制起输入与输出格式。其中一个协程sender负责发送字符串"ABC"的字符&#xff0c;另一个协程reciever负责接收这些字符并打印。 要求接收方reciever协程能够按照顺序&#xff0c;按照行…

解决 idea 无法创建java8 模版

解决 idea 无法创建java8 模版 由于 Spring 官方不再维护 旧的版本了导致的 https://start.spring.io 解决方式很简单 切换到 阿里源就可以了 https://start.aliyun.com/ 项目生成 成功 总结 &#xff1a;IDEA 内置的 Spring Initializr 创建 Spring Boot 项目实际上是依赖官…

【CubeMX-HAL库】STM32F407—无刷电机学习笔记

目录 简介&#xff1a; 学习资料&#xff1a; 跳转目录&#xff1a; 一、工程创建 二、板载LED 三、用户按键 四、蜂鸣器 1.完整IO控制代码 五、TFT彩屏驱动 六、ADC多通道 1.通道确认 2.CubeMX配置 ①开启对应的ADC通道 ②选择规则组通道 ③开启DMA ④开启ADC…

从零构建高可用MySQL集群:Percona XtraDB Cluster 实战部署

实战指南&#xff1a;基于Percona XtraDB Cluster 构建高可用 MySQL 集群架构 引言&#xff1a;为什么选择PXC&#xff1f; Percona XtraDB Cluster&#xff08;PXC&#xff09;是基于Galera协议的MySQL高可用解决方案&#xff0c;提供同步多主复制、数据强一致性等关键特性&…

【用Deepseek搭建免费的个人知识库--综合教程(完整版)】第二篇:Ollama服务器

用Deepseek搭建免费的个人知识库–综合教程&#xff08;完整版&#xff09;&#xff1a;第二篇&#xff1a;Ollama服务器部署 OLLAMA服务器的配置在很多网上都已经介绍的非常清楚了&#xff0c;我们的重点不在于那些简单的步骤&#xff0c;而是在需要为下一步做准备的地方更加…