首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >国产 MCU IAP 移植的 5 个差异:擦除粒度、写保护回读与 MM32 27 型号分区表

国产 MCU IAP 移植的 5 个差异:擦除粒度、写保护回读与 MM32 27 型号分区表

原创
作者头像
用户11831219
修改于 2026-11-09 09:23:18
修改于 2026-11-09 09:23:18
960
举报

这几年国产 MCU 的硬件参数基本追平了,但在 IAP 在线升级这件事上,移植时还是会多踩几个坑。不是芯片不行,是资料和例程给得不够细。

这篇按「实际会卡住的地方」写,不讲选型。

一、M0 内核没有 VTOR,而很多 SDK 没提这件事

先纠正一个流传很广的说法:Cortex-M0 / M0+ 并没有 SCB->VTOR。

ARMv6-M 架构里根本没有 VTOR 寄存器,向量表固定从地址 0 取。所以 M0 内核的片子不能靠写 VTOR 搬向量表——写了要么编译不过,要么写到一个不存在的地址上,静默无效。

真正有 VTOR 的是 M0+ / M3 / M4 / M33 / STAR-MC1:

代码语言:c
复制
SCB->VTOR = APP_ADDR;
__DSB();
__ISB();

M0 只能把向量表拷到 SRAM 起始,再把 SRAM 映射到 0x0:

代码语言:c
复制
memcpy((void *)0x20000000, (void *)APP_ADDR, 0xC0);
RCC_APB2PeriphClockCmd(RCC_APB2ENR_SYSCFG, ENABLE);
SYSCFG_MemoryRemapConfig(SYSCFG_MemoryRemap_SRAM);

三行缺一不可:拷贝、开 SYSCFG 时钟、切映射。少第二行是硬伤——时钟没开,写 CFGR1 直接无效,现象就是「看起来都做了,中断还是飞到 Boot 的表里」。

麻烦在于,不少国产 SDK 的 SystemInit 里既没有 VTOR 概念,也没有重映射示例,只能自己补。STM32 的 HAL 库至少还摆了个 HAL_NVIC_SetVectorTable 在那儿,虽然也得自己调,但至少有这个 API。

二、擦除粒度不一样,分区表不能照抄

这是移植时最容易翻车的地方。

STM32F1 是小容量 1KB 页、大容量 2KB 页;F4 是 16K/64K/128K 不等的扇区。国产片子更杂:有的 512 字节一页,有的 1KB,有的 2KB,还有按块擦的。

我一开始拿 STM32 的分区表直接套到国产片子上,结果信息区(1KB)跨了两个擦除单元,擦信息区的时候把 App 的头 512 字节一起擦了。现象是「升级完第一次能跑,断电再上电就死」——因为 App 的向量表已经被抹掉了。

后来统一改成一条规则:分区大小必须是该型号最大擦除单元的整数倍,信息区按 2KB 预留,宁可浪费 1KB。

按容量算下来是这样一张表:

Flash

Boot

Info

App

App 起始地址

典型型号

16 KB

4 KB

1 KB

11 KB

0x08001400

MM32F0010 / F0020 / F0040

32 KB

8 KB

1 KB

23 KB

0x08002400

MM32F0130 / F0140 / G0001

64 KB

8~16 KB

1 KB

47~55 KB

0x08004400

MM32F0160 / F0270

128 KB

16 KB

1 KB

111 KB

0x08004400

MM32F3270

256 KB+

16~32 KB

1 KB

其余

—

MM32F5270 / F5330

写代码时别在任何地方手写 0x08001400。用两个宏算出来:

代码语言:c
复制
#define BOOT_SIZE   (4 * 1024)
#define FLASH_BASE  (0x08000000)
#define INFO_ADDR   (FLASH_BASE + BOOT_SIZE)
#define APP_ADDR    (INFO_ADDR + 0x400)

改型号时只改 BOOT_SIZE 和 FLASH_SIZE 两个数,四个地址自动跟着走。手写一次地址,就多一个出错点。

三、写保护默认是开着的

