找回密码
 立即注册
首页 业界区 安全 Linux USB应用开发学习笔记

Linux USB应用开发学习笔记

更成痒 6 天前
三、USB应用编程

经过前面内容的学习,我们学习了 USB 的基础知识,接下来我们将探讨如何借助 USB 总线实现对 USB 外设的操作。与之前 I2C SPI 操作外设不同,usb应用采用主要是使用 libusb 库,在这章中,主要对libusb库的使用进行学习。
3.1 libusb库简介

libusb 是一个使用 C 编写的库,它提供 USB 设备的通用访问方法。APP 通过它,可以方便地访问 USB 设备,不需要编写 USB 设备驱动程序。libusb 库有三个特点:
1、可移植性好,支持 Linux,macOS,Windows 系统。
2、简单易用,APP 不需要特权模式,也不需要提升自己权限即可访问 USB 设备
3、支持所有的 USB 协议,从 USB1.0 到 USB3.1,并且 API 接口保持不变,方便开发。
3.2 libusb编译与测试

我们可以先从github上下载libusb库,在终端中输入如下命令
  1. wzy@wzy-JiaoLong:~$ git clone https://github.com/libusb/libusb.git  
  2. Cloning into 'libusb'...  
  3. remote: Enumerating objects: 18197, done.  
  4. remote: Counting objects: 100% (31/31), done.  
  5. remote: Compressing objects: 100% (24/24), done.  
  6. remote: Total 18197 (delta 11), reused 7 (delta 7), pack-reused 18166 (from 2)  
  7. Receiving objects: 100% (18197/18197), 5.51 MiB | 4.23 MiB/s, done.  
  8. Resolving deltas: 100% (13031/13031), done.  
  9. wzy@wzy-JiaoLong:~$
复制代码
进入到libusb源码文件夹中,安装对应的依赖包准备编译
  1. sudo apt install autoconf automake libtool libudev-dev m4
复制代码
接着输入以下命令进行编译和安装
  1. ./bootstrap.sh
  2. ./autogen.sh
  3. make
  4. sudo make install
复制代码
执行完之后,在libusb源文件下会生产一些中间文件
1.png

并且make install之后,会将编译好的库放到/usr/local/lib目录下
2.png

同时在源码文件夹examples路径下执行的脚本会自动将一些例程自动编译
3.png

可以运行其中的listdevs可执行文件来显示系统当前的USB设备信息,包括VID、PID、端口号等
  1. wzy@wzy-JiaoLong:~/softpack/libusb/examples$ ./listdevs    
  2. 026d:0002 (bus 5, device 6) path: 1.4  
  3. 048d:8910 (bus 5, device 5) path: 1.3  
  4. 1bcf:28c4 (bus 5, device 4) path: 1.2  
  5. 13d3:3585 (bus 5, device 3) path: 1.1  
  6. 05e3:0610 (bus 5, device 2) path: 1  
  7. 1d6b:0002 (bus 5, device 1)  
  8. 0bda:9210 (bus 4, device 2) path: 1  
  9. 1d6b:0003 (bus 4, device 1)  
  10. 1d6b:0002 (bus 3, device 1)  
  11. 1d6b:0003 (bus 2, device 1)  
  12. 24ae:1861 (bus 1, device 2) path: 2  
  13. 1d6b:0002 (bus 1, device 1)  
  14. wzy@wzy-JiaoLong:~/softpack/libusb/examples$
复制代码
上述的libusb的测试是在x86的环境下进行的测试,在实际的嵌入式开发中,我们通常会需要在嵌入式平台上使用libusb库,这时我们就需要对libusb库进行交叉编译,先在刚刚编译的libusb文件夹中执行make clean清除刚刚的编译结果,再执行configure脚本,配置编译环境,并且指定相应的目标平台
  1. ./configure --host=aarch64-linux-gnu --disable-udev --prefix=$PWD/install CC=/home/wzy/sdk/rk3576_linux6.1.99_release-rkr5/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-20  
  2. 21.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc CXX=/home/wzy/sdk/rk3576_linux6.1.99_release-rkr5/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aa  
  3. rch64-none-linux-gnu-g++
