Linux应用编程(五)USB应用开发-libusb库

ops/2025/1/21 18:28:56/

       

一、基础知识 

1. USB接口是什么?

        USB接口(Universal Serial Bus)是一种通用串行总线,广泛使用的接口标准,主要用于连接计算机与外围设备(如键盘、鼠标、打印机、存储设备等)之间的数据传输和电力供应。它旨在简化计算机与外部设备之间的连接方式,同时提供更高的传输速度和更好的兼容性。

         在1990年代初,计算机外围设备通常使用专用接口,如鼠标和键盘常见的PS/2接口,打印机使用的LPT接口(并口)等。这些接口虽然在当时各自具有一定的专用性和高效性,但也存在很多问题,随着技术的发展,通用性更强的接口逐渐成为主流,USB(Universal Serial Bus)便是这一发展潮流的代表。 

        1996年,由Intel微软康柏DECIBMNEC北方电信公司等七家业界巨头组成的非盈利组织USB标准化组织USB Implementers Forum,简称USB-IF)开始推动USB(Universal Serial Bus)接口的标准化工作。USB的诞生标志着计算机外围设备连接方式的革命性转变。

USB 1.0/1.1最早的版本,传输速度为12 Mbps。
USB 2.0

提供更高的传输速度(最高480 Mbps),广泛应用于各种设备。

USB 3.0/3.1提供更高的数据传输速率,最高可达5 Gbps及以上,支持更大的电流供电。
USB 4.0最新的USB版本,支持更高的传输速率,最高可达40 Gbps,并且兼容Thunderbolt 3协议。

2. USB命名规则

        在2013年,USB 3.0USB 3.1推出后,USB实现了更高的传输速度和更多功能,但随着技术的进步和市场的需求,USB-IF(USB Implementers Forum)为了更清晰地区分不同的版本和性能,进行了命名上的调整。

第一次命名改变:

                USB 3.0 改名为 USB 3.1 Gen 1

                USB 3.1 改名为 USB 3.1 Gen 2

第二次命名改变:

                USB 3.1 Gen1 = USB 3.2 Gen1
                USB 3.1 Gen2 = USB 3.2 Gen2
                USB 3.2 = USB 3.2 Gen2x2

        其本质上就是, USB 3.0 就是 USB 3.2 Gen1 ,USB 3.1是USB 3.2 Gen2,USB 3.2是USB 3.2 Gen2x2。

3. USB HOST和USB OTG

        USB Host(主设备):是指具备控制和管理USB总线的设备。它负责控制所有USB连接的设备,发起数据传输,并管理设备间的通信。USB Host的核心功能是控制和调度USB数据的流向。典型的USB Host设备包括:计算机(台式机、笔记本电脑等)、打印机(具有USB接口的)等。在USB通信中,Host设备通过发送命令来请求从设备发送或接收数据。Host设备管理USB总线上的电源供给和数据传输。

        USB Slave(从设备):是指依赖于主设备控制的设备。USB从设备由主设备管理,它没有独立的数据传输能力,所有的数据传输都必须经过主设备发起或协调。USB从设备通常是外设设备,例如:键盘、鼠标、U盘等。

        USB OTG(On-The-Go):是一项功能,使得USB设备在需要时可以切换角色,既可以作为主设备,也可以作为从设备,从而直接与其他USB设备进行通信。USB OTG标准通常适用于移动设备,如智能手机、平板电脑等。它允许用户无需通过传统的主设备(如计算机)即可实现设备间的直接连接和数据交换。例如,一台支持OTG的手机可以在连接U盘时充当主设备,而在连接键盘时又可以充当从设备。

4. USB集线器(hub)

        USB Hub(集线器)是一种设备,允许多个USB设备通过一个USB端口与计算机进行连接。它实际上是一个多端口的USB接口扩展器,可以将计算机的一个USB端口扩展为多个端口,方便用户连接多个USB设备。 集线器是为了扩展更多的接口。但是并不是可以无休止的可以扩展,USB2.0协议中最多扩展七层。每一层所有设备相加不能超过127个(包括集线器) 。

        所有从机都必须经过集线器(hub)才能与主机连接,也就是设备不能直接和主机相连接。根集线器:与主机直接连接的集线器叫做根集线器,用户外接的叫做普通集线器。

