
简介面向华大半导体HC32L130/HC32L136系列单片机这是一套基于YModem协议的IAP在线升级完整方案内含BOOTLOADER源码与PC端上位机源码适合需要实现固件远程更新、降低现场维护成本的嵌入式开发人员也适合有一定C语言与单片机基础的开发者对照学习。资源共317个文件、压缩包仅3.38MB以C源码39个.c、57个.h与IAR/Keil双平台工程文件为主辅以xcl/icf链接脚本、hex/bin/axf编译输出、J-Link烧录脚本、PDF说明及SVD外设描述文件覆盖从工程配置、编译链接、烧录调试到固件升级的完整链路。已有1057人学习下载。这份资料的核心价值在于给出了可直接参考的IAP实现Bootloader与APP分区管理、YModem分块传输与CRC校验、Flash擦写与跳转流程、上位机交互界面等关键代码均清晰可查同时工程结构预留了移植接口稍作配置即可迁移至华大ARM0系列其他型号对深入理解串口协议、在线升级机制与嵌入式软件架构都很有帮助。 做单片机开发的迟早会碰上一次“产品已经铺出去了固件却还要改”的尴尬。以前要么把设备拆回来要么带着JTAG/SWD现场刷费时费力客户还不一定愿意配合。我最近把华大小华HC32L130和HC32L136两个系列低功耗MCU的项目整理了一遍用一套基于YModem协议的IAP Bootloader加上配套上位机源码把串口升级这条链路彻底打通了。这篇文章就把这套方案的设计思路、Bootloader端实现、上位机联调方法以及我实际踩过的坑全部写出来给正在做同类IAP开发的朋友一个能直接参考的样板。这套东西适合谁如果你是做华大HC32L130/L136产品开发或者刚接触IAP想搞懂YModem协议到底怎么落地又或者已经写了一个Bootloader但总觉得不够稳那这篇文章基本都能对上你的需求。整个过程不涉及复杂算法但协议状态机、Flash分区、中断向量偏移、CRC校对这些细节一个都躲不掉。1. 项目整体设计与思路拆解1.1 为什么选YModem而不是XModem或自研协议很多人第一次做IAP时第一反应是“协议不就这么几行吗自己定一个算了”。我也这么干过——帧头、长度、数据、校验和看起来简单真用到产品上升级几十KB固件就开始难受了文件名没地方放、文件大小要额外定义、分包序号得自己维护、传输中断后不知道传到哪一包。这些坑YModem协议早就替你填好了。YModem是XModem的扩展版经典串口传输协议。和自研协议、XModem放在一起对比差距很明显特性自研简单协议XModemYModem包长自定义128字节128/1024字节文件名和大小没有没有第0包携带校验方式自定义CRC16或校验和CRC16分包序号管理自己写有简单有完善标准结束流程自己定义简单二次EOT握手现成调试工具无部分支持Tera Term、SecureCRT都支持YModem最大的价值不是性能而是生态。开发Bootloader阶段我直接用Tera Term的YModem发送功能来验证协议不用自己写上位机先把Bootloader这边跑通万一之后自己写的上位机出了Bug还能用现成工具把固件刷进去救砖。如果当初选自研协议这些方便全都没有。1.2 Bootloader Flash分区方案怎么设计更稳华大HC32L130和HC32L136都是Cortex-M0内核的低功耗MCU不同型号Flash和RAM容量有差异。以我项目里用的HC32L136K8TA为例内置64KB Flash、8KB RAM分区我是这样安排的0x00000000 ~ 0x00001FFFBootloader区8KB足够放下YModem协议、串口驱动和Flash操作代码。0x00002000 ~ 0x0000EFFFApp区52KB左右这是产品主程序的位置。0x0000F000 ~ 0x0000FFFF参数区存放升级标志、App CRC校验值等。具体起始地址要根据你手里的型号Flash大小调整但有两个原则必须守住第一Bootloader不要贪大够用就行把空间尽量留给App第二App起始地址一定要对齐Flash扇区边界。有些型号扇区是1KB有些是2KB或更大不对齐的话擦除操作会误伤前一个分区。App工程里的链接脚本.ld文件或分散加载文件也要同步改Flash起始地址设置成0x2000长度改成剩余大小。编译出来的bin文件就是App区镜像。这里有个M0平台共性问题——中断向量表偏移。如果芯片支持VTOR在App启动早期设置好偏移如果找不到VTOR或者华大固件库版本较老就需要查芯片手册用启动文件或库里提供的向量表重映射方式处理。跳转前没搞定向量表偏移App一进中断就死给你看。1.3 上位机源码用什么框架写这套源码里的上位机我用的是C# WinForms原因很简单Windows下部署方便串口操作封装成熟做调试工具UI效率也高。界面分四块串口配置区串口号、波特率、连接按钮、固件文件选择区、下载控制区开始、取消、进度条、日志显示区RichTextBox带时间戳。代码结构上我把协议层单独抽出来不跟UI混在一起。上位机内部拆成三层UI层只负责按钮事件、刷新进度、显示日志。YModem协议层构造文件头包、数据包、解析ACK/NAK/CRC。串口通讯层封装SerialPort读写、超时控制。这样以后如果客户机器要换WPF界面或者想把上位机移植成Qt版本协议层和通讯层可以直接复用不用重写核心逻辑。2. Bootloader端核心细节与实现要点2.1 YModem协议状态机到底怎么转YModem的传输过程配得上“经典”两个字但第一次看协议文档容易懵。我用最直白的方式描述一遍Bootloader上电检查升级标志或者按键决定是否进入IAP模式。Bootloader作为接收方先发一个字符C告诉发送端“我准备好了支持CRC16”。上位机收到C后发送第0包SOH 00 FF 文件名 文件大小 填充 CRC16。Bootloader解析出文件名和大小回复ACK C。上位机开始发数据包STX 序号 (255-序号) 1024字节数据 CRC16。Bootloader每收一包CRC校验通过就回ACK校验失败就回NAK上位机收到NAK会重发当前包。数据全部发完后上位机发EOTBootloader回NAK上位机再发一个EOT这次Bootloader回ACK。上位机接着发结束包SOH 00 FF 全0填充 CRC16Bootloader回ACK。全部结束Bootloader校验App区数据跳转。第一次搞YModem的人基本都会疑惑为什么EOT要发两次这不是冗余是协议规定的握手流程。第一次EOT表示“数据文件传完了”接收方回NAK是为了告诉发送端“我确认了但还没收到会话结束包”第二次EOT之后回ACK表示可以发送结束包了。这个设计能有效避免半包关闭连接被误判成正常结束。协议解析代码我建议写成事件驱动状态机不要在接收函数里用阻塞式等待。核心状态枚举大概是这样的typedef enum { YM_STATE_WAIT_C, YM_STATE_RECV_FILENAME, YM_STATE_RECV_DATA, YM_STATE_WAIT_EOT, YM_STATE_WAIT_END_PACKET, YM_STATE_FINISH } ym_state_t;串口中断或者DMA收到一字节后喂给状态机超时用定时器单独管。好处是接收过程中还能响应超时重发、取消升级等操作代码也不容易堆成一坨。2.2 Flash擦写与跳转的代码逻辑华大官方固件库提供了Flash操作接口逻辑上分三步解锁、擦除、编程。典型代码如下void flash_write_app(uint32_t addr, uint8_t *buf, uint32_t len) { FLASH_Unlock(); FLASH_SectorErase(addr); // 按扇区擦除不支持随机写之前不擦 for (uint32_t i 0; i len; i 2) { uint16_t data buf[i] | (buf[i 1] 8); FLASH_Program(addr i, data); // 按半字编程 } FLASH_Lock(); }Flash操作有几个硬性要求写之前必须擦编程通常按半字或字对齐擦写期间不能进中断。实际项目里我会先把整个扇区数据缓存到RAM关串口中断一次性把该扇区擦完再写入再把中断打开。否则边擦边收串口数据很容易丢包。跳转App的代码是另一个关键点#define APP_START_ADDR 0x00002000 void jump_to_app(void) { uint32_t app_sp *(volatile uint32_t *)APP_START_ADDR; uint32_t app_pc *(volatile uint32_t *)(APP_START_ADDR 4); if ((app_sp 0xFFF00000) 0x20000000) { __disable_irq(); SysTick-CTRL 0; // 根据具体型号决定是否设置向量表偏移 // SCB-VTOR APP_START_ADDR (uint32_t)0xFFFFF800; __set_MSP(app_sp); ((void (*)(void))app_pc)(); } }检查app_sp是否落在RAM地址范围这一步不能省。Flash空白时读出来全是0xFF直接跳转只会进HardFault。跳转前把所有中断关掉关闭SysTick和外设不然App启动过程中突然来一个串口中断向量表还没切好同样要翻车。2.3 CRC16校验函数必须和上位机对齐YModem标准使用CRC16-CCITT也叫XMODEM CRC16多项式0x1021初始值0x0000。网上流传的CRC16变种很多有的初值、结果异或不一样上位机和Bootloader各用一套协议就根本对不上。我在Bootloader里用查表法版本稳定且速度快static uint16_t crc16_update(uint16_t crc, uint8_t byte) { crc ^ (uint16_t)byte 8; for (int i 0; i 8; i) { if (crc 0x8000) crc (crc 1) ^ 0x1021; else crc 1; } return crc; }上位机C#里的实现多项式、初始值、字节处理顺序必须和这个完全一致。联调时最容易出问题的地方就是这里。经验是先在PC上用两个串口工具互发YModem文件验证协议层CRC实现是否正确再往单片机上下载能省下大量排查时间。3. 上位机源码与通讯调试3.1 C#串口上位机界面和防卡死处理上位机界面不算复杂关键的控件是这几个串口选择下拉框、波特率下拉框波特率默认给115200或者57600但产品上我不建议一上来就用太高。连接/断开按钮、选择文件按钮、开始升级按钮、取消按钮。进度条加百分比文本框。日志RichTextBox每一行都带时间戳。C#里用SerialPort类操作串口很简单但有个坑DataReceived事件是在后台线程触发的直接在事件里更新UI会报线程间操作异常必须用Invoke或者async/await。我通常用BaseStream.ReadAsync配合异步方法代码更清爽using (var port new SerialPort(COM3, 115200, Parity.None, 8, StopBits.One)) { port.Open(); await Task.Delay(50); byte[] cmd new byte[] { (byte)C }; port.Write(cmd, 0, 1); }升级动作放到BackgroundWorker里执行文件读取、分包、串口写入这些可能在几十毫秒到几秒的操作不会把界面卡死。日志框统一走一个AppendLog方法内部用锁保证多线程写入安全。3.2 文件分包和YModem发送流程实现上位机把bin文件读进来之后按1024字节拆包最后不足1024字节的部分用0x1A填充。YModem标准填充符是0x1ASUB虽然很多Bootloader不关心填充值但既然协议有约定就按标准来。第0包文件名格式是ASCII字符串形如firmware.bin 53248文件名和文件大小之间是一个空格后面跟上十进制文件长度。分包和发送核心逻辑大致如下byte seq 1; while (remaining 0) { byte[] packet BuildDataPacket(seq, dataChunk); port.Write(packet, 0, packet.Length); // 等待ACK或NAK超时则重发当前包 bool ack WaitForAck(1000); if (ack) { seq; offset chunkLen; UpdateProgress(offset, fileSize); } else { RetryCount; } }数据包序号从1开始最大255超过后回绕到0。每个数据包都要带255 - seq作为序号取反接收端用这个判断包是否重复或乱序。别小看这个字节它专门用来应对串口丢包后的重传场景。3.3 联调时最值得先做的三件事第一先不碰板子用Tera Term的YModem发送功能把Bootloader调通。Tera Term能直接选YModem协议发送文件如果Bootloader能正常下载并跳转说明协议栈本身没问题剩下要看的就是自己上位机的锅。这一步能极大缩小排查范围。第二用逻辑分析仪或串口监听工具抓一次完整交互。重点看三处Bootloader发出C之后上位机有没有立刻回第0包上位机发完数据包后Bootloader回的是ACK还是NAK传输结束后二次EOT流程有没有走完整。YModem是半双工一问一答只要看一个交互循环问题卡在哪立刻清楚。第三波特率先降到9600或者19200跑通全流程再逐步提上去。很多低成本板子用内部RC振荡器标称24MHz实际误差不小115200下容易错一两个bit表现为包序号乱跳、CRC错。等到协议稳定了再评估能不能用更高的波特率。4. 踩坑记录与排查技巧4.1 常见问题速查表我把这次项目里遇到的高频问题整理成一张表排查时直接对照现象可能原因解决办法上位机点了发送板子没反应Bootloader没进入接收模式C没发出检查升级标志/按键逻辑用串口助手手动发C测试一直卡在文件头第0包CRC算错文件名格式不对抓包对比Tera Term发出的第0包内容数据包传一半回NAKFlash擦写时间超过串口超时分扇区擦除并缓存数据上位机加大响应超时跳转后App跑飞向量表偏移没设App链接地址错误查App工程FLASH起始地址确认跳转前关中断整包下载成功但App不运行Flash编程参数或写入地址不对用调试器读回Flash内容和bin文件逐字节比对偶尔下载失败重试又成功波特率偏高或供电不稳降波特率检查板子电源纹波串口线缩短4.2 升级失败的容错和恢复机制产品级IAP最怕什么传输到一半断电设备变成砖头。所以Bootloader里一定要有“升级标志位”机制。思路是这样的参数区放一个固定值比如0xA5A5表示“升级中”。上位机开始发送前先通过一条命令让Bootloader把这个标志写成升级中全部数据接收完成并且App区CRC校验通过后Bootloader把标志清掉再跳转。Bootloader每次上电都检查这个标志标志不是升级中且App区CRC校验通过直接跳App。标志是升级中说明上次升级没完成不跳App留在IAP模式继续等待接收新固件。App区校验失败也留在IAP模式。这样即使升级过程中突然断电下次上电Bootloader依然会进入IAP等待状态不会变砖。有些产品还想做双Bank备份A区坏了从B区启动但对HC32L130/L136这种小Flash芯片来说双Bank占用的空间实在太大我用“升级标志App区CRC校验”就足够了关键是可靠。4.3 实际测试中我总结的几条经验串口缓冲区一定要开大。1024字节的YModem数据包如果只用串口中断逐字节接收在低优先级中断频繁被打断的情况下很容易丢字节。我建议Bootloader端用DMA接收配合空闲中断一包数据到齐后统一处理效果最好。如果固件库版本不支持空闲中断也要保证接收缓冲区和环形队列足够大。上位机超时重发不能太急。我把响应超时设到1000ms因为Flash擦一个扇区在低功耗芯片上可能耗时几十毫秒到上百毫秒加上波特率低时包传输本身就要几百毫秒超时设太短会导致发送端和接收端状态叠加混乱。测试固件准备三份一个正常的App、一个故意加了打印的App、一个全空的bin。正常的用来验证下载和跳转加打印的用来验证跳转后App是否真正接管外设全空的用来验证Bootloader对非法App的拦截逻辑。开发阶段保留UART日志等整套方案稳定后再把日志关掉或者换成加密固件。日志虽然占Flash空间但现场出问题时一串清晰的日志比什么调试工具都管用。最后再分享一个个人体会这套IAP从画分区到完全跑通我前后花了将近三周一半时间耗在协议对齐和Flash驱动细节上协议本身反而是最快写完的。做Bootloader最重要的不是功能多花哨而是稳定、可恢复、可兜底。只要把YModem协议、CRC校验、掉电保护和跳转逻辑都验证透了后面的App迭代基本就是打开上位机、点一下下载的事。本文还有配套的精品资源点击获取