复制代码
通过configure脚本指定完host、prefix、CC和CXX变量之后,就可以直接执行make命令进行编译了,再通过make instal进行安装,libusb库会安装到我们指定的prefix路径下面来
4.png

我们也可以通过file命令查看当前库的平台信息,可以看见已经成功编译到了aarch64平台下面了
5.png

这些库编译完成之后,我们直接通过adb push命令将库文件和头文件拷贝到开发板中
6.png

这里需要注意的是,既可以将动态库拷贝到/lib路径下,也可以拷贝到/usr/lib路径下,优先会使用/lib路径下的libusb动态库,如果存在的话,如果配套开发板的/lib路径下有动态库了,可能会出现和自己编译时版本不匹配的情况,可以直接进行替换。接着拷贝头文件
7.png

这里需要注意的是,拷贝头文件的时候,直接将.h文件拷贝到/usr/include目录下,不要带libusb-1.0这一层路径
接着我们交叉编译示例程序,切换到examples路径下,使用交叉工具编译listdevs.c源文件,命令如下
  1. wzy@wzy-JiaoLong:~/softpack/libusb/examples$ /home/wzy/sdk/rk3576_linux6.1.99_release-rkr5/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc  
  2. listdevs.c -o listdevs -L/home/wzy/softpack/libusb/install/lib -I/home/wzy/softpack/libusb/install/include/libusb-1.0 -lusb-1.0 -lpthread
复制代码
可以看见可执行程序成功编译出来
8.png

接着将该可执行二进制文件拷贝到开发板上,直接运行,发现可执行文件报错,这是因为lisusb库的版本不一致导致的
9.png

这里遇到的这个坑,是因为开发板中动态库与我们交叉编译可执行文件使用的动态库版本不同,导致函数声明差异,这里是通过ldd命令查看了程序连接动态库的路径,发现是在/lib/aarch64-linux-gnu路径下,导致libusb动态库的版本一直不对,一直报错,所以我们需要将编译出来的动态库文件替换到这个路径下才行
  1. root@linaro-alip:/# ldd ./listdevs  
  2.        linux-vdso.so.1 (0x0000007f9ad93000)  
  3.        libusb-1.0.so.0 => /lib/aarch64-linux-gnu/libusb-1.0.so.0 (0x0000007f9ad00000)  
  4.        libpthread.so.0 => /lib/aarch64-linux-gnu/libpthread.so.0 (0x0000007f9acd0000)  
  5.        libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x0000007f9ab20000)  
  6.        libudev.so.1 => /lib/aarch64-linux-gnu/libudev.so.1 (0x0000007f9aad0000)  
  7.        /lib/ld-linux-aarch64.so.1 (0x0000007f9ad56000)root@linaro-alip:/# ldd ./listdevs  
  8.        linux-vdso.so.1 (0x0000007f9ad93000)  
  9.        libusb-1.0.so.0 => /lib/aarch64-linux-gnu/libusb-1.0.so.0 (0x0000007f9ad00000)  
  10.        libpthread.so.0 => /lib/aarch64-linux-gnu/libpthread.so.0 (0x0000007f9acd0000)  
  11.        libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x0000007f9ab20000)  
  12.        libudev.so.1 => /lib/aarch64-linux-gnu/libudev.so.1 (0x0000007f9aad0000)  
  13.        /lib/ld-linux-aarch64.so.1 (0x0000007f9ad56000)
复制代码
可以看到,这里连接的动态库是存放在/lib/aarch64-linux-gnu路径下的,所以需要替换这里的动态库,替换完成之后重新执行,可执行文件成功执行,我们的libusb动态库与示例程序也就移植完成了,接下里可以开发自己的应用程序通过相同的流程部署到嵌入式开发板上
10.png

