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模式
dfuIDLEDFU模式下空闲,等待命令
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.cezusb.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需要注意:

  1. 安装合适的设备驱动(使用Zadig工具安装libusb-win32或libusbK驱动)
  2. 处理权限问题,确保应用程序有足够权限访问USB设备
  3. 注意32位和64位版本的兼容性

Linux平台

Linux平台注意事项:

  1. 创建udev规则文件,设置设备权限:
    SUBSYSTEM=="usb", ATTR{idVendor}=="0483", ATTR{idProduct}=="df11", MODE="0666"
    
  2. 确保用户属于plugdev组,以获得USB设备访问权限
  3. 处理USB设备热插拔事件

macOS平台

macOS平台注意事项:

  1. 禁用系统内核扩展对特定USB设备的独占访问
  2. 处理应用程序沙箱权限,确保有权限访问USB设备
  3. 使用libusb的macOS特定API处理某些特殊设备

总结与最佳实践

开发最佳实践

  1. 设备兼容性:支持多种USB速度(低速、全速、高速、超高速)和设备类型
  2. 错误处理:实现全面的错误检测和恢复机制,处理USB传输错误和设备超时
  3. 进度反馈:提供详细的升级进度反馈,让用户了解当前状态
  4. 固件验证:在升级前验证固件文件的完整性和兼容性
  5. 日志记录:记录详细的升级日志,便于问题诊断
  6. 安全措施:实现固件签名验证,防止恶意固件加载

性能优化

  1. 批量传输:尽可能使用最大数据包大小,减少USB传输次数
  2. 并行处理:在等待设备时执行其他任务,但注意线程安全
  3. 超时优化:根据设备特性调整超时时间,避免不必要的等待
  4. 数据缓存:合理缓存固件数据,减少文件I/O操作

后续扩展方向

  1. 图形界面:开发跨平台图形界面,提升用户体验
  2. 远程升级:实现通过网络进行远程固件升级
  3. 多设备升级:支持同时升级多个USB设备
  4. 固件管理:实现固件版本管理、回滚和发布功能
  5. 安全增强:添加加密传输和身份验证功能

通过本文介绍的方法和代码示例,开发者可以构建可靠的基于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