news 2026/9/14 15:50:27

烧录地址0、0x08000000、0x6000到底啥区别?一文讲透

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
烧录地址0、0x08000000、0x6000到底啥区别?一文讲透

你有没有遇到过这种情况:同一个工程,今天打开烧录软件让你填 0,明天看教程里的截图写的是 0x08000000,后天开始做 Bootloader 时又有人告诉你 App 要从 0x6000 开始烧。三个数都叫“烧录地址”,都像是老手随口蹦出来的词,但你就是不敢追问到底有什么区别,怕被反问一句“这你都不知道”。

我第一次独立烧板子的时候也被这三个数字搞懵过。明明同一个芯片、同一份固件,换个工具、换个教程,地址写法就从“0”变成了“0x08000000”,后来又变成“0x6000”。当时我以为是不同厂家的芯片地址不同,后来才发现,这三个数字背后根本不是一回事,而是“地址”这个概念在嵌入式开发里被不同工种、不同工具、不同存储介质反复包装后的三个侧面。

这篇文章就把这个事彻底说透。我会从芯片的视角、链接脚本的视角、烧录工具的视角分别拆开讲,最后专门拿出 ESP32 的场景,告诉你这类外部 Flash 芯片的烧录地址到底该怎么看、怎么填。看完你不仅能分清 0、0x08000000、0x6000 的关系,以后看到任何奇怪的烧录地址,也能自己推算出它是不是合理。

1. 先对号入座:0、0x08000000、0x6000 分别来自哪类场景

这三个数字之所以让人晕,是因为它们经常出现在完全不同的工具和语境里,但都被笼统地叫做“烧录地址”。先把各自的身份搞清楚,后面就好办了。我整理了一个对照表,你可以直接保存下来当速查卡用。

地址写法典型出现场景实际含义
0J-Flash 新建外部 Flash 工程、部分 IDE 默认显示、Bootloader 跳转目标地址Flash 偏移基准地址,或者是 Arm 芯片的 0 地址别名区
0x08000000STM32 的链接脚本、CubeProgrammer、Keil/IAR 默认 Flash 起始地址CPU 可见的内部 Flash 绝对首地址
0x6000STM32 自定义 Bootloader 后面的 App 起始偏移,或 ESP32 分区表里的某个偏移Flash 内的偏移量,在 STM32 上等价于0x08006000

看到没有,这三个数字不是互相矛盾的三个答案,而是同一个存储空间在不同上下文里的三种表达方式。

0最常见于“偏移地址”的概念。SPI Flash、外部 NOR Flash 这类存储介质本身是从 0 开始编址的,所以烧录工具在往介质里写数据时,让你填的地址自然就是从 0 开始算的偏移量。0x08000000则是 STM32 这类芯片内部 Flash 的绝对地址——它跟 Flash 介质内部从 0 开始编号并不冲突,只是芯片把 Flash 的物理空间映射到了总线地址0x08000000上而已。0x6000就更特殊一点,它本质上是一个偏移量,比如 Bootloader 占用了前0x6000字节,用户程序从0x08006000开始,工具界面为了省事直接显示偏移量。

我见过不少新手在这上面踩坑,最典型的一种情况是:同一个.bin文件,在 A 工具里填0x08000000烧进去能跑,在 B 工具里填0x08000000却起不来,填0反而能跑。这个现象后面会详细解释,它和芯片的启动映射机制有关。

2. 为什么芯片不把所有 Flash 的起始地址都定为 0?

要理解这三个地址为什么存在,先得明白一个核心问题:芯片地址空间是谁“编”的?

地址空间不是软件定的,是芯片设计阶段定死的。CPU 能访问哪些存储器和外设,每个区域落在哪个地址范围,全部写死在芯片的 Memory Map 里。以 STM32F103 为例,数据手册里 Memory Map 那一章写得很清楚:内部 Flash 的起始地址是0x08000000,SRAM 从0x20000000开始,外设寄存器从0x40000000开始。这些数字不是随便选的,而是芯片布线时固定下来的,软件只能遵守,不能修改。

那为什么 Arm Cortex-M 内核明明规定复位后从0x00000000取向量表,STM32 却把 Flash 放在0x08000000

