使用libusb读取鼠标数据


文章目录

  • 使用libusb读取鼠标数据
    • 1. HID协议
      • 1.1 描述符
      • 1.2 数据格式
        • 1.2.1 键盘
        • 1.2.2 LED
        • 1.2.3 鼠标
        • 1.2.4 扫描码
    • 2. 使用同步接口读取鼠标数据
      • 2.1 编写源码
      • 2.2 上机实验
        • 2.2.1 在Ubuntu上实验
        • 2.2.2 在IMX6ULL开发板上实验
    • 3. 使用异步接口读取鼠标数据
      • 3.1 编写源码
  • 致谢

1. HID协议

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

1.1 描述符

HID设备有如下描述符:

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

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

1.2 数据格式

1.2.1 键盘

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

偏移 大小 描述
0 1字节 “Modifier keys status”,就是ctrl、alt、shift等按键的状态
1 1字节 保留
2 1字节 第1个按键的键值
3 1字节 第2个按键的键值
4 1字节 第3个按键的键值
5 1字节 第4个按键的键值
6 1字节 第5个按键的键值
7 1字节 第6个按键的键值

第0个字节中每一位都表示一个按键的状态,某位等于1时,表示对应的按键被按下,格式如下:

长度 描述
0 1 Left Ctrl
1 1 Left Shift
2 1 Left Alt
3 1 Left GUI(Windows/Super key)
4 1 Right Ctrl
5 1 Right Shift
6 1 Right Alt
7 1 Right GUI(Windows/Super key)

读到的键盘数据里有6个按键值,每个按键值都是8位的数据。如果某个按键值不等于0,就表示某个按键被按下了。
按键值跟按键的对应关系,请看后面的《1.2.4 扫描码》。
示例:按键"A"、“B”、“C”、"X"的按键值分别是4、5、6、0x1B。

按下了"A",USB键盘上报的数据为:

00 00 04 00 00 00 00 00

松开"A",USB键盘上报的数据为:

00 00 00 00 00 00 00 00

按下"A"、“B”,USB键盘上报的数据为:

00 00 04 05 00 00 00 00

保持"A"、“B"不松开,继续按下"C”,USB键盘上报的数据为:

00 00 04 05 06 00 00 00

松开"A",但是保持"B"、"C"不松开,USB键盘上报的数据为:

00 00 05 06 00 00 00 00

USB键盘上报的数据里,哪个按键先被按下,就先记录它的按键值。在上面的例子里,“A"松开后只有"B”、"C"这两个按键,“B”、"C"的按键值挪到了前面。

按下"Left shift"、并且按下"X",USB键盘上报的数据为:

02 00 1B 00 00 00 00 00

USB键盘只能上报6个按键值,如果有超过6个按键被按下,那么它讲上报"phantom condition"(6个按键值都是1),但是"Modifier keys status"还是有效的。比如"Right Shift"被按下,另外超过6个的按键也被按下时,USB键盘上报的数据为:

20 00 01 01 01 01 01 01

1.2.2 LED

我们还可控制键盘的LED,需要发出一个控制传输请求:SetReport ,使用这个请求发送一个字节的数据。

这个字节的数据格式如下,某位为1时,会点亮相应的LED:

长度 描述
0 1 Num Lock
1 1 Caps Lock
2 1 Scroll Lock
3 1 Compose
4 1 Kana
5 1 保留,写为0

发出的SetReport,是一个控制传输的"setup packet",格式如下:

以libusb的函数描述它的参数,如下:

int LIBUSB_CALL libusb_control_transfer(libusb_device_handle *dev_handle,uint8_t request_type, uint8_t bRequest, uint16_t wValue, uint16_t wIndex,unsigned char *data, uint16_t wLength, unsigned int timeout);/* 示例代码 */
unsigned char data = (1<<1); /* 点亮Caps Lock */
uint16_t wValue = (0x02<<8)|0; // 0x02: 发给设备, 0: report ID
uint16_t wIndex = 0; // 一般是0, the interface number of the USB keyboard
libusb_control_transfer(dev_handle, 0x21, 0x09, wValue, wIndex, &data, 1, timeout);

1.2.3 鼠标

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

