当前位置: 首页 > news >正文

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

       

一、基础知识 

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

相关文章:

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

一、基础知识 1. USB接口是什么&#xff1f; USB接口&#xff08;Universal Serial Bus&#xff09;是一种通用串行总线&#xff0c;广泛使用的接口标准&#xff0c;主要用于连接计算机与外围设备&#xff08;如键盘、鼠标、打印机、存储设备等&#xff09;之间的数据传输和电…...

Swift语言探索:Sequence与Collection的详细解读

在Swift编程语言中&#xff0c;Sequence和Collection是两个非常重要的协议&#xff0c;它们定义了遍历和访问元素集合的方式。理解这两个协议不仅有助于我们更好地掌握Swift的集合类型&#xff0c;还能让我们在编写代码时更加灵活和高效。本文将详细解读Sequence与Collection&a…...

解锁C# EF/EF Core:从入门到进阶的技术飞跃

一、EF/EF Core 初相识 在.NET 开发的广阔天地中&#xff0c;Entity Framework (EF) 及其轻量级、可扩展、跨平台的版本 Entity Framework Core (EF Core)&#xff0c;犹如两颗璀璨的明星&#xff0c;照亮了数据访问层开发的道路。它们是开源的对象关系映射器&#xff08;ORM&…...

大模型搜广推?对算法工作的影响

大模型与传统应用结合的性质 长期看是一种范式革新。算力和模型定义的边界发生变化&#xff0c;选择生成式AI或大模型发展方向时&#xff0c;会不断发现新的增长曲线&#xff0c;目前在不断被验证。 现阶段大模型确实带来了增量信息&#xff0c;但推荐过程仍在原有流程基础上…...

【教程】最好的pytorch教程

文章目录 Materials for the Learn PyTorch for Deep Learning: Zero to Mastery course from: https://github.com/mrdbourke/pytorch-deep-learning/blob/main/01_pytorch_workflow.ipynb...

基于java线程池和EasyExcel实现异步导出

