admin 管理员组文章数量: 1184232
彻底掌握USB设备固件升级:基于DFU协议的libusb实现指南
【免费下载链接】libusb A cross-platform library to access USB devices 项目地址: https://gitcode/gh_mirrors/li/libusb
引言:USB固件升级的痛点与解决方案
你是否曾遇到过USB设备固件升级失败导致设备变砖的情况?是否在开发中因不了解DFU协议细节而浪费数周时间?本文将系统讲解如何使用libusb库实现基于DFU(Device Firmware Upgrade,设备固件升级)协议的USB设备固件更新方案,帮助开发者避开常见陷阱,构建稳定可靠的固件升级系统。
读完本文后,你将能够:
- 理解DFU协议的工作原理及状态机模型
- 使用libusb库实现USB设备枚举与通信
- 开发完整的固件升级流程,包括错误处理与恢复机制
- 适配不同类型的USB设备,处理厂商特定扩展
- 构建跨平台的固件升级工具,支持Windows、Linux和macOS
DFU协议基础
DFU协议概述
DFU(Device Firmware Upgrade)是USB论坛制定的标准协议,定义了通过USB接口更新设备固件的方法。该协议包含在USB设备类规范中,设备通过特定的USB描述符声明其DFU能力。
DFU协议的核心优势在于:
- 标准化:遵循USB IF规范,确保不同厂商设备的兼容性
- 可靠性:内置错误检测与恢复机制
- 灵活性:支持多种升级模式和厂商特定扩展
- 安全性:可实现固件验证与加密功能
DFU状态机详解
DFU协议定义了复杂的状态机,设备在升级过程中会在不同状态间转换。理解状态机是实现可靠固件升级的关键。
主要状态说明:
| 状态 | 描述 |
|---|---|
| appIDLE | 设备在应用模式下空闲 |
| appDETACH | 设备正在从应用模式切换到DFU模式 |
| dfuIDLE | DFU模式下空闲,等待命令 |
| dfuDNLOAD_SYNC | 准备接收下载数据 |
| dfuDNBUSY | 正在处理下载的数据 |
| dfuDNLOAD_IDLE | 数据下载完成,等待下一个命令 |
| dfuMANIFEST_SYNC | 准备进行固件验证 |
| dfuMANIFEST | 正在验证固件 |
| dfuMANIFEST_WAIT_RESET | 固件验证完成,等待重置 |
| dfuUPLOAD_IDLE | 准备上传数据 |
| dfuERROR | 发生错误 |
DFU请求类型
DFU协议定义了以下主要请求:
| 请求 | 方向 | 描述 |
|---|---|---|
| DFU_DETACH | 主机到设备 | 请求设备从应用模式切换到DFU模式 |
| DFU_DNLOAD | 主机到设备 | 向设备发送固件数据 |
| DFU_UPLOAD | 设备到主机 | 从设备读取数据(通常用于读取现有固件) |
| DFU_GETSTATUS | 主机到设备 | 获取设备当前状态和状态描述 |
| DFU_CLRSTATUS | 主机到设备 | 清除设备错误状态 |
| DFU_GETSTATE | 主机到设备 | 获取设备当前状态 |
| DFU_ABORT | 主机到设备 | 中止当前操作 |
libusb库基础
libusb简介
libusb是一个跨平台的USB设备访问库,允许应用程序在用户空间与USB设备通信。它支持Windows、Linux、macOS等多种操作系统,提供统一的API接口,屏蔽了不同操作系统底层USB实现的差异。
libusb的核心功能包括:
- USB设备枚举与描述符解析
- 配置USB设备(设置配置、接口、备用设置)
- 控制传输、批量传输、中断传输和等时传输
- 异步I/O操作
- 热插拔事件处理
libusb设备枚举
使用libusb枚举USB设备的基本流程如下:
#include <libusb.h>
#include <stdio.h>
int main() {
libusb_context *ctx = NULL;
libusb_device **devs;
ssize_t cnt;
int r;
// 初始化libusb
r = libusb_init(&ctx);
if (r < 0) {
fprintf(stderr, "libusb初始化失败: %s\n", libusb_strerror(r));
return 1;
}
// 获取设备列表
cnt = libusb_get_device_list(ctx, &devs);
if (cnt < 0) {
fprintf(stderr, "获取设备列表失败: %s\n", libusb_strerror(cnt));
libusb_exit(ctx);
return 1;
}
// 遍历设备列表
for (ssize_t i = 0; i < cnt; i++) {
libusb_device *dev = devs[i];
struct libusb_device_descriptor desc;
r = libusb_get_device_descriptor(dev, &desc);
if (r < 0) {
fprintf(stderr, "获取设备描述符失败: %s\n", libusb_strerror(r));
continue;
}
// 打印设备信息
printf("设备 (总线 %u, 地址 %u): %04X:%04X\n",
libusb_get_bus_number(dev),
libusb_get_device_address(dev),
desc.idVendor, desc.idProduct);
}
// 释放设备列表
libusb_free_device_list(devs, 1);
// 退出libusb
libusb_exit(ctx);
return 0;
}
libusb控制传输
DFU协议主要通过USB控制传输实现通信。libusb提供libusb_control_transfer函数执行控制传输:
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);
参数说明:
| 参数 | 描述 |
|---|---|
| dev_handle | 设备句柄 |
| bmRequestType | 请求类型,包含数据传输方向、类型和接收者 |
| bRequest | 请求代码 |
| wValue | 请求的wValue字段 |
| wIndex | 请求的wIndex字段 |
| data | 数据缓冲区 |
| wLength | 数据长度 |
| timeout | 超时时间(毫秒) |
对于DFU操作,bmRequestType通常设置为LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT(输出)或LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_IN(输入)。
DFU固件升级实现
整体架构
基于libusb的DFU固件升级系统通常包含以下组件:
设备枚举与选择
要实现DFU升级,首先需要找到并打开DFU模式的设备:
libusb_device_handle* open_dfu_device(uint16_t vendor_id, uint16_t product_id) {
libusb_context *ctx = NULL;
libusb_device **devs;
libusb_device_handle *handle = NULL;
ssize_t cnt;
int r;
// 初始化libusb
r = libusb_init(&ctx);
if (r < 0) {
fprintf(stderr, "libusb初始化失败: %s\n", libusb_strerror(r));
return NULL;
}
// 获取设备列表
cnt = libusb_get_device_list(ctx, &devs);
if (cnt < 0) {
fprintf(stderr, "获取设备列表失败: %s\n", libusb_strerror(cnt));
libusb_exit(ctx);
return NULL;
}
// 遍历设备查找目标设备
for (ssize_t i = 0; i < cnt; i++) {
libusb_device *dev = devs[i];
struct libusb_device_descriptor desc;
r = libusb_get_device_descriptor(dev, &desc);
if (r < 0) {
fprintf(stderr, "获取设备描述符失败: %s\n", libusb_strerror(r));
continue;
}
// 检查厂商ID和产品ID是否匹配
if (desc.idVendor == vendor_id && desc.idProduct == product_id) {
// 打开设备
r = libusb_open(dev, &handle);
if (r < 0) {
fprintf(stderr, "打开设备失败: %s\n", libusb_strerror(r));
continue;
}
// 检查设备是否支持DFU接口
struct libusb_config_descriptor *config;
r = libusb_get_config_descriptor(dev, 0, &config);
if (r < 0) {
fprintf(stderr, "获取配置描述符失败: %s\n", libusb_strerror(r));
libusb_close(handle);
handle = NULL;
continue;
}
// 查找DFU接口
bool found_dfu = false;
for (int j = 0; j < config->bNumInterfaces; j++) {
const struct libusb_interface *iface = &config->interface[j];
for (int k = 0; k < iface->num_altsetting; k++) {
const struct libusb_interface_descriptor *altsetting = &iface->altsetting[k];
if (altsetting->bInterfaceClass == 0xFE && // DFU类
altsetting->bInterfaceSubClass == 0x01) { // DFU子类
// 声称接口
r = libusb_claim_interface(handle, altsetting->bInterfaceNumber);
if (r < 0) {
fprintf(stderr, "声称接口失败: %s\n", libusb_strerror(r));
continue;
}
found_dfu = true;
break;
}
}
if (found_dfu) break;
}
libusb_free_config_descriptor(config);
if (!found_dfu) {
fprintf(stderr, "设备不支持DFU接口\n");
libusb_close(handle);
handle = NULL;
}
break;
}
}
// 释放设备列表
libusb_free_device_list(devs, 1);
// 如果没有找到设备,退出libusb上下文
if (!handle) {
libusb_exit(ctx);
}
return handle;
}
固件文件解析
固件文件通常采用Intel HEX格式或原始二进制格式。以下是解析Intel HEX文件的示例代码:
typedef struct {
uint32_t address;
uint8_t *data;
size_t length;
} firmware_segment;
typedef struct {
firmware_segment *segments;
size_t num_segments;
size_t total_size;
} firmware_image;
// 释放固件镜像
void free_firmware_image(firmware_image *image) {
if (image) {
for (size_t i = 0; i < image->num_segments; i++) {
free(image->segments[i].data);
}
free(image->segments);
memset(image, 0, sizeof(firmware_image));
}
}
// 解析Intel HEX文件
int parse_hex_file(const char *filename, firmware_image *image) {
FILE *file = fopen(filename, "r");
if (!file) {
perror("打开固件文件失败");
return -1;
}
memset(image, 0, sizeof(firmware_image));
char line[256];
uint32_t extended_address = 0;
while (fgets(line, sizeof(line), file)) {
// 跳过空行
if (line[0] != ':') continue;
// 解析记录长度
uint8_t len;
sscanf(line + 1, "%02hhx", &len);
// 解析地址
uint16_t addr;
sscanf(line + 3, "%04hx", &addr);
// 解析记录类型
uint8_t type;
sscanf(line + 7, "%02hhx", &type);
// 数据缓冲区
uint8_t data[256];
if (len > 0) {
for (int i = 0; i < len; i++) {
sscanf(line + 9 + i * 2, "%02hhx", &data[i]);
}
}
// 处理不同类型的记录
switch (type) {
case 0x00: // 数据记录
// 扩展段地址
uint32_t absolute_addr = extended_address + addr;
// 检查是否需要创建新段
if (image->num_segments == 0 ||
image->segments[image->num_segments - 1].address +
image->segments[image->num_segments - 1].length != absolute_addr) {
// 扩展段数组
image->segments = realloc(image->segments,
(image->num_segments + 1) * sizeof(firmware_segment));
if (!image->segments) {
perror("内存分配失败");
fclose(file);
free_firmware_image(image);
return -1;
}
// 初始化新段
image->segments[image->num_segments].address = absolute_addr;
image->segments[image->num_segments].data = malloc(len);
if (!image->segments[image->num_segments].data) {
perror("内存分配失败");
fclose(file);
free_firmware_image(image);
return -1;
}
memcpy(image->segments[image->num_segments].data, data, len);
image->segments[image->num_segments].length = len;
image->num_segments++;
image->total_size += len;
} else {
// 追加到现有段
firmware_segment *last_segment = &image->segments[image->num_segments - 1];
last_segment->data = realloc(last_segment->data, last_segment->length + len);
if (!last_segment->data) {
perror("内存分配失败");
fclose(file);
free_firmware_image(image);
return -1;
}
memcpy(last_segment->data + last_segment->length, data, len);
last_segment->length += len;
image->total_size += len;
}
break;
case 0x02: // 扩展段地址记录
extended_address = ((uint32_t)data[0] << 8 | data[1]) << 4;
break;
case 0x04: // 扩展线性地址记录
extended_address = ((uint32_t)data[0] << 8 | data[1]) << 16;
break;
case 0x01: // 文件结束记录
fclose(file);
return 0;
default:
fprintf(stderr, "不支持的HEX记录类型: %02hhx\n", type);
fclose(file);
free_firmware_image(image);
return -1;
}
}
fclose(file);
return 0;
}
DFU状态获取与处理
在DFU升级过程中,需要定期获取设备状态,确保操作成功:
typedef struct {
uint8_t bStatus; // 状态码
uint16_t wPollTimeout; // 等待时间(ms)
uint8_t bState; // 当前状态
uint8_t iString; // 状态描述字符串索引
} dfu_status;
int dfu_get_status(libusb_device_handle *handle, int interface, dfu_status *status) {
uint8_t data[6];
int r = libusb_control_transfer(handle,
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_IN,
DFU_GETSTATUS,
0,
interface,
data,
6,
1000);
if (r != 6) {
fprintf(stderr, "获取DFU状态失败: %s\n", libusb_strerror(r));
return -1;
}
status->bStatus = data[0];
status->wPollTimeout = (data[1] << 16) | (data[2] << 8) | data[3];
status->bState = data[4];
status->iString = data[5];
return 0;
}
int dfu_wait_for_state(libusb_device_handle *handle, int interface, uint8_t target_state) {
dfu_status status;
int retries = 0;
while (retries < 10) {
if (dfu_get_status(handle, interface, &status) < 0) {
return -1;
}
if (status.bState == target_state) {
return 0;
}
if (status.bState == DFU_STATE_ERROR) {
fprintf(stderr, "DFU错误状态: %d\n", status.bStatus);
return -1;
}
// 等待设备指定的时间
if (status.wPollTimeout > 0) {
usleep(status.wPollTimeout * 1000);
} else {
usleep(100000); // 默认等待100ms
}
retries++;
}
fprintf(stderr, "超时等待状态: %d\n", target_state);
return -1;
}
固件下载实现
固件下载是DFU升级的核心步骤,通过DFU_DNLOAD请求将固件数据发送到设备:
int dfu_download_firmware(libusb_device_handle *handle, int interface,
firmware_image *image, progress_callback callback) {
// 发送DFU_DNLOAD请求,传输固件数据
uint16_t block_num = 0;
size_t total_sent = 0;
// 遍历所有段
for (size_t i = 0; i < image->num_segments; i++) {
firmware_segment *seg = &image->segments[i];
uint8_t *data_ptr = seg->data;
size_t remaining = seg->length;
// 按块发送段数据
while (remaining > 0) {
size_t chunk_size = remaining > 1024 ? 1024 : remaining;
// 发送DFU_DNLOAD请求
int r = libusb_control_transfer(handle,
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
DFU_DNLOAD,
block_num,
interface,
data_ptr,
chunk_size,
5000);
if (r < 0) {
fprintf(stderr, "DFU下载失败: %s\n", libusb_strerror(r));
return -1;
}
// 等待设备准备就绪
if (dfu_wait_for_state(handle, interface, DFU_STATE_DNLOAD_IDLE) < 0) {
return -1;
}
// 更新进度
block_num++;
data_ptr += chunk_size;
remaining -= chunk_size;
total_sent += chunk_size;
// 调用进度回调
if (callback) {
callback(total_sent, image->total_size);
}
}
}
// 发送结束块(长度为0的DFU_DNLOAD)
int r = libusb_control_transfer(handle,
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
DFU_DNLOAD,
block_num,
interface,
NULL,
0,
5000);
if (r < 0 && r != LIBUSB_ERROR_NO_DEVICE) {
// 设备可能在收到结束块后重置,所以忽略NO_DEVICE错误
fprintf(stderr, "发送结束块失败: %s\n", libusb_strerror(r));
return -1;
}
// 等待固件验证完成
if (dfu_wait_for_state(handle, interface, DFU_STATE_MANIFEST_WAIT_RESET) < 0) {
return -1;
}
printf("固件下载完成,设备正在重置...\n");
return 0;
}
完整升级流程
整合上述组件,实现完整的DFU固件升级流程:
int dfu_upgrade_firmware(const char *hex_file, uint16_t vendor_id, uint16_t product_id) {
// 1. 解析固件文件
firmware_image image;
if (parse_hex_file(hex_file, &image) < 0) {
fprintf(stderr, "解析固件文件失败\n");
return -1;
}
printf("成功解析固件文件: %zu 段, 总大小: %zu 字节\n",
image.num_segments, image.total_size);
// 2. 打开DFU设备
libusb_device_handle *handle = open_dfu_device(vendor_id, product_id);
if (!handle) {
fprintf(stderr, "打开DFU设备失败\n");
free_firmware_image(&image);
return -1;
}
// 3. 获取接口号(简化处理,实际应从设备描述符获取)
int interface = 0;
// 4. 清除任何先前的错误状态
int r = libusb_control_transfer(handle,
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
DFU_CLRSTATUS,
0,
interface,
NULL,
0,
1000);
if (r < 0) {
fprintf(stderr, "清除DFU状态失败: %s\n", libusb_strerror(r));
libusb_close(handle);
free_firmware_image(&image);
return -1;
}
// 5. 等待设备进入空闲状态
if (dfu_wait_for_state(handle, interface, DFU_STATE_IDLE) < 0) {
libusb_close(handle);
free_firmware_image(&image);
return -1;
}
// 6. 下载并升级固件
printf("开始固件升级...\n");
if (dfu_download_firmware(handle, interface, &image, progress_callback) < 0) {
fprintf(stderr, "固件下载失败\n");
libusb_close(handle);
free_firmware_image(&image);
return -1;
}
// 7. 清理资源
libusb_close(handle);
free_firmware_image(&image);
printf("固件升级成功完成\n");
return 0;
}
错误处理与恢复
常见错误及解决方案
DFU固件升级过程中可能遇到多种错误,需要妥善处理:
| 错误 | 原因 | 解决方案 |
|---|---|---|
| 设备无法枚举 | USB连接问题或设备未进入DFU模式 | 检查USB连接,确保设备已进入DFU模式 |
| 无法声称接口 | 接口被其他驱动占用 | 在Linux上使用udev规则,在Windows上使用Zadig更换驱动 |
| 控制传输失败 | 设备不响应或USB通信错误 | 重试操作,检查USB线缆和端口 |
| 固件验证失败 | 固件损坏或不兼容 | 验证固件文件完整性,检查设备型号是否匹配 |
| 升级后设备无响应 | 固件错误或不兼容 | 进入恢复模式重新升级,使用出厂固件 |
设备恢复机制
实现设备恢复机制,处理升级失败的情况:
int dfu_restore_device(uint16_t vendor_id, uint16_t product_id, const char *recovery_firmware) {
printf("尝试恢复设备...\n");
// 1. 强制设备进入DFU模式(某些设备支持硬件复位)
// 注意:这部分因设备而异,可能需要特定的硬件操作
// 2. 等待设备重新枚举
sleep(2);
// 3. 使用恢复固件重新升级
return dfu_upgrade_firmware(recovery_firmware, vendor_id, product_id);
}
厂商特定扩展
不同厂商可能对DFU协议进行扩展,以支持特定功能。例如,Cypress EZ-USB系列设备使用自定义的厂商请求进行固件升级。
libusb示例中提供了ezusb.c和ezusb.h文件,实现了对Cypress EZ-USB设备的支持。核心函数ezusb_load_ram用于将固件加载到设备RAM中:
int ezusb_load_ram(libusb_device_handle *device, const char *path, int fx_type, int img_type, int stage);
该函数支持多种Cypress设备类型(FX_TYPE_AN21、FX_TYPE_FX2、FX_TYPE_FX2LP等)和固件格式(Intel HEX、Cypress IIC等),并通过厂商特定请求(如0xA0和0xA3)实现固件下载。
使用示例:
// 加载固件到Cypress FX2LP设备
int load_cypress_firmware(libusb_device_handle *handle, const char *firmware_path) {
return ezusb_load_ram(handle, firmware_path, FX_TYPE_FX2LP, IMG_TYPE_HEX, 0);
}
跨平台实现注意事项
Windows平台
在Windows上使用libusb需要注意:
- 安装合适的设备驱动(使用Zadig工具安装libusb-win32或libusbK驱动)
- 处理权限问题,确保应用程序有足够权限访问USB设备
- 注意32位和64位版本的兼容性
Linux平台
Linux平台注意事项:
- 创建udev规则文件,设置设备权限:
SUBSYSTEM=="usb", ATTR{idVendor}=="0483", ATTR{idProduct}=="df11", MODE="0666" - 确保用户属于plugdev组,以获得USB设备访问权限
- 处理USB设备热插拔事件
macOS平台
macOS平台注意事项:
- 禁用系统内核扩展对特定USB设备的独占访问
- 处理应用程序沙箱权限,确保有权限访问USB设备
- 使用libusb的macOS特定API处理某些特殊设备
总结与最佳实践
开发最佳实践
- 设备兼容性:支持多种USB速度(低速、全速、高速、超高速)和设备类型
- 错误处理:实现全面的错误检测和恢复机制,处理USB传输错误和设备超时
- 进度反馈:提供详细的升级进度反馈,让用户了解当前状态
- 固件验证:在升级前验证固件文件的完整性和兼容性
- 日志记录:记录详细的升级日志,便于问题诊断
- 安全措施:实现固件签名验证,防止恶意固件加载
性能优化
- 批量传输:尽可能使用最大数据包大小,减少USB传输次数
- 并行处理:在等待设备时执行其他任务,但注意线程安全
- 超时优化:根据设备特性调整超时时间,避免不必要的等待
- 数据缓存:合理缓存固件数据,减少文件I/O操作
后续扩展方向
- 图形界面:开发跨平台图形界面,提升用户体验
- 远程升级:实现通过网络进行远程固件升级
- 多设备升级:支持同时升级多个USB设备
- 固件管理:实现固件版本管理、回滚和发布功能
- 安全增强:添加加密传输和身份验证功能
通过本文介绍的方法和代码示例,开发者可以构建可靠的基于DFU协议的USB设备固件升级系统。无论是标准化的DFU设备还是厂商特定的USB设备,libusb库都提供了灵活强大的API,简化USB通信的复杂性,加速固件升级功能的开发。
收藏与关注
如果本文对你有帮助,请点赞、收藏并关注作者,获取更多USB开发和嵌入式系统相关的技术文章。下期将介绍"USB设备热插拔事件处理与多设备管理",敬请期待!
【免费下载链接】libusb A cross-platform library to access USB devices 项目地址: https://gitcode/gh_mirrors/li/libusb
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
版权声明:本文标题:彻底掌握USB设备固件升级:基于DFU协议的libusb实现指南 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.roclinux.cn/b/1759970792a3141350.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论