尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32F030串口Ymodem IAP Bootloader实现远程固件升级

STM32F030串口Ymodem IAP Bootloader实现远程固件升级 简介面向STM32F030C8T6开发者的IAP串口升级项目基于Ymodem协议通过串口1实现固件在线更新适用于物联网设备、工业控制等需要远程维护的场景。资源共141个文件压缩包1.76MB其中包含46个H头文件和39个C源文件以及编译生成的O、CRF、HEX、AXF等中间文件可帮助读者直接查看工程结构、烧录验证与二次修改。代码采用STM32标准外设库编写覆盖串口初始化、Ymodem帧解析、CRC校验、Flash擦写及状态管理等完整IAP流程并附有Keil工程文件打开即可编译。已有1019人学习下载适合正在学习IAP原理或需要快速搭建固件升级方案的嵌入式工程师由于STM32系列外设库与架构相近稍作引脚和内存调整即可移植到其他型号。1. 串口1做Ymodem IAP的起点F030这样的小片子也能远程刷固件设备已经装箱、贴牌、发货到现场之后SWD接口往往不再可触及。这时候升级固件只有一条路把Bootloader提前烧进去通过串口1接收新App并写入内部Flash。STM32F030C8T6只有64KB Flash、8KB RAM跑不了TCP/IP协议栈Ymodem协议正好卡在这个资源档位里文件传输带CRC校验支持文件名和大小实现复杂度比Zmodem低又比Xmodem更适合超过64KB的固件。本文要落地的是这样一套代码Bootloader运行在Flash前16KB区域通过PA9/PA10的USART1接收PC端Tera Term或SecureCRT发来的Ymodem数据流边收边写入0x08004000起始的App区然后跳转执行。整个过程适合产品工程师、嵌入式固件工程师和用CubeMX做二次开发的人上手。2. IAP与Ymodem协议要点分区、帧格式和一次完整传输2.1 64KB Flash如何分配给Bootloader和AppSTM32F030C8T6的Flash起始地址是0x08000000页大小为1KB编程单位是半字16bit。先定分区后续所有代码和链接脚本都以这个表为准。区域起始地址长度用途Bootloader0x0800000016KB串口1接收、Ymodem解析、Flash写入、跳转App区0x0800400048KB应用程序中断向量表和代码选择16KB给Bootloader是因为HAL库、USART1驱动、Ymodem状态机和Flash擦写函数加起来通常会超过10KB留出余量避免IAR/Keil优化级别变化时溢出。App从0x08004000开始正好是16KB边界也对应1KB页边界擦除时不会误擦Bootloader。App工程需要同步修改链接脚本Flash起始地址改成0x08004000长度改成0xC000。RAM地址不需要变仍是0x20000000长度0x2000但中断向量表重映射问题会在后面单独处理。Release版本生成.bin文件时要确保没有把Bootloader区域覆盖掉。2.2 Ymodem帧格式与校验发送方如何把固件拆包Ymodem的帧比Xmodem多了一个文件信息块。双方协商流程如下接收方MCU发送字符C表示等待Ymodem CRC模式传输。发送方发出块0内容包含文件名、文件大小等ASCII字符串。接收方校验通过后回复ACK并再次发送C。发送方从块1开始发送固件数据每块长度可选128字节或1024字节。所有数据块发完后发送方发出EOT接收方回复ACK。发送方再发一个空块0作为结束标记接收方回复ACK整个传输完成。数据帧的格式很固定字节内容0帧头SOH0x01表示128字节块STX0x02表示1024字节块1块号从1开始255后回到02块号取反3..N数据长度由帧头决定最后2字节CRC16高低字节CRC16采用CCITT多项式0x1021初值0x0000。块0的数据区是filename\0filesize\0多余部分填0x00。filesize是十进制ASCII字符串MCU解析后用来计算剩余字节数也可以在写入Flash前做边界检查。F030C8只有8KB RAM串口接收缓冲区如果支持STX帧需要至少1029字节再加上Ymodem解析、Flash临时缓冲和函数栈仍能控制在4KB以内所以这里推荐直接兼容SOH和STX两种帧长避免上位机软件默认发送1024字节块时直接解析失败。后续代码里的缓冲区按1029字节设计。2.3 F0的Flash写约束和8KB RAM限制F030是M0内核Flash写入时需要遵循两个硬约束写Flash前必须擦除所在页页大小固定1KB。编程只能按半字16bit执行不能用32位字写。因此收到一块数据后不能直接整块写入要按半字拆分。擦除动作会阻塞Flash访问此时所有中断请求都可能取不到向量表指令导致HardFault或字节错乱。写Flash前至少要把串口中断关掉写完再开。8KB RAM的另一个限制是堆栈和DMA缓冲。Ymodem接收如果开DMA需要额外缓冲区做乒乓操作这里不推荐。直接使用HAL_UART_Receive逐个字节接收虽然会占用CPU但115200波特率下每字节约87微秒F030跑到48MHz时完全扛得住。这样内存占用最小逻辑也更直观。3. 串口1上的Ymodem接收端Bootloader的C代码实现3.1 串口1初始化与Flash擦写函数使用STM32CubeMX生成工程时USART1选择PA9/PA10异步模式115200bps8N1无硬件流控。F030的USART1挂载在APB总线上CubeMX会自动计算分频值这里直接贴出关键配置和Flash写入函数。#define APP_START_ADDR 0x08004000u #define FLASH_PAGE_SIZE 0x400u static void flash_write_app(uint32_t addr, uint8_t *buf, uint32_t len) { HAL_FLASH_Unlock(); for (uint32_t i 0; i len; i 2) { uint16_t half_word buf[i] | (buf[i 1] 8); HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, addr i, half_word); } HAL_FLASH_Lock(); }擦除动作在写入前统一做不要在每一包数据到达时都擦整页。可以按App区总大小一次性擦完也可以根据当前接收地址判断是否遇到新页边界再擦。这个函数每次写入2字节参数addr必须落在0x08004000到0x0800FFFF范围内否则HAL_FLASH_Program会返回错误。调用前关闭全局中断写入完成后再打开。3.2 Ymodem状态机从C到最终空包主循环的核心是Ymodem状态机这里给出一个可直接移植的简化版本。typedef enum { YM_WAIT_BLOCK0 0, YM_WAIT_DATA, YM_WAIT_EOT, YM_DONE } ym_state_t; uint8_t ymodem_rx_buf[1029]; // STX帧最多: 1帧头 2序号 1024数据 2CRC uint16_t crc16_ccitt(uint8_t *buf, uint32_t len) { uint16_t crc 0; for (uint32_t i 0; i len; i) { crc ^ (uint16_t)buf[i] 8; for (int bit 0; bit 8; bit) { crc (crc 0x8000u) ? (crc 1) ^ 0x1021u : (crc 1); } } return crc; } void uart1_send(uint8_t ch) { HAL_UART_Transmit(huart1, ch, 1, 100); } static int ymodem_recv_packet(uint8_t *buf, uint32_t timeout_ms) { uint8_t hdr[3]; uint32_t data_len, i; if (HAL_UART_Receive(huart1, hdr, 1, timeout_ms) ! HAL_OK) return -1; // 超时 switch (hdr[0]) { case 0x01: data_len 128; break; case 0x02: data_len 1024; break; case 0x04: return 0x04; // EOT case 0x18: return 0x18; // CAN default: return -2; // 噪声字符 } HAL_UART_Receive(huart1, hdr 1, 2, timeout_ms); buf[0] hdr[0]; buf[1] hdr[1]; buf[2] hdr[2]; HAL_UART_Receive(huart1, buf 3, data_len 2, timeout_ms); uint16_t recv_crc buf[data_len 3] | (buf[data_len 4] 8); if (recv_crc ! crc16_ccitt(buf 3, data_len)) { uart1_send(0x15); // NAK return -3; } return hdr[0]; } void iap_ymodem_loop(void) { ym_state_t state YM_WAIT_BLOCK0; uint32_t blk_seq 0; uint32_t flash_addr APP_START_ADDR; uint32_t file_size 0, received_size 0; uart1_send(C); while (state ! YM_DONE) { int pkt_type ymodem_recv_packet(ymodem_rx_buf, 3000); if (pkt_type -1) { uart1_send(C); continue; } if (pkt_type 0x01 || pkt_type 0x02) { uint32_t data_len (pkt_type 0x01) ? 128 : 1024; uint8_t blk_no ymodem_rx_buf[1]; uint8_t blk_inv ymodem_rx_buf[2]; if (blk_no ! (uint8_t)(blk_seq 0xFF) || blk_inv ! (uint8_t)(~blk_no)) { uart1_send(0x15); continue; } if (blk_no 0) { if (state YM_WAIT_BLOCK0) { // 解析文件名\0 文件大小\0 file_size 0; for (i 0; i data_len; i) { if (ymodem_rx_buf[3 i] \0) break; } // 文件名在 buf[3] 到结束符之间继续找文件大小 i; while (ymodem_rx_buf[3 i] 0 ymodem_rx_buf[3 i] 9) { file_size file_size * 10 (ymodem_rx_buf[3 i] - 0); i; } state YM_WAIT_DATA; } else { // 空块结束最后一包数据 } uart1_send(0x06); // ACK if (state YM_WAIT_DATA) uart1_send(C); blk_seq; } else { // 数据块 flash_write_app(flash_addr, ymodem_rx_buf 3, data_len); flash_addr data_len; received_size data_len; uart1_send(0x06); blk_seq; } } } }这个代码省略了块0之后的文件名保存实际项目里可以用strtok或固定偏移解析file_size。参数说明ymodem_recv_packet返回负值时表示超时、CRC错误或噪声主循环对超时重新发送C对CRC错误已经内部回复NAK发送方会自动重传当前块。blk_seq控制块号连续性Ymodem的块号从0开始255之后自动回绕所以强制转成uint8_t比较即可。3.3 跳转AppSTM32F030没有VTOR时的向量表重定位Cortex-M0没有Cortex-M3/M4的VTOR寄存器App的向量表不能靠硬件自动搬移。常见做法是在Bootloader跳转时把App起始地址处的向量表复制到SRAM头部再把SRAM重映射到地址0。typedef void (*pFunction)(void); static void jump_to_app(uint32_t app_addr) { uint32_t msp *(uint32_t *)app_addr; pFunction reset (pFunction)(*(uint32_t *)(app_addr 4)); uint32_t *src_vt (uint32_t *)app_addr; uint32_t *dst_vt (uint32_t *)0x20000000u; uint32_t i, vt_size 0xC0; // 覆盖整个向量表F030共48个中断 if ((msp 0x2FFE0000u) ! 0x20000000u) { return; // 栈顶不在SRAM范围内拒绝跳转 } for (i 0; i vt_size / 4u; i) { dst_vt[i] src_vt[i]; } __DSB(); SYSCFG-CFGR1 ~SYSCFG_CFGR1_MEM_MODE; SYSCFG-CFGR1 | SYSCFG_CFGR1_MEM_MODE_SRAM; // 选择SRAM映射到0x0 __set_MSP(msp); reset(); }这段代码在跳转前把App的向量表从0x08004000复制到SRAM起始地址0x20000000然后设置SYSCFG_MEM_MODE为SRAM。这样当M0内核在中断发生时从0x0地址取向量表实际取到的是SRAM里的新表App的中断就可以正常响应。msp校验非常关键如果上位机传了一个不是完整固件的bin栈顶地址可能落在Flash或非法RAM区加上校验后至少不会跑飞。跳转前还需要调用HAL_UART_DeInit(huart1); SysTick-CTRL 0;把外设中断和SysTick全部关掉否则App启动后可能被残留中断干扰。4. Tera Term发起Ymodem传输串口参数与三个高频失败点4.1 用Tera Term发送.bin的具体步骤先把Bootloader编译烧录到Flash前16KB区域然后打开Tera Term。新建Serial连接选择正确的COM口波特率115200数据位8停止位1校验无。连接后MCU会持续发送C字符Tera Term的终端上通常看不到明显输出但串口灯会闪。依次点击File - Transfer - YMODEM - Send...选择编译生成的.bin文件Tera Term会立刻发起Ymodem传输。传输完成后终端会显示文件大小和CRC校验状态MCU跳转到App。Linux环境下也可以用lrzsz工具配合串口终端执行sb firmware.bin前提是终端会话已经打开串口。Windows下Tera Term和SecureCRT都支持YMODEMTera Term免费实际项目里用得最多。4.2 波特率、停止位、Ymodem包长这几个参数怎么匹配参数推荐值说明波特率115200F030的USART1时钟源设置正确时误差低于0.5%数据位8Ymodem帧按字节对齐停止位12停止位会增加传输时间不必要校验位无校验交给Ymodem的CRC16包长128或1024接收端代码同时支持SOH和STX波特率不是越高越好。F030内部Flash写入时间约20ms左右如果一包数据被上位机连续发出MCU来不及擦写时会在软件层产生背压。115200下1024字节块传输约89msMCU有足够时间完成擦写。如果使用460800需要在Bootloader里加XON/XOFF流控或者DMA乒乓复杂度会明显上升。4.3 三个高频故障发不出包、写入一半停住、跳转后跑飞第一个现象是Tera Term点发送后立刻失败终端没有进度。先检查串口1引脚有没有接反MCU的PA9是USART1_TXPA10是USART1_RXPC端USB转串口的TXD必须接PA10RXD接PA9。共地缺失也会导致完全无响应。第二个现象是传输进行到一半停止进度条停在某个百分比。打开IDE的Debugger查看Ymodem状态机是否在反复发送NAK。如果NAK循环集中在某个地址附近大概率是Flash写入越界或页擦除失败。在flash_write_app里加一个地址边界判断if (addr len 0x0800FFFF) return;防止最后一块越界到非法Flash区。第三个现象是传输完成后UART没有任何输出但设备像死机一样。多数是App端链接脚本没改对或者中断向量表没有重映射。检查App工程的.map文件确认RESET和SystemInit位于0x08004000附近并在App的main函数最前面执行同样的SRAM向量表重映射代码。F030没有VTOR这一步是在Bootloader跳转前做的如果App里还想在运行时重新设置中断向量就需要再拷贝一次。5. 升级失败能回滚吗备份区、版本号和一个双槽技巧批量产品的IAP最怕的不是传不上去而是传上去后App起不来。Bootloader里只做一次跳转设备就挂在现场这时候没有SWD口基本等于返厂。常见的缓解方案是双槽位Flash里保留一个稳定版App新固件先写到另一个槽位校验通过后再切换启动。F030C8只有64KBBootloader占16KB后还剩48KB。把App区拆成两个24KB槽位Slot A从0x08004000到0x08009FFFSlot B从0x0800A000到0x0800FFFF。当前正常运行的是Slot A收到新固件时先擦写Slot B整个Ymodem传输完成后Bootloader对Slot B做一次CRC和栈顶校验通过才把启动入口切到Slot B。如果校验失败仍旧启动Slot A实现回滚。实现时在每个固件头部固定偏移处放一个版本号和CRC值。常见做法是typedef struct { uint32_t magic; // 固定0xA5A5A5A5 uint32_t version; // 版本号例如0x00000101 uint32_t len; // 固件有效长度 uint32_t crc; // 局部CRC } app_header_t;Bootloader启动时先读取对应槽位地址的magic等于固定值才继续读version和len然后针对整个固件区域计算CRC与头部存储的CRC比较。所有检查通过才跳转否则切到另一个槽位。这个方案不像双Bank那样需要硬件支持F030用软件分区即可。实际量产时最后一步是把回滚判断放进上电自检流程里启动后延时300ms等待上位机主动进入Ymodem升级超时后读两个槽位的状态先启动优先级高的合法版本。这样既保留了升级入口又让现场断电不会把设备锁死在错误固件里。本文还有配套的精品资源点击获取
返回列表