LV030-FLASH APP设计
一、 定位与功能
1. 它解决什么问题
flash-app 是 IAP 方案里"被升级的那一方",也是整个方案的主线:它从内部 Flash 第 2 块(0x08020000)运行,因此可以通过网络或串口把新的镜像写进同一块区域来替换自己。所有"下载、校验、落盘"的设计都是为了让这个目标成立。
它与 Bootloader 的关系是一次性的交接:Bootloader 在 0x08000000 完成初始化、判断 APP 区有没有合法镜像,然后把控制权交出去,此后不再参与运行。
2. 功能清单
| 功能 | 实现位置 | 说明 |
|---|---|---|
| 串口日志输出 | fputc() 重定向到 USART1 | 115200,中断接收、轮询发送 |
行命令 reboot | app_cmd_reboot() | 打印后等 TC,软复位回 Bootloader |
| 串口回显 | usart_echo_test() | 把收到的整行原样回发,用于验证链路 |
| 看门狗 | iwdg_start() + 主循环喂狗 | 3 秒超时,初始化完成后使能 |
| 网络 | 无 | flash-app 不含 lwIP 与 DM9000 驱动 |
一句话概括:它是个最小可运行的应用,只保留"证明自己活着"和"能被重启回升级流程"两件事。业务代码是后续往上加的,升级通道与启动衔接已经打通。
3. 与其它镜像的关系
| 镜像 | 落盘位置 | 运行位置 | 交付方式 |
|---|---|---|---|
| Bootloader | 0x08000000 | 0x08000000 | SWD 下载 |
flash-app | 0x08020000 | 0x08020000 | app.img,经串口/TCP 写回 |
sram-app | 0x08040000 暂存 | 0x20007000 | app.img,再 go sram 搬运 |
sram-app 与 flash-app 源码同源、链接地址不同,功能集合基本一致;差别在于 sram-app 掉电即失、需要额外的装载步骤(见 LV035-SRAM-APP设计)。
二、 链接地址
1. IROM1 与 IRAM1 的取值
| 项 | 取值 | 含义 |
|---|---|---|
| IROM1 | 0x08020000,大小 0x20000(128KB) | 代码与只读数据的起始地址与容量 |
| IRAM1 | 0x20000000,大小 0x10000(64KB) | 全局变量、堆、栈;此时 Bootloader 已退出,整块 RAM 可用 |
2. 为什么必须是 0x08020000
三个原因叠加,缺一不可:
- Bootloader 自己占第 1 块(
0x08000000~0x08020000),APP 必须让开; - IAP 的帧头只接受两个容器基址,
0x08020000是其中之一(另一个是0x20007000),别的地址直接拒绝,不做"就近对齐"这种猜测; - 页擦除以 2KB 为粒度,128KB 块边界正好是页边界,擦除时不会碰到 Bootloader。
3. 向量表与 APP_BASE
APP 自己的向量表就放在镜像起始处,main() 开头、HAL_Init() 之前先重定位一次:
// flash-app/Core/Src/main.c
#ifndef APP_BASE
#define APP_BASE 0x08020000U /* IAP_APP_BASE: 与 IROM1 / pack_iap.py 一致 */
#endif
/* 向量表重定位到 APP 基址: 必须早于 HAL_Init(), 否则中断仍会走 Bootloader 的向量表 */
SCB->VTOR = APP_BASE;这一步在"由 Bootloader 跳转"时是冗余的(跳转前已经设过),但它让镜像具备"独立启动"的能力:只要把 APP_BASE 覆盖成实际链接地址,同一个工程既能被 IAP 装载,也能被调试器直接下载运行。
三、 工程与构建
1. 两个 Target
flash-app.uvprojx 里有两个 Target,源码完全共享,只有宏定义不同:
| Target | 宏 | 链接地址 | 用途 |
|---|---|---|---|
flash-app | 默认,APP_BASE=0x08020000 | 0x08020000 | 交付镜像,走 app.img |
flash-app-debug | FLASH_APP_BASE=0x08000000 | 0x08000000 | 直跑调试,SWD 下载后直接启动 |
flash-app-debug 的存在解决了一个具体问题:IAP 链路没跑通时,仍然需要一个"能独立上电运行"的版本去验证业务代码。它覆盖掉 Bootloader 的地址,因此只能配合整片擦除的下载方式——用调试器下载它会把 Bootloader 覆盖掉。
构建脚本也据此加了门禁:-d 只对 -t debug 生效,release 目标带 -d 只提示不下载(见 build_flash.ps1)。
2. 构建产物
一次 release 构建会产出三个文件,各有明确用途:
| 产物 | 生成方式 | 用途 |
|---|---|---|
flash-app.axf | ARMCC 链接输出 | 调试符号,J-Link 下载用 |
flash-app.bin | fromelf --bin | 裸镜像,给 pack_iap.py 打包 |
app.img | 构建后自动执行 tools/pack_iap.py | IAP 的传输格式 |
.bin 与 app.img 的关系见 LV040-image镜像文件打包原理与生成。
3. 构建命令
# release: 生成 app.img,供串口/TCP 下载
.\build_flash.ps1 -b
# 调试: 链接到 0x08000000,可 SWD 直接下载
.\build_flash.ps1 -b -t debug -d四、 上电到运行
1. Bootloader 的判定
Bootloader 启动后不会无条件跳转,先看 0x08020000 处的向量表:
- 初始栈顶必须落在
0x20000000~0x20010000; - 复位向量必须落在
0x08020000~0x08040000内,且最低位为 1(Thumb 地址)。
两条都过才跳,否则停在命令行等待下载。所以"APP 区是空的"不会导致跑飞,只会看到 Bootloader 的命令提示。
2. 运行时的内存分配
flash-app 运行期间独占内部 SRAM:.data 由 __main 从 Flash 拷贝到 RAM,.bss 清零,栈顶由向量表第 1 项给出(初始值 0x20010000)。Bootloader 此时已经交出控制权,它的 RW/ZI 不再被访问,因此不存在抢占 RAM 的问题。
3. 交给它的硬约束
- 链接地址一旦改动,
pack_iap.py的APP_BASE、iap.h的IAP_APP_BASE、向量表合法性检查三处都要同步; - 镜像不能超过 128KB,
pack_iap.py在打包阶段就会拒绝超限; - 中断向量表必须随镜像一起落盘(它在镜像头部),否则跳转后第一次中断就会取到 Bootloader 的处理函数。
五、 常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
下载后敲 go flash 没反应,日志说 no app | APP 区未写入或向量表不合法 | 先发 flash-app 的 app.img,确认 flash write OK |
| 跳转后立刻 HardFault | 镜像链接地址与帧头地址不一致 | 用 -t debug 编出的镜像走 IAP 就会这样,改用 release 构建 |
| 串口无输出 | flash-app-debug 覆盖了 Bootloader,但没整片擦除 | 重新整片擦除后再下载,或先下载 Bootloader |
| 复位后回到 Bootloader 而不是 APP | 正常运行行为 | Bootloader 每次上电都要重新判定,用 go flash 或等自动跳转 |
本文档由 markdowncli 技能辅助生成