
刚入行那会儿我也被烧录地址搞得头大过。同一块开发板有人让你填0有人让你填0x08000000还有人教程里写的是0x6000三个数字看着都有道理可真换个数字烧进去板子有时候就是纹丝不动。后来被 STM32 的 IAP 折磨了一周又把 ESP32 的分区表翻了个底朝天才彻底想明白这不是谁对谁错的问题而是芯片的内存映射方式、你手里烧录工具采用的地址基准、以及你烧的是 ELF 还是 BIN这三个变量共同决定了地址栏该填什么。这篇文章就把0、0x08000000、0x6000这三个常驻地址一次聊透再顺手解决一个大家高频搜索的问题怎么看 ESP32 的烧录地址。内容尽量不绕弯子适合刚入门的朋友也适合已经在做 IAP/OTA、但偶尔还会被地址坑一把的工程师。1. 先聊明白三个数字背后其实是三种“地址基准”很多人把烧录地址当成一个简单的数字其实它背后是一整套地址规则。同样是“往 Flash 里写东西”有的芯片要求你给绝对地址有的芯片只认相对偏移有的工具甚至在你什么都不填的时候默认从0开始。不搞懂这套规则今天记住了0x08000000换个芯片、换个工具照样抓瞎。1.1 统一编址与分区偏移芯片设计路线的分界Cortex-M 内核的 MCU比如 STM32和 ESP32 这类 WiFi/蓝牙 SoC在地址处理方式上走了两条完全不同的路。STM32 属于“统一编址”阵营。CPU 的数据总线、指令总线能直接看到外部 FlashFlash 被映射到 CPU 地址空间的一个固定区域。对你来说这就意味着代码里每一个函数、每一个中断向量都有唯一的 32 位绝对地址地址长什么样直接由芯片厂在数据手册里定死。STM32F103 的 Flash 首地址是0x08000000这个数字不是 ARM 公司规定的而是 ST 在芯片设计时把 Flash 安排在这个位置。换一家厂商地址就可能完全不同。ESP32 则相反。它的 Flash 是外部 SPI FlashCPU 虽然能通过 Cache 窗口访问 Flash 里的代码但对“烧录”这件事来说你操作的对象是 Flash 物理扇区上的“偏移位置”。工具只认“从 Flash 开头算起的第几个字节”没有“Flash 基地址”这个概念。这也是很多从 STM32 转过来的人第一次烧 ESP32 时特别别扭的原因你找不到0x08000000因为这东西在 ESP32 的世界里根本不存在。1.2 一张表先盘清三者的关系先给个速查表后面再逐个展开地址值常见出现场景地址本质与基地址的关系0工具默认值某些 Flash 基地址为0x00000000的 MCU如 CH32V003ESP32 合并固件从 Flash 开头写入要么是偏移量要么是真正的 Flash 首地址等价于“从 Flash 开头开始”0x08000000STM32/GD32 等 Cortex-M 芯片的 Flash 基地址绝对物理地址整个用户 Flash 区域的起点0x6000STM32 IAP 工程里 App 相对 Bootloader 的偏移部分工具显示的是“相对偏移量”有些教程里还混用了 ESP 分区大小绝大多数情况是相对 Flash 基地址的偏移量绝对地址 基地址 偏移如0x08000000 0x6000 0x08006000所以当你看到0x6000第一反应不应该是背下这个数字而是去找它背后的“基地址”。同样看到0的时候也要多问一句这个工具让我填的到底是“绝对地址”还是“偏移量”2. Cortex-M 上的 0 与 0x08000000同一块 Flash 的两种叫法先拿最经典的 STM32 开刀因为0x08000000是绝大多数人入坑的第一个“大地址”。2.1 为什么 STM32 的 Flash 固定住在 0x08000000Cortex-M 内核复位后CPU 是从0x00000000取向量表的但 STM32 的 Flash 物理地址却在0x08000000。这中间靠的是芯片内部的“内存别名区”机制根据 BOOT0/BOOT1 引脚的电平芯片可以把不同存储介质Flash、系统存储器、SRAM映射到0x00000000这个别名区。所以你在调试器里看到的0x08000000是 Flash 的“真实身份地址”而0x00000000启动地址只是它的一个马甲。之所以要把 Flash 放在0x08000000是为了给 SRAMSTM32F103 从0x20000000开始、片上外设寄存器从0x40000000开始留出足够的编址空间。芯片厂在设计内存映射表的时候会兼顾内核启动逻辑和外设分布最终 Flash 的落点就成了0x08000000。这里必须强调一点0x08000000不是所有 Cortex-M 芯片的通用答案。有一批国产 MCU比如 CH32V003的 Flash 基地址就是0x00000000烧录地址填0完全没问题。所以千万别死记一个地址当万能解一切以对应芯片的参考手册“Memory Map”章节为准。2.2 链接脚本在中间起了什么作用地址不是编译完才冒出来的它在链接阶段就被决定了。拿 STM32 工程里的.ld链接脚本举例MEMORY { FLASH (rx) : ORIGIN 0x08000000, LENGTH 512K RAM (xrw) : ORIGIN 0x20000000, LENGTH 64K }链接器看到这个脚本后会把代码段、只读数据段、中断向量表统统安排在0x08000000到0x0807FFFF这个区间内。于是你编译出来的 ELF 文件里每一个段都有明确的 VMA虚拟地址比如Reset_Handler在0x08000155向量表在0x08000000。这些地址最终会被写进 HEX 文件下载器一看 HEX 记录里的地址就知道往哪烧。问题出在 BIN 文件。BIN 是纯粹的二进制数据流不包含任何地址信息。你把同一个 App 编译出.hex和.bin两个文件.hex烧录时不用填地址因为文件记录里明明白白写着0x08000000.bin烧录时必须手动告诉下载器“放在哪”。如果你忘填了工具可能默认从0开始那代码就被写进了 STM32 的0x00000000区域——这个位置在 Flash 上根本不存在或者只是别名区上电自然跑不起来。2.3 工具什么时候显示 0什么时候显示 0x08000000用 Keil MDK 的时候Options - Target 里的 IROM1 起始地址是0x08000000这决定的是编译出来的代码位置而 Utilities - Settings - Flash Download 里勾选的烧录算法起始地址一般也是0x08000000这决定的是下载器把数据写到哪。如果你在做 IAP想把 App 安排在 Bootloader 后面偏移量是0x6000那 Keil 里 IROM1 要改成0x08006000烧录 App 的算法地址也要跟着改成0x08006000。那0在哪出现呢主要出现在两类场景。第一类某些串口 ISP 上位机或者国产下载器把“烧录起始地址”这个概念简化成了“偏移地址”。比如你选 STM32F103它知道你 Flash 基地址是0x08000000于是让你填0内部自动加上基地址。第二类你直接用 J-Flash、STM32CubeProgrammer 这类专业工具却把地址栏留空或者填了0。这时候工具是严格按照数值寻址的0就是0x00000000数据会被写到 Flash 控制器的非用户区或者被直接判为地址非法。3. ESP32 的 0x6000 与 0x10000烧录地址由分区表说了算聊完 Cortex-M再来看 ESP32。热搜词“怎么看 esp32 的烧录地址”说明不少人栽在这上面。实际上 ESP32 的地址规则比 STM32 更贴近“文件系统”思维。3.1 ESP32 没有“Flash 基地址”只有分区偏移ESP32 上电后ROM 里的 Bootloader 会读取 Flash 开头特定偏移位置的“分区表”分区表里记录了每个分区NVS、OTA、APP、SPIFFS 等的名字、类型、偏移和大小。整个 Flash 就像一个微型硬盘烧录命令里的地址本质上是硬盘上的“柱面号”。IDF 默认分区表长这样这是一个很典型的例子不同版本略有差异# Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 0x1F0000,那么烧录时常见地址就对应如下烧录地址Flash 偏移烧的是什么说明0x1000BootloaderESP32 经典的 Bootloader 偏移0x8000分区表Bootloader 启动后先读这里0x9000NVS 分区掉电保存参数的地方0xF000PHY 初始化数据Wi-Fi 校准数据0x10000用户 Appfactory最常见的 App 烧录地址对比一下就能看出来ESP32 的烧录地址完全由分区表说了算没有也不需要0x08000000这种“CPU 视角”的绝对地址。3.2 如何在项目里看到 app 真正的烧录地址问“怎么看 esp32 的烧录地址”最直接的办法是看工程里的partitions.csv。如果你的工程是默认配置打开这个文件找到 SubType 为factory的那一行Offset 列写得清清楚楚。IDF 4.x 默认单 App 工程里factory分区偏移就是0x10000。如果你已经拿到了一个编译好的 App 固件或者板子上烧了别人给的固件想反查 App 偏移可以用 ESP-IDF 自带的parttool.py# 查看 app 分区的偏移和大小 python parttool.py --port COM3 get_partition_info --partition-nameapp --info offset,size输出会直接告诉你偏移是多少比如offset: 0x10000 size: 0x1F0000想知道板子上当前分区表长什么样可以先把分区表读出来再解析# 读出 Flash 0x8000 处、大小 0x1000 的区域这就是分区表 esptool.py --port COM3 read_flash 0x8000 0x1000 partitions.bin # 用 gen_esp32part.py 解析 python gen_esp32part.py partitions.bin解析出来的表格里每个分区的偏移和大小一目了然。这种方式在调试异常固件时特别有用能帮你确定板上实际烧的固件和当前代码是不是一个分区。3.3 0x6000 在 ESP32 语境下到底是什么我得先帮你排掉一个答案0x6000并不是 ESP32 的经典默认 App 地址。至少我没见过哪个官方默认分区表把 factory app 放在0x6000。如果你在 ESP32 教程里看到0x6000大概率是两种情况第一种讲的是分区大小而不是地址。比如上面那个分区表里NVS 分区的大小就是0x600024KB。文章写得潦草一点就把“NVS 分区大小 0x6000”和“烧录地址”混在一起说新手一看就是个地址。第二种是自定义分区表或者老 ESP8266 时代的教程。ESP8266 的非 OS SDK 里用户程序经常从0x00000或者0x10000偏移烧录也有早期工程把用户区安排在0x6000附近。如果你是从一篇老教程里看到0x6000建议先确认教程针对的是 ESP8266 还是 ESP32以及它的分区表文件到底长什么样。换到 STM32 语境0x6000就是很常见的东西了App 相对 Flash 基地址的偏移量。比如 Bootloader 占了 24KBApp 偏移就是0x6000绝对地址是0x08006000。这说明同一个数字在不同的芯片体系里含义可能完全不同拿到一个地址先分清楚它描述的是“偏移”还是“绝对地址”比背下来重要得多。4. 填地址的正确姿势按工具类型区分绝对地址与偏移量同样是烧 BIN不同工具对地址栏的理解不一样常见的坑主要出在“工具要的是绝对地址你却填了个偏移量”或者反过来。4.1 需要填绝对地址的工具STM32CubeProgrammer、J-Flash、OpenOCD 这一类通用烧录器地址栏填的是“CPU 视角”的绝对地址。以 OpenOCD 为例烧一个偏移在0x6000的 Appopenocd -f interface/stlink.cfg -f target/stm32f1x.cfg -c program app.bin 0x08006000 verify reset exit这里的0x08006000就是绝对地址。如果你手滑填成0x6000数据会被写到0x00006000一个 STM32F103 上不存在 Flash 映射的位置烧录可能报错就算侥幸写进去上电也执行不到。STM32CubeProgrammer 的命令行模式同理STM32_Programmer_CLI -c portSWD modeUR -w app.bin 0x08006000 -v图形界面里 Address 字段也填0x08006000不要填成偏移量。4.2 需要填偏移地址的工具ESP32 的esptool.py是典型的偏移地址派。它的写 Flash 命令esptool.py --port COM3 write_flash 0x10000 app.bin这里0x10000是 Flash 偏移不是 CPU 地址更不是像0x08010000那样拼接出来的地址。ESP32 的 Flash 通常 4MB地址范围0x000000到0x3FFFFF你把地址填成0x08010000工具要么报错说地址超出范围要么把数据写进一个你根本想不到的位置。如果想把整个 Flash 一次清空或者烧录一个合并固件那就从偏移0x0000开始写。合并固件比如把 Bootloader、分区表、App 拼在一起烧录时地址就是0x0这也是“烧录地址有时是 0”在 ESP32 世界里最常见的原因。4.3 ELF、HEX、BIN 三种文件的地址信息差异新手最容易忽略的是文件格式对地址处理方式的决定性影响。ELF 文件自带段信息和符号表下载器能直接读出 VMA/LMA烧录时不用填地址。比如你用 Keil 的 Debug 模式直接下载走的就是 ELF所以很少碰到地址问题。HEX 文件每一行记录里都带有 16 位或 32 位地址比如:020000040800F2这种行就表示后面的数据要放到0x08000000区域。所以烧 HEX 时工具会自动识别地址你也不用填。BIN 文件纯裸数据没有任何地址信息。这是唯一必须手动填地址的格式也是最容易填错的地方。用命令查看 ELF 的段地址可以验证链接地址是否和预期一致arm-none-eabi-objdump -h app.elf输出里留意.isr_vector和.text段的 VMA如果是0x08006000开头说明链接脚本改对了如果还是0x08000000那说明你只改了烧录地址没改链接脚本烧进去之后大概率要出问题。5. 烧录地址填错后的真实症状与排查链路光知道怎么填还不够得知道你填错了会怎么表现以及怎么一步步定位。下面几种情况是我实际调试中遇到最多的。5.1 症状一烧录显示成功目标板却没有任何反应这是最迷惑人的一种。下载器提示Verified OK板子却纹丝不动。我第一次做 IAP 就遇到过把 App 烧到0x08000000Bootloader 被覆盖了但烧录工具没报错因为它只负责写数据不负责判断“写在这里能不能跑”。出现这种情况第一步要做的不是重新烧而是把 Flash 读回来对比。用 STM32CubeProgrammer 读回 App 区域的头部STM32_Programmer_CLI -c portSWD modeUR -r 0x08006000 0x2000 readback.bin然后和原始 BIN 对比cmp app.bin readback.bin如果完全一致说明烧录内容没问题问题出在地址、链接脚本或者启动逻辑如果不一致可能是读保护、擦除配置或 Flash 算法有问题。ESP32 同理esptool.py --port COM3 read_flash 0x10000 0x2000 app_readback.bin cmp app.bin app_readback.bin5.2 症状二跳转 App 失败一跑就进 HardFaultCortex-M 的 IAP 跳转有个经典三件套改向量表偏移、设置主栈指针、跳转函数指针。很多人只记得跳转忘改SCB-VTOR于是 App 的中断一进来就 HardFault。App 偏移在0x6000时跳转代码大致是这样的typedef void (*pFunction)(void); void jump_to_app(uint32_t app_addr) { uint32_t app_sp *(volatile uint32_t *)app_addr; uint32_t app_pc *(volatile uint32_t *)(app_addr 4); // 检查栈顶地址是否落在 SRAM 范围防止跳到空地址 if ((app_sp 0xFFF00000) ! 0x20000000) { return; } // 关键把向量表偏移改成 App 的起始地址 SCB-VTOR app_addr; // 设置主栈指针再跳转 __set_MSP(app_sp); pFunction jump (pFunction)app_pc; jump(); }调用时传入0x08006000。注意向量表里偏移 0 存的是初始栈指针偏移 4 存的是复位中断函数地址这两个值必须和链接脚本计算出来的结果一致。还有种比较隐蔽的情况烧录地址改了链接脚本没改。比如你把 App 烧到了0x08006000但链接脚本起始地址还是0x08000000那向量表里的Reset_Handler还是按0x08000000链接的。烧录时虽然数据在0x08006000但 CPU 一旦跳进去执行所有内部地址引用全部错位中断也会乱套。这种问题用 5.3 里的方法一眼就能看出来。5.3 用反汇编和 Map 文件反推“程序到底在哪个地址”排查地址类问题最快的方法是看 Map 文件或者反汇编。ARM 工具链下arm-none-eabi-objdump -d app.elf | grep -A5 Reset_Handler:输出的地址如果是080060xx说明链接正确如果是080000xx说明链接脚本还是旧的。Map 文件也能确认。打开工程生成的.map文件找到这部分内容.isr_vector 0x08006000 0x130 ...如果.isr_vector的起始地址是0x08006000说明代码就是从 Flash 偏移0x6000开始链接的烧录时也必须填0x08006000。ESP32 的固件可以这样看esptool.py image_info app.bin输出里会显示 Entry point、Segment 地址等。App 的段地址一般是0x4008xxxxESP32 的指令总线映射区这个地址和 Flash 偏移0x10000不是一回事但你能通过 Segments 的偏移确认固件是从哪个 Flash 偏移编出来的。5.4 我自己的核对习惯踩过几次坑之后我现在拿到一个新板子或新工程的烧录地址都会按固定顺序过一遍查手册确认 Flash 基地址。STM32 是0x08000000CH32V003 是0x00000000ESP32 则直接没有基地址概念以 Flash 偏移为准。确认 Bootloader 实际占用大小。这个大小决定 App 偏移。要注意扇区对齐F103 的扇区从 1KB 到 16KB 不等Bootloader 结束位置最好落在扇区边界否则后面 App 擦除时会把 Bootloader 尾巴一块擦掉。核对链接脚本的 ORIGIN。App 工程的FLASH ORIGIN必须等于“Flash 基地址 App 偏移”。确认烧录工具填的是绝对地址还是偏移。OpenOCD、J-Flash 填绝对地址esptool.py 填偏移。烧完务必 Read Back 对比。别嫌麻烦对比一次能挡住绝大多数低级错误。这一步做下来烧录地址基本不会再坑到你。尤其是 IAP 和 OTA 这种涉及多个地址体系的工程地址概念越清晰后期排查越省力。最后再分享一个小习惯拿到一个 bin 文件先看它前四个字节。如果前四个字节是0x2000xxxx这种栈顶地址说明这个 bin 很可能是个完整的裸机或 RTOS 镜像烧录地址大概率是 Flash 基地址或基地址加偏移如果前四个字节是奇怪的指令码就得怀疑文件本身是不是已经带了头部信息的合并固件。这个习惯帮我避免过好几次“把分区表当 App 烧”的乌龙你也可以试试。