尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

嵌入式烧录地址全解析:0x08000000、0x00000000与0x6000到底啥关系?

嵌入式烧录地址全解析:0x08000000、0x00000000与0x6000到底啥关系? 搞嵌入式开发的朋友应该都遇到过这种困惑同样是烧录程序有时候编译器里 Flash 起始地址填 0有时候填 0x08000000换个带 Bootloader 的工程又要填 0x6000这三者到底什么关系网上资料东一句西一句今天我把这块彻底说透。先说结论这三个地址背后其实是三套不同的逻辑0x08000000 是 STM32 这类 Cortex-M 内核芯片内部 Flash 的实际起始地址0x00000000 是芯片上电后 CPU 取向量表时映射出来的“假地址”而 0x6000 则是你在 Bootloader 后面放应用时人为算出来的偏移地址。搞清楚这三者的关系你不仅不会再填错遇到程序跑飞、无法跳转、擦除错扇区这类问题也能自己排查出个大概方向。这篇文章是我在实际调试中踩了不少坑之后整理的会结合原理、Keil/IAR 工程配置、STM32CubeProgrammer、ESP32 的 esptool 等真实场景从“三个地址为什么不一样”一直讲到“我到底该怎么确认当前板子的烧录地址”适合刚入门的小白也适合被 Bootloader 跳转折磨过的老手。文章没有任何水分全程干货。1. 三个烧录地址先分清各是谁家的“门牌号”1.1 0x08000000Cortex-M 内核芯片内部 Flash 的真正起点如果你用 Keil 新建一个 STM32 工程打开 Options for Target - Target 标签页IROM1 的起始地址默认就是 0x08000000大小根据芯片型号填 0x80000512KB、0x1000001MB等。这个地址就是芯片内部 Flash 的物理起始地址由芯片设计厂商决定ST 的 Cortex-M 系列基本都是这个值。为什么非要放在这个位置而不是从 0 开始这牵扯到 ARM Cortex-M 内核的存储器映射规则。简单打个比方Cortex-M 内核就像一个“规定好各房间位置”的公寓楼0x00000000 到 0x1FFFFFFF 这段是给代码区准备的0x20000000 到 0x3FFFFFFF 是 SRAM 区0x40000000 开始才是外设区。ST 把内部 Flash 物理地放在 0x08000000属于代码区里靠后的一段。因为规定得死所以这个地址对所有 STM32 基本统一。有个细节容易混淆物理地址 0x08000000 是 Flash 出厂就被硬件固定映射的但程序里如果直接声明const uint32_t data 0x55;这个常量有机会落在 Flash 里并不代表所有只读数据都从 0x08000000 开始排。只能说烧录器把二进制文件烧进 Flash 时默认的基地址就是它。1.2 0x00000000启动时的“影子地址”和映射逻辑很多入门教材会说“STM32 上电后从 0x00000000 取栈顶指针从 0x00000004 取复位向量”于是有人就困惑了那到底是从 0 启动还是从 0x08000000 启动答案是CPU 从 0x00000000 读取但 0x00000000 并不是 Flash 本身而是一个可以重映射的通路。芯片上电时BOOT0/BOOT1 引脚决定了 0x00000000 这个区域映射到什么物理介质上从主 Flash 启动0x00000000 映射到 0x08000000读 0 地址实际读的是 Flash 的最前面从系统存储器启动0x00000000 映射到系统区内置 Bootloader 的地址从 SRAM 启动0x00000000 映射到 0x20000000。这就是为什么“烧录地址填 0”在某些场景下也能跑起来。严格说烧录工具最终写入的还是物理 Flash 地址 0x08000000只是部分工具或一些极简的裸机工程在描述“起始地址”时直接从用户视角的 0 开始写。你可以把它理解为“系统看到的地址”和“物理存在的地址”这两套坐标。调试器里看 PC 指针复位后的值通常会停在 0x08000000 附近的地址上本质上就是从映射后的 Flash 区域开始执行的。1.3 0x6000偏移量短小却不简单0x6000 这个值十六进制展开是 0x6000十进制 24576也就是 24KB。在 STM32 的上下文中它不像 0x08000000 那样是芯片手册里写死的物理地址而是一个“偏移量”或者“应用起始地址”。最常见的情况是产品里先运行一段 Bootloader放在 Flash 最开头即 0x08000000然后引导跳转到真正的应用程序而应用程序不从头放而是放在 0x08000000 0x6000 的位置。为什么是 0x6000因为 Bootloader 的镜像大小是 24KB预留出 24KB 给它应用程序从第 24KB 处开始放自然就是 0x6000 偏移。也可能是你用的外置 SPI FlashNor Flash 的起始地址在某些映射方案里写作 0x6000但这相对少见。多数人遇到 0x6000都是 Bootloader App 分区方案里应用工程的 Flash 起始地址。2. 为什么 Flash 不直接从 0x00000000 开始2.1 ARM Cortex-M 的存储器映射规则前面提到 Cortex-M 内核规定了一套地址划分本质上是为了让同一套编译器、调试器、RTOS 能适配不同芯片厂商的产品。如果每个厂商的 Flash 地址都不一样工具链没办法统一支持所以 ARM 规定代码区必须放在 0x00000000 到 0x1FFFFFFF。问题来了既然整段 0x00000000~0x1FFFFFFF 都是代码区那芯片厂商可以自由选择把 Flash 放在这个区间的任意位置吗可以但业界普遍的做法是放在 0x08000000既不会和 0x00000000 附近的“启动映射区”冲突又留有充足空间。Flash 不直接放 0 地址还有一个重要原因0 地址附近往往还要承担中断向量表、启动配置、系统存储器等功能如果 Flash 物理上也从 0 开始一旦启动模式变化就容易冲突。2.2 从 0 到 0x08000000 的“跳转”逻辑有人会问那我直接用调试器把程序下载到 0x08000000上电后 CPU 从 0x00000000 开始取指它怎么知道去 0x08000000 找代码答案是硬件自动映射不需要软件干预。芯片内部有一个启动配置逻辑根据 BOOT 引脚的电平状态决定将哪个物理存储器的地址映射到 0x00000000。如果选择从主 Flash 启动那么 CPU 在 0x00000000 和 0x00000004 取到的内容就等同于 0x08000000 和 0x08000004 的内容。这也是为什么你可以用__attribute__((section(.isr_vector)))把向量表放在任意位置但不代表启动时 CPU 会自动去那个位置找向量表。向量表偏移需要软件设置SCB-VTOR寄存器否则 CPU 还是老老实实从 0x00000000 拿复位向量。2.3 外置 Flash 与 0x60000000 的扩展还有一个容易混的地址是 0x60000000。在 STM32 的 FSMC/FMC 控制器里Bank1 的起始地址就是 0x60000000用于映射外部 NOR Flash、PSRAM 等。如果你听到有人说“外部 Flash 地址 0x6000”千万别和外置存储控制器地址搞混0x6000 只是 24KB 偏移0x60000000 是整整 1.5GB 之后的一个段两码事。不过在一些带外部 Flash 启动的芯片方案里比如从外部 Nor Flash 启动的 i.MX RT 系列它的启动地址确实会在 0x60000000 附近因为芯片允许直接从外部存储器映射执行代码XIP。所以当你在恩智浦、华邦等方案的文档里看到 0x60000000 字样这就是另一条技术路线了。3. 0x6000 的来龙去脉Bootloader 与应用分区3.1 为什么 Bootloader 要占前面一段做产品级固件几乎不可能只有一个裸机程序从头写到尾。你需要远程升级、需要校验固件完整性、需要在应用卡死时还能恢复所以常见方案是Flash 最前面放 BootloaderBootloader 负责检查是否有新固件、是否触发跳转后面区域放应用 App。Bootloader 必须放在 Flash 起始位置 0x08000000因为芯片上电后第一段执行的代码就是它。App 则不能覆盖 Bootloader 区域所以给它分配一个偏移地址。这个偏移是多少完全由你的工程规划决定0x6000 只是其中一种选择偏移 0x800032KB、0x1000064KB的也都有。3.2 0x6000 是怎么算出来的如果 Bootloader 编译完的固件大小是 0x5A00 字节约 23KB你会预留多少空间给它如果只留 0x5A00App 紧贴其后一旦 Bootloader 后续加了功能、改了编译选项体积变大就会把 App 的开头覆盖掉。所以工程上习惯按扇区对齐并留出余量。STM32 内部 Flash 的扇区大小不是均匀的以常见的 STM32F103 为例前 4 个扇区每个 16KB后面的是 64KB。如果你的 Bootloader 固件是 20KB逻辑上需要占用 2 个 16KB 扇区那预留 32KB 也就是 0x8000 更合理。如果 Bootloader 压缩到 10KB 以内预留一个 16KB 扇区再往后推一点用 0x6000 也就说得通——16KB 是 0x4000再加点余量变成 0x6000或者这个数直接来自你 Bootloader 工程里scatter file或.icf文件的定义。我自己遇到过一种更尴尬的情况Bootloader 编译出来 23.5KB当时图省事把 App 起始地址设成 0x600024KB结果半年后改了 Bootloader 一次功能体积涨到 25KBApp 开头直接被覆盖产品在 OTA 后集体变砖。从此我学乖了Bootloader 区域必须比实际固件至少大 1 倍以上并且要在链接脚本里把 Bootloader 区域末尾的“哨兵值”或 CRC 校验固定下来一旦超规划马上报警。3.3 偏移之后向量表也要跟着搬家App 放在 0x08000000 0x6000 之后你需要在 App 工程的启动文件里设置向量表偏移。Cortex-M 内核提供了向量表偏移寄存器VTOR在 SystemInit() 函数或者 main() 最开始设置#define APP_ADDRESS 0x08006000 SCB-VTOR APP_ADDRESS;如果 App 使用 RTOS还需要确认vPortSVCHandler等中断的向量名有没有正确匹配否则一旦发生系统调用会跳到 Bootloader 的中断向量表里去程序立刻跑飞。一个比较容易忽略的坑擦除 Flash 的时候如果 App 里的 IAP 程序需要擦除自身所在扇区而你的扇区计算是按 0x08000000 为基地址算的App 运行在 0x08006000很容易算错擦除范围。我见过有人 App 里做在线升级结果升级时把 Bootloader 扇区给擦了断电后整个设备变砖只能重新用烧录器连 SWD 恢复。排查这种问题最好的办法是在擦除前打印目标扇区地址和地图比对。4. 实操怎么看、怎么确认当前板子的烧录地址4.1 从 IDE 和链接脚本出发拿到一个别人的工程第一件事就是看烧录地址。用 Keil 的直接打开 Options for Target - Target找到 IROM1 的起始地址用 IAR 的打开 Project - Options - Linker看 Config 里的#define或者 .icf 文件里对FLASH起始地址的定义用 STM32CubeIDE 的看链接脚本 .ld 文件MEMORY { FLASH (rx) : ORIGIN 0x08000000, LENGTH 512K RAM (xrw) : ORIGIN 0x20000000, LENGTH 128K }如果 ORIGIN 写的是 0x08006000那这个工程就是偏移过的 App。如果 ORIGIN 写 0x08000000那它是从 Flash 最开头直接运行的裸机工程或者 Bootloader。4.2 用命令行工具确认不依赖 IDE 也可以确认固件本身的地址信息。编译生成的 .hex 文件里每一行记录都有地址字段比如:020000040800E2这行里的 0800 表示扩展地址是 0x0800后面数据的基地址就是 0x08000000。可以用文本打开 hex 文件搜索第一行数据记录看扩展地址段写的是什么。用 bin 文件的话则需要用烧录工具或脚本读偏移。用 STM32 的话装一个 STM32CubeProgrammer连上板子后执行STM32_Programmer_CLI -c portSWD modeUR -r8 0x08000000 256这条命令从 0x08000000 读 256 字节你可以对比读到的内容是否和你编译的二进制前缀一致。如果 App 在 0x08006000那 0x08000000 处读到的很可能是 Bootloader 的向量表两个地址开头的内容会有明显差异。4.3 串口打印与调试器读取实测更直观的方式是在代码里打印当前程序所在的位置。比如在 App 的 main() 开头加一句printf(App run, vector table at 0x%X\r\n, (unsigned int)SCB-VTOR); printf(Flash base: 0x%X\r\n, (unsigned int)__Vectors);注意__Vectors的地址通常在链接脚本里被定义为段起始地址可以直接反映出当前镜像被链接到哪个地址。如果打印出来是 0x08006000就证明这个 App 确实跑在偏移地址处。还有一种方法是调试器暂停后看 PC 指针如果 PC 停在 0x08006xxx说明程序执行流确实来自偏移区配合反汇编窗口看函数地址就一目了然。用 J-Link 的话命令行执行JLinkExe -device STM32F103C8 -if SWD -speed 4000 -autoconnect 1 mem32 0x08000000 16读出来 0x08000000 的内容如果和 Bootloader 的.bin开头一致那就是 Bootloader再用mem32 0x08006000 16读一下如果内容和 App 的.bin前缀一致说明 App 确实放在了偏移地址。5. ESP32 的烧录地址又是另一套逻辑5.1 ESP32 的 Flash 布局这个话题最近热度很高很多人从 STM32 转到 ESP32 之后对烧录地址一头雾水。ESP32 与 STM32 有个本质区别它没有内部 Flash而是通过 SPI 接口外挂一块 Flash 芯片CPU 通过 Cache 映射方式直接在这个 SPI Flash 上执行代码所以它的地址体系和 STM32 完全不同。ESP32 默认的分区方案里烧录地址大概是这样bootloader一般烧到 0x1000partition table分区表一般烧到 0x8000应用程序factory app一般烧到 0x10000看到 0x1000、0x8000、0x10000 这些短地址别用 STM32 的思维去套它不是“偏移 4KB”而是整个 Flash 的起始地址就这么规划。ESP32 的 Flash 起始地址是 0x0000芯片上电后 ROM 里的 bootloader 会从 0x1000 加载二级 bootloader再根据分区表找到应用入口。所以你看 ESP32 的烧录地址经常是直接用esptool.py write_flash 0x1000 bootloader.bin 0x8000 partitions.bin 0x10000 firmware.bin这种命令行方式地址和文件一一对应。5.2 怎么看 ESP32 当前烧录地址查看 ESP32 的烧录地址最直接的方法是看工程生成的 partition CSV 文件。在 ESP-IDF 里partitions.csv会写明每个分区的类型、偏移和大小比如# Name, Type, SubType, Offset, Size factory, app, factory, 0x10000, 1M你用idf.py partition-table生成的二进制分区表烧到 0x8000之后 bootloader 就是靠这张表找到 app 的偏移地址。也可以用 esptool 回读esptool.py -p COM3 read_flash 0x10000 0x1000 app_dump.bin这会把偏移 0x10000 处的 4KB 读出来用十六进制编辑器看是不是你的 app 开头。更省事的办法是idf.py monitor启动后看启动日志ESP-IDF 的 bootloader 启动时会在日志里打出一行类似I (30) boot: Loaded app from partition at offset 0x10000这就直接告诉你当前是从哪个偏移启动的。如果你用 Arduino 生态esptool.py -p COM3 read_flash 0x10000 0x1000 dump.bin同样有效Arduino 的 bootloader 通常也在 0x1000分区表在 0x8000app 偏移是 0x10000。6. 烧录地址相关的常见坑与排查实录6.1 烧了 App 之后程序跑飞但 Bootloader 单独烧又能跑这个情况八成是 App 偏移和向量表设置不匹配。Bootloader 跳转到 App 时需要先把 App 的向量表设好具体做法是读 App 起始位置的栈顶指针SP和复位向量PC然后设置 VTOR再跳转。如果你的 Bootloader 在跳转前没有正确拿到 App 的向量表地址或者 App 内部又把 VTOR 改错了就会表现为单独烧 Bootloader 没问题单独烧 App 也能跑因为从 0 启动时默认映射但组合在一起跳转过去就死。排查思路先在 Bootloader 跳转前把 App 地址区域的 8 个字节读出来打印确认栈顶指针是否指向有效 RAM 区域复位向量是否落在 App 的 Flash 范围。栈顶指针是一个 RAM 地址比如 0x20002000复位向量应该在 0x0800xxxx 区间。如果读出来的值是 0xFFFFFFFF说明 App 根本没烧进去或者烧录地址和你设置的偏移不一致。6.2 用下载器烧录时地址填错芯片直接“变砖”这是新手最容易犯的错用 ST-Link 下载程序时在烧录工具的“编程地址”或“下载地址”里直接填了 0x08006000这是不对的。烧录工具默认会把你的 .hex/.bin 烧到芯片的物理 Flash 起始地址也就是 0x08000000。如果你想做 Bootloader App 分区不是让下载器从 0x08006000 开始“物理写”而是要在链接脚本里把 App 的代码段地址设成 0x08006000烧录器仍然从 0x08000000 开始把你编译出的 App 的机器码写到 0x08000000 0x6000 处。怎么解决如果用的是 .hex 文件它自带地址信息烧录器按 hex 里的地址写入不需要你填偏移如果用 .bin 文件烧录工具往往会让你指定烧录地址这时你要填 0x08006000工具才会把 bin 的内容放到偏移处。简单记hex 自带门牌号bin 不带需要你告诉它门牌号。还有一种情况是 OTA 升级时固件包里既有 Bootloader 又含 AppBootloader 在跳转前会对 App 做 CRC 校验如果你 OTA 工具写入 App 时基地址和 App 编译时链接地址不一致校验会失败出现“升级成功了但一直跳不过去”的假现象。排查时先确认生成 OTA 包时的地址参数和后端下发的地址参数是否一致这个坑我见人踩过不止三次。6.3 设置了偏移之后还是从 0 执行有一个非典型的坑你在 App 工程里设置了偏移地址 0x6000也设置了SCB-VTOR但程序一上电还是从 0x08000000 执行。这通常是你的 App 里包含了一个全量擦除 Flash 的 IAP 功能执行时把 0x08000000 开头的区域也擦掉了或者你的下载器配置成了“整片擦除”烧 App 的时候把 Bootloader 一起擦掉了。另外也检查一下是不是中断向量表段名写错。GCC 环境下启动文件把.isr_vector段放在 FLASH 最前面如果你自定义了段名链接脚本里的.isr_vector没有跟着改向量表就会被放在别的段后面导致 CPU 上电时取到的复位向量并不是你的 main。排查办法是反汇编看生成的 .map 文件__isr_vector地址如果在 0x08006000 附近说明放置正确如果在 0x08000000那你的偏移设置其实没生效重新检查链接脚本的段布局。6.4 擦除扇区时地址算错把 Bootloader 干掉了这个话题在前面提过值得单独列一条。在 App 里做 Flash 擦除时不同芯片的扇区大小不一样STM32F103 前 4 个扇区是 16KB之后是 64KBSTM32F407 的扇区布局又完全不同前 4 个 16KB第 5 个 64KB后面还有 128KB。如果你要擦除 0x08006000 开始的一个扇区不能简单往上加 4KB 再按 16KB 对齐。正确做法是拿 Flash 编程手册里的扇区映射表核对。我写了一个简单的扇区偏移计算函数每次擦除前都校验目标地址是否落在 Bootloader 保护区域之外低于阈值直接报错返回#define BOOTLOADER_END_ADDR 0x08006000 #define APP_START_ADDR 0x08006000 static uint32_t flash_sector_start(uint32_t addr) { if (addr 0x08000000 addr 0x08004000) return 0x08000000; if (addr 0x08004000 addr 0x08008000) return 0x08004000; return 0xFFFFFFFF; }这套方法虽然粗暴但能阻止我在调试阶段因为算错基地址而把 Bootloader 整片擦掉。把保护范围写在宏里而不是靠人记这才是嵌入式代码该有的态度。7. 烧录地址速查表与几条实用结论如果你时间紧不想看原理这里直接给你一张速查表地址含义典型场景0x00000000启动映射地址硬件别名CPU上电取向量表BOOT引脚决定映射目标0x08000000STM32内部Flash物理起始地址无Bootloader工程、Bootloader自身、绝大多数标准工程0x08006000内部Flash 24KB偏移Bootloader占用前24KB时的App起始地址0x60000000FMC/FSMC外部NOR Flash起始地址外部并行总线Flash映射0x1000ESP32二级bootloader偏移ESP32 bootloader固件0x8000ESP32分区表偏移ESP32 partition table0x10000ESP32 factory app偏移ESP32应用程序默认入口调试时记住这几条结论就够了芯片物理 Flash 地址优先看数据手册而不是凭经验猜STM32 系列绝大多数是 0x08000000但不是所有 Cortex-M 芯片都一样有些低功耗芯片 Flash 从 0x00000000 开始。Bootloader 工程和 App 工程的偏移量必须一致且要预留足够余量我个人的习惯是 Bootloader 区域至少留出固件实际大小的 1.5 到 2 倍并且用编译后检查脚本卡住体积。应用工程一定要设置 VTOR否则中断向量表错位程序大概率一进中断就跑飞。用 bin 文件烧录时必须手动指定烧录地址用 hex 文件则不用但下载器如果配置了全片擦除还是要小心。最后再分享一个小技巧如果你拿到一块二手板子不知道程序是什么状态就先读一下 0x08000000 开头的 4 个字节看看是不是 0x2000xxxx 开头的栈顶指针再读 0x08000004 看看复位向量地址。这两个值如果是有效的板子大概率有可运行固件如果全是 0xFFFFFFFF那就是空片或者 Flash 被擦过了。这个检查方法我用了很多年比直接上电看现象快得多推荐你也养成这个习惯。
返回列表