3.3 libusb库应用程序编写

在前面的章节中讲解 libusb 库的安装和使用,交叉编译了上层应用 demo 程序,并运行在开发板上。本章节将参考之前的 demo 程序,学习 libusb 库应用程序的编写。在前面的章节中讲解 libusb 库的安装和使用,交叉编译了上层应用 demo 程序,并运行在开发板上。本章节将参考之前的 demo 程序,学习 libusb 库应用程序的编写。
3.3.1 libusb库常用API接口介绍

1.libusb_init
该函数用于初始化libusb库
  1. int libusb_init(libusb_context **ctx);
复制代码
参数:

  • libusb_context **ctx:上下文指针,可为 NULL 使用默认上下文。
    返回值:
  • 成功返回0,失败返回负数
2.libusb_get_device_list
该函数用于获取当前连接的USB设备列表
  1. ssize_t libusb_get_device_list(libusb_context *ctx, libusb_device ***list)
复制代码
参数:

  • libusb_context ctx:上下文指针
  • libusb_device ***list:输出设备指针数组
    返回值:
  • 成功时返回设备数量,负值表示错误。
3.libusb_open_device_with_vid_pid
该函数用于获取当前连接的USB设备列表
  1. libusb_device_handle *libusb_open_device_with_vid_pid(libusb_context *ctx, uint16_t vid, uint16_t pid)
复制代码
参数:

  • libusb_context *ctx:上下文指针
  • libusb_device ***list:输出设备指针数组
    返回值:
  • 成功时返回设备数量,负值表示错误。
4.libusb_open_device_with_vid_pid
该函数用来根据idVendor和idProduct的值打开对应的USB设备。
  1. libusb_device_handle *libusb_open_device_with_vid_pid(libusb_context *ctx, uint16_t vid, uint16_t pid)
复制代码
参数:

  • libusb_context *ctx:上下文指针
  • uint16_t vid:要搜索的idVendor值
  • uint16_t pid:要搜索的 idProduct 值
    返回值:
  • 成功返回设备句柄(libusb_device_handle *),失败返回 NULL。
5.libusb_open
该函数用来通过 libusb_device指针打开一个usb设备,并返回一个libusb_device_handle句柄
  1. int libusb_open(libusb_device *dev, libusb_device_handle **dev_handle);
复制代码
参数:

  • libusb_device *dev:要打开的 USB 设备指针。
  • libusb_device_handle **dev_handle:返回的设备句柄的指针。
返回值:

  • 成功时返回 0,失败时返回负数,表示具体的错误码。
6.libusb_bulk_transfer
该函数用来实现USB的批量传输
  1. int libusb_bulk_transfer(
  2.         libusb_device_handle *dev_handle,
  3.         unsigned char endpoint,
  4.         unsigned char *data,
  5.         int length,
  6.         int *actual_length,
  7.         unsigned int timeout
  8. );
复制代码
参数:

  • libusb_device_handle **dev_handle:指向设备句柄的指针。
  • unsigned char endpoint:端点地址,最高位为 1 表示输入。
  • unsigned char *data:发送或接收缓冲区指针。
  • int length:缓冲区长度。
  • int *actual_length:实际传输长度。
  • unsigned int timeout:超时的毫秒数,0 表示永不超时
返回值:

  • 成功时返回 0,失败时返回负数,表示具体的错误码。
7.libusb_interrupt_transfer 函数
该函数用来实现USB的中断传输
  1. int libusb_interrupt_transfer(
  2.         libusb_device_handle *dev_handle,
  3.         unsigned char endpoint,
  4.         unsigned char *data,
  5.         int length,
  6.         int *actual_length,
  7.         unsigned int timeout
  8. );
复制代码
参数:

  • libusb_device_handle dev_handle:指向设备句柄的指针
  • unsigned char endpoint:端点地址,最高位为 1 表示输入
  • unsigned char *data:发送或接收缓冲区指针。
  • int length:缓冲区长度。
  • int *actual_length:实际传输长度。
  • unsigned int timeout:超时的毫秒数,0 表示永不超时。
