ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

烧录地址是什么?ESP32与STM32的Flash偏移彻底讲透

烧录地址是什么?ESP32与STM32的Flash偏移彻底讲透 上周一个做硬件的朋友突然发消息问我我往 ESP32 烧程序一个教程让我填 0一个让我填 0x10000还有一份截图让我填 0x6000。上次用 STM32 的时候明明填的是 0x08000000这仨数到底哪个是对的这个问题我几乎每年都会被问好几次而且问的人往往已经来回折腾了一两个小时把不同芯片、不同工具、不同教程里的地址抄在一张纸上越看越乱。其实这三个数字背后是三套完全不同的坐标系统它们之间根本不冲突。真正的问题不在于哪个地址正确而在于你手上拿的是哪个芯片、哪个文件、哪把烧录工具。这篇文章就把烧录地址这件事彻底讲透三个常见数字分别代表什么、为什么不同芯片不同工具会给出不同的数、遇到一个不认识的地址时怎么判断、烧错之后怎么救。适合刚接触嵌入式的小白也适合已经写了一阵代码、但一遇到改地址心里就发虚的人。搞清楚这套东西你以后再看任何烧录教程眼睛一扫就知道对方在说什么再也不用靠截图和记忆硬凑。1. 三个数字其实是三套坐标系1.1 0一个最容易骗人的起点0 这个数字最迷惑人因为它在不同场合有完全不同的含义。我把它归成两类第一类它是货真价实的物理起点。典型例子是 ESP8266它的用户程序很多时候就是烧在 SPI Flash 偏移 0x0 的位置ESP32-C3 在新版 ESP-IDF 里二级 bootloader 的默认烧录偏移也是 0x0还有像 nRF52 这类 Cortex-M 芯片片内 Flash 直接映射到 CPU 地址空间的 0x00000000烧录工具填 0 也确实能写进去。所以填 0不是错的它只在特定坐标系下成立。第二类0 是工具把基址藏起来之后显示出来的假起点。很多下载工具把芯片的 Flash 基址写死在设备驱动里用户在界面上看到的是相对于基址的偏移比如显示地址 0内部实际操作却是从 0x08000000 开始的。这种抽象对用户是友好的它造成的副作用就是你根本不知道工具背后替你干了什么于是换个工具、换个芯片地址看起来就变了。记住一句话看到 0 不要高兴得太早先问自己——它是什么坐标系下的 01.2 0x08000000STM32 的家门牌号0x08000000 是绝大多数 STM32 片内 Flash 的总线基址。Cortex-M 内核规定 0x00000000 到 0x1FFFFFFF 这段是代码区但这段空间具体怎么分配、Flash 挂在哪个地址由芯片厂商自己决定。ST 把主 Flash 挂在了 0x08000000数据手册里的存储器映射表写得清清楚楚。那为什么不直接用 0x00000000 呢因为对 STM32 来说0x00000000 是一个可以重映射的别名区。当 BOOT 引脚配置成从主 Flash 启动时CPU 会把 0x08000000 的内容别名到 0x00000000 来取向量表但如果你从系统 bootloader 启动0x00000000 指向的又是另外一块存储区。所以代码和烧录工具都认准 0x08000000 这个固定地址而不是可变的别名区。顺便说一句很多国产兼容芯片比如 GD32 的某些型号为了做到 pin-to-pin 兼容Flash 基址也是 0x08000000。所以从 STM32 切过去的人烧录地址经验基本可以无缝平移这大概也是 0x08000000 在国产芯片圈子里显得很普遍的原因。1.3 0x6000一个量被当成地址的典型0x6000 这个数最值得拿出来单独讲因为它几乎不是某个芯片手册里规定的固定基址而是某个具体工程的约定。0x6000 等于十进制 24576最常见的来源有两个一是把分区大小当成了地址。ESP-IDF 默认分区表里有这么一行nvs, data, nvs, 0x9000, 0x6000 phy_init, data, phy, 0xf000, 0x1000 factory, app, factory, 0x10000, 1M这行意思是NVS 分区放在偏移 0x9000大小是 0x6000 字节。网上有些教程截图里标了 0x6000新手没仔细看字段对应关系很容易把长度抄成地址。二是部分开源项目的自定义分区表确实把某个小分区放在了 0x6000。这类工程通常有自己的 partition CSV里面地址是作者为特定需求比如省空间、配合 bootloader手动设的。你照着那个工程烧没问题但换一个工程0x6000 很可能就不成立了。结论先放在这里0x08000000 是查手册能查到的硬地址0x10000 这类偏移是 ESP32 官方流程里相对稳定的默认值而 0x6000 属于具体工程私有约定。看到这种非整数规格的偏移正确反应不是背下来而是去项目里找分区表文件确认。2. 同样叫烧录地址为什么含义能差出十万八千里2.1 Flash 的编址方式分两派这是理解整个问题的分水岭。不同芯片的 Flash 编址方式按CPU 怎么看这块存储可以分为两派。一派以 STM32 为代表Flash 是芯片内部总线上的一个外设CPU 可以直接按内存地址访问它执行代码就是直接从 0x08000000 取指令。烧录的本质就是往 CPU 内存地址空间里的某个位置写数据。所以工具让你填 0x08000000因为它就是那个位置的门牌号。另一派以 ESP32 为代表Flash 是外挂的 SPI 存储芯片CPU 不能直接按固定地址访问它而是要经过芯片内部的 MMU 动态映射。同一个物理偏移在不同的映射配置下CPU 看到的地址完全不一样。程序员日常说的 0x400D0000、0x3F400000 这类地址其实是映射之后的结果不是一个稳定的烧录目标。所以 ESP32 世界的烧录工具只能退回到最底层、最稳定的标识——SPI Flash 的物理偏移。这就是为什么 esptool 一律让你写 0x1000、0x8000、0x10000 这样的数字而不是一个看起来像内存地址的数。打个比方STM32 的快递直接送进你家客厅门牌号就是家庭地址ESP32 的快递送到小区物业物业再按几栋几号分发这个几栋几号就是 flash 偏移。2.2 烧录文件自己带不带地址决定你要不要填很多人一开始不理解为什么下载程序有时候要指定地址有时候又不用的这跟烧录文件的格式有直接关系。Intel HEX.hex和 Motorola S 记录.s19这类文本格式在文件内部记录了每条数据的绝对地址。你打开一个 STM32 的 .hex第一行往往是:020000040800F2这行是扩展线性地址记录意思是接下来的数据基地址是 0x0800后面所有的数据都落在 0x0800xxxx 这个区间。下载工具读到这一行就知道该往 0x08000000 写。所以用 .hex 烧录时工具一般不让你填地址或者填了也会被文件里的地址覆盖。而 .bin 是纯粹的裸数据一个字节的地址信息都没有。它就是一段连续的内容你让工具把它放在 0x08000000它就放在 0x08000000你放在 0x0它也照做。所以用 .bin 烧录时填地址这一步绝对跑不掉。这就解释了很常见的一个现象同一个 STM32 工程你下载 .hex 时没填地址下载 .bin 时却要填 0x08000000。不是工具变了而是文件格式变了你没意识到。2.3 工具把基址封装了多少层最后一层变量是工具本身。不同工具对地址的封装程度完全不同列个表就一目了然工具/场景它让你填的东西背后的地址基准esptool.py write_flashSPI Flash 物理偏移0x1000、0x8000、0x10000乐鑫 Flash Download Tool每行一个 SPI Flash 偏移同上STM32CubeProgrammer 烧 .bin绝对内存地址0x08000000STM32CubeProgrammer 烧 .hex不用填文件自带 0x08000000J-Flash按设备包自动带出默认 0x08000000Keil MDK 烧录藏在工程选项里链接脚本 IROM1 起始地址Arduino IDE 普通模式完全不让你看见背后是 esptool 默认偏移Arduino IDE 详细输出模式显示完整命令可看到具体偏移工具越智能你看不到的东西就越多看不到不等于不存在。很多人被地址搞晕根源就在于工具替我决定的那些东西我不知道它替我决定了。3. 常见芯片烧录地址速查与背后的规律3.1 STM32 系记住从 0x08000000 起步这一条一次把常见型号列全芯片Flash 基址备注STM32F1030x08000000经典入门片STM32F4070x08000000除了双 Bank 的型号Bank2 另有地址STM32F0 系列0x08000000STM32L4/L50x08000000STM32H7430x08000000双 Bank 在 0x08100000GD32 兼容型号0x08000000基本抄 STM32 布局这里有一个重要提醒不是所有 Cortex-M 芯片都把 Flash 放在 0x08000000。nRF52 系列的 Flash 基址是 0x00000000有些飞思卡尔/NXP 的 Cortex-M 芯片也是从 0x0 开始。所以最稳妥的姿势是拿到新芯片先翻数据手册的 memory map 章节而不是拿 STM32 的经验硬套。另外如果 STM32 工程里加了自定义 bootloaderapp 一般会被挪到 0x08008000 或 0x08010000 这样的地址。这时候光改烧录地址还不够还要同步改链接脚本里的 IROM 起始地址和程序里的向量表偏移VTOR否则 CPU 跳过去后从错误的位置取向量表直接 hardfault。烧录地址只是表象向量表才是灵魂。3.2 ESP32/ESP8266 系偏移量是唯一语言ESP 系的地址没有基址一说全部以 SPI Flash 物理偏移为坐标芯片/场景文件烧录偏移ESP8266 NONOS SDKboot.bin0x00000ESP8266 NONOS SDKuser1.bin0x01000ESP8266 NONOS SDKuser2.binOTA0x81000ESP32经典款bootloader.bin0x1000ESP32经典款partition-table.bin0x8000ESP32经典款factory app0x10000ESP32经典款OTA app0x110000ESP32-C3/S3新 IDFbootloader.bin0x0ESP32-C3/S3新 IDFpartition-table.bin0x8000Arduino-ESP32bootloader.bin0x1000Arduino-ESP32partitions.bin0x8000Arduino-ESP32boot_app0.bin0xE000Arduino-ESP32firmware.bin0x10000看表能发现一个规律ESP32 系的地址不是靠死记硬背而是靠分区表这条主线串起来的。bootloader 该放哪、app 该放哪、NVS 该放哪全部定义在 partition table 里。你只要会读分区表就永远不会猜地址。3.3 为什么同一个 ESP32教程里能看到 0、0x6000、0x10000、0xE000如果你把网上教程里的地址截图都收集起来会发现同一颗 ESP32 有好多说法。别慌这些数字各有出处芯片不同ESP8266 的代码就是放 0x0ESP32 经典款 bootloader 要放 0x1000ESP32-C3 新 IDF 的 bootloader 又回到 0x0。文件角色不同bootloader、分区表、app、boot_app0、NVS 各有各的默认偏移它们不是一个东西自然不是一个地址。框架不同Arduino-ESP32 比裸 IDF 多烧一个 boot_app0.bin放在 0xE000很多 IDF 教程里根本没这个文件。自定义分区表0x6000 这类看上去不太整齐的地址基本都来自某个工程的私有 CSV抄过来就要连工程一起抄。所以不要收藏别人的截图学会看自己工程的输出这才是正经事。4. 怎么看你的 ESP32 该烧到哪个地址4.1 编译输出的最后几行把地址写得明明白白回到那个热搜问题怎么看 ESP32 的烧录地址。答案其实特别简单——打开编译的详细输出看一眼命令行就行。Arduino IDE 里在文件 → 首选项勾选显示详细输出信息上传然后正常上传日志最后会出现类似这样的命令esptool.py --chip esp32 --port COM3 --baud 921600 write_flash -z \ --flash_mode dio --flash_freq 80m --flash_size detect \ 0x1000 bootloader.bin \ 0x8000 partitions.bin \ 0xE000 boot_app0.bin \ 0x10000 firmware.bin注意write_flash后面的内容全部是地址 空格 文件名成对出现的。最后烧进去的 firmware.bin 是你写的程序它在 0x10000bootloader 在 0x1000分区表在 0x8000。如果哪天你自己用 esptool 手动烧录照抄这一行就行。PlatformIO 用户更简单编译并上传时加个-v参数同样能看到完整的 esptool 命令。ESP-IDF 用户则在idf.py -p COMx flash --verbose的输出里能看到或者直接打开构建目录下的flasher_args.json里面用 JSON 格式记录了每个 bin 文件要写到的偏移。这是最权威的答案文件。4.2 用工具反向读取验证成品芯片里的地址有时候你手头只有一个 .bin或者一块已经烧好程序的板子看不到编译日志怎么反推地址先泼一盆冷水esptool.py image_info xxx.bin虽然能解析固件头但它打印出来的是段加载地址比如Segments: Segment 1: IROM 0x400d0000 ... Segment 2: DROM 0x3f400020 ...这些 0x400d0000 是 CPU 映射后的地址不是烧录偏移。想用 image_info 直接得到该烧到 flash 的哪个偏移是得不到的。真正的做法是读分区表。ESP-IDF 自带的 parttool.py 可以直接问芯片python parttool.py -p COM3 get_partition_info --partition-namefactory它会返回 factory 分区的类型、偏移和大小比如 offset 0x10000、size 0x200000。不同 IDF 版本的参数略有差异先跑一次-h看帮助。没有这个工具时也可以用esptool.py read_flash 0x8000 0x1000 partition_backup.bin把分区表整体读出来再对照分区表格式人工解析稍微麻烦一点但思路一样。4.3 三步确认法文件角色 → 工程来源 → 工具命令把上面所有方法浓缩成一套判断流程从今天起遇到任何不知道该填什么地址的场景按三步走第一步判断这个 bin 是什么角色。是 bootloader、分区表、app还是某个数据分区文件名和来源通常直接告诉你。第二步确认它来自哪个构建系统。ESP-IDF 工程看partitions.csv和idf.py partition_table的输出Arduino 工程看详细上传日志或 boards.txt 里的分区定义PlatformIO 工程看platformio.ini里的board_build.partitions。不要靠猜。第三步把上一步查到的偏移原样填进烧录工具。填完如果系统能正常启动就说明这条链路是通的不能启动回到上一步重新确认。举个例子你拿到一个app.bin文件名叫 app说明它是主程序。再看工程是 Arduino 的日志里 firmware.bin 烧在 0x10000于是你用 esptool 烧 0x10000。就这么简单。5. 填错地址的翻车现场与补救方案5.1 几个高频翻车姿势和它们的症状地址填错的后果各有不同我先列一个症状对照表方便你对号入座错误操作现象根因把 ESP32 app 填成 0x08000000esptool 报错或写入失败严重的把 flash 头部内容覆盖拿 STM32 的内存地址习惯去套 SPI Flash 偏移ESP32 经典款 bootloader 填成 0x0上电反复重启串口一堆 rst 日志0x0 是 ESP8266 和 ESP32-C3 的约定经典款要 0x1000分区表 bin 随便放个偏移工具不报错但 app 启动时找不到分区日志里显示 partition 错误分区表位置和 app 读取位置不一致STM32 程序烧到 0x0工具报无法访问目标存储区该芯片的 Flash 不在 0x0app 从 bootloader 跳转但没改 VTOR跳过去就 hardfault向量表还指向 bootloader 区域5.2 排查链路一个 0x6000 引发的重启-loop案例讲一个我经手过的真实案例帮你建立排查直觉。同事拿到一块 ESP32 板子照着网上教程执行了一条命令esptool.py write_flash 0x6000 app.bin。烧完上电串口不停输出类似这样的日志rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)板子怎么都不进主程序反复重启。他怀疑是硬件坏了把板子寄给我看。我做的第一件事不是查板子而是查他项目的分区表文件。打开partitions.csv里面赫然写着nvs, data, nvs, 0x9000, 0x6000 factory, app, factory, 0x10000, 0x200000破案了。教程截图里标红圈的 0x6000在这份分区表里是 nvs 分区的大小不是地址。同事把大小当地址烧了 app。重新用 0x10000 烧一遍板子秒进系统。复盘这个案例有三个关键点第一0x6000 这个数字本身没错但它只属于那一个工程的约定照抄到另一份工程就失效第二看到rst:0x10这类复位原因先怀疑启动链路上有东西不对优先检查 bootloader、分区表、app 三者是否各就各位第三串口日志里的分区错误、跳转失败等信息是比任何工具都更直接的排查线索。5.3 补救动作救砖的常规顺序如果已经把地址烧乱、板子变砖了别急着换芯片按这个顺序救ESP32 系第一步是擦除整个 SPI Flashesptool.py --chip esp32 -p COM3 erase_flash这条命令会把 flash 全部清成 0xFF回到出厂状态。然后重新按正确组合烧写bootloader、分区表、app。烧之前务必打开flasher_args.json或详细日志把地址确认一遍再动手。如果擦完还是不行多半是芯片根本没进入下载模式。按住 BOOT 键再按一下 RST 键最后松开 BOOT让芯片重新以下载模式启动。同时检查串口芯片是不是正常很多烧不进去其实是 USB 转串口供电不稳或没有流控导致的。STM32 的补救方式类似先用 STM32CubeProgrammer 做全片擦除再烧正确地址的 .hex如果 SWD 接口也连不上了把 BOOT0 引脚拉高从系统 bootloader 进恢复模式。这里我养成的一个习惯是量产前先把整片 flash dump 一份esptool.py read_flash 0 0x400000 full.bin一旦翻车直接写回去比什么都管用。最后分享一个我自己的小习惯每个工程无论多小都在 README 里记一行烧录清单例如esptool.py write_flash 0x1000 bootloader.bin 0x8000 partitions.bin 0x10000 app.bin脚本化之后就再也不用靠脑子记地址了。地址这个东西背得再熟不如查得快查得再快不如写进脚本直接执行。下次再看到 0、0x08000000、0x6000你至少能一眼判断这个数字到底该不该信以及该去哪里验证它。
返回列表