十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

GD32F470 USB HOST与U盘IAP固件升级实战指南

GD32F470 USB HOST与U盘IAP固件升级实战指南 简介面向嵌入式开发者的GD32F470 USB Host实战资源演示用C语言驱动USB主机读写U盘并实现基于U盘的IAP固件升级适合需要掌握GD32 USB OTG与Bootloader设计的工程师。压缩包共180个文件以87个h头文件、74个c源文件和6个汇编s文件为主涵盖USB主机协议栈、GD32外设驱动、FatFs文件系统及IAP升级示例另有IAR工程配置与说明文档整体约3.04MB。已有549人学习下载。资源不仅包含可直接编译的IAR V9.30.1工程还提供设备枚举、批量传输、固件校验和Flash编程等关键实现配合PDF说明可快速理解从U盘读取固件到跳转运行的完整链路能显著缩短相关项目开发周期。1. 为什么要在 GD32F470 上把 USB HOST 和 U 盘 IAP 绑在一起设备已经出厂、板子上没有预留调试串口、现场又拉不出网线这种场景里你手里唯一能用的外设往往就是那个 USB 口。GD32F470 这颗国产 Cortex-M4 内置 USB OTG 控制器C 语言环境下把它配成 USB HOST就能直接读写 U 盘中的文件。把这份能力跟 IAP 结合升级固件就变成了“拷贝一个 bin 文件到 U 盘插上去等几秒”的操作不需要上位机不需要厂内专用烧录器产线和售后都能自己动手。这条路对已经有一定嵌入式基础、想在自己板子上跑通 U 盘升级的工程师最实用。真正落地时你会遇到的不是“能不能读”而是枚举失败、FATFS 挂载报错、擦写 Flash 时间不对、掉电导致 App 起不来这一串连带问题。本文按硬件初始化 → U 盘文件读写 → IAP 升级流程 → 现场排错的顺序讲清楚。2. GD32F470 USB 主机初始化从 OTG 到 U 盘枚举USB 主机和 USB 从机的初始化思路差异很大。从机只要被动响应枚举即可主机却要主动检测设备、下发复位、配置地址、读取描述符最后拿到 Bulk 端点才能跟 U 盘通信。GD32F470 的 OTG 控制器可以做主机也可以在主机和从机之间切换。实际项目中我一般不会手写完整主机栈而是用开源的 USB Host 库或芯片厂提供的 Host 驱动C 语言侧只做硬件适配和转接。2.1 硬件连接、电源和相关引脚GD32F470 做主机的硬件比做从机多几个关键点VBUS 电源、ID 引脚、D/D- 差分对以及 48MHz 时钟。VBUS 在 HOST 模式下必须由外部电路供电不能直接从 MCU 的 3.3V LDO 拉因为 U 盘启动瞬间电流可能超过 100mA满载时接近 500mA需要一颗带限流和过流保护的负载开关比如 AP2141、TPS2051。软件在枚举前打开 VBUS检测到过流中断后立即断开防止短路烧坏主板。D/D- 靠近 MCU 端要串接 22Ω 电阻做阻抗匹配。很多板子为了省事不串电阻短距离调试时看似正常一旦线一长或者 U 盘种类变多位错误率会明显上升。时钟方面USB 全速要求 48MHzGD32F470 可以从外部晶振经过 PLL 得到也可以用内部 HSI 校准但要量产建议直接外部晶振误差控制在 0.25% 以内。初始化代码我常写成下面这样以 GD32F470 标准外设库风格为例void usb_host_hw_init(void) { /* 打开 USB 和 GPIOA 时钟 */ rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_USB0); /* OTG_FS 的 DM/DP 在 PA11/PA12复用功能号按芯片手册确认 */ gpio_af_set(GPIOA, GPIO_AF_12, GPIO_PIN_11 | GPIO_PIN_12); gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_11 | GPIO_PIN_12); gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_11 | GPIO_PIN_12); /* 先让 USB 控制器处于复位态再使能避免上电时序异常 */ usb_global_disable(); usb_core_reset(); usb_global_enable(); }这里的要点在最后三步组合上电瞬间 VUSB 可能不稳定先关闭控制器、做一次软复位、再开启能减少“有时识别 U 盘有时不识别”的问题。GPIO_AF_12只是参考值不同封装和复用表要按实际芯片手册调整。如果你用的是 RT-Thread Studio 或者 SDK 自动生成的工程这一步通常会放在驱动初始化里不需要你手写。2.2 强制主机模式与枚举状态机OTG 控制器默认根据 ID 引脚的电平判断设备角色。ID 接地是 A 设备主机ID 悬空是 B 设备从机。在自定义主板中ID 引脚一般是固定下拉的但做产品时我会在软件里强制主机模式避免硬件线路被干扰后角色翻转。usb_otg_core_configure(USB0, USB_FORCE_HOST); usb_otg_power_on_vbus(USB0); usb_host_attach_and_enum();第一行强制 HOST第二行打开 VBUS 给 U 盘供电第三行进入枚举流程。usb_host_attach_and_enum()是 Host 库提供的阻塞函数内部会完成检测设备插入、下发 USB 总线复位、设置地址、读取描述符、配置设备等流程。阻塞模式简单适合裸机 Bootloader如果你在 RTOS 里跑建议改成事件驱动和回调防止枚举阶段把系统卡死。2.3 枚举 U 盘的关键参数与状态表设备插入后MCU 需要在控制传输的EP0上完成一系列标准请求。这些请求的时序和返回内容决定了后续 Bulk 传输能否工作。枚举阶段Host 发出的控制请求需要记录的参数失败原因读设备描述符GET_DESCRIPTOR(Device)bMaxPacketSize0设备无响应设置地址SET_ADDRESS分配地址 1地址冲突读配置描述符GET_DESCRIPTOR(Config)bNumInterfaces, Bulk EP 地址描述符过长截断配置设备SET_CONFIGURATION完成设备配置驱动不支持BOT 初始化MASS_STORAGE_RESET / GET_MAX_LUNLUN 数量设备不支持 MSD对 U 盘来说我们要在配置描述符里找到接口类bInterfaceClass等于 8 的 Mass Storage 接口再拿到 Bulk OUT 和 Bulk IN 两个端点地址。很多 U 盘在产品端还有子类 6 和协议 0x50 的 BOT 协议。如果枚举后无法读写先用抓包工具或调试串口打印解析出的端点信息确认是否错把中断端点当成批量端点。3. C 语言中 U 盘文件读写挂载、FATFS 和 FAT 文件操作USB 枚举只解决了“能看到设备”文件级操作要靠文件系统把扇区变成目录和文件。当前主流做法是搭配 FATFS它体积小、无内存碎片C 语言提供 API 友好适合 GD32F470 这种中等资源的 MCU。整个数据流是FATFS 调用磁盘 IO 接口磁盘 IO 接口调用 USB Host 的 MSD 驱动MSD 驱动通过 Bulk 端点读扇区。3.1 集成 FATFS 并实现 USB 主机块设备驱动FATFS 属于上层逻辑完全不知道底层是 U 盘、SD 卡还是 Flash。它靠几个底层函数完成物理读写最核心的是disk_read和disk_write。把 USB Host 的 MSD 驱动映射到这两个函数上就能让 FATFS 识别到 U 盘。DRESULT disk_read(BYTE pdrv, BYTE *buff, LBA_t sector, UINT count) { int ret usb_host_msd_read(0, sector, buff, count); if (ret 0) { return RES_OK; } return RES_ERROR; }usb_host_msd_read是 Host 库提供的读扇区函数0表示第一个 LUN逻辑单元。这里的sector是逻辑块地址FATFS 默认按照 512 字节扇区工作。如果你的 U 盘逻辑扇区是 4KB一定要在disk_ioctl里实现GET_SECTOR_SIZE并且把 FATFS 的_MIN_SS和_MAX_SS编译宏改成 4096否则读取目录时会错乱。3.2 遍历 U 盘、打开文件、读写数据挂载 U 盘的最小 C 代码路径如下FATFS fs; FIL fp; uint8_t buf[1024]; UINT bw; FRESULT res f_mount(fs, 0:, 1); if (res FR_OK) { res f_open(fp, 0:/fw.bin, FA_READ | FA_OPEN_EXISTING); while (f_read(fp, buf, sizeof(buf), bw) FR_OK bw 0) { /* 这里每次读 1KB分包写入 Flash */ flash_write_part(APP_ADDR offset, buf, bw); offset bw; } f_close(fp); } f_mount(NULL, 0:, 0);f_mount的第三个参数是挂载模式1表示立即挂载如果 U 盘未就绪会返回FR_NOT_READY0表示延迟挂载直到首次访问时才真正挂载适合不确定 U 盘是否插好的场景。f_open里的路径0:是卷名底层对应disk_read的pdrv多路存储时要小心对应关系。最后一行卸载 FATFS让文件系统释放内部状态热插拔时很有必要。如果你希望先读取 U 盘上的版本号文件比如version.txt再决定是否升级可以在打开fw.bin之前先用f_open读取版本文件。这样能在不擦写 Flash 的前提下提前拦截“版本已是最新”的情况缩短升级时间。3.3 常见错误和错误处理FATFS 的返回值很直观但嵌入式开发里往往判断不够细节。返回值含义处理建议FR_NOT_READY设备未准备好检查 VBUS 供电、U 盘是否枚举成功FR_NO_FILESYSTEMU 盘没有 FAT/FAT32 分区提示用户重新格式化FR_INVALID_OBJECT文件句柄无效查看 f_open 是否失败或被拔盘FR_DENIED文件权限不允许只读挂载时执行了写操作FR_MKFS_ABORTED格式化失败确认扇区大小参数错误处理不要只停留在打印返回值要结合 USB Host 的状态。比如FR_NOT_READY大概率不是文件系统问题而是 U 盘枚举后进入了异常状态。出现这种情况我会先调用 Host 库的断开函数再重新执行枚举而不是直接重试 FATFS 操作。4. 使用 U 盘进行 IAP 升级从启动代码、映像验证到跳转U 盘 IAP 的本质是 Bootloader 把外部存储中的固件映像搬运到内部 Flash。只做搬运是不够的必须考虑防呆如果固件文件本身损坏或者下载过程中断了电设备不能因此变成砖头。IAP 结构里最常用的方案是 Bootloader APP 两级分区并在固定偏移处存放描述信息。4.1 划分闪存与 Bootloader/APP/标志位GD32F470 的内部 Flash 从0x08000000开始。Bootloader 区域存放 USB Host 驱动、FATFS、Flash 写函数APP 区域存放业务程序之后单独划分 APP 信息区。信息区不存放代码只写版本号、固件长度、CRC、启动标志等元数据。Flash 区域起始地址大小用途Bootloader0x0800000032KB做枚举、文件读写、Flash 写入APP 主区0x08008000512KB业务固件APP 信息区0x080F00004KB固件元数据与启动标志备份 APP 区0x080A0000512KB回滚用的上一个有效版本APP 区内偏移 0 处必须有中断向量表APP 启动的第一件事是重定位向量表基地址否则任何中断都会跳回 Bootloader 的向量区然后跑飞。#define APP_ADDR (0x08000000 32 * 1024) void jump_to_app(void) { uint32_t app_stack *(volatile uint32_t *)APP_ADDR; uint32_t app_reset *(volatile uint32_t *)(APP_ADDR 4); /* 检查复位向量是否落在合法的 Flash 范围避免跳到空地址 */ if (app_reset 0x08000000 || app_reset 0x080FFFFF) { return; } SCB-VTOR APP_ADDR; __set_MSP(app_stack); ((void (*)(void))app_reset)(); }代码开头读取的两个uint32_t分别是 APP 的栈顶指针和复位函数地址。__set_MSP会把主栈指针切换到 APP 需要的栈顶对 Cortex-M 来说很关键因为 APP 如果有全新的堆栈布局沿用 Bootloader 的栈指针会产生不可预期的栈溢出。4.2 从 U 盘读取固件并写入闪存U 盘里的固件文件我一般会在编译时加上自定义头避免 Bootloader 每次重复校验全文件。头结构如下typedef struct { uint32_t magic; /* 固定魔数 0xCAFE1234 */ uint32_t length; /* 有效固件长度 */ uint32_t crc32; /* CRC32 校验值 */ } fw_header_t;Bootloader 读取头后先判断magic再根据length循环读文件内容写入 Flash。GD32F470 的 Flash 擦除粒度通常是 4KB 到 64KB 不等写入前必须把目标扇区先擦除否则写入数据是旧的。写函数要做 32 位对齐处理最后一个不满 4 字节的包补 0xFF。uint32_t offset 0; UINT bw 0; res f_open(fp, 0:/fw.bin, FA_READ); while (f_read(fp, buf, sizeof(buf), bw) FR_OK bw 0) { /* 每个 1KB 分包调用 Flash 写函数 */ if (flash_write_part(APP_ADDR offset, buf, bw) ! 0) { f_close(fp); return -1; } offset bw; } f_close(fp);flash_write_part内部会处理对齐和地址超限判断。要注意的是写入过程中不能被打断否则 Flash 控制器会出现不稳定状态。我一般在调用前__disable_irq()写完后重新使能中断这样能避免 USB 中断或其他外设中断把擦写时序拆碎。4.3 映像完整性验证和 IAP 回滚固件写入结束后Bootloader 应重新读取 Flash 内容计算 CRC与 U 盘文件中的crc32做对比。只有校验通过才把 APP 信息区设置为“新固件有效”然后再跳转。uint32_t calc_crc crc32_flash(APP_ADDR, fw_header.length); if (calc_crc ! fw_header.crc32) { set_boot_state(BOOT_ROLLBACK_NEW); jump_to_app(); } else { set_boot_state(BOOT_FROM_NEW); jump_to_app(); }回滚用最简洁的“双槽 启动标志”方案即可。上一版固件在备份 APP 区保留APP 信息区里的boot_index决定启动哪一个槽位。如果新固件校验失败boot_index被置 0下次上电跳回备份区设备至少能维持可运行状态。如果没有备份区也可以做简化版CRC 失败后不跳转继续留在 Bootloader等待用户重新插 U 盘。这个方案的缺点是一旦现场没有 U 盘设备就停摆了双备份对量产产品更稳妥。5. 调试、热插拔和优化 U 盘 IAP 的现有陷阱U 盘 IAP 初次跑通后真正麻烦的是各种边界条件和现场行为。这里整理三个我实际踩过最多的点。5.1 枚举失败电源、上拉和 U 盘类型枚举失败先看 VBUS 电压有没有掉到 4.5V 以下。很多 U 盘启动瞬间会拉低 VBUS如果负载开关限流值设得太小5V 会直接掉到 3.8V导致设备进入欠压保护。对策是选额定电流不低于 500mA 的开关并且 VBUS 电容稍微加大例如 47µF 电解电容并联 100nF 陶瓷电容。D/D- 的上拉不要刻意去做。主机模式下D 和 D- 的下拉由控制器内部承担设备端会拉高别在 MCU 侧额外把 D/D- 接到 3.3V否则设备速率检测会出错。U 盘种类方面一些老式 USB 2.0 U 盘对 BOT 时序要求很高枚举成功后第一次GET_MAX_LUN请求如果失败了我再重试两次依旧失败就提示用户更换 U 盘。5.2 热插拔问题与文件系统卸载U 盘拔掉的一瞬间FATFS 的内部文件句柄和目录缓冲还留着旧状态。如果不卸载就直接重新挂载可能出现FR_INVALID_OBJECT或者文件写入了一半的问题。正确流程是在 USB Host 的断开回调里先卸载文件系统再关闭 USB 控制器。检测到断开时执行if (usb_host_is_disconnected(USB0)) { f_mount(NULL, 0:, 0); usb_host_disable(); set_iap_status(IAP_STATUS_NO_DISK); }这里的关键是f_mount的第一个参数传NULLFATFS 会释放对应卷的挂载信息并关闭所有打开的目录和文件。之后 USB 控制器断开防止控制器继续输出信号。5.3 优化升级耗时、防止掉电写挂写入 512KB 固件用 1KB 分包写整片擦除和写入时间通常在 20 秒到 40 秒之间。多数时间其实花在擦除上优化办法是只擦除实际用到的扇区。先按f_read读到的长度计算出需要多少扇区再逐个擦除避免整片 Flash 都擦一遍。擦除动作优先选择大扇区模式GD32F470 的静态擦除比逐页擦除快不少。掉电写挂的核心防护是“先写信息区再跳转”。也就是 CRC 校验完成后先在 APP 信息区写入BOOT_STATE_ARMED表示“固件已准备好等待跳转”再跳转到 APP。如果写的过程中掉电重新上电后 Bootloader 看到BOOT_STATE_ARMED就知道有未完成的升级会再次尝试从 U 盘读取而不是直接进入一个不完整的 App。这个状态机比单纯依赖 CRC 判断灾难恢复要可靠得多。本文还有配套的精品资源点击获取
返回列表