FEATURED · 精选文章

烧录地址0x08000000、0与0x6000:从Flash映射到分区偏移的嵌入式地址体系解析

发布时间 / 2026/9/14 14:16:36
来源 / 创域科博编辑部
栏目 / 资讯中心
烧录地址0x08000000、0与0x6000:从Flash映射到分区偏移的嵌入式地址体系解析 搞嵌入式这几年经常被刚入行的小伙伴问到一个问题同样一个bin文件为什么有人烧录地址填0有人填0x08000000还有人填0x6000这三个数字看起来毫无关联偏偏在不同的教程里都能看到。第一次遇到这事的时候我也对着烧录软件愣了半天生怕填错就把板子搞废了。后来把原理理清楚才发现这其实不是某一个地址对或错的问题而是背后存在两套完全不同的地址坐标系再加上各家芯片的启动方式、Flash映射方式不一样才让同一个词在不同人的嘴里变成了完全不同的含义。这篇就好好捋一捋看完以后再碰到烧录地址你就知道该查什么、该信谁了。1. 烧录地址不是玄学先分清“两个坐标系”1.1 处理器看到的地址空间总线地址不是文件位置先看第一个容易混淆的地方。烧录软件里填的地址到底是给谁看的答案很清楚给CPU看的。CPU通过地址总线访问代码和数据它眼中的世界是一个固定布局的“地址空间”芯片厂家在设计的时候就把Flash、SRAM、外设寄存器安排在这个空间的不同位置。比如STM32F103芯片手册里清清楚楚写着内部Flash从0x08000000开始SRAM从0x20000000开始外设寄存器的起点是0x40000000。你把bin文件烧到0x08000000CPU复位后从那里取指令这是“处理器地址空间”的逻辑。外部SPI Flash就不一样了。它是一颗独立的存储芯片自身只有一套从0开始的字节偏移跟CPU的地址空间没有直接关系。很多Wi-Fi/蓝牙SoC比如ESP32基板上就外挂一颗SPI NOR Flash里面存Bootloader、分区表、应用程序、文件系统。在这颗Flash自己的坐标系里第一个字节就是0x000000第一个扇区是0x000000到0x000FFF。烧录工具往0x000000写数据就是把数据放到这颗Flash的最前面跟CPU怎么映射是两码事。理解这个区别之后再回到最初的问题为什么同一个词会出现三个完全不同的值因为人们在说“烧录地址”的时候有时候指的是CPU地址空间里的位置比如STM32的Flash基址0x08000000有时候指的是外部存储介质上的物理偏移比如ESP32的SPI Flash偏移还有时候指的是工程里自定义的分区偏移比如0x6000。不把这两个坐标系分开后面所有讨论都会打架。1.2 常见烧录地址的形式与含义速览与其死记“STM32填0x08000000、ESP32填0x10000”不如先把常见的地址形式归类。我按实际项目中见到的情况整理了一个速见表方便对照地址形式出现场景背后含义0x000000008051/AVR等单片机、SPI Flash物理首地址、烧录工具默认值Flash第一字节或程序入口0x08000000STM32等Cortex-M内核单片机内部Flash在CPU地址空间中的映射基址0x08006000STM32 IAP工程Bootloader占用0x08000000开头的24KBApp从偏移0x6000处开始0x00001000ESP32默认BootloaderSPI Flash物理偏移4KB对齐0x00008000ESP32默认分区表SPI Flash物理偏移记录各分区信息0x00010000ESP32默认App区SPI Flash物理偏移通常存放factory固件0x00006000自定义分区或第三方引导偏移由分区表/链接脚本自行决定这张表里的0x6000尤其有意思。它既不是任何芯片的标准Flash基址也不是固定的“万能偏移”而是特定工程里人为设计出来的偏移量。下面逐个展开讲。2. 0x08000000Cortex-M内核的“隐藏规则”2.1 ARM的固定地址规划Flash基址为什么不是0很多刚从51单片机转过来的人看到STM32的烧录地址是0x08000000第一反应是“这地址怎么这么大”。这其实不是ST故意为难你而是ARM公司给Cortex-M内核定下的规矩。Cortex-M系列把4GB地址空间划分成几个大区0x00000000到0x1FFFFFFF是Code区0x20000000到0x3FFFFFFF是SRAM区0x40000000以上是外设区。半导体厂商拿到这个框架后再把自己芯片里的Flash、SRAM挂到对应的区段里。ST选择把内部Flash映射到0x08000000而不是从0x00000000直接开始是因为0地址附近还有别的用途。Cortex-M的架构规定复位后处理器从0地址读取初始栈指针MSP从0x00000004读取复位向量PC这两个地址必须安排得稳妥。ST的做法是允许通过BOOT引脚把0地址区域重映射到Flash、系统存储器或SRAM再配合一个“物理地址”主Flash的本体地址就是0x08000000当BOOT00时0x00000000被硬件自动映射到0x08000000上。这就解释了为什么在STM32的数据手册里Flash起始地址写的是0x08000000而你在调试器的反汇编窗口里看到的复位向量又可能落在0x08000000附近。两个地址是“别名”关系物理上是同一个Flash。烧录时必须写物理映射地址0x08000000因为烧录器要直接操作Flash控制器而不是靠CPU去“读0地址”。2.2 链接脚本与向量表的联动第二个要点是链接脚本。用Keil、IAR或STM32CubeIDE打开一个STM32工程启动文件里通常能看到类似这样的定义// 以STM32CubeIDE默认链接脚本为例简化 MEMORY { FLASH (rx) : ORIGIN 0x08000000, LENGTH 512K RAM (rw) : ORIGIN 0x20000000, LENGTH 128K }这段描述告诉链接器可执行代码的起始地址是0x08000000。编译生成的.axf/elf文件里所有函数都被分配了以0x08000000为起点的地址最后生成的bin文件会被安排到从这个地址开始的Flash空间里。如果你把ORIGIN改成0x08006000生成出来的程序就从偏移0x6000处开始执行这就是IAP应用内升级的常见做法。向量表也跟着这个起始地址走。Cortex-M的中断向量表默认放在0x00000000但实际工程里通常通过启动文件里的指令把它重定位到0x08000000也就是镜像的开头。如果烧录地址和链接脚本不一致比如链接脚本说程序在0x08006000你偏要烧到0x08000000CPU启动后从0地址取到的第一条指令其实是“偏移0x6000处的程序前导数据”大概率直接跑飞。2.3 STM32启动模式下的0地址别名再补一个容易把人绕晕的点STM32的BOOT0/BOOT1引脚选择启动模式后0地址区域会被映射到不同的物理介质。BOOT00时从主Flash启动0地址其实就是Flash的别名BOOT01、BOOT10时从系统存储器启动0地址映射到出厂BootloaderBOOT01、BOOT11时从SRAM启动0地址映射到SRAM。所以有些讲ISP下载的资料里会提到“芯片上电后从0x00000000取出向量表”这里的0不是烧录地址而是CPU启动时的逻辑地址。网上有些教程为了简化把这种“启动逻辑地址”和“烧录物理地址”混在一起说时间长了就传成了“STM32也能烧0地址”。真去烧0ST-Link会直接报错或者写进去的东西根本不会被Flash启动逻辑识别。所以碰到STM32系列该填0x08000000就填0x08000000千万别被网上的简化说法带偏。3. 0从“Flash第一字节”到“处理器入口”3.1 8位/16位单片机的0地址就是程序入口再说说0这个地址。它出现得最合情合理的场景是传统8位/16位单片机。51单片机、AVR系列的Flash/ROM都是直接从0地址开始的CPU复位后程序计数器PC被置为0第一条指令就从0地址取。烧录器往0地址写bin文件芯片上电后直接从这个位置跑起来完全不需要任何映射。这类芯片的存储空间很小也没有复杂的总线设计0地址作为程序入口是最高效的方案。到了ARM7这类早期架构情况也很类似。ARM7没有像Cortex-M那样把Flash固定映射到0x08000000它的中断向量表就在0x00000000想从外部Flash启动就得把Flash映射到0地址。后来Cortex-M为了兼顾不同厂家的外设布局和中断向量设计才把Code区整体留出来让厂商自己决定内部Flash映射到哪里。所以“0地址”和“0x08000000”的差异本质上就是芯片设计思路的差异。3.2 外部SPI Flash的物理偏移0另一个“0”的出处就是前面提到的外部SPI Flash。ESP8266、ESP32这类方案主芯片内部没有大容量的并行Flash代码放在外挂的SPI NOR Flash里。在烧录工具的目光里这颗Flash就是一颗普通的存储芯片地址从0开始按扇区组织。于是你在ESP系列下载工具里看到的地址经常是0x00000、0x10000这种小数字它们代表的是Flash物理偏移而不是CPU的地址空间。比如用esptool.py下载一个合并好的固件命令可能是这样esptool.py --chip esp32 --port COM3 write_flash 0x00000 merged_bin.bin这里的0x00000表示从整个Flash的最开头写入前面的0x1000、0x10000等偏移都建立在“Flash物理偏移”这个坐标系里。对ESP32来说0地址处通常是第一个引导扇区或保留区域要靠外面的工具链和引导程序决定具体用法不能简单理解为“程序入口”。如果把STM32的“0地址是程序入口”的经验硬套过来就会出问题。3.3 烧录工具里“0”的另外两种意思还有一种场景0不是地址而是“默认参数”或“自动计算”。有些下载工具尤其是国产的串口烧录助手地址栏默认填0意思是“我不关心地址你按芯片默认方式传”。有些工具里指定某个文件地址为0代表“从Flash起始处开始烧录”而不是小心翼翼地避开头部的引导程序。这种工具级的默认值往往会把新手搞懵明明填0也烧进去了为啥别人说要填0x1000我的建议是工具默认值只能作为参考真正可靠的信息来源是编译日志。编译的时候打开Verbose详细输出看命令行里每个文件落在哪个地址。工具界面里显示0很可能只是界面没把地址读出来或者工具做了隐藏处理不代表实际写入地址就是0。4. 0x6000偏移量、分区表和页对齐的产物4.1 0x6000通常出现在哪类项目里0x6000既不是标准的Flash基址也不是哪个芯片的固定启动地址它更多时候是“偏移量”的产物。最常见的一类场景是STM32的IAP工程Bootloader占用了Flash开头的0x08000000到0x08005FFF也就是24KB用户应用程序从0x08006000开始链接。这样做的好处是Bootloader和App在Flash里各占一块互不重叠的空间上电先跑BootloaderBootloader校验完App的合法性之后再跳转过去。在这个场景里0x6000就是Bootloader区域的结束地址也是App区域的起始偏移。烧录工具里有人填完整地址0x08006000有人简化成“偏移0x6000”还有人在链接脚本里写成ORIGIN 0x08006000几种写法看着不一样指的都是同一个位置。如果App工程里烧录地址写0x08006000而链接脚本也是0x08006000两边一致就能跑起来有一边错App要么进不去要么覆盖掉Bootloader。另一类场景在ESP32这类带外部Flash的平台上。自定义分区表时完全可以把某个分区起始地址设在0x6000。比如一段这样的partitions.csv# name, type, subtype, offset, size otadata, data, ota, 0x6000, 0x2000 nvs, data, nvs, 0x8000, 0x4000 factory, app, factory, 0x10000, 0x200000在这个例子里0x6000是OTA数据分区的物理偏移它被安排在Flash靠前的位置。所以当你在某个项目文档里看到“烧录地址0x6000”别急着套用先翻翻它的分区表或链接脚本确认这个偏移到底对应什么。4.2 为什么偏移量喜欢选0x6000这种“看着不整”的数字很多初学者会问偏移量为什么不选个整数比如0x1000、0x10000这就涉及Flash的物理特性。SPI NOR Flash的擦除操作是按扇区或块来做的常见的扇区大小是4KB0x1000块大小可能是32KB0x8000或64KB0x10000。Flash擦除不能只擦一个字节一次最少擦一个扇区。如果你把一个App分区放在0x6100而下一个分区的起始地址是0x7000中间那部分空间就没办法被独立管理擦除时会跨边界。0x6000 24 × 0x400也就是24个4KB扇区的整数倍。这样Bootloader占用0x0000到0x5FFFApp从0x6000开始两边恰好对齐扇区边界。项目里约定俗成地用十六进制偏移就是为了方便脑子快速判断“这个地址是不是4KB对齐”。看到0x6000、0x8000、0x10000第一反应就应该是“对齐过了可以直接支配”。4.3 链接脚本、分区表与烧录地址三方对齐在实际项目中0x6000这样的偏移不是拍脑袋想出来的。它要求链接脚本里的ORIGIN、烧录工具里的写入地址、以及启动代码里的跳转目标三者完全一致。我见过一个案例App工程链接脚本写0x08006000但烧录时用J-Flash的默认配置烧到了0x08000000结果上电后Bootloader把Flash开头当成App数据读取解析出来的向量表乱成一团板子直接没有反应。后来定位到原因就是在“烧录地址”上偷了懒。所以当你看到0x6000或者别的什么奇怪偏移时正确的处理方式是先确认三样东西第一这是什么芯片第二这个地址是CPU地址空间里的绝对地址还是Flash物理偏移第三工程里是否包含引导程序或分区表它是否占用了一部分Flash空间。把这三个问题问完0x6000的含义基本就水落石出了。5. 实操怎么看ESP32的烧录地址5.1 方法一从编译日志里找esptool命令回到开头那个热搜问题怎么看ESP32的烧录地址最直接、最不会出错的方法就是看编译日志里esptool.py实际执行的命令。ESP-IDF和Arduino ESP32在烧录时都会调用esptool.py日志里会明文打印每个文件对应的写入地址。以ESP-IDF为例用命令行烧录时加上-v参数idf.py -p COM3 -v flash运行后会在日志里看到类似下面这一行实际版本可能略有差异esptool.py --chip esp32 --port COM3 --baud 921600 write_flash \ --flash_mode dio --flash_size 4MB --flash_freq 40m \ 0x1000 build/bootloader/bootloader.bin \ 0x8000 build/partition_table/partition-table.bin \ 0x10000 build/app.bin看仔细了0x1000处写的是Bootloader0x8000处写的是分区表0x10000处写的是应用固件。这三组数字就是当前工程的实际烧录地址。不同版本的ESP-IDF、不同芯片型号可能略有差异但“看日志”这个思路在任何平台都通用。如果你用的是Arduino IDE操作也很简单菜单里选择文件——首选项——编译时显示详细输出勾选“编译”和“上传”。然后在烧录时复制上传日志找到esptool.write_flash这一行就能看到Arduino给你的开发板选好的地址。Arduino ESP32的默认布局通常是0x1000写bootloader0x8000写分区表0x10000写应用但选了大Flash或自定义分区方案后地址会跟着变。5.2 方法二用idf.py查看和导出分区表ESP32的烧录地址主要由分区表决定所以看懂分区表就等于看懂了地址。在ESP-IDF工程目录下执行idf.py partition-table这条命令会生成并打印当前分区表的内容。也可以直接打开工程里的partitions.csv文件里面每一行的offset列就是每个分区的起始地址。默认表格可能长这样# name, type, subtype, offset, size nvs, data, nvs, 0x9000, 0x4000 otadata, data, ota, 0xd000, 0x2000 app, app, factory, 0x10000, 0x1F0000注意这里分区的offset写的是Flash物理偏移。Bootloader虽然不在CSV里但它通常在0x1000分区表本身放在0x8000这些值由IDF构建系统写入规范里。如果你想确认某个bin文件被链接到了哪个地址还可以用esptool.py的image_info子命令python -m esptool --chip esp32 image_info build/app.bin输出里会列出镜像的entry point、segment地址和长度。看到segments的地址范围就能知道这个固件实际是以哪个基址链接的。5.3 方法三直接读出Flash内容核对如果烧录过程已经完成你想确认真实写在Flash里的内容到底在什么位置可以用esptool.py的read_flash把整片Flash读出来检查python -m esptool --chip esp32 --port COM3 read_flash 0x0 0x200000 flash_dump.bin这会从偏移0开始读2MB数据到本地的flash_dump.bin。然后用十六进制编辑器或者binwalk打开这个文件找到写入的固件特征、分区表字符串。这个方法比较“重”但定位问题最靠谱。尤其是怀疑某个分区被烧到了错误位置时一读便知。5.4 图形工具里的地址填充方式很多文章和视频教程喜欢用乐鑫官方的Flash Download Tool来演示烧录界面上一行行的SPI Flash地址框很容易让人困惑。其实它的填充原则和esptool命令行是完全一致的Bootloader固件填0x1000分区表填0x8000如果界面里提供了boot_app0.bin这个文件它一般填0xe000App固件填0x10000。填之前先看一遍自己的编译日志别直接照抄网上的数字——Flash容量、分区方案、芯片型号不同实际地址可能完全不同。6. 烧错地址的典型翻车现场6.1 现象一烧进去没反应板子像砖了一样把ESP32的App固件直接烧到0x00000把Bootloader覆盖掉是很多新手最容易犯的错。上电之后芯片的ROM引导程序找不到合法的第二级Bootloader串口打印不出任何信息能进下载模式但跑不了用户程序。处理办法是用esptool.py重新把Bootloader写回0x1000再把App写回0x10000千万别只擦不写。STM32那边也一样如果把App烧到0x08000000而这里本来应该放Bootloader且App又没有链接到0x08000000这个地址复位后大概率直接进HardFault。表面现象是“烧录成功、运行没反应”实际上向量表全乱了。6.2 现象二启动后跑飞或过一段时间才崩有时候程序能启动但跑着跑着就进HardFault。这种问题最容易出在IAP跳转地址上Bootloader跳转到App时用的地址是App的起始地址而App的向量表还留在Flash开头的默认位置没有调用SCB-VTOR来重定位。解决方式是让App工程在初始化时设置SCB-VTOR APP_BASE_ADDRESS其中APP_BASE_ADDRESS就是烧录地址。烧录地址、链接地址、跳转地址、VTOR四者必须一致少一个都容易出怪病。6.3 现象三下载工具报校验失败或擦除冲突烧录地址重叠也会触发下载工具的校验问题。比如分区表正好覆盖了Bootloader末尾或者App偏移和某个参数存储分区重叠下载工具在擦除扇区时会发现“地址冲突”直接报错。这种问题不需要改芯片只需要调整分区表或者链接脚本把各个区域的地址错开并保证每个分区起始地址对齐到扇区边界。6.4 常见烧录地址速查表这里整理一份速查参考遇到问题先对照一下当前工程属于哪一类平台/场景典型地址说明STM32主Flash0x08000000内部Flash映射基址绝大多数烧录场景STM32 IAP App区0x08006000 / 0x08008000取决于Bootloader占用空间由链接脚本决定51/AVR等0x0000Flash物理起始也是程序入口ESP32 Bootloader0x1000外部SPI Flash物理偏移IDF默认ESP32 分区表0x8000外部SPI Flash物理偏移IDF默认ESP32 App区0x10000外部SPI Flash物理偏移分区表默认ESP32自定义分区0x6000等由partitions.csv里的offset列决定这张表只适合作为“怀疑自己填错了”时的检查线索。真正可靠的依据永远是编译日志、分区表和链接脚本三件套。最后说点个人体会。烧录地址这个问题90%的困惑都来自“听别人说了一个数然后照抄”。遇到0、0x08000000、0x6000甚至更离谱的0x1FFFC800不用慌先分清楚它是CPU地址空间里的绝对地址还是Flash物理偏移再看当前工程有没有Bootloader、有没有分区表。只要有日志和链接脚本在手地址就永远不是玄学。新手阶段最容易踩的坑就是“地址不一致还硬烧”多花一分钟查日志比烧坏板子之后再折腾强得多。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