富士通FM3单片机Flash擦写与IAP Bootloader实现全解析

发布时间:2026/9/3 0:07:52
富士通FM3单片机Flash擦写与IAP Bootloader实现全解析 简介面向嵌入式开发者和单片机学习者这份资源以富士通 MB95F318 的 Flash 操作为核心演示其编程、擦除与校验流程并覆盖 ISP 与 IAP 应用场景便于理解在线与离线固件更新方式。压缩包共 29 个文件约 79KB包含 C 源码、头文件、汇编启动文件、Keil 工程文件及 .obj/.mhx 等编译输出便于对照源码、目标文件与工程配置理解 Flash 操作实现。已有 469 人学习下载可作为 MB95F318 固件开发入门的参考实例。通过示例代码可掌握 Flash 分块管理、写保护、超时处理等细节也能直接复用工程模板或移植到自有项目。 去年做一款变频器控制器的时候MCU选的是富士通FM3系列MB9BF568。产品已经量产了一百多台结果客户突然提需求希望后续固件更新不用开壳、不用拆回来现场用串口就能升级。富士通这颗料不像ST那么大众网上资料少片内也没有出厂预置的ISP引导程序要支持远程升级就只能自己做于是我把这颗单片机片内Flash的擦写逻辑彻底摸了一遍。这篇文章就记录从原理到落地、再到排查下载器报错的完整过程给正在用富士通单片机做Bootloader或者需要操作片内Flash的朋友一个参考。1. 富士通单片机Flash操作的应用场景和基本概念1.1 从产品升级需求引出IAPFlash操作这个词听着很底层但如果做产品你迟早得面对它。常见场景就这么几个一是Bootloader引导跳转也就是IAPIn Application Programming产品出厂后通过UART、CAN或者USB升级应用固件二是参数存储把设备的校准值、序列号、运行状态写进Flash断电不丢三是OTA服务器下发固件包本地校验后写入Flash。这几个场景背后涉及的都是同一套东西片内Flash怎么擦、怎么写、擦写到一半掉电了怎么办。富士通FM3系列基于ARM Cortex-M3/M4内核片内Flash容量从128KB到512KB不等具体看你选的型号。这类芯片定位偏工控和汽车很多用在电机驱动、变频器、PLC和医疗设备上。实际开发中和Flash打交道最多的就是Bootloader。FM3没有单独划分出一块专门给Bootloader使用的隐藏ROM区所以引导程序和应用代码都在同一块主Flash里地址空间你自己规划。规划Flash布局时我的做法是Bootloader占最低地址区往上是App起始区最后留一块独立的参数存储区。FM3的Flash擦除操作按扇区来不能跨扇区擦所以Bootloader的大小必须压到一个扇区内或者用两个相邻扇区否则升级逻辑会复杂很多。1.2 Flash与EEPROM的区别为什么不能按字节写很多刚从51单片机转过来的朋友会有一个误区Flash是不是和EEPROM一样随时可以按字节改不是。EEPROM可以单字节擦写Flash不行。Nor Flash的物理特性决定了写1容易、写0也容易但要从0变回1就必须整块擦除。所以Flash编程的逻辑是先擦后写而且是整扇区擦除。这带来一个实用结论如果你的产品需要用Flash保存一些经常更新的参数比如PID参数、用户设置不建议每次都整扇区擦除重写。因为Flash擦写次数有限FM3的片内Flash数据保持寿命是10万次擦写以上听起来很多但如果你每秒存一次参数几天就磨穿了。更合理的做法是日志式写入只在扇区末尾追加新值读的时候从后往前找最新的写满一个扇区再擦。另外说一句外部Flash。很多人看到Flash会想到W25Q64这类SPI Nor Flash它的操作思路和片内Flash类似也是先擦后写、扇区管理但是寻址方式和命令时序完全不同。片内Flash直接挂在芯片内部总线上通过寄存器触发编程而外部SPI Flash要通过SPI协议发命令。这篇重点讲片内Flash外部Flash只在Bootloader做固件暂存时会顺带提一下。2. FM3片内Flash的擦除与编程原理2.1 扇区划分与地址映射用FM3之前我先翻了型号对应的硬件手册找到Flash那一章。FM3的Flash扇区划分在不同子系列里差别挺大以MB9BF5xx为例大致是起始地址从0x00000000开始低地址区可能是几个8KB的小扇区后面是64KB的大扇区顶部还有独立的配置区域用来存放加密位、时钟配置等。具体每个扇区的起始地址和大小手册里有一张表写代码前一定要把这张表找到并确认。地址映射有个容易被坑的地方Flash的起始地址是0x00000000但向量表也在0x00000000。你在做Bootloader的时候Bootloader和App都有自己的向量表Bootloader的程序入口是0x00000000App程序入口是你定义的偏移地址。擦除操作千万不要拿着地址随便擦比如想把App区擦掉结果地址算错一位把Bootloader自己的程序擦了一半设备直接变砖。片内Flash还有一个独立的配置区富士通叫Security或者Option里面有关闭调试口的保护位。这个区域一般不在主Flash的普通用户地址范围内要用专门的命令操作。后面我讲下载器报错的时候还会提到它。2.2 命令序列与状态位轮询FM3的片内Flash编程不是直接往地址写数据就行的而是通过一个Flash控制器来执行自动编程算法。你往地址总线写数据只是把数据放到了缓冲区真正落盘要靠控制器来触发。标准流程大致是这样的解锁Flash寄存器写保护让CPU可以访问Flash控制寄存器。写目标地址到Flash地址寄存器。按手册要求往控制寄存器写入一系列命令字通常是编程使能再编程执行。等待Flash状态寄存器中的忙标志变为空闲。查询编程结果状态确认成功后再锁回寄存器写保护。擦除操作也是类似流程只是命令字不同。下面这段代码是FM3系列Flash操作最核心的骨架我注释掉了具体寄存器位重点是整个流程的顺序static flash_status_t fm3_flash_execute_command(uint32_t cmd, uint32_t addr) { /* 1. 解锁Flash控制器 */ FLASH_Unlock(); /* 2. 设置目标地址 */ FLASH-FADR addr; /* 3. 写入命令序列不同命令的时序由芯片手册定义 */ FLASH-CS FLASH_CS_CST | cmd; /* 4. 轮询Busy位等待控制器执行完成 */ uint32_t timeout 0xFFFFF; while ((FLASH-CS FLASH_CS_BUSY) timeout--) { /* wait for flash idle */ } /* 5. 读取状态并锁定 */ flash_status_t st (FLASH-CS FLASH_CS_ERR) ? FLASH_ERROR : FLASH_OK; FLASH_Lock(); return st; }有一点要特别提醒命令序列的时序非常敏感步骤之间不能加多余的语句或者延时代码。我一开始在写地址和写命令字之间插了一句调试用的串口打印结果Flash操作时而成功时而失败排查了半天才反应过来。调试接口的数据输出会打乱指令流水Flash控制器对时序窗口的要求很严格所以正式代码里这段逻辑必须是纯粹的寄存器操作。2.3 代码必须从RAM执行的隐藏规则这是FM3最容易被忽视的一个坑执行Flash擦写操作的时候CPU执行的那段代码不能放在同一个Flash里。因为Flash控制器做擦写编程时整个Flash阵列处于忙状态无法响应取指访问你在Flash里跑的代码会直接卡死或者取到全FF。解决办法有两种。第一种是中断向量表、Flash驱动代码全部放RAMBootloader启动后先把自己搬到RAM再执行。第二种是写Flash的时候关闭中断然后把擦写函数放到RAM中执行。因为中断向量表如果在Flash里擦写Flash时一旦有中断进来CPU去Flash取中断向量就取不到了。在Keil MDK里把函数放到RAM可以这样声明/* 将函数放到RAM执行避免Flash忙时取指失败 */ #if defined(__CC_ARM) __attribute__((section(RAMCODE), noinline)) #elif defined(__ICCARM__) __ramfunc #endif flash_status_t fm3_flash_erase_sector(uint32_t addr) { return fm3_flash_execute_command(FLASH_ERASE_CMD, addr); }在IAR环境下更简单直接加__ramfunc关键字。但不管用哪种方式都要确认链接脚本里预留了足够的RAM空间给这段代码和它调用的子函数。FM3的Flash驱动代码不大一般几KB就够但对于RAM本身只有几十KB的小容量型号这个细节要提前预留免得后面开优化或者加功能时链接报溢出。3. IAP Bootloader中Flash驱动的实现3.1 封装一个可复用的Flash擦写模块看手册原理都能懂真正难的是落地成稳定的工程代码。我建议把Flash操作独立成一个模块至少提供四个接口擦除指定扇区、写入一段数据、读取一段数据、整片校验。不要直接在上层业务代码里到处写FLASH-CS寄存器操作否则后期维护会想骂人。接口设计方面这是我用了很久的一个版本#define APP_BASE_ADDR 0x00010000u #define BOOT_SECTOR_SIZE 0x00002000u #define MAIN_FLASH_END 0x00080000u flash_status_t fm3_flash_erase_sectors(uint32_t start_addr, uint32_t end_addr); flash_status_t fm3_flash_write_data(uint32_t dst_addr, const uint8_t *src_buf, uint32_t len); flash_status_t fm3_flash_verify_data(uint32_t src_addr, const uint8_t *expect_buf, uint32_t len);写入数据时需要注意FM3的自动编程算法要求地址按4字节对齐长度也建议按16字节对齐不然边界处理会很麻烦。上面代码里我在写入前对地址做了范围检查原因就是一次误操作把Bootloader区擦了那真的是半夜抱着仿真器哭的心都有。安全校验宁可做厚一分不要薄一分。实际往App区写固件时我还做了分块写入。UART接收一包比如512字节CRC校验OK后先暂存到RAM缓冲区攒够一块再写入Flash。边收边写的方案遇到串口丢包时会在Flash里留下半截固件可靠性太差。我现在的流程是先通过串口把整个固件包接收完整放在一个足够大的RAM数组里或者放在外挂SPI Flash里全部校验完毕后再开始擦写片内Flash。这样掉电风险也小因为擦写窗口很短。3.2 跳转到App前的关键操作Bootloader擦写完成之后就要跳转到App区执行。跳转不是一刀就完事有几个动作必须做否则App跑起来就是HardFault关掉全局中断把SysTick、外设中断全部停掉。把栈顶指针MSP设为App向量表中的第一个字。把PC指针指向App复位处理函数即向量表中的第二个字。跳转前把数据缓存、指令缓存失效掉防止执行了旧代码。FM3基于Cortex-M内核参考代码是这样void jump_to_app(uint32_t app_base) { uint32_t stack_top *(volatile uint32_t *)app_base; uint32_t reset_handler *(volatile uint32_t *)(app_base 4); __disable_irq(); /* 构造一个函数指针并跳转 */ void (*app_reset)(void) (void (*)(void))reset_handler; __set_MSP(stack_top); app_reset(); /* 跳转成功后不会返回 */ while (1) { } }这里需要注意接收固件包时Bootloader用的串口中断、DMA等如果跳转前没有正确关闭App启动时可能会收到残留中断导致程序行为异常。我当初遇到的问题是Bootloader里的串口接收中断没有完全关闭跳转到App后App一旦初始化串口马上收到一个历史遗留的字符误以为来了命令直接进了错误分支。3.3 中断向量表重映射跳转之后App代码里的中断向量表默认还是放在0x00000000也就是Bootloader的地址位置这样App里任何中断触发比如SysTick、串口中断都会跳回Bootloader的向量表找不到对应的App处理函数程序立即崩溃。所以App工程里必须重新设置向量表偏移。在Cortex-M3/M4内核上有一个寄存器叫VTORVector Table Offset Register专门用来设置向量表基地址。FM3的App工程里main函数最开头加这一行SCB-VTOR APP_BASE_ADDR;注意这行代码必须在任何中断使能之前执行。我习惯放在SystemInit之后、所有外设初始化之前的第一行代码位置。还有一个坑如果你的App使用RTOSRTOS的启动文件也可能重设VTOR两者要协调别让RTOS在你没看的地方又把向量表指回0x00000000。另外编译App工程时需要把链接脚本里的Flash起始地址改为APP_BASE_ADDR否则编译器生成的代码地址还是从0开始。这个不难在IAR的链接器配置里改一下ROM起始地址即可Keil里改Target页的IROM1起始地址。4. 下载器写Flash失败的排查链路4.1 保护位和读锁导致的flash download failed用J-Link或者Keil MDK往FM3下载程序时如果目标板本身能跑、仿真器也能连上但一点下载按钮就报Error: Flash Download failed - Target DLL has been cancelledflash download faild cortex-m3flash download faild cortex-m4这类错误我踩过之后把排查顺序固定成了下面几条第一个怀疑对象是芯片的安全保护位。FM3有Security机制一旦通过专用命令设置了加密位调试口就被锁死外部仿真器无法读写Flash。这时仿真器虽然能访问内核但无法操作Flash编程算法下载就报错。解决办法是使用仿真器配套的unlock命令做全片擦除把安全位擦掉。注意这个操作会把整个Flash清空如果你里面有量产固件请先把二进制备份出来。第二个常见原因是用错了Flash下载算法。Keil的Flash Download页面里有一堆Flash Algorithm选项必须选对FM3对应系列的. FLM文件。选成其他Cortex-M3芯片的算法下载器一执行就会报奇怪的错误。每次拿到一颗新型号的富士通MCU第一件事就是确认Flash驱动文件是否匹配型号和Flash容量。第三个原因是芯片供电或者复位问题。Flash编程时电流需求会突然加大如果电源线过细、供电能力不足内核电压跌落下载算法瞬间跑飞同样会报DLL cancelled。4.2 探针速率、复位和时钟的干扰J-Link连接FM3目标板时还有一个常见坑是SWD速率设太高。FM3默认上电后使用的内部时钟频率不高如果SWD时钟设成几MHz调试器和CPU握手就可能不稳定。表现为第一次能连上第二次就连不上或者掉电再上电后连接失败。把SWD频率降到1MHz以下通常就稳了。再有一个是复位引脚。FM3的复位脚上一般会有一个10nF到100nF的下拉电容起上电复位延时作用。电容太大复位引脚的低电平时间太长下载器等待复位完成的时间不够单片机还没从复位状态完全起来下载器已经放弃握手了。我遇到过一次板子复位脚上焊接了一个100nF大电容J-Link死活连不上换小电容之后立刻正常。这个现象在低温下更明显因为电容低温容值会偏大所以批量产品的复位电容最好控制在手册推荐范围内。最后时钟源也要检查。FM3的Flash擦写时序有一部分依赖主时钟频率。如果你在调试时把时钟初始化改成外部高速晶振而板子上根本没有这个晶振那不仅系统跑不起来Flash下载也会失败。所以排查下载问题的时候我会先看启动配置引脚和时钟使能状态排除硬件时钟源与软件配置不一致的问题。5. Flash可靠性设计掉电保护与擦写寿命管理5.1 双Bank区和数据校验Bootloader做得差不多了就要考虑升级过程中掉电怎么办。一次固件升级可能需要擦写几十KB的Flash窗口按秒算。如果用户在这个窗口内突然断电App区和Bootloader区都可能是损坏状态设备就彻底变砖了。我的做法是给固件更新增加AB区备份机制。FM3主Flash容量如果够可以把代码区划分为三个部分Bootloader区、AppA区、AppB区。平时运行在AppA区升级时往AppB区写新固件写完后校验CRC32和版本号全部通过后把启动标志写入参数区再重启到AppB区。如果写入中途掉电AppA区还是好的下次启动Bootloader发现AppB区的固件不完整就继续从AppA区引导。这套方案在工程上叫Dual Bank OTA可靠性远高于单区升级。如果Flash容量紧张退而求其次也至少要做到先写新固件到临时扇区全部校验通过后再擦除目标扇区并复制的思路给掉电留一个恢复的退路。5.2 擦写均衡的实际考量Flash的擦写寿命问题前面提过这里展开一下。FM3手册给的Flash擦写寿命通常是以万次为单位但这个数据是基于合理使用的。频繁擦写同一扇区会让该区域提前老化而其他扇区还很健康。所以我做参数存储时用的是循环写入策略。比如分配8个扇区每4个一组每组内按顺序写入最新的参数记录。每次只写一条记录到当前扇区的下一个空位不用动其他扇区。等这一组的4个扇区都写满了才擦写其中最早的那一个。这样实际磨损被摊薄到4倍甚至更多。反正也不复杂就是在参数头里加一个序列号读取时遍历找序列号最大的那条记录。另外固件升级用的参数区最好单独划分两个扇区交替使用。每次升级前写一个更新中标志升级完成再写更新完成标志。启动时Bootloader读取这个标志判断是否需要恢复。不要小看这个标志位它用的是最普通的单次写但能帮你把升级掉电的判断逻辑变得非常简单。还有一个细节是Flash写超时。FM3的Flash控制器执行擦除编程单片时间理论上是固定的。但如果异常情况导致Flash一直Busy你的代码会卡死在轮询循环里。之前我把超时判断放成常量0xFFFFF后来改成根据主频动态计算超时时间比如100毫秒还没结束直接返回错误并尝试重新初始化Flash控制器避免系统真死掉。最后我分享一个自己的体会富士通单片机的Flash操作核心难点不在API调用上而在于你是否理解Flash的物理限制和异常场景。建议拿到评估板后不要急着写Bootloader先用手册里的官方例程把擦除一个扇区、写一段数据、回读校验这个最小闭环跑通再做掉电测试。把调试器拔掉反复插拔电源看看板子能不能稳定恢复。这一步做到位了后面的IAP、OTA都只是水到渠成的事。本文还有配套的精品资源点击获取