返回值:

  • 成功时返回 0,失败时返回负数,表示具体的错误码。
8.libusb_get_device_descriptor 函数
该函数用于获取USB设备描述符。
  1. int libusb_get_device_descriptor(
  2.         libusb_device *dev,
  3.         struct libusb_device_descriptor *desc
  4. );
复制代码
参数:

  • libusb_device *dev:要读取的设备。
  • struct libusb_device_descriptor *desc:获取到的设备描述符
返回值:

  • 成功时返回 0,失败时返回负数,表示具体的错误码。
9.libusb_get_config_descriptor 函数
该函数用于根据索引值获取配置描述符。
  1. int libusb_get_config_descriptor(
  2.         libusb_device *dev,
  3.         uint8_t config_index,
  4.         struct libusb_config_descriptor **config
  5. );
复制代码
参数:

  • libusb_device *dev:要读取的设备。
  • uint8_t config_index:配置描述符的索引(一个 USB 设备可能有多个配置)。
  • struct libusb_config_descriptor **config:获取到的配置描述符
返回值:

  • 成功时返回 0,失败时返回负数,表示具体的错误码。
10. libusb_free_device_list 函数
该函数用于释放设备列表。
  1. void libusb_free_device_list(
  2.         libusb_device **list,
  3.         int unref_devices
  4. );
复制代码
参数:

  • libusb_device **list:要释放的设备列表。
  • int unref_devices:如果设置成 1,则设备的引用计数-1。
返回值:

  • 成功时返回 0,失败时返回负数,表示具体的错误码。
11.libusb_free_config_descriptor函数
该函数用于释放配置描述符。
  1. void libusb_free_config_descriptor(
  2. struct libusb_config_descriptor *config
  3. );
复制代码
参数:

  • struct libusb_config_descriptor *config:要释放的配置描述符。
    返回值:
  • 成功时返回 0,失败时返回负数,表示具体的错误码。
12 .libusb_set_auto_detach_kernel_driver函数
该函数用于解决“内核驱动占用 USB 设备”的问题,让程序员可以直接用 libusb 操作设备,并且自动处理 detach / attach,针对USB设备的接口而言不是针对某一个USB设备
  1. int libusb_set_auto_detach_kernel_driver(
  2.         libusb_device_handle *dev_handle,
  3.         int enable
  4. );
复制代码
参数:

  • libusb_device_handle *dev_handle:已打开的 USB 设备句柄。
  • int enable:启用(1)或禁用(0)自动分离功能。
    返回值:
  • 成功时返回 0,失败时返回负数,表示具体的错误码。
举例:
  1. //打开一个usb设备
  2. libusb_open(dev, &handle);
  3. //自动内核分离
  4. libusb_set_auto_detach_kernel_driver(handle, 1);
  5. //如果申请的接口已经被内核占用,会自动分离
  6. libusb_claim_interface(handle, 0);
复制代码
13.libusb_claim_interface
该函数用来给设备申请接口。
  1. int libusb_claim_interface(
  2.         libusb_device_handle *dev_handle,
  3.         int interface_number
  4. );
复制代码
参数:

  • libusb_device_handle *dev_handle:已打开的 USB 设备句柄。
  • int interface_number:要声明的接口编号(从 0 开始),对应接口描述符的 bInterfaceNumber 成员。
    返回值:
  • 成功时返回 0,失败时返回负数,表示具体的错误码。
14.libusb_alloc_transfer
该函数用于分配一个用于异步传输的结构体,即 libusb_transfer 结构体。
  1. struct libusb_transfer *libusb_alloc_transfer( int iso_packets);
复制代码
参数:

  • int iso_packets:等时传输包数量(非等时传输设为 0)。
    返回值:
  • 成功时返回 struct libusb_transfer *结构体指针,失败时返回负数,表示具体的错误码。
