使用 libusb 读取鼠标数据
来源:嵌入式Linux系统开发 发布时间:2023-06-18 分享至微信

参考资料:

韦东山老师驱动大全

libusb API 接口:https://libusb.sourceforge.io/api-1.0/
libusb 示例:https://github.com/libusb/libusb/tree/master/examples
HID 规范:https://www.usb.org/sites/default/files/hid1_11.pdf
文档:USB Human Interface Devices, https://wiki.osdev.org/USB_Human_Interface_Devices

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 字节的数据,格式如下:

偏移大小描述
01字节"Modifier keys status",就是ctrl、alt、shift等按键的状态
11字节保留
21字节第1个按键的键值
31字节第2个按键的键值
41字节第3个按键的键值
51字节第4个按键的键值
61字节第5个按键的键值
71字节第6个按键的键值

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

长度描述
01Left Ctrl
11Left Shift
21Left Alt
31Left GUI(Windows/Super key)
41Right Ctrl
51Right Shift
61Right Alt
71Right GUI(Windows/Super key)

读到的键盘数据里有 6 个按键值,每个按键值都是 8 位的数据。如果某个按键值不等于 0,就表示某个按键被按下了。按键值跟按键的对应关系,请看后面的《1.2.4 扫描码》。

示例:按键"A"、"B"、"C"、"X"的按键值分别是 4、5、6、0x1B。

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

0000040000000000

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

0000000000000000

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

0000040500000000

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

0000040506000000

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

0000050600000000

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

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

02001B0000000000

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

2000010101010101

1.2.2 LED

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

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

长度描述
01Num Lock
11Caps Lock
21Scroll Lock
31Compose
41Kana
51保留,写为0

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

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

intLIBUSB_CALLlibusb_control_transfer(libusb_device_handle*dev_handle,
uint8_trequest_type,uint8_tbRequest,uint16_twValue,uint16_twIndex,
unsignedchar*data,uint16_twLength,unsignedinttimeout)
;

/*示例代码*/
unsignedchardata=(1<<1);/*点亮CapsLock*/
uint16_twValue=(0x02<<8)|0;//0x02:发给设备,0:reportID
uint16_twIndex=0;//一般是0,theinterfacenumberoftheUSBkeyboard
libusb_control_transfer(dev_handle,0x21,0x09,wValue,wIndex,data,1,timeout);

1.2.3 鼠标

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

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

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

长度描述
01鼠标的左键
11鼠标的右键
21鼠标的中间键
35保留,设备自己定义
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 来表示。

libusb 有同步接口和异步接口,异步接口可以同时支持多个鼠标使用。

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

2.1 编写源码

#include<errno.h>
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#include<libusb-1.0/libusb.h>

intmain(intargc,char**argv)
{
interr;
libusb_device*dev,**devs;
intnum_devices;
intendpoint;
intinterface_num;
intfound=0;
inttransferred;
intcount=0;
unsignedcharbuffer[16];
structlibusb_config_descriptor*config_desc;
structlibusb_device_handle*dev_handle=NULL;

/*libusb_init*/
err=libusb_init(NULL);
if(err<0){
fprintf(stderr,"failedtoinitialiselibusb%d-%s\n",err,libusb_strerror(err));
exit(1);
}

/*getdevicelist*/
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");

/*foreachdevice,getconfigdescriptor*/
for(inti=0;i<num_devices;i++){
dev=devs[i];

/*parseinterfacedescriptor,findusbmouse*/
err=libusb_get_config_descriptor(dev,0,config_desc);
if(err){
fprintf(stderr,"couldnotgetconfigurationdescriptor\n");
continue;
}
fprintf(stdout,"libusb_get_config_descriptor()ok\n");

for(intinterface=0;interface<config_desc->bNumInterfaces;interface++){
conststructlibusb_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,"findusbmouseok\n");
for(intep=0;ep<intf_desc->bNumEndpoints;ep++)
{
if((intf_desc->endpoint[ep].bmAttributes3)==LIBUSB_TRANSFER_TYPE_INTERRUPT||
(intf_desc->endpoint[ep].bEndpointAddress0x80)==LIBUSB_ENDPOINT_IN){
/*找到了输入的中断端点*/
fprintf(stdout,"findinintendpoint\n");
endpoint=intf_desc->endpoint[ep].bEndpointAddress;
found=1;
break;
}

}
}

if(found)
break;
}

libusb_free_config_descriptor(config_desc);

if(found)
break;
}

if(!found)
{
/*freedevicelist*/
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,"failedtoopenusbmouse\n");
exit(1);
}
fprintf(stdout,"libusb_openok\n");
}

/*freedevicelist*/
libusb_free_device_list(devs,1);

/*claiminterface*/
libusb_set_auto_detach_kernel_driver(dev_handle,1);
err=libusb_claim_interface(dev_handle,interface_num);
if(err)
{
fprintf(stderr,"failedtolibusb_claim_interface\n");
exit(1);
}
fprintf(stdout,"libusb_claim_interfaceok\n");