这里有个关键机制:别名映射。STM32 允许你把0x00000000这一块地址“映射”到别的存储区。芯片上有 BOOT0、BOOT1 引脚,上电时芯片根据引脚电平决定0x00000000到底指向谁:

  • BOOT0 拉低,主 Flash 被映射到0x00000000,也就是0x080000000x00000000能访问到同一块物理 Flash;
  • BOOT0 拉高、BOOT1 拉低,系统存储器(System Memory,里面是出厂 Bootloader)被映射到0x00000000
  • 两个引脚都拉高,SRAM 被映射到0x00000000

这个机制就是很多混乱的源头。如果 BOOT 引脚配置成从主 Flash 启动,那么你往0x00000000写数据,实际上就是在往主 Flash 里写,工具可能也允许你这么填。但这种写法绕过了官方定义,一旦 BOOT 引脚配置改变,或者用在 Bootloader+App 的升级场景里,就会出大问题。

用个生活化的类比:0x00000000相当于你家小区的“临时门牌”,保安看见这块牌子就把你引到指定楼栋;0x08000000才是那栋楼在房产局备案的正式门牌号。你写“临时门牌”偶尔也能找到地方,但正规文件上必须用备案门牌号,否则物业换人、系统升级,这套地址就失灵了。

不同类型芯片的地址规划也完全不同。NXP 的 LPC 系列很多就把内部 Flash 放在0x00000000,所以用那些芯片时填 0 反而是标准做法;GD32、AT32 这类国产兼容芯片沿用了 STM32 的0x08000000布局;而 ESP32 这种使用外部 SPI Flash 的芯片又是另一套逻辑——CPU 通过 Cache 和 MMU 把外部 Flash 映射到诸如0x3F400000这样的地址区域,但烧录工具写入的是 Flash 介质内部的偏移量,比如0x10000。所以烧录 ESP32 时填的地址和读芯片手册时的 CPU 地址,完全不是一个坐标系。

3. 三个地址的换算关系:看到 0x6000 时,脑子要自动补成 0x08006000

现在我们把三个数字拉到同一个坐标系里做一次数学换算。

0x08000000是 STM32 内部 Flash 的绝对首地址。假如 Bootloader 编译出来占了 24KB 的空间,那用户程序要从哪开始?

24KB = 24 × 1024 = 24576 字节。把 24576 转成十六进制:24576 / 16 = 1536余 0,1536 / 16 = 96余 0,96 / 16 = 6余 0,所以24576 = 0x6000。于是用户程序的起始地址就是0x08000000 + 0x6000 = 0x08006000

很多人在 Keil、IAR 的 Linker 配置里看到IROM1 Start 0x8006000,也就是从这里来的。但烧录工具的地址栏有时只让你填0x6000,不让你填0x08006000,因为工具认为你已经选择了“芯片内部 Flash”这个区域,后面填的自动加上了基地址0x08000000

下面这张表把常见换算列出来,方便你查:

Bootloader 占用大小十六进制偏移App 绝对地址
8KB0x20000x08002000
16KB0x40000x08004000
24KB0x60000x08006000
32KB0x80000x08008000
64KB0x100000x08010000

这个换算关系不只是为了做题。你在 J-Flash 里加载一个.bin文件时,工具会弹窗问你“加载地址填多少”,如果你填了0x08006000,那数据会从绝对地址0x08006000开始写;如果工具当前选择的是“内部 Flash”设备,你填0x6000它也能正确换算;但如果你填了0,就很可能把本该写到0x08006000的数据写到了 Flash 开头,把你的 Bootloader 覆盖掉。

还有一点经常被忽略:.bin文件是不带地址信息的,它就是一段纯粹的二进制数据,你必须在烧录工具里告诉它“这段数据要放到哪”。而.hex文件每个段都自带绝对地址,工具会自动解析并写到你指定的地址上。

这里就引出一个实操建议:凡是需要严谨控制烧录位置的场合——比如带 Bootloader 的项目、OTA 项目、量产烧录——优先使用.hex.s19格式,别为了省事直接发.bin.bin文件在烧录地址填错时没有任何自我保护机制,工具不会帮你检查,烧进去就是覆盖,等你发现启动不了,原来的固件早就没了。

4. 实操定位:怎么确定自己的固件该烧到哪个地址

前面讲的都是原理,现在说点能直接“抄作业”的操作流程。每次拿到一个新的板子、一个陌生的工程,我建议按下面四步走,基本不会翻车。

4.1 第一步:查芯片 Memory Map,确定内部 Flash 基地址

