文章目录
- 1、声明
- 2、HID协议
- 2.1、描述符
- 2.2、鼠标数据格式
- 3、应用程序
- 4、编译应用程序
- 5、测试
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字节的数据,格式如下:
偏移 | 大小 | 描述 |
---|---|---|
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位数据表示。
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
移动鼠标:
滚轮滑动:
按键状态:
另外,每个鼠标的数据格式是不一样的。以上测试结果只是我使用的鼠标。