偏移 大小 描述
0 1字节
1 1字节 按键状态
2 2字节 X位移
4 2字节 Y位移
6 1字节或2字节 滚轮

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

长度 描述
0 1 鼠标的左键
1 1 鼠标的右键
2 1 鼠标的中间键
3 5 保留,设备自己定义
bit3: 鼠标的侧边按键
bit4:

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

1.2.4 扫描码

USB规范里为每个按键定义了16位的按键值,注意:它是16位的,但是USB键盘只使用8位表示按键值。所以有些按键需要通过"Modifier keys status"来确定。比如"Left Ctrl"的按键值是224,这无法通过8位数据来表示,在USB键盘上报的数据里,使用第0字节的bit4来表示。

2. 使用同步接口读取鼠标数据

2.1 编写源码

#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 found = 0;int transferred;int count = 0;unsigned char buffer[16];struct libusb_config_descriptor *config_desc;struct libusb_device_handle *dev_handle = NULL;/* 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];/* 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;if (intf_desc->bInterfaceClass != 3 || intf_desc->bInterfaceProtocol != 2)continue;else{/* 找到了USB鼠标 */fprintf(stdout, "find usb mouse ok\n");for (int ep = 0; ep < intf_desc->bNumEndpoints; ep++){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);}if (found){/* libusb_open */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, 16, &transferred, 5000);if (!err) {/* parser data */printf("%04d datas: ", count++);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 {fprintf(stderr, "libusb_interrupt_transfer err : %d\n", err);//exit(1);}}/* libusb_close */libusb_release_interface(dev_handle, interface_num);libusb_close(dev_handle);libusb_exit(NULL);
}

2.2 上机实验

2.2.1 在Ubuntu上实验

// 1. 安装开发包
$ sudo apt install libusb-1.0-0-dev// 2. 修改源码,包含libusb.h 头文件时用如下代码
#include <libusb-1.0/libusb.h>// 3. 编译程序指定库
gcc -o readmouse readmouse.c -lusb-1.0

2.2.2 在IMX6ULL开发板上实验

  • 交叉编译libusb

    sudo apt-get install libtoolunzip libusb-1.0.26.zip
    cd libusb-1.0.26
    ./autogen.sh./configure --host=arm-buildroot-linux-gnueabihf --prefix=$PWD/tmpmakemake installls tmp/
    include  lib
  • 安装库、头文件到工具链的目录里

    libusb-1.0.26/tmp/lib$ cp * -rfd /home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/usr/lib/libusb-1.0.26/tmp/include$ cp libusb-1.0 -rf /home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/usr/include/
    
  • 交叉编译app

    arm-buildroot-linux-gnueabihf-gcc -o  readmouse.c -lusb-1.0
    
  • 在开发板上插入USB鼠标,执行命令

    ./readmouse
    

3. 使用异步接口读取鼠标数据

3.1 编写源码

#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_mouses(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;if (intf_desc->bInterfaceClass != 3 || intf_desc->bInterfaceProtocol != 2)continue;else{/* 找到了USB鼠标 */fprintf(stdout, "find usb mouse ok\n");for (int ep = 0; ep < intf_desc->bNumEndpoints; ep++){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_mouses(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);
}

致谢

以上笔记源自韦东山老师的视频课程,感谢韦老师,韦老师是嵌入式培训界一股清流,为嵌入式linux开发点起的星星之火,也愿韦老师桃李满园。聚是一团火,散是满天星!

在这样一个速食的时代,坚持做自己,慢下来,潜心琢磨,心怀敬畏,领悟知识,才能向下扎到根,向上捅破天,背着世界往前行!
仅此向嵌入行业里的每一个认真做技术的从业者致敬!


