LV035-SRAM APP设计
一、 运行原理
1. 为什么代码可以直接在 SRAM 里跑
Cortex-M3 的取指只看地址落在哪个总线区域:0x20000000 起是 SRAM,挂在 D-bus / I-bus 上,同样可以被取指。只要满足三个条件,代码在哪块可以随机访问的存储器里都能执行:
- 链接时的执行地址就是实际装载地址(位置相关代码,不能被搬运到别处);
- 镜像的向量表在运行时被
SCB->VTOR指向,中断才能找到处理函数; - 装载完成后按向量表把 MSP 换成镜像自己的栈顶。
sram-app 就是把这三点做到位的一个普通工程,没有任何"特殊运行方式"。它唯一特殊的地方在于:运行位置不是落盘位置。
2. 掉电即失
SRAM 是易失介质,sram-app 一旦断电就没了,重新上电后 0x20007000 处是随机值。所以它不能像 flash-app 那样"烧一次、上电就跑",每次都要经过一次装载:
| 阶段 | 介质 | 谁来做 |
|---|---|---|
| 保存 | 内部 Flash 第 3 块 0x08040000 | Bootloader 收到 app.img 后写入 |
| 装载 | 内部 SRAM 0x20007000 | Bootloader 收到 go sram 后整块 memcpy |
| 运行 | 内部 SRAM 0x20007000 | 跳转后由镜像自己执行 |
掉电后 Flash 里的那一份还在,重新上电发一条 go sram 就能再装载一次,不必重发镜像。这是把"暂存区"放进 Flash、而不是"收完直接写 SRAM"的直接收益。
3. 两条进入途径
| 途径 | 谁触发 | 发生了什么 |
|---|---|---|
| IAP 装载 | 串口/TCP 发 app.img,再发 go sram | 写暂存区 → 校验 → 拷进 SRAM → 跳转 |
| 调试器直接下载 | sram-app-debug + SWD | 链接在 0x08000000,下载后上电即跑,用于验证业务逻辑 |
第二条途径用的是另一个 Target,链接地址不同,跑起来占的是 Flash 第 1 块——它会覆盖 Bootloader,所以只用于"先把业务代码调通"的阶段。
二、 链接地址
1. IROM1 与 IRAM1
| 项 | 取值 | 说明 |
|---|---|---|
| IROM1(代码容器) | 0x20007000,大小 0x6000(24KB) | 镜像装载与执行的位置 |
| IRAM1(数据区) | 0x2000D000,大小 0x2C00(11KB) | .data/.bss/堆/栈 |
| 栈顶 | 0x2000FC00 | 由向量表第 1 项给出 |
| NOINIT | 0x2000FC00 起 1KB | 复位不清零的保留区,Bootloader 与 APP 共用约定 |
2. 三个边界为什么这样切
0x20000000~0x20007000留给 Bootloader:它的 RW/ZI 实际用到0x20005E18,剩下的空间是余量,不能拿来放 APP;0x20007000~0x2000D000正好 24KB,是代码容器。取整到 4KB 边界是为了页对齐和调试查看方便;0x2000D000~0x2000FC00放 APP 的全局变量与栈,与代码区隔开,避免栈溢出直接把指令覆盖掉(栈溢出会先撞到 NOINIT 区,症状比重叠代码更好查)。
3. 三处必须同步
| 位置 | 内容 | 不同步的后果 |
|---|---|---|
sram-app.uvprojx 的 IROM1 | 0x20007000 / 0x6000 | 链接出的镜像按错误地址编址,装载后必然跑飞 |
iap.h 的 IAP_SRAM_APP_BASE/MAX_SIZE | 0x20007000 / 24KB | 帧头地址校验拒绝,或搬运越界 |
pack_iap.py 的 SRAM_APP_BASE/END | 同上 | 打包时就报 address or size out of range |
三、 功能说明
1. 功能清单
| 功能 | 说明 |
|---|---|
| 串口日志 | 与 flash-app 相同,115200,fputc 轮询发送 |
行命令 reboot | 打印后等 TC,软复位回 Bootloader |
| 串口回显 | 收到整行原样回发,用于验证装载后外设是否正常 |
| 看门狗 | iwdg_start() 后主循环喂狗,3 秒超时 |
| 网络 | 无 |
2. 与 FLASH APP 的差异
| 维度 | flash-app | sram-app |
|---|---|---|
| 链接地址 | 0x08020000 | 0x20007000 |
| 落盘位置 | 就是运行位置 | 0x08040000 暂存 |
| 掉电后 | 内容保留 | 内容丢失,需重新 go sram |
| 装载步骤 | 写完即可跳 | 写完还要搬运、回读校验 |
| 调试豁免 | flash-app-debug 链接 0x08000000 | sram-app-debug 同样链接 0x08000000 |
| 代码体积上限 | 128KB | 24KB(容器限制) |
24KB 的上限是实打实的约束:一旦业务代码把 .text 撑到 24KB 以上,链接阶段就会报 region 溢出,此时只能压缩代码或改分区。
四、 启动与向量表
1. 进入 main 之前
跳转是"硬跳":Bootloader 读暂存区向量表拿到 MSP 与复位向量,设好 SCB->VTOR,__set_MSP(),然后跳到 Reset_Handler。于是 sram-app 的启动流程与普通 Flash 启动完全一致:
(1)Reset_Handler 调 SystemInit(),配时钟与 Flash 预取;
(2)__main 把 .data 初值从镜像里拷到 0x2000D000 起,把 .bss 清零;
(3)main() 里第一件事是把 VTOR 指向自己。
2. VTOR 必须在 HAL_Init 之前
// sram-app/Core/Src/main.c
#ifndef APP_BASE
#define APP_BASE 0x20007000U /* IAP_SRAM_APP_BASE: 与 IROM1 / pack_iap.py 一致 */
#endif
/* 向量表重定位到 APP 基址: 必须早于 HAL_Init(), 否则中断仍会走 Bootloader 的向量表 */
SCB->VTOR = APP_BASE;
HAL_Init();HAL_Init() 里会配 SysTick 并开中断,如果那时 VTOR 还没改,SysTick 中断会按 Bootloader 的向量表跳到 0x08000000 + 0x3C——落进 Bootloader 的代码里,症状是随机 HardFault 或"程序莫名跑回 Bootloader"。
同样的道理,SystemInit() 里也不要依赖中断:它执行时 VTOR 还是 Bootloader 设的值(恰好正确),但一旦有别的镜像经由其它路径进入,就会踩到同一个坑。
3. 中断都落在 SRAM 里
VTOR 指向 0x20007000 之后,所有异常向量都指向 SRAM 中的处理函数。这也意味着装载完成前不能开中断:Bootloader 在 memcpy 期间保持自己的中断设置,拷完校验通过才切 VTOR 并跳转。
五、 构建与装载
1. 构建
# release: 链接 0x20007000,构建后由 pack_iap.py -a 0x20007000 打成 app.img
.\build_sram.ps1 -b
# 调试: 链接 0x08000000,可 SWD 直接下载运行
.\build_sram.ps1 -b -t debug -drelease 构建的产物序列是:sram-app.axf → fromelf --bin → sram-app.bin → pack_iap.py -a 0x20007000 → app.img。
2. 装载流程
| 步骤 | 命令 | 校验 |
|---|---|---|
| 写入暂存区 | 发 app.img(串口或 TCP) | 帧 CRC32 + 回读 CRC32 |
| 搬运 | go sram | 向量表三条判据 + 搬运后 CRC32 |
| 跳转 | 同上 | iap_load_app(0x20007000) 再查一次向量表 |
搬运这一步的细节值得单独看清:从 Flash 暂存区到 SRAM 容器是两块不同介质之间的拷贝,长度固定取容器容量 24KB(24576 字节),而不是帧头里记录的实际长度——暂存区没写到的尾部是 0xFF,落在容器内永远不会被执行到。
3. 常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
go sram 提示 no sram-app image in flash staging | 暂存区没写过 | 先发 app.img,确认落盘成功 |
| 跳转后立即 HardFault | 镜像按 0x08000000 链接(用了 debug Target) | 用 release 构建重新打包 |
| 运行一段时间后复位 | 24KB 容器被写穿,或看门狗未喂 | 查 .map 的 region 占用与喂狗周期 |
| 复位后 APP 消失 | 正常现象,SRAM 易失 | 重新发 go sram |
| 中断进了 Bootloader 的处理函数 | VTOR 未在 HAL_Init() 之前设置 | 检查 main() 开头的 SCB->VTOR = APP_BASE |
本文档由 markdowncli 技能辅助生成