下图的集线器就是普通集线器。

下图中集线器就是根集线器, 例如U盘的插入就是连接到根集线器上。

     

5. USB描述符

        描述符通常是一种数据结构,在 Linux里面就是一个结构体,使用结构体来描述当前的设备有哪些特征,用于描述某个对象或设备的属性和特征。在USB和其他通信协议中,描述符扮演着重要的角色,它们包含了设备的基本信息,如设备类别、供应商ID、产品ID等,这些信息对于主机来说至关重要,因为主机需要依靠这些信息来识别和管理连接的设备。例如下述内容。

/* USB Device Descriptor */
struct usb_device_descriptor {uint8_t  bLength;              // 描述符的长度(以字节为单位),对于设备描述符来说总是18字节uint8_t  bDescriptorType;      // 描述符类型,对于设备描述符来说总是0x01uint16_t bcdUSB;               // USB规范版本号,以二进制编码的十进制数表示uint8_t  bDeviceClass;         // 设备类别代码uint8_t  bDeviceSubClass;      // 设备子类代码uint8_t  bDeviceProtocol;      // 设备协议代码uint8_t  bMaxPacketSize0;      // 端点0的最大数据包大小(以字节为单位)uint16_t idVendor;             // 供应商IDuint16_t idProduct;            // 产品IDuint16_t bcdDevice;            // 设备版本号,以二进制编码的十进制数表示uint8_t  iManufacturer;        // 供应商字符串描述符的索引uint8_t  iProduct;             // 产品字符串描述符的索引uint8_t  iSerialNumber;        // 序列号字符串描述符的索引uint8_t  bNumConfigurations;   // 支持的配置数量
} __attribute__((packed));

        USB 描述符主要有:设备描述符,配置描述符,接口描述符,端点描述符。这些描述符共同构建了USB设备的特征和功能,通过读取这些描述符,系统可以配置驱动和参数,使得操作系统可以正确识别设备和通信。

(1)设备描述符:是USB设备中最重要的描述符之一,它包含了设备的基本信息,如设备类型、供应商ID、产品ID、设备版本等。这个描述符通常位于设备的EEPROM或闪存中,并在设备枚举过程中被主机读取。通过读取设备描述符,主机可以了解设备的基本属性,并决定如何与设备进行通信。设备描述符是设备连接到主机时第一个被请求和返回的信息。它提供了设备的本特征。

(2)配置描述符:描述设备支持的不同配置。

(3)接口描述符:描述了配置中的一个接口。

(4)端点描述符:描述接口中的一个端点。端点是数据在设备和主机之间传输的终点。一个具体的端点只能属于四种传输模式中的一种。

注意:
        一个USB设备有1个设备描述符。
        一个USB设备有1个或多个配置描述符。
        一个USB配置有1个或多个接口描述符。
        一个USB接口有0个或多个点描述符(不括端点0)。

6. 传输模式

        数据通过USB总线传输,通常是通过端点(Endpoints)进行单向或双向的传输。USB使用一种层次化的协议栈,有四种传输模式:批量传输、中断传输、实时传输、控制传输。

(1)批量传输:适用于大数据量传输,且对实时性要求不高。主要用于传输不需要定时保证的数据,传输时没有时间限制,可以在空闲带宽下传输数据。常见应用:U盘、外部硬盘等。

(2)中断传输:用于传输少量、具有时间限制的数据,主机定期请求数据,传输间隔由设备和主机协商。实时性要求较高,但数据量较小,且数据传输频率是固定的。常见应用:鼠标、键盘、游戏手柄等输入设备。

(3)实时传输:适用于需要固定传输速率的场景,实时性要求高,但允许一定程度的丢包或误差。一般用于音频、视频等流式数据传输,确保数据能够以固定速率传输,尽管可能会丢失一些数据。常见应用:音视频设备、USB音响、摄像头等。

(4)控制传输(Control Transfer):用于设备配置、查询和命令的传输,数据量较小,且是双向传输。常用于 USB 设备的初始化和命令交换,传输的内容通常较为简单。常见应用:设备配置、读取设备状态、设置设备功能等。控制传输分为三个阶段:①建立阶段。②数据阶段。③确认阶段。

