1. 什么是 libusb
libusb是一个提供对 USB 设备的通用访问的 C 库。它旨在供开发人员用于促进与 USB 硬件通信的应用程序的生产。
- 可移植的:使用单一的跨平台 API,它提供对 Linux、macOS、Windows 等 USB 设备的访问
- 用户模式:应用程序与设备通信不需要特殊特权或提升。
- 与usb 版本无关:支持所有版本的 USB 协议,从 1.0 到 3.1(最新)。
- 跨平台:Linux、macOS、Windows(Vista 和更新版本)、Android、OpenBSD/NetBSD、Haiku、Solaris。
2. 常用接口介绍
libusb 接口可以参考官网wiki
- 支持所有传输方式:中断、控制、同步、批量
- 传输接口:Synchronous (简单),Asynchronous
libusb 模块如下:
- 设备初始化;
- 设备控制与枚举;
- 描述符处理;
- 热插拔事件;
- 同步I/O;
- 异步I/O;
- 其他处理;
2.1 初始化与反初始化
/* * @brief : libusb 初始化。 * @param: 参数可选,默认为NULL。 */ int libusb_init(libusb_context ** ctx); void libusb_exit(libusb_context * ctx);
2.2 设备控制和枚举
2.2.1 枚举设备
/* * @brief:获取设备列表 * @param: * ctx:上下文,可选 * list:枚举的设备list * @return: 返回设备数量 */ ssize_t libusb_get_device_list (libusb_context *ctx, libusb_device ***list) /* * @brief:释放设备列表 * @param: * list:枚举的设备list * unref_devices:是否取消引用列表中的设备 * @return: no */ void libusb_free_device_list (libusb_device **list, int unref_devices); /* * @brief: 获取设备信息 * @param: * dev_handle:设备句柄 * @return: 返回底层设备信息 */ ibusb_device * libusb_get_device (libusb_device_handle *dev_handle); /* * @brief: 获取配置信息 * @param: * dev_handle:设备句柄 * config:活动配置的 bConfigurationValue * @return: 0 */ int libusb_get_configuration (libusb_device_handle *dev_handle, int *config); /* * @brief: 设备配置 * 如果您在已经配置了所选配置的设备上调用此函数, * 则此函数将充当轻量级设备重置: * 它将使用当前配置发出 SET_CONFIGURATION 请求, * 导致大多数与USB 相关的设备状态被重置(altsetting reset为零,端点停止清 * 除,切换复位。一般不手动调用。 * @param: * dev_handle:设备句柄 * config:活动配置的 bConfigurationValue * @return: 0 */ int libusb_set_configuration (libusb_device_handle *dev_handle, int configuration);
2.2.2 设备控制
/* * @brief: 打开设备 * @param: * dev:要打开的设备 * dev_handle:设备句柄 * @return: 0 */ int libusb_open (libusb_device *dev, libusb_device_handle **dev_handle); /* * @brief:打开设备 * @param: * ctx:上下文,默认为NULL * vendor_id:vid * product_id:pid * @return: 返回设备句柄 */ libusb_device_handle * libusb_open_device_with_vid_pid (libusb_context *ctx, uint16_t vendor_id, uint16_t product_id); void libusb_close (libusb_device_handle *dev_handle); /* * @brief: 复位设备 * 执行 USB 端口重置以重新初始化设备。重置完成后, * 系统将尝试恢复之前的配置和备用设置。 * 这是一个阻塞功能,通常会导致明显的延迟。 * @param: * dev_handle:设备句柄 * @return: 0 */ int libusb_reset_device (libusb_device_handle *dev_handle);
2.2.3 接口处理
接口激活与绑定:
/* * @brief :接口驱动激活。 * 确定内核驱动程序是否在接口上处于活动状态。 * 如果内核驱动程序处于活动状态,则无法声明接口,libusb 将无法执行 I/O。 * 此功能在 Windows 上不可用。 * @param: * dev_handle:设备句柄 * interface_number:接口号 * @return: 0 */ int libusb_kernel_driver_active (libusb_device_handle *dev_handle, int interface_number); /* * @brief :从接口中分离内核驱动程序。 * 如果成功,您将能够声明接口并执行 I/O。 * 此功能在 Windows 上不可用。 * @param: * dev_handle:设备句柄 * interface_number:接口号 * @return: * 0: 接口处于非活动状态 * 1:接口处于活动状态 */ int libusb_detach_kernel_driver (libusb_device_handle *dev_handle, int interface_number); /* * @brief :重新附加接口的内核驱动程序, * 该驱动程序以前使用libusb_detach_kernel_driver()分离。 * 此功能在 Windows 上不可用。 * @param: * dev_handle:设备句柄 * interface_number:接口号 * @return: 0 */ int libusb_attach_kernel_driver (libusb_device_handle *dev_handle, int interface_number); /* * @brief :启用/禁用 libusb 的自动内核驱动程序分离。 * 启用此选项后,libusb 将在声明接口时自动分离接口上的内核驱动程序, * 并在释放接口时附加它。 * 默认情况下,在新打开的设备句柄上禁用自动内核驱动程序分离。 * @param: * dev_handle:设备句柄 * enable:使能 * @return: 0 */ int libusb_set_auto_detach_kernel_driver (libusb_device_handle *dev_handle, int enable);
接口声明和配置:
/* * @brief :声明一个接口。 * 您必须先声明您希望使用的接口,然后才能在其任何端点上执行 I/O。 * 声明接口是纯逻辑操作;它不会导致通过总线发送任何请求。 * 接口声明用于指示您的应用程序希望获得接口所有权的底层操作系统 * 如果 auto_detach_kernel_driver 设置为 1 dev,内核驱动程序将在必要时deattach,失败时返回deattach 错误。 * 非阻塞函数 * @param: * dev_handle:设备句柄 * interface_number:接口号 * @return: 0 */ int libusb_claim_interface (libusb_device_handle *dev_handle, int interface_number); /* * @brief:释放声明的接口。 * 您应该在关闭设备句柄之前释放所有声明的接口。 * 将向设备发送 SET_INTERFACE 控制请求,将接口状态重置* 为第一个备用设置。 * 如果 auto_detach_kernel_driver 设置为 1 dev,则释放接口后内核驱动程序将重新attach。 * 阻塞函数 * @param: * dev_handle:设备句柄 * interface_number:接口号 * @return: 0 */ int libusb_release_interface (libusb_device_handle *dev_handle, int interface_number); /* * @brief: 激活接口的备用设置。该接口必须先前已使用libusb_claim_interface()声明。 * 您应该始终使用此函数,而不是制定自己的 SET_INTERFACE 控制请求。 * 这是因为底层操作系统需要知道何时发生此类更改。 * 阻塞函数。 * @param: * dev_handle:设备句柄 * interface_number:接口号 * alternate_setting:可选配置 * @return: 0 */ int libusb_set_interface_alt_setting (libusb_device_handle *dev_handle, int interface_number, int alternate_setting);
端点:
/* * @brief: 清除端点的停止/停止条件。 * 在停止条件停止之前,处于停止状态的端点无法接收或传输数据。 * 阻塞函数 * @param: * dev_handle:设备句柄 * endpoint:端点地址 * @return: 0 */ int libusb_clear_halt (libusb_device_handle *dev_handle, unsigned char endpoint);
2.3 USB 描述符
2.3.1 设备
/* * @brief: 获取给定设备的 USB 设备描述符。 * 这是一个非阻塞函数;设备描述符缓存在内存中。 * @param: * dev:设备 * desc:设备描述符 * @return: 0 */ int libusb_get_device_descriptor (libusb_device *dev, struct libusb_device_descriptor *desc);
2.3.2 配置
/* * @brief: 获取当前活动配置的 USB 配置描述符。 * 这是一个非阻塞功能,不涉及向设备发送任何请求。 * @param: * dev:设备 * config:配置描述符。使用后必须使用libusb_free_config_descriptor()释放。 * @return: 0 */ int libusb_get_active_config_descriptor (libusb_device *dev, struct libusb_config_descriptor **config); /* * @brief: 根据索引获取 USB 配置描述符。 * 这是一个非阻塞功能,不涉及向设备发送任何请求。 * @param: * dev:设备 * config_index:配置索引 * config:配置描述符。使用后必须使用 libusb_free_config_descriptor()释放。 * @return: 0 */ int libusb_get_config_descriptor (libusb_device *dev, uint8_t config_index, struct libusb_config_descriptor **config); /* * @brief: 获取具有特定 bConfigurationValue 的 USB 配置描述符。 * 这是一个非阻塞功能,不涉及向设备发送任何请求。 * @param: * dev:设备 * bConfigurationValue:检索的配置的 bConfigurationValue * config:配置描述符。使用后必须使用 libusb_free_config_descriptor()释放。 * @return: 0 */ int libusb_get_config_descriptor_by_value (libusb_device *dev, uint8_t bConfigurationValue, struct libusb_config_descriptor **config); /* * @brief: 释放配置描述符 * @param: * config:配置描述符. * @return: no */ void libusb_free_config_descriptor(struct libusb_config_descriptor *config);
2.3.3 其他描述符
bos 描述符:
int libusb_get_bos_descriptor (libusb_device_handle *dev_handle, struct libusb_bos_descriptor **bos); void libusb_free_bos_descriptor (struct libusb_bos_descriptor *bos);
usb 2.0 扩展:
int libusb_get_usb_2_0_extension_descriptor (libusb_context *ctx, struct libusb_bos_dev_capability_descriptor *dev_cap, struct libusb_usb_2_0_extension_descriptor **usb_2_0_extension); void libusb_free_usb_2_0_extension_descriptor (struct libusb_usb_2_0_extension_descriptor *usb_2_0_extension)
字符串描述符:
int libusb_get_string_descriptor_ascii (libusb_device_handle *dev_handle, uint8_t desc_index, unsigned char *data, int length)
2.4 热插拔事件
/* * @brief: 注册一个热插拔回调函数 * @param: * ctx:注册此回调的上下文 * events:触发此回调的热插拔事件 * flags:热插拔标志 * vendor_id:vid * product_id:pid * dev_class:设备类 * cb_fn:回调函数 * user_data:传递给回调函数的用户数据 * callback_handle:存储已分配的回调句柄指针 * @return: 0 */ int libusb_hotplug_register_callback ( libusb_context * ctx, int events, int flags, int vendor_id, int product_id, int dev_class, libusb_hotplug_callback_fn cb_fn, void * user_data, libusb_hotplug_callback_handle * callback_handle ); /* * @brief: 注销一个热插拔回调函数 * @param: * ctx:注册此回调的上下文 * callback_handle:存储已分配的回调句柄指针 * @return: 0 */ void libusb_hotplug_deregister_callback ( libusb_context * ctx, libusb_hotplug_callback_handle callback_handle ); /* * @brief: 获取与热插拔回调关联的 user_data。 * @param: * ctx:注册此回调的上下文 * callback_handle:存储已分配的回调句柄指针 * @return: 0 */ void* libusb_hotplug_get_user_data( libusb_context * ctx, libusb_hotplug_callback_handle callback_handle )‘
2.5 同步I/O
2.5.1 控制传输
/* * @brief: usb 控制传输 * @param: * dev_handle: 设备句柄 * bmRequestType: 请求类型 * bRequest: 请求字段 * wValue: 值字段 * wIndex: 索引字段 * data: 请求数据 * wLength: 数据包长度 * timeout: 超时(ms), 0为阻塞等待 * @return 返回实际传输的字节 */ int libusb_control_transfer( libusb_device_handle * dev_handle, uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned char * data, uint16_t wLength, unsigned int timeout );
3. 下载与使用
官网:
- https://libusb.info/开发文档:
下载:
- 最新稳定版本
源码下载:
编译与使用:在pc 端使用libusb 可以下载二进制包,也可用源码编译。
linux 下源码编译库
#编译前准备
git clone https://github.com/libusb/libusb.git
sudo apt-get install autoconf
sudo apt-get install libtool
sudo apt-get install libudev-dev
编译:
cd libusb ./configure --prefix=$HOME/github/libusb/out make make install
直接将对应的库移植到相应的工程下。
4. 常规使用流程
- 初始化libusb usb 库
- 打开指定设备
- 声明某个接口
- 设置接口绑定和分离
- 复位设备
- 读写设备
- 关闭设备
- 关闭库
4.1 初始化
void CLibusbControl::bulkusb_init() { uint16_t pid = 0; uint16_t vid = 0; int ret = -1; /*Step1: 初始化 */ libusb_init(NULL); vid = m_usbid.vid; pid = m_usbid.pid; /*Step2:打开设备 */ libusb_device_handle* pHandle = libusb_open_device_with_vid_pid(NULL, vid, pid); if (pHandle == NULL) { printf("libusb_open_device_with_vid_pid(%d, %d) failed \n", vid, pid); libusb_exit(NULL); return; } m_usbdev.handle = pHandle; /*Step3: 声明某个接口*/ int iface = get_interface_attr(pHandle); if (iface < 0) { libusb_close(pHandle); libusb_exit(NULL); } libusb_set_auto_detach_kernel_driver(pHandle, 1); libusb_reset_device(pHandle); }
声明接口:
int CLibusbControl::get_interface_attr(libusb_device_handle* handle) { int ret = -1; /*Step1: 获取设备描述符 */ libusb_device* dev = NULL; dev = libusb_get_device(handle); if (dev == NULL) { printf("libusb_get_device error\n"); return -1; } struct libusb_device_descriptor desc; ret = libusb_get_device_descriptor(dev, &desc); if (ret < 0) { fprintf(stderr, "failed to get device descriptor"); return -1; } /* Step2: 获取配置描述符 */ struct libusb_config_descriptor* conf_desc; const struct libusb_endpoint_descriptor* endpoint; uint8_t endpoint_in = 0, endpoint_out = 0; // default IN and OUT endpoints int nb_ifaces; uint8_t interfaceClass; ret = libusb_get_config_descriptor(dev, 0, &conf_desc); if (ret < 0) { fprintf(stderr, "failed to get config descriptor"); return -1; } /* Step3:获取接口描述符 */ nb_ifaces = conf_desc->bNumInterfaces; m_usbdev.nb_ifaces = nb_ifaces; for(int i = 0; i < nb_ifaces; i++) { for (int j = 0; j < conf_desc->interface[i].num_altsetting; j++) { //只获取接口类别为:厂商自定义类(0xFF)和CDC数据类(0xA) interfaceClass = conf_desc->interface[i].altsetting[j].bInterfaceClass; if (interfaceClass != 0xFF) { //0xA CDC continue; } m_usbdev.bulk_interface = i; for (int k = 0; k < conf_desc->interface[i].altsetting[j].bNumEndpoints; k++) { endpoint = &conf_desc->interface[i].altsetting[j].endpoint[k]; // Use the first bulk IN/OUT endpoints as default for testing if ((endpoint->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) & (LIBUSB_TRANSFER_TYPE_BULK)) {//只获取批量传输端点 if (endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) { if (!endpoint_in) { endpoint_in = endpoint->bEndpointAddress; m_usbdev.endpoint_in = endpoint_in;//赋值 // libusb_clear_halt(handle, bdev->endpoint_in);//清除暂停标志 } } else { if (!endpoint_out) { endpoint_out = endpoint->bEndpointAddress; m_usbdev.endpoint_out = endpoint_out;//赋值 // libusb_clear_halt(handle, bdev->endpoint_out); } } } } break; } } libusb_free_config_descriptor(conf_desc); return m_usbdev.bulk_interface; }
4.2 控制传输
int CLibusbControl::control_write(uint8_t bRequest, void *data, int len, int ms) { libusb_device_handle* handle = m_usbdev.handle; int status = libusb_control_transfer( handle, /* bmRequestType */ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_INTERFACE, /* bRequest */ bRequest, /* wValue */ 0, /* wIndex */ 0, /* Data */ (unsigned char *)data, /* wLength */ len, ms); return status; } int CLibusbControl::control_read(uint8_t bRequest, void **data, int len, int ms) { libusb_device_handle* handle = m_usbdev.handle; int status = libusb_control_transfer( handle, /* bmRequestType */ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_INTERFACE, /* bRequest */ bRequest, /* wValue */ 0, /* wIndex */ 0, /* Data */ (unsigned char*)(*data), /* wLength */ len, ms); return status; }
4.3 批量传输
批量读
/** * @brief bulkusb_read * @param dev * @param buffer * @param len * @param ms * @return */ int CLibusbControl::bulkusb_read(void* buffer, int len, int ms) { int size, errcode; libusb_device_handle* handle = m_usbdev.handle; uint8_t endpoint_in = m_usbdev.endpoint_in; libusb_claim_interface(handle, m_usbdev.bulk_interface); errcode = libusb_bulk_transfer(handle, endpoint_in,(unsigned char*) buffer, len, &size, ms); if (errcode < 0) { printf("read: %s\n", libusb_strerror((enum libusb_error)errcode)); return -1; } int offset = 0, rsize = 0; if (size != len) { offset = len - size; errcode = libusb_bulk_transfer(handle, endpoint_in,(unsigned char*) buffer + offset, offset, &rsize, ms); if (errcode < 0) { printf("read: %s\n", libusb_strerror((enum libusb_error)errcode)); return -1; } } libusb_release_interface(handle, m_usbdev.bulk_interface); return size + offset; }
批量写
/** * @brief bulkusb_write * @param dev * @param buffer * @param len * @param ms * @return */ int CLibusbControl::bulkusb_write(void* buffer, int len, int ms) { int size, errcode; libusb_device_handle* handle = m_usbdev.handle; uint8_t endpoint_out = m_usbdev.endpoint_out; libusb_claim_interface(handle, m_usbdev.bulk_interface); errcode = libusb_bulk_transfer(handle, endpoint_out, (unsigned char*)buffer, len, &size, ms); if (errcode<0) { printf("write: %s\n", libusb_strerror((enum libusb_error)errcode)); return -1; } int offset = 0, rsize = 0; if (size != len) { offset = len - size; errcode = libusb_bulk_transfer(handle, endpoint_out,(unsigned char*) buffer + offset, offset, &rsize, ms); if (errcode < 0) { printf("write: %s\n", libusb_strerror((enum libusb_error)errcode)); return -1; } } libusb_release_interface(handle, m_usbdev.bulk_interface); return size+offset; }
注意读写之前声明一下接口:libusb_claim_interface/libusb_release_interface 读写要校验数据是否读完,特别是当一次读取数据量非常大,并且最后一包小于usb 一帧packsize 时容易少接数据。
5. 总结
本文介绍了 libusb 以及使用方式,对于想学习usb host 开发的可以深入读一下libusb 源码学习一下。
参考:https://libusb.sourceforge.io/api-1.0/
demo:git@github.com:Vinson-001/libusbsample.git