这一步的目的不是背数字,而是确认你手里的芯片到底把 Flash 放在哪个地址。打开芯片数据手册,找到“Memory Map”或“Memory Organization”章节,看 Flash 的起始地址是多少。

以 STM32F103 为例,手册里会写Flash memory 0x08000000 - 0x0801FFFF(容量不同上限不同)。如果你用的是 NXP 的 LPC1768,开头可能就是0x00000000。不要凭经验猜,不同系列、不同厂商差异很大,一页纸的事,花两分钟翻一下手册,能省掉后面一整天的排查时间。

4.2 第二步:看链接脚本,确认工程自己的 ORIGIN

打开工程的 Linker 脚本,这是决定“程序认为自己该放在哪”的权威文件。不同工具链叫法不同:

  • GCC 工具链:.ld文件,里面写着FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
  • Keil MDK:.sct文件,里面有LR_IROM1 0x08000000 0x00080000
  • IAR EWARM:.icf文件,里面有place in flash之类的段定义。

这个 ORIGIN 就是你程序期望的烧录地址。如果工程是带 Bootloader 的 App,这一项通常会直接写成0x08006000而不是0x08000000,因为链接器要把整个程序的地址空间放在0x08006000以上,这样代码里所有跳转、函数指针、常量表才会指向正确的位置。

4.3 第三步:看 Bootloader 实际占用的大小,算 App 起始地址

如果你是在给一个已有的 Bootloader 做配套 App,那最好去看 Bootloader 工程的编译 Map 文件。在.map文件里找到 Flash 段(通常叫FLASHER_FLASH)的结束地址,比如写在0x08005A34。这说明 Bootloader 实际用了0x5A34字节,小于 24KB。

但你千万别图省事直接把 App 放在0x08005A34。因为 Flash 擦除是按页/扇区来的,如果 Bootloader 的擦除范围没算好,或者升级程序时要整页擦除,很可能会波及 App。通用的做法是向上取整到擦除块对齐的边界。24KB 对应的0x6000通常就是这么来的——它不是 Bootloader 的真实大小,而是“安全边界”。

我列几个常见芯片的擦除粒度,对齐时可以参考:

芯片系列Flash 擦除粒度常见 App 起始对齐
STM32F1 低密度1KB 页4KB 或 8KB
STM32F1 高密度2KB 页8KB 或 16KB
STM32F416KB / 64KB Sector16KB 或 64KB
GD32F30x4KB 页8KB 或 16KB

这里顺带说一个很多人忽略的问题:向量表。Cortex-M 内核上电后是从0x00000000找向量表,但在 STM32 上0x00000000被映射到0x08000000,所以 Bootloader 的向量表在0x08000000是成立的。App 跑到0x08006000之后,必须把向量表偏移也设置过去,通常写SCB->VTOR = 0x08006000;,否则中断一触发,CPU 还是去0x08000000找向量表,拿到的全是 Bootloader 的中断处理函数。这就是很多 App 跳转过去后“一进中断就死机”的根因。

4.4 第四步:在烧录工具里正确填写地址

流程走到这一步,该填什么已经很清楚了。但我还是把几个常用工具的场景列一下,因为工具之间的显示逻辑确实不一样:

  • STM32CubeProgrammer:地址栏填的是绝对地址。烧 App 时填0x08006000,它就能只写这一段,不动前面的 Bootloader。如果你用“全片擦除”,那 Bootloader 也没了,上电变砖。
  • Keil MDK:在 Options for Target → Debug → Settings → Flash Download 里配置。如果勾选了 Erase Full Chip,每次下载都会擦整个 Flash,对带 Bootloader 的项目是致命的。正确做法是用 Erase Sectors,并在 Download Function 里选只擦需要的扇区。
  • J-Flash:新建工程时选择芯片型号,然后 Project Settings 里可以设置Base Addr。如果你加载.bin文件,工具会问加载地址,填0x08006000稳妥。
  • OpenOCD:命令行烧录时用flash write_image erase xxx.hex或者flash write_image xxx.bin 0x08006000.bin必须显式带地址,.hex可以不带。

5. 专门说说 ESP32:烧录地址是 Flash 偏移,不是 CPU 绝对地址

我知道很多搜这个问题的人其实在用 ESP32,因为 ESP32 的烧录命令里有0x10000x80000x10000这样的地址,偶尔还会看到0x6000这样的自定义偏移,跟 STM32 的0x08000000完全是两套语言。这里单独写一节说清楚。

5.1 ESP32 的默认分区和地址体系

