USB(Universal Serial Bus)通信协议是一种广泛应用于计算机和外部设备之间的串行通信协议,具有热插拔、即插即用、高速传输等特点。
基本架构
- 主机:通常是计算机或其他具有USB主机控制器的设备,负责管理和控制USB总线上的所有通信。
- 设备:连接到USB总线上的各种外部设备,如鼠标、键盘、打印机、U盘等。每个设备都有唯一的地址和设备描述符,用于向主机标识自己的功能和特性。
- 集线器:用于扩展USB接口数量,允许多个设备连接到同一个USB总线上。集线器可以是内置在主机中的,也可以是独立的外部设备。
数据传输类型
- 控制传输:主要用于主机与设备之间的配置、命令和状态信息的传输。例如,当设备插入USB接口时,主机通过控制传输获取设备的描述符,了解设备的功能和需求,并对设备进行配置。
- 批量传输:适用于大量数据的传输,如U盘的数据读写。这种传输类型具有较高的数据传输效率,但不保证实时性,数据会在总线上排队等待传输。
- 中断传输:通常用于需要及时响应的设备,如鼠标和键盘。设备会定期向主机发送中断请求,主机在收到请求后会立即处理,以保证设备的实时性。
- 等时传输:用于对时间要求严格的实时数据传输,如音频和视频数据。它以固定的时间间隔传输数据,保证数据的连续性和实时性,但不保证数据的准确性,可能会出现少量的数据丢失或错误。
传输过程
- 建立连接:当设备插入USB接口时,主机通过检测USB总线上的电平变化来发现新设备,并通过一系列的控制传输来获取设备的描述符,进行设备枚举和配置,为设备分配地址和资源。
- 数据传输:在设备配置完成后,主机和设备之间就可以进行数据传输了。根据不同的传输类型,数据会按照相应的协议格式进行打包和传输。例如,批量传输的数据会被分成多个数据包,每个数据包包含数据和相关的控制信息,然后通过USB总线发送到目标设备。
- 传输结束:当数据传输完成后,主机或设备会发送相应的结束信号,通知对方传输结束。设备可以选择继续保持连接,等待下一次数据传输,也可以断开连接,节省能源。
数据包格式
- 令牌包:用于标识数据传输的类型、方向和设备地址等信息,是数据传输的起始标志。常见的令牌包有输入令牌包(IN)、输出令牌包(OUT)和设置令牌包(SETUP)等。
- 数据包:包含实际要传输的数据内容。根据传输类型和数据量的不同,数据包的大小和格式也有所不同。例如,批量传输的数据包可以包含多个字节的数据,而控制传输的数据包通常较小,只包含一些命令和参数。
- 握手包:用于确认数据的接收和传输状态。常见的握手包有确认包(ACK)、否定确认包(NAK)和错误包(STALL)等。当设备成功接收到数据时,会发送ACK包给主机;如果数据接收错误或设备忙,则发送NAK包;如果设备出现严重错误,无法继续传输,则发送STALL包。
电源管理
- USB总线为设备提供电源:设备可以从USB总线上获取电力,以满足其工作需求。根据USB标准,每个USB端口可以提供一定的电流,如USB 2.0端口通常可以提供500mA的电流,USB 3.0端口可以提供900mA的电流。
- 设备的电源管理:设备可以根据自身的工作状态和需求,向主机请求不同的电源模式,如待机、睡眠和唤醒等。主机也可以通过发送电源管理命令来控制设备的电源状态,以节省能源。
以下是一个使用 libusb
库实现简单 USB 通信的 C++ 示例代码。libusb
是一个跨平台的库,用于访问 USB 设备。
代码示例
#include <iostream>
#include <libusb-1.0/libusb.h>
#define VENDOR_ID 0xXXXX // 替换为实际的厂商 ID
#define PRODUCT_ID 0xXXXX // 替换为实际的产品 ID
int main() {
libusb_context *ctx = NULL;
libusb_device_handle *dev_handle = NULL;
int r;
// 初始化 libusb
r = libusb_init(&ctx);
if (r < 0) {
std::cerr << "初始化 libusb 失败: " << libusb_error_name(r) << std::endl;
return 1;
}
// 打开指定的 USB 设备
dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID);
if (dev_handle == NULL) {
std::cerr << "无法打开 USB 设备" << std::endl;
libusb_exit(ctx);
return 1;
}
// 检查设备是否已被内核驱动占用,如果是则释放
if (libusb_kernel_driver_active(dev_handle, 0) == 1) {
r = libusb_detach_kernel_driver(dev_handle, 0);
if (r < 0) {
std::cerr << "无法释放内核驱动: " << libusb_error_name(r) << std::endl;
libusb_close(dev_handle);
libusb_exit(ctx);
return 1;
}
}
// 声明接口
r = libusb_claim_interface(dev_handle, 0);
if (r < 0) {
std::cerr << "无法声明接口: " << libusb_error_name(r) << std::endl;
libusb_close(dev_handle);
libusb_exit(ctx);
return 1;
}
// 发送数据
unsigned char data_to_send[] = {0x01, 0x02, 0x03, 0x04};
int transferred;
r = libusb_bulk_transfer(dev_handle, 0x01, data_to_send, sizeof(data_to_send), &transferred, 5000);
if (r < 0) {
std::cerr << "数据发送失败: " << libusb_error_name(r) << std::endl;
} else {
std::cout << "成功发送 " << transferred << " 字节数据" << std::endl;
}
// 接收数据
unsigned char received_data[64];
r = libusb_bulk_transfer(dev_handle, 0x81, received_data, sizeof(received_data), &transferred, 5000);
if (r < 0) {
std::cerr << "数据接收失败: " << libusb_error_name(r) << std::endl;
} else {
std::cout << "成功接收 " << transferred << " 字节数据: ";
for (int i = 0; i < transferred; ++i) {
std::cout << std::hex << static_cast<int>(received_data[i]) << " ";
}
std::cout << std::endl;
}
// 释放接口
libusb_release_interface(dev_handle, 0);
// 关闭设备
libusb_close(dev_handle);
// 退出 libusb
libusb_exit(ctx);
return 0;
}
代码解释
- 初始化
libusb
:调用libusb_init
函数来初始化libusb
库。 - 打开 USB 设备:使用
libusb_open_device_with_vid_pid
函数根据厂商 ID 和产品 ID 打开指定的 USB 设备。 - 释放内核驱动:检查设备是否被内核驱动占用,如果是,则使用
libusb_detach_kernel_driver
函数释放内核驱动。 - 声明接口:使用
libusb_claim_interface
函数声明要使用的 USB 接口。 - 数据传输:
- 发送数据:使用
libusb_bulk_transfer
函数进行批量数据发送。 - 接收数据:同样使用
libusb_bulk_transfer
函数进行批量数据接收。
- 发送数据:使用
- 释放资源:
- 使用
libusb_release_interface
函数释放之前声明的接口。 - 使用
libusb_close
函数关闭 USB 设备。 - 使用
libusb_exit
函数退出libusb
库。
- 使用
注意事项
- 你需要将
VENDOR_ID
和PRODUCT_ID
替换为你实际要通信的 USB 设备的厂商 ID 和产品 ID。 - 编译代码时需要链接
libusb
库,例如使用以下命令:
g++ -o usb_demo usb_communication_demo.cpp -lusb-1.0
- 运行代码可能需要管理员权限,因为访问 USB 设备通常需要较高的权限。在 Linux 系统中,可以使用
sudo
来运行程序。