7. USB数据格式

        US8通信数据格式由域、包、事务、传输组成。多个域组成包,多个包组成事务,多个事务组成传输。所以域是 USB 数据传输中最小的单位。事务是最基本的单位,对于我们开发人员来说关注事务和传输就可以了。

(1)域(Domain)

  • USB 数据传输中的最小单位。每个域有特定的作用和格式,可以包含一个或多个字段。
  • 七种类型的域
    • 同步域(SYNC):用于同步传输时钟,确保数据传输的时序性。
    • 地址域(ADDR):标识 USB 设备的地址,通常用于指定数据的目标设备。
    • 端点域(ENDPT):指定数据的目标端点,用于区分设备的不同数据流。
    • 帧号域(FRAME):用于标识当前传输的帧号,在多帧传输中起到区分作用。
    • 标识域(ID):用于标识不同的传输或者数据包。
    • 数据域(Data):实际的数据内容区域,是传输的核心部分。
    • 校验域(CRC):包含用于检验数据完整性的校验码,确保数据传输过程中没有错误。

(2)包(Packet)

        包是由多个域组成的数据单元。根据包的不同类型,域的结构会有所不同。USB 包有四种主要类型:

令牌包(Token Packet):用于向设备发送请求或命令,包含地址和端点等信息。

数据包(Data Packet):用于传输实际的数据,包含数据域和校验域。

握手包(Handshake Packet):用于确认数据传输的结果,常见的有 ACK(确认)和 NAK(未确认)等。

特殊包(Special Packet):用于特殊的控制或管理操作,例如 RESET 或 SOF(帧开始信号)。

        

        在 USB 中,PID 代表 Packet ID,即 数据包标识符。它用于标识 USB 数据包的类型,并且是在令牌包(Token Packet)和握手包(Handshake Packet)中必不可少的字段。常见的 PID 类型:

  • 令牌包(Token Packet)

    • OUT:用于主机向设备传输数据。
    • IN:用于设备向主机传输数据。
    • SETUP:用于设置设备的控制传输。
  • 数据包(Data Packet)

    • DATA0:表示数据包的第一个数据包,常用于控制传输。
    • DATA1:表示数据包的第二个数据包。
  • 握手包(Handshake Packet)

    • ACK:确认数据包传输成功。
    • NAK:表示数据包传输失败。
    • STALL:表示设备在处理数据时发生错误,无法完成请求。
  • 特殊包(Special Packet)

    • PRE:预留包(用于扩展等特殊用途)。

(3)事务(Transaction)

  • 事务USB 数据传输的基本单位,是由令牌包、数据包和握手包组成的。事务是传输过程中最小的可操作单元。
  • 事务的结构
    • 令牌包(Token Packet):发起事务,标识目标设备、端点等信息。
    • 数据包(Data Packet):可选,用于传输实际的数据。
    • 握手包(Handshake Packet):可选,用于确认数据传输是否成功。
  • 对开发者而言,事务是关注的重点,因为它直接影响数据的传输成功与否。

(4)传输(Transfer)

  • 传输是更高层次的概念,通常由多个事务组成,多个事务之间在时间上有一定的顺序。
  • 一个完整的传输可能涉及多个包、事务和域,最终确保数据从主机到设备(或反向)成功传送。

举例:

8. USB枚举

        USB枚举(USB Enumeration)是指USB设备连接到计算机时,操作系统对该设备进行识别和配置的过程。这个过程包括一系列步骤,确保计算机能够正确识别设备、安装必要的驱动程序并为设备分配资源。

        在USB枚举过程中主要使用控制传输模式。通常用于设备和主机之间的命令和状态交换。它的工作流程分为三个阶段:建立阶段(Setup)数据阶段(Data)确认阶段(Status)

(1)建立阶段。

        由USB主机发起,该阶段使用的是一个SETUP数据包,其结构包含了请求的命令、参数、设备的目标等信息。通过SETUP数据包,主机向设备发送命令,或者设备准备接收或发送数据。

        如果是输入请求(例如获取设备信息),则设备会准备好数据供主机接收。如果是输出请求(例如配置设备),则设备会准备好数据接收主机发出的命令。

