1. 硬件与软件环境搭建搞嵌入式开发的朋友都知道环境搭建是第一步也是最关键的一步。这次我们用的是华大HC32F460这款性能不错的MCU搭配RT-Thread这个国产实时操作系统。说实话第一次用这个组合时我也踩了不少坑现在把这些经验都分享出来。先说说硬件准备。我用的是官方HC32F460评估板这个板子自带USB接口调试起来特别方便。U盘建议选个质量好点的最好是知名品牌我用的是闪迪32GB FAT32格式的U盘。这里有个小技巧U盘容量不要太大32GB以内最稳妥太大容量的U盘有时候会出现兼容性问题。软件环境方面Keil MDK 5.29是我的首选IDE毕竟用习惯了。RT-Thread我选的是4.0.0.1标准版这个版本稳定性不错。安装时要注意RT-Thread的pack包需要单独下载安装建议直接从官网获取最新版本。装好之后记得在Keil里把HC32F460的Device选对这个细节很多人容易忽略。2. USB主机驱动移植实战2.1 驱动库的获取与配置华大官方提供的USB驱动库确实挺良心的基本功能都封装好了。我用的V2.2.0版本直接从官网下载就行。这里要注意的是USB库文件要整个拷贝到工程目录下不要只复制部分文件否则容易出问题。驱动移植主要修改几个关键文件usb_bsp.c这个文件负责底层硬件初始化usb_host_user.c处理主机模式下的应用逻辑时钟配置相关文件在RT-Thread环境下移植时要特别注意系统滴答时钟的处理。因为RT-Thread自己已经实现了SysTick_Handler所以要把驱动库里的屏蔽掉否则会冲突。我当初就是没注意这点调试了好久才发现问题。2.2 时钟配置详解时钟配置是USB工作的关键。HC32F460的USB模块需要48MHz时钟这个一定要配置正确。我在board.c里是这样设置的void ClockInit(void) { stc_clk_mpll_cfg_t stcMpllCfg; MEM_ZERO_STRUCT(stcMpllCfg); /* MPLL配置 */ stcMpllCfg.pllmDiv 1u; stcMpllCfg.plln 42u; stcMpllCfg.PllpDiv 2u; CLK_SetPllSource(ClkPllSrcXTAL); CLK_MpllConfig(stcMpllCfg); CLK_MpllCmd(Enable); /* 等待MPLL就绪 */ while(Set ! CLK_GetFlagStatus(ClkFlagMPLLRdy)); /* 设置USB时钟源 */ CLK_SetUsbClkSource(ClkUsbSrcMpllr); }这个配置确保了USB模块能得到稳定的48MHz时钟。实际调试时可以用示波器测量一下时钟信号确保频率准确。3. FatFs文件系统集成3.1 FatFs组件配置RT-Thread已经集成了FatFs组件这给我们省了不少事。在menuconfig里找到以下配置项并启用RT-Thread Components → Device virtual file systemRT-Thread Components → Device virtual file system → Enable elm-chan fatfsFatFs版本是R0.12b如果需要新版本可以手动替换不过我觉得这个版本已经够用了。配置好后要特别注意USB驱动和FatFs的对接部分。3.2 磁盘接口实现FatFs通过五个标准函数访问存储设备disk_initialize初始化磁盘disk_status获取磁盘状态disk_read读取扇区disk_write写入扇区disk_ioctl控制命令华大驱动库已经实现了这些函数位于usb_host_msc_fatfs.c文件中。我们需要在dfs_elm.c里屏蔽原有的实现改用华大的版本。这里有个技巧可以用条件编译来灵活切换实现方式。/* 在dfs_elm.c中添加 */ #define USE_HC32_USB_DRIVER 1 #if USE_HC32_USB_DRIVER #include usb_host_msc_fatfs.h #endif4. 固件升级方案设计4.1 升级文件格式设计为了实现可靠的固件升级我设计了一个简单的文件格式文件头包含魔数、版本号、文件大小等信息固件数据实际的程序二进制数据校验和CRC32校验值升级文件建议放在U盘根目录下命名为firmware.bin。这样查找起来方便也不容易搞错。4.2 升级流程实现完整的升级流程分为以下几个步骤检测U盘插入通过USB主机回调函数检测设备连接挂载文件系统使用dfs_mount挂载U盘查找升级文件遍历目录寻找firmware.bin验证文件有效性检查文件头、校验和擦除Flash准备写入新固件写入新固件分块写入每块都做校验重启设备完成升级这里给出关键代码片段int firmware_update(const char *path) { int fd open(path, O_RDONLY); if (fd 0) { rt_kprintf(Open firmware file failed\n); return -1; } /* 读取文件头 */ read(fd, header, sizeof(header)); /* 验证魔数 */ if (header.magic ! FIRMWARE_MAGIC) { rt_kprintf(Invalid firmware format\n); close(fd); return -1; } /* 擦除Flash */ flash_erase(APP_ADDRESS, header.size); /* 写入新固件 */ uint8_t buffer[256]; uint32_t offset 0; while (offset header.size) { int len read(fd, buffer, sizeof(buffer)); flash_write(APP_ADDRESS offset, buffer, len); offset len; } close(fd); return 0; }5. 稳定性优化技巧在实际项目中我发现有几个地方特别容易出问题USB枚举失败有时候U盘插入后枚举不成功。解决办法是增加重试机制最多尝试3次枚举。文件系统挂载失败可能是U盘没有正常初始化。可以在挂载前先执行一下disk_initialize。升级过程中断电这是最危险的情况。我的解决方案是使用双Bank Flash保持一个可用的旧版本写入前先校验整个文件每写入一个块都计算校验和内存不足HC32F460的内存有限处理大文件时要特别注意。建议使用小块缓冲区分多次处理。#define RETRY_TIMES 3 int mount_retry() { int i; for (i 0; i RETRY_TIMES; i) { if (dfs_mount(sd, /, elm, 0, 0) 0) { return 0; } rt_thread_mdelay(100); } return -1; }6. 实际项目经验分享在最近的一个工业控制器项目中我们就是用这套方案实现了现场U盘升级功能。这里分享几个实战心得版本兼容性新固件要能兼容旧版本的配置文件这点很重要。我们采用了版本号检测机制自动转换旧格式。升级反馈在LCD屏幕上显示升级进度同时用LED指示灯表示状态。绿色闪烁表示升级中常亮表示成功红色表示失败。异常处理要考虑各种异常情况比如U盘突然拔出、文件损坏等。我们的做法是记录错误日志方便后续分析。性能优化大文件升级时可以适当调高USB传输速度。但要注意不要影响其他实时任务。void show_progress(int percent) { char buf[16]; snprintf(buf, sizeof(buf), Updating: %d%%, percent); lcd_show_string(10, 10, buf); if (percent % 10 0) { led_toggle(); } }这套方案经过半年多的现场验证稳定性相当不错。最让我自豪的是有一次客户现场30台设备同时升级全部一次成功。这种通过U盘升级的方式确实比传统的串口或JTAG方便多了特别适合现场维护。