参考资料:
韦东山老师驱动大全
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 设备,有一套协议。
HID 设备有如下描述符:
HID 设备的"设备描述符"并无实际意义,没有使用"设备描述符"来表示自己是 HID 设备。 HID 设备只有一个配置,所以只有一个配置描述符 接口描述符 bInterfaceClass 为 3,表示它是 HID 设备 bInterfaceSubClass 是 0 或 1,1 表示它支持"Boot Interface"(表示 PC 的 BIOS 能识别、使用它),0 表示必须等操作系统启动后通过驱动程序来使用它。 bInterfaceProtocol:0-None, 1-键盘, 2-鼠标 端点描述符:HID 设备有一个控制端点、一个中断端点
对于鼠标,HOST 可以通过中断端点读到数据。
通过中断传输可以读到键盘数据,它是 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 键盘上报的数据为:
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
我们还可控制键盘的 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 的函数描述它的参数,如下:
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 字节的数据,格式如下:
偏移 | 大小 | 描述 |
---|---|---|
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 位数据表示。
USB 规范里为每个按键定义了 16 位的按键值,注意:它是 16 位的,但是 USB 键盘只使用 8 位表示按键值。所以有些按键需要通过"Modifier keys status"来确定。比如"Left Ctrl"的按键值是 224,这无法通过 8 位数据来表示,在 USB 键盘上报的数据里,使用第 0 字节的 bit4 来表示。
libusb 有同步接口和异步接口,异步接口可以同时支持多个鼠标使用。
2. 使用同步接口读取鼠标数据
#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);
}
//1.安装开发包
$sudoaptinstalllibusb-1.0-0-dev
//2.修改源码,包含libusb.h头文件时用如下代码
#include<libusb-1.0/libusb.h>
//3.编译程序指定库
gcc-oreadmousereadmouse.c-lusb-1.0
交叉编译 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. 使用异步接口读取鼠标数据
#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);
}
暂无评论哦,快来评论一下吧!