ESP32 用的是外部 SPI Flash,芯片内部没有存放用户代码的 Flash。CPU 虽然可以通过 Cache/MMU 把外部 Flash 的内容映射到地址空间里,但烧录工具操作的是 Flash 介质本身,所以烧录地址全部是“Flash 内部偏移量”,从0x00000000开始往后算。

以最常见的 ESP32-IDF 默认分区表为例,典型偏移如下:

Flash 偏移内容说明
0x0000二级 Bootloader旧版/部分系列放在这里
0x1000二级 BootloaderESP32 经典芯片的常见位置
0x8000Partition Table分区表,固定位置
0x9000NVS非易失存储
0xe000OTA DataOTA 信息
0xf000PHY Init DataPHY 校准数据
0x10000Factory App工厂固件,或 OTA App 起点

不同芯片、不同 IDF 版本会有偏移差异,比如部分新系列把 Bootloader 放在0x0000,有的保留区大小也不同。所以千万不要看到一个教程写0x1000就照搬到所有芯片上,要看你自己的工程实际生成的文件。

0x6000在 ESP32 里是什么?它不是 ESP32 出厂默认分区,最常见的是出现在自定义分区表partitions.csv里。比如有人为了压缩空间,把 NVS 往前提,或者给某个自定义分区安排了0x6000的偏移。如果你在烧录命令、编译日志、或者别人的配置里看到了0x6000,第一反应应该是“这个工程改过分区表”,然后去项目里的partitions.csv查证,而不是猜它是固定值。

5.2 怎么查看 ESP32 的烧录地址和分区表

这里直接回答热词“怎么看 esp32 的烧录地址”。有四种常用方式,按推荐程度排序。

第一种,看编译输出和烧录脚本。IDF 编译完成后,终端会打印烧录命令,通常在build目录下还有flash_args文件。用文本编辑器打开flash_args,里面就是标准的烧录地址和文件对应关系:

--flash_mode dio --flash_freq 40m --flash_size detect 0x1000 build/bootloader/bootloader.bin 0x8000 build/partition_table/partition-table.bin 0x10000 build/app.bin

这一行命令是 IDF 自己生成的,它就是你这次工程最权威的“烧录地址表”。

第二种,用esptool.pyimage_info命令查看固件信息。对已经编译出来的.bin文件,运行:

python -m esptool image_info build/app.bin

能看到这个固件是给谁编译的、入口地址、段信息,但注意它不一定直接告诉你烧录偏移,只能辅助确认固件没问题。

第三种,直接读取芯片里的分区表。如果手上有一块已经烧过程序的板子,可以使用 esptool 读取:

python -m esptool --port COMx read_flash 0x8000 0x1000 partition_table.bin

然后使用 ESP-IDF 自带的gen_esp32part.py工具解析:

python tools/gen_esp32part.py partition_table.bin

这样就能看到当前板子里实际生效的分区表和每个分区的偏移、大小。

第四种,如果你只想从运行中的设备读回完整固件,也能用read_flash全片读取,再逐个偏移去比对。这个方法适合逆向别人的板子或者确认自己是否烧错位置,日常开发用前三种就足够了。

5.3 修改 ESP32 烧录地址的注意事项

ESP32 的烧录地址不是不能改,但改起来要动分区表,或者动 Bootloader 偏移,风险要高不少。提几个关键点:

第一,分区表里的 offset 必须满足 4KB 对齐,很多分区还要求 64KB 对齐,这取决于 Flash 的扇区大小和 OTA 的功能需求。你写0x6000这种偏移完全没问题,它本身是 4KB 对齐的,但前面分区的尾部和后面分区的头部都要算好,不能重叠。

第二,Bootloader 的偏移不是随便改的。Flash 上前 4KB(或者芯片出厂 ROM 指定的区域)被 ROM Bootloader 使用,二级 Bootloader 的偏移在 menuconfig 里有专门配置,改它意味着 Bootloader 自身、分区表偏移、App 偏移可能全都要跟着变。我见过有人把 Bootloader 烧到0x10000去,结果芯片一上电 ROM Bootloader 找不到二级 Bootloader,直接进入下载模式,看着像变砖了,其实只是地址错位。

第三,ESP32 烧录时还有--flash_size参数,如果你填的地址超过了 Flash 的实际容量,或者flash_size配置和实际芯片不符,烧录可以完成,但运行起来可能莫名其妙崩溃,因为 Cache 映射区域超出了物理 Flash。