15.libusb_fill_interrupt_transfer
该函数用于配置一个异步中断传输对象。
  1. void libusb_fill_interrupt_transfer(
  2.         struct libusb_transfer *transfer,
  3.         libusb_device_handle *dev_handle,
  4.         unsigned char endpoint,
  5.         unsigned char *buffer,
  6.         int length,
  7.         libusb_transfer_cb_fn callback,
  8.         void *user_data,
  9.         unsigned int timeout
  10. );
复制代码
参数:

  • struct libusb_transfer *transfer:已分配的传输对象(需先通过libusb_alloc_transfer 分配)。
  • libusb_device_handle *dev_handle: 已打开的 USB 设备句柄。
  • unsigned char endpoint:端点地址。
  • unsigned char *buffer:数据缓冲区指针。
  • int length:缓冲区长度(字节数)
  • libusb_transfer_cb_fn callback:传输完成时的回调函数。
  • void *user_data:用户自定义数据(会传递给回调函数)。
  • unsigned int timeout:超时时间(毫秒,0 表示不超时),单位 ms。
    返回值:
  • void
16.libusb_submit_transfer
该函数用于提交一个异步传输请求,传输完成或者出错时候调用回调函数进行处理。
  1. int libusb_submit_transfer(struct libusb_transfer *transfer);
复制代码
参数:

  • struct libusb_transfer *transfer:已通过 libusb_fill_interrupt_transfer 配置的传输对象。
    返回值:
  • 成功返回 0,失败返回负数。
17.libusb_cancel_transfer
该函数用于取消已提交但未完成的异步 USB 传输。
  1. int libusb_submit_transfer(struct libusb_transfer *transfer);
复制代码
参数:

  • struct libusb_transfer *transfer:要取消的传输对象指针。
    返回值:
  • 成功返回 0,失败返回负数。
18.libusb_free_transfer
该函数用于释放传输结构。
  1. void libusb_free_transfer(struct libusb_transfer *transfer);
复制代码
参数:

  • struct libusb_transfer *transfer:要取消的传输对象指针。
    返回值:
  • 成功返回 0,失败返回负数。
19.libusb_handle_events
该函数用于在阻塞模式下处理任何待处理事件。
  1. int libusb_handle_events(libusb_context *ctx);
复制代码
参数:

  • libusb_context *ctx:USB上下文
    返回值:
  • 成功返回 0,失败返回负数。
3.3.2 USB设备枚举应用程序实验

接下来实现一个基于libusb库的USB设备枚举程序,主要功能是扫描并列出当前系统中所有已连接的USB设备的基本信息。
  1. #include <stdio.h>
  2. #include "libusb.h"
  3. int main(void)
  4. {
  5.         int ret; // 函数返回值存储
  6.         int cnt; // 设备计数
  7.         libusb_device **device = NULL; // 设备列表指针
  8.         libusb_device *dev; // 单个设备指针
  9.         int i = 0; // 循环计数器
  10.        
  11.         // 初始化libusb库
  12.         ret = libusb_init(NULL);
  13.         if (ret < 0) {
  14.                 printf("libusb_init error \n");
  15.                 return ret;
  16.         }
  17.        
  18.         // 获取USB设备列表
  19.         cnt = libusb_get_device_list(NULL, &device);
  20.         if (cnt < 0) {
  21.                 printf("libusb_get_device_list error \n");
  22.                 return cnt;
  23.         }
  24.        
  25.         // 遍历设备列表
  26.         while (dev = device[i++]) {
  27.                 struct libusb_device_descriptor desc; // 设备描述符
  28.                 // 获取设备描述符
  29.                 ret = libusb_get_device_descriptor(dev, &desc);
  30.                 if (ret < 0) {
  31.                         printf("libusb_get_device_descriptor error \n");
  32.                         return ret;
  33.                 }
  34.                 // 打印厂商ID和产品ID
  35.                 printf("%04x : %04x\n", desc.idVendor, desc.idProduct);
  36.         }
  37.        
  38.         // 释放设备列表(unref=1表示减少引用计数)
  39.         libusb_free_device_list(device, 1);
  40.         // 释放 libusb 库占用的所有资源
  41.         libusb_exit(NULL);
  42.         return 0;
  43. }