使用libusb读取鼠标数据相关推荐

  1. python读数据-如何用Python读取开放数据?

    当你开始接触丰富多彩的开放数据集时,CSV.JSON和XML等格式名词就会奔涌而来.如何用Python高效地读取它们,为后续的整理和分析做准备呢?本文为你一步步展示过程,你自己也可以动手实践. 需求 ...

  2. 树莓派 rfid_树莓派工控机做Modbus RTU主站读取RFID数据

    KUNBUS Revpi Core 3是工业级的树莓派,可作为小型的工业PC用,外观十分小巧,操作简单,DIN导轨模块化安装,RevPi core 3能与RevPi IO连接,能实时对这些IO的控制. ...

  3. 如何在QT中读取串口数据

    总是能在别人的博客中学到太多太多,谢谢各位对知识的无私共享,谢谢大家 前言 去年我使用Qt编写串口通信程序时,将自己的学习过程写成了教程(Qt编写串口通信程序全程图文讲解),但是由于时间等原因,我只实 ...

  4. stm32读取驾驶模拟器数据 stm32F407读取joystick数据

    需求 实习工作,老板要求用单片机读取驾驶模拟器(Joystick)返回的数据,驾驶模拟器usb输出,输出信息包括:方向盘转角.左右拨杆.按键等. 硬件 采用正点原子探索者开发板,即插即用,硬件不需要改 ...

  5. 【TensorFlow学习笔记】完美解决 pip3 install tensorflow 没有models库,读取PTB数据

    安装tensorflow 我使用的是最最最简单的容易的 pip3 install <TensorFlow学习笔记> 一. 安装win10下python3.6的tensorflow的CPU版 ...

  6. [Rx86OS-IX] 解读鼠标数据 移动鼠标

    平台 处理器:Intel Celeron(R) Dual-Core CPU 操作系统:Windows7 专业版 x86 阅读书籍:<30天自制操作系统>-川合秀实[2015.03.23 ] ...

  7. TensorFlow csv读取文件数据(代码实现)

    TensorFlow csv读取文件数据(代码实现) 大多数人了解 Pandas 及其在处理大数据文件方面的实用性.TensorFlow 提供了读取这种文件的方法. 前面章节中,介绍了如何在 Tens ...

  8. SharePoint2010沙盒解决方案基础开发——关于TreeView树形控件读取列表数据(树形导航)的webpart开发及问题...

    转:http://blog.csdn.net/miragesky2049/article/details/7204882 SharePoint2010沙盒解决方案基础开发--关于TreeView树形控 ...

  9. Kinect V1读取图像数据(For Windows)

    Kinect V1读取图像数据(For Windows) 这篇博客 Kinect V1介绍 数据读取的基本流程 运行代码和注释 结尾 这篇博客  刚好有一台现成的Kinect V1相机,所以就拿过来学 ...

最新文章

  1. Windows下更改MySQL 数据库文件存放位置
  2. WPF ListView DoubleClick
  3. Struts2的Action和Servlet有什么联系,区别?
  4. Spring集成Junit步骤和代码实现
  5. 如何在Hexo中实现自适应响应式相册功能
  6. linux 启动nacos报错_Spring Cloud:Alibaba 之 Nacos
  7. 大数据_Spark_VS_Hadoop_框架---Spark工作笔记0002
  8. Matlab imfilter函数
  9. Ice_cream's world I( 并查集 + 判环 )
  10. 深度学习这些年那些超重要的idea回顾总结
  11. 力扣 2104. 子数组范围和
  12. 通过google插件Thumbnails实现图片指定大小压缩
  13. C语言课程设计-满分作业
  14. 干货来袭!java怎么创建包和类
  15. 少儿图形编程语言哪个最好
  16. 「软件测试4」一文详解四大典型的白盒测试方法
  17. 微软做好了放弃Flash Player的准备
  18. Redis之地理坐标
  19. 交换机的vlan划分和VLAN Trunk技术
  20. 牛客网:接雨水的双指针问题

热门文章

  1. 树莓派3b+安装ubuntu 16.04+ROS kinetic过程详解及踩坑总结
  2. AMBA总线—APB总线协议详解
  3. 南卡NANK Runner CC3 耳机评测:骨传导耳机入门级别最强款
  4. [C]数字炸弹小游戏
  5. C++中的内存管理、内存泄漏和内存回收
  6. Supervisor守护Java进程_使用Supervisor来守护我们的服务
  7. mysql icp特性_MySQL:关于ICP特性的说明(未完)
  8. 深圳首辆数字人民币主题观光巴士亮相
  9. 第七次网页前端培训(JavaScript)
  10. java.lang.IllegalArgumentException的问题解决