USB驱动(1)——libusb获取鼠标输入数据及代码解读

1、简介

        学习usb驱动的难度比i2c、spi等又上升了一个台阶。因为它包含了太多内容。需要设置设备、配置、接口、端点四类描述符(usb_device_descriptorusb_config_descriptorusb_interface_descriptorusb_endpoint_descriptor),它们之间的关系可以简单概括为下图。每个设备有不同的工作模式,不同的工作模式就可以理解为对应不同的配置。每种工作配置下,又可以又很多种usb接口设备(鼠标、键盘等等)同时工作。每个接口通过其下面的不同端点与主设备通信。端点又分为输入端点、输出端点、控制端点等等。

        每类结构体下又有很多字段需要配置,以上还仅是usb驱动的一部分内容。USB驱动内容多到难以入手,看别人的驱动源码也不知所云。因此从应用层入手usb驱动未尝不是个选择。

2、libusb

        libusb是一个开源的、跨平台的库,用于与USB设备进行交互。它提供了对USB硬件的底层访问,允许用户空间的应用程序与USB设备通信,而不需要编写内核驱动程序。        

        libusb库的安装方式很简单:

sudo apt-get install libusb-1.0-0-dev
sudo apt-get install libusb-dev

        使用时引用#include<libusb-1.0/libusb.h>头文件即可 。编译姿势(这里我是在内核为linux-5.10.66的开发板上编译):

aarch64-linux-gnu-gcc -o usb_test usb_test.c -lusb-1.0

        再从网上粘一份代码下来测试(07_使用libusb读取鼠标数据 — Linux设备驱动开发教程中心 (100ask.net)):

#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);
}

        能够正常接收鼠标发上来的数据:

3、代码解读 

        下面我将用浅薄的知识对代码进行解读

        首先是每个变量各代表什么意思。

    int err;    //函数返回值
    libusb_device *dev, **devs;     /*dev是单指针,用于引用单个USB设备
                                      devs是双指针,用于引用多个USB设备*/
    int num_devices;    //存储libusb_get_device_list函数返回的设备数量,即系统中连接了多少个USB设备
    int endpoint;       //存储USB设备中端点的地址,每个端点都有唯一一个地址
    int interface_num;  //存储USB设备中接口的编号,每个接口都有唯一一个编号
    int found = 0;      //判断有没有找到一个鼠标设备
    int transferred;    //存储实际传输的数据量,以字节为单位
    int count = 0;      //统计接收的消息数量
    unsigned char buffer[16];   //存储USB设备上发的数据
    struct libusb_config_descriptor *config_desc;   //存储USB设备的配置信息
    struct libusb_device_handle *dev_handle = NULL; //存储打开的USB设备的句柄

        接下来使用libusb_init(NULL)初始化libusb,创建供libusb使用的“上下文”。这个“上下文”包含了libusb库内部使用的全局数据,用于管理库的内部状态,包括设备列表、事件处理等。参数若为“NULL”表示由libusb库创建一个默认的“上下文”。

        再通过libusb_get_device_list(NULL,&devs)获得连接到主机的usb设备。其包含两个参数,第一个参数是指向“上下文”的指针,此处为NULL表示使用默认的“上下文”。第二个参数是指向libusb_device的指针地址,用于存储libusb库返回的设备列表。

    /* libusb_init */
    err = libusb_init(NULL);//初始化,创建一个供libusb使用的默认“上下文”
    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) {  //获取连接到计算机的USB设备列表
        fprintf(stderr, "libusb_get_device_list() failed\n");
        libusb_exit(NULL);
        exit(1);
    }
    fprintf(stdout, "libusb_get_device_list() ok\n");

        获得usb设备列表后,就在这个列表当中寻找有无“鼠标设备”。 首先使用libusb_get_config_descriptor(dev, 0, &config_desc)获取USB设备的配置描述符。第一个参数dev为指向libusb_device结构的指针,表示要获取配置描述符的USB设备。第二个参数指定要获取哪个配置的描述符,大多数设备通常只有一个配置,因此这个参数通常为0。第三个参数指向libusb_config_descriptor结构,用于存储获取到的配置描述符。

        获取设备的配置描述符后,使用config_desc->bNumInterfaces遍历配置下的所有接口。通过&config_desc->interface[interface].altsetting[0]获得接口描述符。通过判断bInterfaceClass值是否等于3,以及bInterfaceProtocol是否等于2来判断此接口是否为鼠标。

    /* 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++) { //遍历配置描述符中的所有接口描述符,寻找符合USB鼠标特征的接口
            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)  //USB鼠标属于HID类,bInterfaceClass为3,bInterfaceProtocol为2
                continue;
            else
            {
                /* 找到了USB鼠标 */
                fprintf(stdout, "find usb mouse ok\n"); //找到了USB鼠标对应的接口
                for (int ep = 0; ep < intf_desc->bNumEndpoints; ep++)   //对于找到的接口,遍历所有端点,寻找输入中断端点
                {                                                       //bmAttributes低2位为LIBUSB_TRANSFER_TYPE_INTERRUPT,bEndpointAddress最高位为1即LIBUSB_ENDPOINT_IN
                    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;        
    }