基于java线程池和EasyExcel实现异步导出 1.controller层 GetMapping("export") public void exportExcel(HttpServletResponse response) throws IOException, InterruptedException {exportService.exportExcel(response); }2. service public void exportExcel(H…...

vue3组件传值具体使用

问&#xff1a; left.vue文件调用接口获取了后端返回的urlLink字段&#xff0c;我该怎么传递给总的父组件index.vue中&#xff0c;我需要点击父组件的一个按钮来触发跳转&#xff1f; 回答&#xff1a; 在 Vue 3 中使用 TypeScript 和 setup 语法糖时&#xff0c;可以通过 e…...

《CPython Internals》阅读笔记:p336-p352

《CPython Internals》学习第 17天&#xff0c;p336-p352 总结&#xff0c;总计 17 页。 一、技术总结 1.GDB GDB 是 GNU Dbugger 的缩写。 (1)安装 sudo apt install gdb(2)创建 .gdbinit 文件 touch ~/.gdbinitvim ~/.gdbinit(3)配置 .gdbinit 文件 add-auto-load-saf…...

WPF基础 | WPF 常用控件实战:Button、TextBox 等的基础应用

WPF基础 | WPF 常用控件实战&#xff1a;Button、TextBox 等的基础应用 一、前言二、Button 控件基础2.1 Button 的基本定义与显示2.2 按钮样式设置2.3 按钮大小与布局 三、Button 的交互功能3.1 点击事件处理3.2 鼠标悬停与离开效果3.3 按钮禁用与启用 四、TextBox 控件基础4.…...

Langchain+讯飞星火大模型Spark Max调用

1、安装langchain #安装langchain环境 pip install langchain0.3.3 openai -i https://mirrors.aliyun.com/pypi/simple #灵积模型服务 pip install dashscope -i https://mirrors.aliyun.com/pypi/simple #安装第三方集成,就是各种大语言模型 pip install langchain-comm…...

2025年华为云一键快速部署幻兽帕鲁联机服务器教程

华为云如何快速部署幻兽帕鲁联机服务器&#xff1f;为了方便新手玩家搭建专属游戏联机服务器&#xff0c;华为云推出了云游戏专场&#xff0c;无需专业技术&#xff0c;新手小白也能一键快速部署幻兽帕鲁联机服务器&#xff01; 华为云快速部署幻兽帕鲁联机服务器教程如下&…...

Langchain+文心一言调用

import osfrom langchain_community.llms import QianfanLLMEndpointos.environ["QIANFAN_AK"] "" os.environ["QIANFAN_SK"] ""llm_wenxin QianfanLLMEndpoint()res llm_wenxin.invoke("中国国庆日是哪一天?") print(…...

C++AVL树(一)详解

文章目录 AVL树概念AVL树的插入平衡因子的更新旋转的规则左单旋右单旋抽象的情况h0h1h 2h 3 AVL树 概念 AVL树是一棵平衡二叉查找树&#xff0c;AVL树是空树&#xff0c;保证左右子树都是AVL树&#xff0c;AVL树要求高度差的绝对值不超过1&#xff0c;因为最好情况是1&#…...

ceph新增节点,OSD设备,标签管理(二)

一、访问客户端集群方式 方式一: 使用cephadm shell交互式配置 [rootceph141 ~]# cephadm shell # 注意&#xff0c;此命令会启动一个新的容器&#xff0c;运行玩后会退出&#xff01; Inferring fsid c153209c-d8a0-11ef-a0ed-bdb84668ed01 Inferring config /var/lib/ce…...

c++学习第七天

创作过程中难免有不足&#xff0c;若您发现本文内容有误&#xff0c;恳请不吝赐教。 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考。 一、const成员函数 //Date.h#pragma once#include<iostream> using namespace std;class Date { public:Date…...

F/V/F/I频率脉冲信号转换器

F/V/F/I频率脉冲信号转换器 概述&#xff1a;捷晟达科技的JSD TFA-1001系列是一进一出频率脉冲信号转换器(F/V转换器),该频率转换器是将频率脉冲信号(方波、正弦波、锯齿波)转换成国际标准的模拟量电压(电流)信号,并远距离无失真传送到控制室(如:PLC,DCS,AD,PC采集系统)产品的输…...

SQLServer中DBCC INPUTBUFFER显示从客户端发送到 SQL Server 实例的最后一个语句

SQLServer中DBCC INPUTBUFFER显示从客户端发送到 SQL Server 实例的最后一个语句 1、本文内容 语法参数结果集权限示例 适用于&#xff1a; SQL ServerAzure SQL 数据库Azure SQL 托管实例 显示从客户端发送到 SQL Server 实例的最后一个语句。 2、语法 DBCC INPUTBUFFE…...

Mysql面试题----MySQL中CHAR和VARCHAR的区别

存储方式 CHAR&#xff1a;是一种固定长度的字符串类型。它会按照定义的长度来分配存储空间&#xff0c;无论实际存储的字符串长度是多少。例如&#xff0c;定义一个 CHAR (10) 的列&#xff0c;如果存储的值是 ‘ab’&#xff0c;那么它仍然会占用 10 个字符的存储空间&#…...

github汉化

本文主要讲述了github如何汉化的方法。 目录 问题描述汉化步骤1.打开github&#xff0c;搜索github-chinese2.打开项目&#xff0c;打开README.md3.下载安装脚本管理器3.1 在README.md中往下滑动&#xff0c;找到浏览器与脚本管理器3.2 选择浏览器对应的脚本管理器3.2.1 点击去…...

探索JavaScript前端开发:开启交互之门的神奇钥匙(二)

目录 引言 四、事件处理 4.1 事件类型 4.2 事件监听器 五、实战案例&#xff1a;打造简易待办事项列表 5.1 HTML 结构搭建 5.2 JavaScript 功能实现 六、进阶拓展&#xff1a;异步编程与 Ajax 6.1 异步编程概念 6.2 Ajax 原理与使用 七、前沿框架&#xff1a;Vue.js …...

什么是网络爬虫?Python爬虫到底怎么学?

最近我在研究 Python 网络爬虫&#xff0c;发现这玩意儿真是有趣&#xff0c;干脆和大家聊聊我的心得吧&#xff01;咱们都知道&#xff0c;网络上的信息多得就像大海里的水&#xff0c;而网络爬虫就像一个勤劳的小矿工&#xff0c;能帮我们从这片浩瀚的信息海洋中挖掘出需要的…...

React 中hooks之 React.memo 和 useMemo用法总结

1. React.memo 基础 React.memo 是一个高阶组件&#xff08;HOC&#xff09;&#xff0c;用于优化函数组件的性能&#xff0c;通过记忆组件渲染结果来避免不必要的重新渲染。 1.1 基本用法 const MemoizedComponent React.memo(function MyComponent(props) {/* 渲染逻辑 *…...

springboot基于微信小程序的手机银行系统

Spring Boot基于微信小程序的手机银行系统是一种结合现代Web技术和移动应用优势的创新金融服务平台。 一、系统背景与意义 随着信息技术的快速发展和用户对便捷金融服务需求的日益增长&#xff0c;传统手机银行系统的人工管理方法已逐渐显露出效率低下、安全性低以及信息传输…...

Kafka后台启动命令

#保存日志 nohup ./kafka-server-start.sh ../config/server.properties > /path/to/logfile.log 2>&1 &#不保存日志 nohup ./kafka-server-start.sh ../config/server.properties >/dev/null 2>&1 & nohup: 是一个Unix/Linux命令&#xff0c;用于…...

面试题目1

文章目录 C语言函数调用详细过程C语言中const与C中const的区别关系运算符有哪些互斥锁与读写锁的区别gcc的编译过程 C语言函数调用详细过程 调用函数&#xff1a;当程序执行到函数调用语句时&#xff0c;会将当前函数的返回地址、参数列表等信息压入栈中&#xff0c;然后跳转到…...

Android AutoMotive --CarService

1、AAOS概述 Android AutoMotive OS是谷歌针对车机使用场景打造的操作系统&#xff0c;它是基于现有Android系统的基础上增加了新特性&#xff0c;最主要的就是增加了CarService&#xff08;汽车服务&#xff09;模块。我们很容易把Android AutoMotive和Android Auto搞混&…...

CSDN 博客之星 2024:默语的技术进阶与社区耕耘之旅

CSDN 博客之星 2024&#xff1a;默语的技术进阶与社区耕耘之旅 &#x1f31f; 默语&#xff0c;是一位在技术分享与社区建设中坚持深耕的博客作者。今年&#xff0c;我有幸再次入围成为 CSDN 博客之星TOP300 的一员&#xff0c;这既是对过往努力的肯定&#xff0c;也是对未来探…...

【Vim Masterclass 笔记24】S10L43 + L44:同步练习10 —— 基于 Vim 缓冲区的各类基础操作练习(含点评课)

文章目录 S10L43 Exercise 12 - Vim Buffers1 训练目标2 操作指令2.1. 打开 buf* 文件2.2. 查看缓冲区 View the buffers2.3. 切换缓冲区 Switch buffers2.4. 同时编辑多个缓冲区 Edit multiple buffers at once2.5. 缓冲区的增删操作 Add and delete buffers2.6. 练习 Vim 内置…...

python如何使得pdf加水印后的大小尽可能小

在 Python 中为 PDF 添加水印并尽可能减少文件大小&#xff0c;可以采取以下优化策略&#xff1a; 1. 使用合适的库 常用的 PDF 处理库&#xff1a; PyMuPDF&#xff08;fitz&#xff09;&#xff1a;高效且优化的 PDF 处理reportlab pdfrw&#xff1a;可实现水印合并&#…...

Leetcode 3429. Paint House IV

Leetcode 3429. Paint House IV 1. 解题思路2. 代码实现 题目链接&#xff1a;3429. Paint House IV 1. 解题思路 这一题解法上就是一个动态规划的思路&#xff0c;由于题目有两个限制条件&#xff0c;即相邻不可以同色&#xff0c;以及前后同位置不可以同色&#xff0c;因此…...

ASP.NET Core 实战:JWT 身份验证

一、引言 在当今数字化时代&#xff0c;Web 应用的安全性至关重要。ASP.NET Core 作为一种广泛应用的开发框架&#xff0c;为开发者提供了强大的工具来构建安全可靠的应用程序。而 JWT&#xff08;JSON Web Token&#xff09;身份验证则是保障应用安全的关键环节之一。 JWT 身…...

【学习笔记15】如何在非root服务器中,安装属于自己的redis

一、下载安装包 官网下载黑马程序员给的安装包&#xff08;redis-6.2.6&#xff09; 二、将安装包上传至服务器 我将安装包上传在我的文件夹/home/XXX&#xff0c;指定路径中/src/local/redis/&#xff0c;绝对路径为/home/XXX/src/local/redis/解压安装包 XXXomega:~$ cd …...

基于深度学习的微出血自动检测及解剖尺度定位|文献速递-视觉大模型医疗图像应用

Title 题目 Toward automated detection of microbleeds with anatomical scale localization using deep learning 基于深度学习的微出血自动检测及解剖尺度定位 01 文献速递介绍 基于深度学习的脑微出血&#xff08;CMBs&#xff09;检测与解剖定位 脑微出血&#xff…...

Couchbase UI: Dashboard

以下是 Couchbase UI Dashboard 页面详细介绍&#xff0c;包括页面布局和功能说明&#xff0c;帮助你更好地理解和使用。 1. 首页&#xff08;Overview&#xff09; 功能&#xff1a;提供集群的整体健康状态和性能摘要 集群状态 节点健康状况&#xff1a;绿色&#xff08;正…...

Python

1 变量 1.1 定义 变量&#xff1a;为快速定义目标&#xff0c;将数据在内存占据的存储空间分配的一个名称。 定义&#xff1a;变量名 数据值 作用&#xff1a;临时存储数据 message "hello" print(message)#输出&#xff1a;hello message "hello Pytho…...

一个软件分发和下载的网站源码,带多套模板

PHP游戏应用市场APP软件下载平台网站源码手机版 可自行打包APP&#xff0c;带下载统计&#xff0c;带多套模板&#xff0c;带图文教程 代码下载&#xff1a;百度网盘...

war包 | Docker部署flowable-ui

文章目录 引言I war包部署flowable-ui下载war包配置Tomcat访问 flowable-uiII Docker启动flowable-ui并修改配置Docker启动flowable-ui修改配置访问Flowable UI界面。III 知识扩展加速源docker run -i -t -d 参数引言 Flowable 支持 BPMN 2.0 行业标准,同时提供了一些 Flowab…...

07_游戏加载窗口

隐藏动态提示窗口 创建空节点 命名为 LoadingWnd 意为加载窗口 并设置全屏 在子级下创建Image作为加载背景 也设置成全屏 将以下资源放进Art文件夹中 设置好精灵模式后拖拽至 Image的Source Image框选 创建文本作为提示内容 增加描边组件OutLine可以美化字体 创建Image作为加载…...

proxyman抓包Java中feign请求以及断点请求响应内容修改或流转到本地

proxyman抓包Java中feign请求以及断点请求响应内容修改或流转到本地 配置流程第一步: 借助arthas配置请求代理第二步: 借助proxyman配置远程映射第三步: 借助SwitchHosts配置hosts域名最后: 借助ssh的LocalForward功能, 打通网络(这步网络不通才需要) 最近在修bug的过程中, 因为…...

PyTorch使用教程(10)-torchinfo.summary网络结构可视化详细说明

1、基本介绍 torchinfo是一个为PyTorch用户量身定做的开源工具&#xff0c;其核心功能之一是summary函数。这个函数旨在简化模型的开发与调试流程&#xff0c;让模型架构一目了然。通过torchinfo的summary函数&#xff0c;用户可以快速获取模型的详细结构和统计信息&#xff0…...

centos9编译安装opensips 二【进阶篇-定制目录+模块】推荐

环境&#xff1a;centos9 last opensips -V version: opensips 3.6.0-dev (x86_64/linux) flags: STATS: On, DISABLE_NAGLE, USE_MCAST, SHM_MMAP, PKG_MALLOC, Q_MALLOC, F_MALLOC, HP_MALLOC, DBG_MALLOC, CC_O0, FAST_LOCK-ADAPTIVE_WAIT ADAPTIVE_WAIT_LOOPS1024, MAX_RE…...

MongoDB 备份与恢复综述

目录 一、基本概述 二、逻辑备份 1、全量备份 2、增量备份 3、恢复 三、物理备份 1、cp/tar/fsync 2、WiredTiger 热备份 3、恢复 四、快照备份 一、基本概述 MongoDB 是一种流行的 NoSQL 数据库&#xff0c;它使用文档存储数据&#xff0c;支持丰富的查询语言和索引…...

Apache Hive3定位表并更改其位置

Apache Hive3表 1、Apache Hive3表概述2、Hive3表存储格式3、Hive3事务表4、Hive3外部表5、定位Hive3表并更改位置6、使用点表示法引用表7、理解CREATE TABLE行为 1、Apache Hive3表概述 Apache Hive3表类型的定义和表类型与ACID属性的关系图使得Hive表变得清晰。表的位置取决于…...

Flutter项目和鸿蒙平台的通信

Flutter项目和鸿蒙平台的通信 前言Flutter和Harmonyos通信MethodChannelBasicMessageChannelEventChannel 前言 大家在使用Flutter开发项目的时候&#xff0c; Flutter提供了Platfrom Channel API来和个个平台进行交互。 Flutter官方目前提供了一下三种方式来和个个平台交互&…...

5. 马科维茨资产组合模型+政策意图AI金融智能体(Qwen-Max)增强方案(理论+Python实战)

目录 0. 承前1. AI金融智能体1.1 What is AI金融智能体1.2 Why is AI金融智能体1.3 How to AI金融智能体 2. 数据要素&计算流程2.1 参数集设置2.2 数据获取&预处理2.3 收益率计算2.4 因子构建与预期收益率计算2.5 协方差矩阵计算2.6 投资组合优化2.7 持仓筛选2.8 AI金融…...

嵌入式知识点总结 ARM体系与架构 专题提升(一)-硬件基础

嵌入式知识点总结 ARM体系与架构 专题提升(一)-硬件基础 目录 1.NAND FLASH 和NOR FLASH异同 ? 2.CPU,MPU,MCU,SOC,SOPC联系与差别? 3.什么是交叉编译&#xff1f; 4.为什么要交叉编译&#xff1f; 5.描述一下嵌入式基于ROM的运行方式和基于RAM的运行方式有什么区别? 1…...

thinkphp8在使用apidoc时, 4层的接口会有问题 解决办法

thinkphp8 4层的接口会有问题, 比如这样的接口 /adminapi/notice/announcements/lists, 应该换成 /adminapi/notice.announcements/lists 这样才行, 有没有人处理过? 实际上在官网的帮助里有描述 自动生成的url不对&#xff1f; | Apidoc // config/apidoc.php //... auto_url…...

【jmeter】下载及使用教程【mac】

1.安装java 打开 Java 官方下载网站https://www.oracle.com/java/technologies/downloads/选择您想要下载的 Java 版本&#xff0c;下载以 .dmg 结尾的安装包&#xff0c;注意 JMeter 需要 Java 8下载后打开安装包点击“安装”按钮即可 2.下载jmeter 打开 Apache JMeter 官方…...

C# ASP.NET MVC项目内使用ApiController

1.在App_Start文件夹新建WebApiConfig.cs文件&#xff0c;建立webApi路由的注册方法。 using System.Web.Http;namespace PrivilegeManager {public class WebApiConfig{public static void Register(HttpConfiguration config){config.MapHttpAttributeRoutes();config.Route…...

Langchain+FastApi+Vue前后端Ai对话(超详细)

一、引入 首先可以先看下作者的文章 FastApi相关文章&#xff1a;创建最简单FastApi的项目Vue相关文章&#xff1a;最简单的aixos二次封装Langchain相关文章&#xff1a;如何使用LangSmith跟踪deepseek模型 二、后端搭建 1 项目文件结构 routers&#xff1a;存放api接口se…...