有几颗国产片子出厂时选项字节里带写保护,或者 Boot 区被锁。表现是 Flash 编程函数返回成功,读回来还是 0xFF。

这个坑很难查,因为代码逻辑全对、返回值全对。判断方法只有一个:写完立刻回读比对,不一致就报错,别信返回值。

代码语言:c
复制
if (memcmp((void *)addr, buf, len) != 0) return ERR_VERIFY;

我现在在 Boot 里固定加这一步,代价是每个块多一次读,几十 KB 的固件也就多几十毫秒。省下的是排查一整天的时间。

四、厂商例程多数停在「能跳转的 demo」

这是最耗时间的一块。

ST 的 IAP 有 AN2557、AN4657 这类应用笔记,Boot 工程、App 工程、协议文档、上位机全套给齐。国产厂商给的 IAP 例程,多数停在一个能跳转的最小 demo:协议自己定,上位机自己写,CRC 自己算,Boot 和 App 怎么合并自己想办法。

单看每一项都不难,但一个工程师从零趟一遍,两周就没了。中间那些坑——擦完掉电、元数据写坏、CRC 变体两边不一致、跳转前没校验栈顶——每一个都要撞一次才记得住。

而且这些坑有个共同特点:在实验室里不出现,在现场才出现。 掉电时机、电压跌落、4G 断线,都是台架上复现不了的。

五、所以我把它做成了一套可移植的实现

我手上项目用的片子比较杂,灵动微 MM32 系列居多,也混着 STM32。干脆把 IAP 抽成一套可移植的实现:

  • 覆盖 27 个型号,四种内核(M0 / M0+ / M3 / STAR-MC1)
  • Boot 固定 4KB,信息区独立 1KB,App 从 0x08001400 起
  • 三重 CRC32:帧级(64 字节)、块级(4KB)、整包,断线只重传一帧
  • 跳转前四道门:升级标志、长度、整包 CRC、栈顶落在 RAM 区间
  • 附带上位机、64 字节定长帧协议表、Boot+App 合并工具
  • 支持 UART、USB HID、USB 虚拟串口、TinyUSB MSC 四种升级通道

已经在几十块板子上跑过,Apache-2.0,商用随便改:

https://gitee.com/skydiver/mm32-iap-demo

六、移植到新片子只改三个文件

这是这套方案的设计目标。移植成本必须压到「一个下午」这个量级,否则没人愿意用。

要改的只有三处:

  1. 分区表(boot.h 里的容量宏)
  2. Flash 驱动(擦除、写入、回读,三个函数)
  3. 串口收发(收帧、超时判断)

协议层、校验层、跳转逻辑、上位机全部不用动。因为这三层里没有一行代码依赖具体型号——地址从宏来,擦除粒度从宏来,其他都是纯逻辑。

七、这套方案的边界

说清楚不做什么,比说做什么有用:

  • 不做双备份(A/B 分区)。 这套是「单 APP + 强校验 + 可重试」,不是「双分区无缝切换」。Flash 小于 64KB 的型号,做扎实校验比硬上双分区划算。
  • 不做签名验签。 需要来源认证的场景要自己加,RSA-2048 和 ECDSA P-256 的取舍在另一篇里讨论过。
  • 不解决传输可靠性以外的问题。 断线重传做了,但网络质量差到长时间掉线,还是得靠上层调度。

远程 OTA 场景下怎么在这套基础上加分发和灰度,见同系列的《从串口 IAP 到远程 OTA:MM32 上的一次分区与校验链改造》。

本文基于 Gitee 开源仓库 mm32-iap-demo(V2.0)整理,源码版权归原作者(Skydiver)所有。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、M0 内核没有 VTOR,而很多 SDK 没提这件事
  • 二、擦除粒度不一样,分区表不能照抄
  • 三、写保护默认是开着的
  • 四、厂商例程多数停在「能跳转的 demo」
  • 五、所以我把它做成了一套可移植的实现
  • 六、移植到新片子只改三个文件
  • 七、这套方案的边界
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档