6. 填错地址的典型翻车现场,和几条防呆经验

地址这东西,填错了当场报错还好说,最怕的是不报错、烧进去了、但跑不起来。我把自己见过和踩过的翻车现象整理成一张表,你可以对照排查。

现象大概率原因
烧录后上电完全没反应,电流正常但串口无输出烧录地址写到了 Flash 开头,把 Bootloader/启动代码覆盖了,或者写到了错误的绝对地址
能连上调试器,但 PC 停在 HardFault_HandlerApp 的链接地址和实际烧录地址不一致,或者向量表偏移没设置
校验通过但程序行为像“旧固件”工具没有真正擦除目标扇区,旧代码残留,导致中断向量表或校验值混乱
烧录提示写入超时或地址非法地址越过了芯片 Flash 末尾,或者访问了读保护区域
在工具里填 0 能跑,填 0x08000000 反而怪怪的芯片按 BOOT 引脚的别名映射在兜底,掩盖了地址体系的不一致

第二行的情况在 Bootloader+App 项目里尤其常见。App 的 Linker 脚本写的是0x08006000,烧录工具也填了0x08006000,但代码里忘了设置SCB->VTOR,中断一来 CPU 跑回0x08000000找向量表。这个坑我至少帮三个同事排查过,每次都是同样的原因。

最后给几条实在的防呆经验,都是拿板子换来的:

第一,拿到一个新板子,第一次烧录前,把芯片手册 Memory Map 那一页截图存到工程根目录。以后任何人问你“这芯片 Flash 从哪开始”,直接甩图,不用猜。

第二,带 Bootloader 的项目,所有涉及到 Flash 写入的地方都要先算边界。Bootloader 多大、App 起始、App 最大容量、OTA 下载区在哪,先画个内存布局图,再动手写代码。

第三,量产烧录尽量用带地址信息的.hex.s19文件。.bin文件不是不能用,但要求烧录工位上的每个人都清楚当前产品的基地址和偏移,这在产线上是隐患。用.hex文件,工具按文件内部地址写入,即使某个工位选错了芯片,工具也会大概率报错,而不是默默写坏。

第四,如果教程里让你“烧录地址写 0”并且真的跑起来了,你要意识到这只是在特定芯片、特定 BOOT 引脚配置下的一个巧合,是别名映射兜了底,不代表这是通用做法。换成 STM32 之外的芯片,或者换一个 BOOT 设置,这套操作可能立刻失效。

回头再想想最开始那三个数字:0 是基准,0x08000000 是这块存储空间在 CPU 世界里的门牌号,0x6000 是从门牌号往里走了多少步。搞懂它们之间的关系,比死记任何一个地址都管用。以后不管换了什么芯片、什么工具,拿着 Memory Map 和链接脚本一对,烧录地址就不会再是玄学。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 15:50:08

华为Pura 80 Ultra与vivo X200 Ultra旗舰影像哲学深度对比

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 15:49:13

企业级AI效能管理:可度量、可治理的智能体落地指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 15:47:59

Bazel 如何用 mod 命令查看模块依赖图并定位模块依赖来源?

Bazel 如何用 mod 命令查看模块依赖图并定位模块依赖来源? 【免费下载链接】bazel a fast, scalable, multi-language and extensible build system 项目地址: https://gitcode.com/GitHub_Trending/ba/bazel 使用 bzlmod 管理外部依赖的项目里,M…

作者头像 李华
网站建设 2026/9/14 15:46:47

让AI看着你的屏幕干活:UI-TARS 桌面自动化从安装到跑通全记录

让AI看着你的屏幕干活:UI-TARS 桌面自动化从安装到跑通全记录 【免费下载链接】UI-TARS-desktop The Open-Source Multimodal AI Agent Stack: Connecting Cutting-Edge AI Models and Agent Infra 项目地址: https://gitcode.com/GitHub_Trending/ui/UI-TARS-des…

作者头像 李华
网站建设 2026/9/14 15:45:21

基于SSM与Flask的校园事务自助指南服务系统设计与实现

不需要去行政楼跑三趟才知道要盖哪个章,也不用在公告栏前一张一张翻纸质通知——把你所在高校里所有办事流程整理成在线指南,按分类展示、按关键词检索、按步骤追踪,这就是这个校园事务自助指南服务系统在做的事。项目本身是典型的Java后端项…

作者头像 李华