(2)数据阶段。

        如果建立阶段是输入请求,则数据阶段由设备向主机传输数据。如果建立阶段是输出请求,则数据阶段由主机向设备传输数据。如果没有实际的数据传输需求(即请求的长度为零),此阶段依然会发送一个长度为0的数据包来表示数据传输完成。

(3)确认阶段。

        确认阶段刚好跟建立阶段相反。如果数据阶段是输入请求(即主机接收设备数据),那么确认阶段是设备向主机发送一个输出数据包。如果数据阶段是输出请求(即主机向设备发送数据),那么确认阶段是主机向设备发送一个输入数据包。

        确认阶段的主要目的是验证数据是否已经正确传输。如果传输成功,确认阶段不会包含任何数据;它只是作为一种状态确认,以告知数据传输是否正常完成。通常,成功的确认阶段只包含一个零长度数据包,这意味着数据传输完成且没有错误。

查看USB枚举过程中的数据内容,如下所示:

(1)传输。

(2) 传输和事务。

(3) 传输、事务、包和域。

 

 ★举例USB枚举过程:

二、应用编程 

1. libusb库安装

  libusb 是一个用 C 语言编写的开源库,主要用于简化 USB 设备的操作。通过 libusb,开发者可以方便地与 USB 设备进行通信,而不需要深入了解 USB 协议的底层细节。

  libusb 提供了跨平台的 API,可以在 Linux、macOS 和 Windows 等操作系统中使用,极大地提升了应用的可移植性。libusb 提供了一套简洁的 API,支持从 USB 1.0 到 USB 3.1 等不同版本的 USB 协议。无论是在传输数据、控制 USB 设备还是处理设备的其他操作时,API 接口保持一致,使用起来非常方便。

(1)下载。

        下载地址:libusb官网。如下图所示,下载最新的版本即可。

(2)编译安装(X86下)。

        我们下载所得到的就是libusb库的源码包,我们需要对源码包进行编译操作。在不同平台上使用的编译器不同,例如X86使用gcc,ARM使用交叉编译器。

<1> 安装依赖包。

sudo apt install -y libudev-dev

<2> 指定编译环境,这里为x86_64-linux 架构。

./configure --build=x86_64-linux

<3> 编译和安装。

make
sudo make install

<4> 验证:进入/usr/local/lib查看。

(3)编译安装(ARM下)。 

 <1> 安装依赖包。

sudo apt install -y libudev-dev

<2> 指定编译环境,这里为ARM架构。(先在源码包路径下新建install文件夹,用于存放编译后的libusb库)

//--build:编译平台
//--host:目标平台
//--prefix:安装路径
//CC: 编译器
//CXX:g++编译器
//--disable-udev:用于禁用对 udev 的支持
./configure --build=x86_64-linux --host=arm-linux --prefix=/home/qjl/libusb/libusb-1.0.27/install CC=arm-linux-gnueabihf-gcc CXX=arm-linux-gnueabihf-g++ --disable-udev

<3> 编译和安装。

make
sudo make install

<4> 使用验证。

        将源码目录下的example中的一个测试源码(例如listdevs.c)拷贝到install文件夹中。然后使用交叉编译器编译这个源码生成可执行文件。如下所示。将生产的可执行文件使用命令file来查看格式是否正确。将可执行文件传至开发板运行。

//-I ./include/libusb-1.0 :头文件路径
//-L ./lib :静态库路径
//-lusb-1.0 -lpthread -static 连接usb静态库 和线程thread库arm-linux-gnueabihf-gcc listdevs.c -o listdevs -I ./include/libusb-1.0  -L ./lib  -lusb-1.0 -lpthread -static

2. 使用libusb库编写应用程序

        编写应用程序,在编译时和第一小节编译验证类似,使用交叉编译器,并且需要连接静态库和头文件。注意:USB每次传输8个字节的数据!!

实验(一):打印USB设备的ID。