bInterfaceClass值不同,就对应不同种类的设备,下面是不同值和设备的对应关系。

#define USB_DEVICE_CLASS_RESERVED               0x00    //保留类,不用于特定类别的设备
#define USB_DEVICE_CLASS_AUDIO                  0x01    //音频类设备,如:麦克风、扬声器等
#define USB_DEVICE_CLASS_COMMUNICATIONS         0x02    //通信设备,如:调制解调器、网络适配器
#define USB_DEVICE_CLASS_HUMAN_INTERFACE        0x03    //人机接口设备,如:键盘、鼠标等。
#define USB_DEVICE_CLASS_MONITOR                0x04    //监视设备,如:显示器
#define USB_DEVICE_CLASS_PHYSICAL_INTERFACE     0x05    //物理接口设备,如:读卡器
#define USB_DEVICE_CLASS_POWER                  0x06    //电源设备,通常用于电源管理
#define USB_DEVICE_CLASS_IMAGE                  0x06    //图像设备,如扫描仪、相机等
#define USB_DEVICE_CLASS_PRINTER                0x07    //打印设备,如:打印机
#define USB_DEVICE_CLASS_STORAGE                0x08    //存储设备,如硬盘驱动器,USB闪存
#define USB_DEVICE_CLASS_HUB                    0x09    //集线器设备,用于连接多个USB
#define USB_DEVICE_CLASS_CDC_DATA               0x0A    //CDC数据设备,通常用于网络设备
#define USB_DEVICE_CLASS_SMART_CARD             0x0B    //智能卡设备
#define USB_DEVICE_CLASS_CONTENT_SECURITY       0x0D    //内容安全设备,用于数字版权管理
#define USB_DEVICE_CLASS_VIDEO                  0x0E    //视频设备,如摄像头、视频监控设备
#define USB_DEVICE_CLASS_PERSONAL_HEALTHCARE    0x0F    //个人医疗保健设备
#define USB_DEVICE_CLASS_AUDIO_VIDEO            0x10    //音频、视频设备
#define USB_DEVICE_CLASS_BILLBOARD              0x11    //广告牌设备,用于显示信息
#define USB_DEVICE_CLASS_DIAGNOSTIC_DEVICE      0xDC    //诊断设备
#define USB_DEVICE_CLASS_WIRELESS_CONTROLLER    0xE0    //无线控制器
#define USB_DEVICE_CLASS_MISCELLANEOUS          0xEF    //杂项设备,用于不属于其他类别的设备
#define USB_DEVICE_CLASS_APPLICATION_SPECIFIC   0xFE    //特定应用程序设备
#define USB_DEVICE_CLASS_VENDOR_SPECIFIC        0xFF    //供应商特定设备

bInterfaceProtocol是USB设备的接口描述符中的一个字段,它用于指定特定的接口的通信协议。通常与bInterfaceClass一起使用,以唯一标识设备上的一个接口。

        随后根据设备查找结果进行错误处理和资源管理。

    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);  //自动分离与设备关联的内核驱动程序,以便libusb可以控制设备
    err = libusb_claim_interface(dev_handle, interface_num);//声明设备的接口,以便libusb可以独占访问
    if (err)
    {
        fprintf(stderr, "failed to libusb_claim_interface\n");
        exit(1);
    }
    fprintf(stdout, "libusb_claim_interface ok\n");

        随后使用libusb库进行USB中断传输。中断传输使用函数libusb_interrupt_transfer(dev_handle, endpoint, buffer, 16, &transferred, 5000);第一个参数dev_handle为设备句柄,用于标识要进行通信的设备。第二个参数endpoint为端点地址,指定从哪个端点进行数据传输。第三个参数buffer为数据缓冲区,用于存储设备接收到的数据。第四个参数为要传输数据的最大长度。第五个参transferred为指向整数变量的指针,用于存储实际传输的字节数。第六个参数为超时时间,以毫秒为单位。

        如果接收没出错就把接收到的数据打印出来。

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值