复制代码
使用如下命令编译程序,再将可执行文件拷贝到开发板上执行
  1. wzy@wzy-JiaoLong:~/Downloads$ /home/wzy/sdk/rk3576_linux6.1.99_release-rkr5/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc usb1.  
  2. c -o usb1 -I/home/wzy/softpack/libusb/install/include/libusb-1.0 -L/home/wzy/softpack/libusb/install/lib -lusb-1.0 -lpthread
复制代码
得到正确结果
  1. root@linaro-alip:/# ./usb1    
  2. 05e3 : 0626  
  3. 1d6b : 0003  
  4. 2c7c : 0125  
  5. 1a86 : 8091  
  6. 05e3 : 0610  
  7. 1d6b : 0002  
  8. root@linaro-alip:/#v
复制代码
3.3.3 USB HID键盘设备检测程序实验
  1. #include <stdio.h>
  2. #include "libusb.h"
  3. int main(void)
  4. {
  5.         // 变量声明区
  6.         int ret; // 存储函数返回值
  7.         int cnt; // 设备计数器
  8.         libusb_device **device = NULL; // USB设备列表指针
  9.         libusb_device *dev; // 单个USB设备指针
  10.         int i = 0, k = 0, j = 0; // 循环计数器
  11.         int endpoint; // 端点地址
  12.         int flag = 0; // 标志位
  13.         // 描述符结构体
  14.         struct libusb_config_descriptor *config_desc = NULL; // USB配置描述符
  15.         const struct libusb_interface_descriptor *interface_descriptor = NULL; // 接口描述符
  16.         libusb_device_handle *dev_handle = NULL; // USB设备句柄
  17.         // 数据缓冲区
  18.         unsigned char buffer[16]; // 数据接收缓冲区
  19.         int transferred; // 实际传输字节数
  20.         // 初始化libusb库
  21.         ret = libusb_init(NULL);
  22.         if (ret < 0) {
  23.                 printf("libusb初始化失败\n");
  24.                 return ret;
  25.         }
  26.         // 获取USB设备列表
  27.         cnt = libusb_get_device_list(NULL, &device);
  28.         if (cnt < 0) {
  29.                 printf("获取设备列表失败\n");
  30.                 return cnt;
  31.         }
  32.         // 遍历设备列表查找HID设备
  33.         while (dev = device[i++]) {
  34.                 // 获取设备配置描述符
  35.                 ret = libusb_get_config_descriptor(dev, 0, &config_desc);
  36.                 if (ret < 0) {
  37.                         printf("获取配置描述符失败\n");
  38.                         return ret;
  39.                 }
  40.                 // 遍历所有接口
  41.                 for (k = 0; k < config_desc->bNumInterfaces; k++) {
  42.                         interface_descriptor = &config_desc->interface[k].altsetting[0];
  43.                         // 查找HID类设备(Class=3),3为HID人机交互设备且协议为键盘(Protocol=1),在USB协议中,是固定的规定
  44.                         if (interface_descriptor->bInterfaceClass == 3 && interface_descriptor->bInterfaceProtocol == 1) {
  45.                         // 遍历端点查找中断输入端点
  46.                                 for (j = 0; j < interface_descriptor->bNumEndpoints; j++) {
  47.                                         if ((interface_descriptor->endpoint[j].bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_INTERRUPT && (interface_descriptor->endpoint[j].bmAttributes & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN) {
  48.                                                 endpoint = interface_descriptor->endpoint[j].bEndpointAddress;
  49.                                                 flag = 1; // 找到目标设备
  50.                                                 break;
  51.                                         }
  52.                                 }
  53.                         }
  54.                         if (flag) break;
  55.                 }
  56.                 if (flag) break;
  57.         }
  58.         // 打开目标设备
  59.         ret = libusb_open(dev, &dev_handle);
  60.         if (ret < 0) {
  61.                 printf("打开设备失败\n");
  62.                 return ret;
  63.         }
  64.         // 设置自动卸载内核驱动
  65.         libusb_set_auto_detach_kernel_driver(dev_handle, 1);
  66.         // 声明接口,一般和libusb_set_auto_detach_kernel_driver函数搭配使用
  67.         libusb_claim_interface(dev_handle, interface_descriptor->bInterfaceNumber);
  68.        
  69.         // 主循环:持续读取键盘数据
  70.         while (1) {
  71.         // 中断传输读取键盘数据
  72.                 ret = libusb_interrupt_transfer(dev_handle, endpoint, buffer, 16, &transferred, 5000);
  73.                 if (ret >= 0) { // 读取成功
  74.                 // 打印接收到的键盘数据
  75.                         for (i = 0; i < transferred; i++) {
  76.                                 printf("%02x ", buffer[i]);
  77.                         }
  78.                         printf("\n");
  79.                 }
  80.         }
  81.         // 资源释放
  82.         libusb_free_config_descriptor(config_desc); // 释放配置描述符
  83.         libusb_free_device_list(device, 1); // 释放设备列表
  84.         libusb_exit(NULL); // 释放libusb资源
  85.         return 0;
  86. }
复制代码
交叉编译之后可执行程序拷贝到开发板上,执行之后插入键盘,按下按键后可以看见打印出了键盘的按键信息
11.png

上面的版本是使用的libusb_interrupt_transfer接口函数接收USB数据,这种方式是一种阻塞同步的方式,当前线程会完全停住,接收到数据之后才会继续执行代码,这种方式性能差,拓展性差,下面对接收方式进行优化,使用异步方式来处理,主要使用到以下四个API函数
  1. libusb_alloc_transfer()
  2. libusb_fill_interrupt_transfer()
  3. libusb_submit_transfer()
  4. libusb_handle_events()
复制代码
完整优化代码如下
  1. #include <stdio.h>
  2. #include "libusb.h"
  3. // 全局变量声明
  4. struct libusb_transfer *keyboard_transfer = NULL; // 异步传输控制块
  5. unsigned char buffer[16]; // 键盘数据接收缓冲区
  6. int ret; // 函数返回值存储
  7. // 异步传输回调函数
  8. void callback_recv(struct libusb_transfer *transfer) {
  9.         int k;
  10.         if (transfer->status == LIBUSB_TRANSFER_COMPLETED) {
  11.                 if (transfer->actual_length > 0) {
  12.                         // 打印接收到的键盘数据(16进制)
  13.                         for (k = 0; k < transfer->actual_length; k++) {
  14.                                 printf("%02x ", buffer[k]);
  15.                         }
  16.                         printf("\n");
  17.                 }
  18.         }
  19.         // 重新提交传输请求(实现持续监听)
  20.         ret = libusb_submit_transfer(keyboard_transfer);
  21.         if (ret < 0) {
  22.                 libusb_cancel_transfer(keyboard_transfer);
  23.                 libusb_free_transfer(keyboard_transfer);
  24.         }
  25. }
  26. int main(void) {
  27.         // 设备枚举相关变量
  28.         int cnt; // 设备数量计数器
  29.         libusb_device **device = NULL; // USB设备列表
  30.         libusb_device *dev = NULL; // 当前设备指针
  31.         int i = 0, k = 0, j = 0; // 循环计数器
  32.         int endpoint; // 端点地址
  33.         int flag = 0; // 设备查找标志
  34.        
  35.         // USB描述符指针
  36.         struct libusb_config_descriptor *config_desc = NULL; // 配置描述符
  37.         const struct libusb_interface_descriptor *interface_descriptor = NULL; // 接口描述符
  38.         libusb_device_handle *dev_handle = NULL; // 设备操作句柄
  39.         // 初始化libusb库
  40.         ret = libusb_init(NULL);
  41.         if (ret < 0) {
  42.                 printf("libusb初始化失败\n");
  43.                 return ret;
  44.         }
  45.         // 获取USB设备列表
  46.         cnt = libusb_get_device_list(NULL, &device);
  47.         if (cnt < 0) {
  48.                 printf("获取设备列表失败\n");
  49.                 return cnt;
  50.         }
  51.         // 遍历设备查找HID键盘
  52.         while ((dev = device[i++])) {
  53.                 // 获取默认配置描述符
  54.                 ret = libusb_get_config_descriptor(dev, 0, &config_desc);
  55.                 if (ret < 0) {
  56.                         printf("获取配置描述符失败\n");
  57.                         return ret;
  58.                 }
  59.                 // 遍历所有接口
  60.                 for (k = 0; k < config_desc->bNumInterfaces; k++) {
  61.                 interface_descriptor = &config_desc->interface[k].altsetting[0];
  62.                 // 检查是否为HID键盘设备(Class=3, Protocol=1)
  63.                 if (interface_descriptor->bInterfaceClass == 3 &&
  64.                 interface_descriptor->bInterfaceProtocol == 1) {
  65.                                 // 查找中断输入端点
  66.                                 for (j = 0; j < interface_descriptor->bNumEndpoints; j++) {
  67.                                         if ((interface_descriptor->endpoint[j].bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_INTERRUPT &&
  68.                                         (interface_descriptor->endpoint[j].bmAttributes & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN)
  69.                                         {
  70.                                                 endpoint = interface_descriptor->endpoint[j].bEndpointAddress;
  71.                                                 flag = 1; // 标记已找到目标设备
  72.                                                 break;
  73.                                         }
  74.                                 }
  75.                         }
  76.                         if (flag) break;
  77.                 }
  78.                 if (flag) break;
  79.         }
  80.         // 打开找到的键盘设备
  81.         ret = libusb_open(dev, &dev_handle);
  82.         if (ret < 0) {
  83.                 printf("打开设备失败\n");
  84.                 return ret;
  85.         }
  86.        
  87.         // 设置自动卸载内核驱动(避免冲突)
  88.         libusb_set_auto_detach_kernel_driver(dev_handle, 1);
  89.         // 声明使用该接口
  90.         libusb_claim_interface(dev_handle, interface_descriptor->bInterfaceNumber);
  91.        
  92.         // 配置异步传输
  93.         keyboard_transfer = libusb_alloc_transfer(0); // 分配传输控制块
  94.         libusb_fill_interrupt_transfer( // 填充中断传输参数
  95.                 keyboard_transfer,
  96.                 dev_handle,
  97.                 endpoint,
  98.                 buffer,
  99.                 16,
  100.                 callback_recv,
  101.                 NULL, 5000);
  102.                
  103.         // 提交异步传输请求
  104.         ret = libusb_submit_transfer(keyboard_transfer);
  105.         if (ret < 0) {
  106.                 libusb_cancel_transfer(keyboard_transfer);
  107.                 libusb_free_transfer(keyboard_transfer);
  108.                 return ret;
  109.         }
  110.         // 主事件循环(处理USB事件)
  111.         while (1) {
  112.                 libusb_handle_events(NULL);
  113.         }
  114.        
  115.         // 资源清理(实际不会执行到此处)
  116.         libusb_free_config_descriptor(config_desc);
  117.         libusb_free_device_list(device, 1);
  118.         libusb_exit(NULL);
  119.         return 0;
  120. }
复制代码
这种异步的方法可以理解为
1、预提交一个或多个 USB 传输任务
2、然后不断轮询事件
3、在事件循环中处理完成,如果任务完成,就执行回调函数

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册