#include <stdio.h>
#include <libusb-1.0/libusb.h>int main() {libusb_device **device_list;  //定义设备列表,包含ID等各种信息libusb_device *dev; struct libusb_device_descriptor desc; int ret, i = 0;ssize_t cnt;//初始化libusb库ret = libusb_init(NULL);if (ret < 0) {printf("libusb init error\n");return ret;}//获取设备列表数量cnt = libusb_get_device_list(NULL, &device_list);if (cnt < 0) {printf("libusb get device list error\n");return (int)cnt;}// Iterate through the deviceswhile ((dev = device_list[i++]) != NULL) {ret = libusb_get_device_descriptor(dev, &desc);if (ret < 0) {printf("libusb get device descriptor error\n");return ret;}// 打印厂商ID和产品IDprintf("Device %d: Vendor ID: 0x%04x, Product ID: 0x%04x\n", i, desc.idVendor, desc.idProduct);}// 释放设备列表资源libusb_free_device_list(device_list, 1);// 退出libusblibusb_exit(NULL);return 0;
}

 

 实验(二):操作USB设备。(同步传输)

        将键盘插到开发板的USB接口,读取键盘按下的数据。同步传输:只有执行完上一次,才可以执行下一次。注意:USB每次传输8个字节的数据!!

        总体流程:初始化 libusb → 2. 获取设备列表 → 3. 遍历设备并查找中断传输端点 → 4. 打开设备并分离内核驱动 → 5. 进行中断传输 → 6. 打印接收到的数据并继续传输 → 7. 释放接口并关闭设备 → 8. 释放设备列表和退出 libusb

#include <stdio.h>
#include <libusb-1.0/libusb.h>int main() {libusb_device **device_list;  //定义设备列表libusb_device *dev;   //定义设备struct libusb_device_descriptor desc;  //定义设备描述struct libusb_config_descriptor *config_desc;  //定义配置描述const struct libusb_interface_descriptor *interface_desc;  //接口描述libusb_device_handle *dev_handle;  //设备句柄unsigned char buffer[16];  //数据缓冲区int ret, i = 0, j, k, flag = 0;ssize_t cnt;int endpoint;  // 假设是设备的输入端点地址ssize_t transferred;// 初始化 libusbret = libusb_init(NULL);if (ret < 0) {printf("libusb 初始化失败\n");return ret;}// 获取设备列表cnt = libusb_get_device_list(NULL, &device_list);if (cnt < 0) {printf("获取设备列表失败\n");return (int)cnt;}// 遍历设备列表while ((dev = device_list[i++]) != NULL) {
/*ret = libusb_get_device_descriptor(dev, &desc);  if (ret < 0) {printf("获取设备描述符失败\n");return ret;}
*/// 获取设备的配置描述符ret = libusb_get_config_descriptor(dev, 0, &config_desc);if (ret < 0) {printf("获取配置描述符失败\n");return ret;}// 遍历接口for (k = 0; k < config_desc->bNumInterfaces; k++) {interface_desc = &config_desc->interface[k].altsetting[0];// 检查接口是否为人机设备接口(比如鼠标或键盘)if (interface_desc->bInterfaceClass == 3 || interface_desc->bInterfaceProtocol == 1) {// 遍历端点for (j = 0; j < interface_desc->bNumEndpoints; j++) {if ((interface_desc->endpoint[j].bmAttributes & 3) == LIBUSB_TRANSFER_TYPE_INTERRUPT) {// 找到中断传输类型的端点printf("找到中断端点,端点地址: 0x%02x\n", interface_desc->endpoint[j].bEndpointAddress);endpoint = interface_desc->endpoint[j].bEndpointAddress;  // 设置实际的端点地址flag = 1;break;}}}if (flag) break;}if (flag) break;// 释放配置描述符libusb_free_config_descriptor(config_desc);}if (!flag) {printf("未找到支持中断传输的端点\n");libusb_free_device_list(device_list, 1);libusb_exit(NULL);return -1;}// 打开设备ret = libusb_open(dev, &dev_handle);if (ret < 0) {printf("打开设备失败\n");return ret;}// 自动分离内核驱动ret = libusb_set_auto_detach_kernel_driver(dev_handle, 1);if (ret < 0) {printf("自动分离内核驱动失败\n");return ret;}// 声明接口ret = libusb_claim_interface(dev_handle, interface_desc->bInterfaceNumber);if (ret < 0) {printf("声明接口失败\n");return ret;}// 进行中断传输while (1) {ret = libusb_interrupt_transfer(dev_handle, endpoint, buffer, sizeof(buffer), &transferred, 5000);  // 5秒超时if (ret < 0) {printf("中断传输失败\n");break;}// 打印接收到的数据printf("接收到的数据: ");for (i = 0; i < transferred; i++) {printf("%02x ", buffer[i]);}printf("\n");}// 释放接口libusb_release_interface(dev_handle, interface_desc->bInterfaceNumber);// 关闭设备libusb_close(dev_handle);// 释放设备列表libusb_free_device_list(device_list, 1);// 退出 libusblibusb_exit(NULL);return 0;
}