/*libusb_interrupt_transfer*/
while(1)
{
err=libusb_interrupt_transfer(dev_handle,endpoint,buffer,16,transferred,5000);
if(!err){
/*parserdata*/
printf("%04ddatas:",count++);
for(inti=0;i<transferred;i++)
{
printf("%02x",buffer[i]);
}
printf("\n");
}elseif(err==LIBUSB_ERROR_TIMEOUT){
fprintf(stderr,"libusb_interrupt_transfertimout\n");
}else{
fprintf(stderr,"libusb_interrupt_transfererr:%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.安装开发包
$sudoaptinstalllibusb-1.0-0-dev

//2.修改源码,包含libusb.h头文件时用如下代码
#include<libusb-1.0/libusb.h>

//3.编译程序指定库
gcc-oreadmousereadmouse.c-lusb-1.0

2.2.2 在 IMX6ULL 开发板上实验

  • 交叉编译 libusb

    sudoapt-getinstalllibtool

    unziplibusb-1.0.26.zip
    cdlibusb-1.0.26
    ./autogen.sh

    ./configure--host=arm-buildroot-linux-gnueabihf--prefix=$PWD/tmp

    make

    makeinstall

    lstmp/
    includelib
  • 安装库、头文件到工具链的目录里

    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$cplibusb-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-oreadmouse.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>

structusb_mouse{
structlibusb_device_handle*handle;
intinterface;
intendpoint;
unsignedcharbuf[16];
inttransferred;
structlibusb_transfer*transfer;
structusb_mouse*next;
};

staticstructusb_mouse*usb_mouse_list;


voidfree_usb_mouses(structusb_mouse*usb_mouse_list)
{
structusb_mouse*pnext;
while(usb_mouse_list)
{
pnext=usb_mouse_list->next;
free(usb_mouse_list);
usb_mouse_list=pnext;
}
}


/**/
intget_usb_mouses(libusb_device**devs,intnum_devices,structusb_mouse**usb_mouse_list)
{
interr;
libusb_device*dev;
intendpoint;
intinterface_num;
structlibusb_config_descriptor*config_desc;
structlibusb_device_handle*dev_handle=NULL;
structusb_mouse*pmouse;
structusb_mouse*list=NULL;
intmouse_cnt=0;

/*foreachdevice,getconfigdescriptor*/
for(inti=0;i<num_devices;i++){
dev=devs[i];

/*parseinterfacedescriptor,findusbmouse*/
err=libusb_get_config_descriptor(dev,0,config_desc);
if(err){
fprintf(stderr,"couldnotgetconfigurationdescriptor\n");
continue;
}
fprintf(stdout,"libusb_get_config_descriptor()ok\n");

for(intinterface=0;interface<config_desc->bNumInterfaces;interface++){
conststructlibusb_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,"findusbmouseok\n");
for(intep=0;ep<intf_desc->bNumEndpoints;ep++)
{
if((intf_desc->endpoint[ep].bmAttributes3)==LIBUSB_TRANSFER_TYPE_INTERRUPT||
(intf_desc->endpoint[ep].bEndpointAddress0x80)==LIBUSB_ENDPOINT_IN){
/*找到了输入的中断端点*/
fprintf(stdout,"findinintendpoint\n");
endpoint=intf_desc->endpoint[ep].bEndpointAddress;

/*libusb_open*/
err=libusb_open(dev,dev_handle);
if(err)
{
fprintf(stderr,"failedtoopenusbmouse\n");
return-1;
}
fprintf(stdout,"libusb_openok\n");

/*记录下来:放入链表*/
pmouse=malloc(sizeof(structusb_mouse));
if(!pmouse)
{
fprintf(stderr,"cannotmalloc\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;
returnmouse_cnt;
}


staticvoidmouse_irq(structlibusb_transfer*transfer)
{
staticintcount=0;
if(transfer->status==LIBUSB_TRANSFER_COMPLETED)
{
/*parserdata*/
printf("%04ddatas:",count++);
for(inti=0;i<transfer->actual_length;i++)
{
printf("%02x",transfer->buffer[i]);
}
printf("\n");

}

if(libusb_submit_transfer(transfer)<0)
{
fprintf(stderr,"libusb_submit_transfererr\n");
}
}

intmain(intargc,char**argv)
{
interr;
libusb_device**devs;
intnum_devices,num_mouse;
structusb_mouse*pmouse;

/*libusb_init*/

err=libusb_init(NULL);
if(err<0){
fprintf(stderr,"failedtoinitialiselibusb%d-%s\n",err,libusb_strerror(err));
exit(1);
}

/*getdevicelist*/
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");

/*getusbmouse*/
num_mouse=get_usb_mouses(devs,num_devices,usb_mouse_list);

if(num_mouse<=0)
{
/*freedevicelist*/
libusb_free_device_list(devs,1);
libusb_exit(NULL);
exit(1);
}
fprintf(stdout,"get%dmouses\n",num_mouse);

/*freedevicelist*/
libusb_free_device_list(devs,1);

/*claiminterface*/
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,"failedtolibusb_claim_interface\n");
exit(1);
}
pmouse=pmouse->next;
}
fprintf(stdout,"libusb_claim_interfaceok\n");

/*foreachmouse,alloctransfer,filltransfer,submittransfer*/
pmouse=usb_mouse_list;
while(pmouse)
{
/*alloctransfer*/
pmouse->transfer=libusb_alloc_transfer(0);

/*filltransfer*/
libusb_fill_interrupt_transfer(pmouse->transfer,pmouse->handle,pmouse->endpoint,pmouse->buf,
sizeof(pmouse->buf),mouse_irq,pmouse,0);

/*submittransfer*/
libusb_submit_transfer(pmouse->transfer);

pmouse=pmouse->next;
}

/*handleevents*/
while(1){
structtimevaltv={5,0};
intr;

r=libusb_handle_events_timeout(NULL,tv);
if(r<0){
fprintf(stderr,"libusb_handle_events_timeouterr\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系统开发,更多精彩资讯请下载icspec App。如对本稿件有异议,请联系微信客服specltkj]
存入云盘 收藏
举报
全部评论

暂无评论哦,快来评论一下吧!