这几年国产 MCU 的硬件参数基本追平了,但在 IAP 在线升级这件事上,移植时还是会多踩几个坑。不是芯片不行,是资料和例程给得不够细。
这篇按「实际会卡住的地方」写,不讲选型。
先纠正一个流传很广的说法:Cortex-M0 / M0+ 并没有 SCB->VTOR。
ARMv6-M 架构里根本没有 VTOR 寄存器,向量表固定从地址 0 取。所以 M0 内核的片子不能靠写 VTOR 搬向量表——写了要么编译不过,要么写到一个不存在的地址上,静默无效。
真正有 VTOR 的是 M0+ / M3 / M4 / M33 / STAR-MC1:
SCB->VTOR = APP_ADDR;
__DSB();
__ISB();M0 只能把向量表拷到 SRAM 起始,再把 SRAM 映射到 0x0:
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。用两个宏算出来:
#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。
这个坑很难查,因为代码逻辑全对、返回值全对。判断方法只有一个:写完立刻回读比对,不一致就报错,别信返回值。
if (memcmp((void *)addr, buf, len) != 0) return ERR_VERIFY;我现在在 Boot 里固定加这一步,代价是每个块多一次读,几十 KB 的固件也就多几十毫秒。省下的是排查一整天的时间。
这是最耗时间的一块。
ST 的 IAP 有 AN2557、AN4657 这类应用笔记,Boot 工程、App 工程、协议文档、上位机全套给齐。国产厂商给的 IAP 例程,多数停在一个能跳转的最小 demo:协议自己定,上位机自己写,CRC 自己算,Boot 和 App 怎么合并自己想办法。
单看每一项都不难,但一个工程师从零趟一遍,两周就没了。中间那些坑——擦完掉电、元数据写坏、CRC 变体两边不一致、跳转前没校验栈顶——每一个都要撞一次才记得住。
而且这些坑有个共同特点:在实验室里不出现,在现场才出现。 掉电时机、电压跌落、4G 断线,都是台架上复现不了的。
我手上项目用的片子比较杂,灵动微 MM32 系列居多,也混着 STM32。干脆把 IAP 抽成一套可移植的实现:
已经在几十块板子上跑过,Apache-2.0,商用随便改:
https://gitee.com/skydiver/mm32-iap-demo
这是这套方案的设计目标。移植成本必须压到「一个下午」这个量级,否则没人愿意用。
要改的只有三处:
boot.h 里的容量宏)协议层、校验层、跳转逻辑、上位机全部不用动。因为这三层里没有一行代码依赖具体型号——地址从宏来,擦除粒度从宏来,其他都是纯逻辑。
说清楚不做什么,比说做什么有用:
远程 OTA 场景下怎么在这套基础上加分发和灰度,见同系列的《从串口 IAP 到远程 OTA:MM32 上的一次分区与校验链改造》。
本文基于 Gitee 开源仓库 mm32-iap-demo(V2.0)整理,源码版权归原作者(Skydiver)所有。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。