实验结果: 

 实验(三):操作USB设备。(异步传输)

        将键盘插到开发板的USB接口,读取键盘按下的数据。异步传输:程序能够异步地接收数据,而不需要每次都等待传输完成,可以有效地提高程序的响应能力。注意:USB每次传输8个字节的数据!!

        总体流程:初始化 libusb → 2. 获取设备列表 → 3. 遍历设备并查找合适接口和端点 → 4. 打开设备并分离内核驱动 → 5. 配置并分配传输对象 → 6. 提交传输并处理事件 → 7. 回调函数处理接收数据并重新提交传输 → 8. 释放资源并退出

#include <stdio.h>
#include <libusb-1.0/libusb.h>struct libusb_transfer *keyboard_transfer = NULL;  // 用于传输的对象
#define BUFFER_SIZE 16
unsigned char buffer[BUFFER_SIZE];  // 数据缓冲区// 回调函数,处理接收到的数据
void callback_revc(struct libusb_transfer *transfer) {int i;int ret;if (transfer->status == LIBUSB_TRANSFER_COMPLETED) {if (transfer->actual_length > 0) {// 打印接收到的数据unsigned char *buffer = transfer->buffer;for (i = 0; i < transfer->actual_length; i++) {printf("%02x ", buffer[i]);}printf("\n");}// 重新提交传输,继续接收数据ret = libusb_submit_transfer(transfer);if (ret < 0) {printf("重新提交传输失败\n");libusb_cancel_transfer(transfer);libusb_free_transfer(transfer);}} else {printf("传输状态错误: %d\n", transfer->status);libusb_cancel_transfer(transfer);libusb_free_transfer(transfer);}
}int main() {libusb_device **device_list;  // 定义设备列表libusb_device *dev;           // 定义设备struct libusb_device_descriptor desc;  // 设备描述struct libusb_config_descriptor *config_desc;  // 配置描述struct libusb_interface_descriptor *interface_desc;  // 接口描述libusb_device_handle *dev_handle;  // 设备句柄int ret, i = 0, j, k, flag = 0;ssize_t cnt;int endpoint;  // 假设是设备的输入端点地址// 初始化 libusbret = libusb_init(NULL);// 获取设备列表cnt = libusb_get_device_list(NULL, &device_list);// 遍历设备列表while ((dev = device_list[i++]) != NULL) {ret = libusb_get_device_descriptor(dev, &desc);  // 获取设备的配置描述符ret = libusb_get_config_descriptor(dev, 0, &config_desc);// 遍历接口for (k = 0; k < config_desc->bNumInterfaces; k++) {interface_desc = &config_desc->interface[k].altsetting[0];// 检查接口是否为人机设备接口(比如鼠标或键盘)if (interface_desc->bInterfaceClass == 3 || interface_desc->bInterfaceProtocol == 1) {// 遍历端点for (j = 0; j < interface_desc->bNumEndpoints; j++) {if ((interface_desc->endpoint[j].bmAttributes & 3) == LIBUSB_TRANSFER_TYPE_INTERRUPT) {// 找到中断传输类型的端点printf("找到中断端点,端点地址: 0x%02x\n", interface_desc->endpoint[j].bEndpointAddress);endpoint = interface_desc->endpoint[j].bEndpointAddress;  // 设置实际的端点地址flag = 1;break;}}}if (flag) break;}if (flag) break;// 释放配置描述符libusb_free_config_descriptor(config_desc);}if (!flag) {printf("未找到支持中断传输的端点\n");libusb_free_device_list(device_list, 1);libusb_exit(NULL);return -1;}// 打开设备ret = libusb_open(dev, &dev_handle);// 自动分离内核驱动ret = libusb_set_auto_detach_kernel_driver(dev_handle, 1);// 声明接口ret = libusb_claim_interface(dev_handle, interface_desc->bInterfaceNumber);// 分配传输对象keyboard_transfer = libusb_alloc_transfer(0);// 配置中断传输libusb_fill_interrupt_transfer(keyboard_transfer, dev_handle, endpoint, buffer, BUFFER_SIZE, callback_revc, keyboard_transfer, 0);// 提交传输ret = libusb_submit_transfer(keyboard_transfer);// 处理事件while (1) {// libusb_handle_events 处理传输完成的事件libusb_handle_events(NULL);}libusb_release_interface(dev_handle, interface_desc->bInterfaceNumber); // 释放接口libusb_close(dev_handle);  // 关闭设备libusb_free_device_list(device_list, 1);  // 释放设备列表libusb_exit(NULL);   // 退出 libusbreturn 0;
}


http://www.ppmy.cn/ops/151970.html

相关文章

C# OpenCvSharp Yolov8 Face Landmarks 人脸特征检测

目录 介绍 效果 模型信息 项目 代码 下载 介绍 github地址&#xff1a;https://github.com/derronqi/yolov8-face yolov8 face detection with landmark 效果 模型信息 Model Properties description&#xff1a;Ultralytics YOLOv8-lite-t-pose model trained on w…

C语言内存之旅:从静态到动态的跨越

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言正文一 动态内存管理的必要性二 动态…

彻底讲清楚 单体架构、集群架构、分布式架构及扩展架构

目录 什么是系统架构 单体架构 介绍 示例图 优点 缺点 集群架构 介绍 示意图 优点 缺点 分布式架构 示意图 优点 缺点 生态扩展 介绍 示意图 优点 缺点 扩展&#xff1a;分布式服务解析 纵切拆服务 全链路追踪能力 循环依赖 全链路日志&#xff08;En…

PyCharm中解决依赖冲突

1. 创建虚拟环境 确保为项目创建了一个虚拟环境&#xff0c;这样可以隔离项目的依赖&#xff0c;避免全局依赖冲突。 检查当前项目的 Python 环境 打开 PyCharm。点击菜单栏的 File > Settings > Project: [Your Project Name] > Python Interpreter。确保已选择一…

AI 之网:网络诈骗者的 “高科技伪装术”—— 智能诈骗的神秘面纱

本篇文章博主将以AI的反面应用为例&#xff1b;配合代码辅助说明&#xff1b;带大家了解背后的“黑面纱”&#xff1b;也同时希望大家能够正反结合&#xff1b;不要误入歧途。 &#xff1a;羑悻的小杀马特.-CSDN博客羑悻的小杀马特.擅长C/C题海汇总,AI学习,c的不归之路,等方面的…

栈和队列(C语言)

目录 数据结构之栈 定义 实现方式 基本功能实现 1&#xff09;定义&#xff0c;初始化栈 2&#xff09;入栈 3&#xff09;出栈 4&#xff09;获得栈顶元素 5)获得栈中有效元素个数 6&#xff09;检测栈是否为空 7&#xff09;销毁栈 数据结构之队列 定义 实现方…

Jupyter notebook中运行dos指令运行方法

Jupyter notebook中运行dos指令运行方法 目录 Jupyter notebook中运行dos指令运行方法一、DOS(磁盘操作系统&#xff09;指令介绍1.1 DOS介绍1.2 DOS指令1.2.1 DIR - 显示当前目录下的文件和子目录列表。1.2.2 CD 或 CHDIR - 改变当前目录1.2.3 使用 CD .. 可以返回上一级目录1…

PHP企业IM客服系统

&#x1f4ac; 企业IM客服系统——高效沟通&#xff0c;无缝连接的智慧桥梁 &#x1f680; 卓越性能&#xff0c;释放无限可能 在瞬息万变的商业环境中&#xff0c;我们深知沟通的力量。因此&#xff0c;基于先进的ThinkPHP5框架与高性能的Swoole扩展&#xff0c;我们匠心独运…