烧录地址的本质:芯片启动模式、存储器映射与Bootloader协同机制
2026/9/13 18:38:40 网站建设 项目流程

1. 烧录地址不是“乱填的数字”,而是芯片上电那一刻就写进硬件基因里的坐标

你第一次在Keil里点“Download”、第一次用ST-Link烧STM32、第一次拿USB转串口刷ESP32,弹出那个“Start address: 0x08000000”的对话框时,有没有盯着它发过呆?——为什么偏偏是这个数?为什么有时候又变成0?还有人说“我烧0x6000就能跑”,这到底是凑巧还是真有门道?别急,这不是编译器随便写的占位符,也不是程序员拍脑袋定的魔法数字。它本质上是你写的代码在芯片物理空间里“落户”的门牌号,而这个门牌号,由三股力量共同决定:芯片的启动模式、Flash/ROM的物理地址映射、以及Bootloader的加载逻辑。这三个要素像齿轮一样咬合,少一个,烧进去的程序就找不到家。比如你把一段本该放在0x08000000的STM32固件,硬塞进0x6000去烧,芯片上电后会从0x08000000开始取指令,结果读到的全是未初始化的随机值,直接死机;反过来,如果你给ESP32烧0x00000000,它可能根本不会执行你的APP,而是卡在Bootloader里等串口命令。所以,“烧录地址”这个词本身就带误导性——它不是“你想烧到哪就烧到哪”,而是“芯片只认这个地址,你必须把代码放对位置”。我见过太多新手在调试阶段反复烧录失败,最后发现只是因为没看懂芯片手册第27页的“Memory Map”表格,把Flash起始地址和SRAM起始地址搞混了。真正决定烧录地址的,从来不是IDE的默认设置,而是你手头那颗芯片的数据手册里白纸黑字画出的地址空间图。它不讲道理,只讲物理事实。

2. 地址背后的三重真相:启动模式、存储器映射与Bootloader协同机制

2.1 启动模式:芯片上电后的第一道选择题

所有单片机上电或复位后,并不是直接跳到你写的main函数。它首先要执行一段固化在芯片内部的启动代码(Boot ROM),这段代码会先读取几个特定引脚(比如STM32的BOOT0/BOOT1、ESP32的GPIO0/GPIO2)的电平状态,据此决定“从哪里开始找程序”。这个过程叫“启动模式选择”,它是烧录地址的底层开关。

以STM32F103为例,它的启动模式有三种:

  • 主闪存存储器(Main Flash Memory):BOOT0=0, BOOT1=x → 芯片从0x08000000开始取指令;
  • 系统存储器(System Memory):BOOT0=1, BOOT1=0 → 从0x1FFFF000(内置Bootloader地址)开始;
  • 内置SRAM:BOOT0=1, BOOT1=1 → 从0x20000000(SRAM起始)开始。

注意,这里说的“从XX地址开始取指令”,指的就是CPU复位后PC寄存器(程序计数器)被硬件强制加载的初始值。这个值是芯片出厂时就硬编码在逻辑电路里的,不可更改。所以当你看到烧录地址是0x08000000,本质是因为你选择了“从主Flash启动”,而主Flash的物理起始地址,就是0x08000000。这个地址不是Keil或STM32CubeProgrammer“发明”的,它是ST公司在设计芯片时,把Flash控制器的地址总线基址焊死在这个位置的结果。你可以把它理解成一栋大楼的“1楼大厅入口”,无论你装修得多么豪华,入口永远在东侧大门——0x08000000就是那个东侧大门的门牌号。

再看ESP32,它的启动流程更复杂一层。上电后,ROM Bootloader会先检查Flash中偏移0x1000处的“image header”(镜像头),从中读取真正的APP代码起始地址(即entry_point字段)。这个entry_point通常被设为0x10000(也就是0x00010000),但开发者可以在编译链接脚本里修改它。所以当你用esptool.py烧录时指定--flash_mode dio --flash_size 4MB --flash_freq 40m,它实际把你的固件写入Flash的0x1000偏移处,而ROM Bootloader会在0x1000处解析header,然后跳转到header里声明的0x10000去执行。因此,ESP32常见的烧录地址0x1000,指的是“固件二进制文件写入Flash的起始偏移”,而0x10000才是CPU真正开始执行的第一条指令地址。这就是为什么有人问“怎么看ESP32的烧录地址”——答案不是看IDE,而是看你的partitions.csv分区表和链接脚本里ENTRY_POINT的定义。

提示:很多初学者混淆“烧录地址”和“执行地址”。烧录地址是数据写入Flash的物理偏移,执行地址是CPU复位后PC寄存器加载的值。两者在大多数情况下相同(如STM32),但在ESP32、NXP i.MX RT系列中,它们是解耦的。务必查清你用的芯片属于哪种模型。

2.2 存储器映射:芯片内部的“地理信息系统”

如果说启动模式是“选路”,那么存储器映射(Memory Map)就是芯片内部的“地图”。它是一张静态的、由芯片厂商定义的地址-功能对照表,规定了从0x00000000到0xFFFFFFFF这个4GB空间里,每一段地址对应什么物理资源:是Flash?是SRAM?是外设寄存器?还是保留区域?

我们以STM32F103C8T6(经典“蓝 pill”)为例,它的标准存储器映射如下:

地址范围名称大小说明
0x0000 0000 - 0x0000 0FFFAlias of Flash4KB主Flash的别名区,用于启动
0x0800 0000 - 0x0800 FFFFMain Flash64KB用户程序存储区,起始地址0x08000000
0x2000 0000 - 0x2000 FFFFSRAM20KB数据存储区,起始地址0x20000000
0x4000 0000 - 0x4000 0FFFAPB1外设4KB如USART1、TIM2等
0x4001 0000 - 0x4001 0FFFAPB2外设4KB如GPIOA、USART1等

关键点来了:0x08000000这个数字,就是这张地图上“Main Flash”区块的左上角坐标。它不是随意选的,而是为了满足ARM Cortex-M内核的向量表对齐要求(必须4字节对齐,且通常放在段首)以及Flash控制器的地址译码逻辑。同样,0x6000这个地址,在STM32上几乎不会作为烧录地址出现,因为它落在0x08000000之前的地址空间——那里要么是“Alias区”(仅4KB,且内容与Flash相同),要么是“保留区”(读写无效)。但为什么网上有人提0x6000?答案藏在另一类芯片里:8051架构的STC单片机

STC89C52RC这类经典51单片机,其内部Flash(或EEPROM模拟的程序存储器)起始地址通常是0x0000,但它的ISP下载协议规定:用户程序必须从0x0000开始,而ISP引导区(Bootloader)则固定占用0x0000~0x07FF(2KB)。所以当你用STC-ISP软件烧录时,如果勾选“下载应用程序”,它会把hex文件从0x0000开始写;但如果勾选“下载用户程序到指定地址”,你就可以手动输入0x0600——这意味着你把main函数的入口强行挪到0x0600,跳过前面的中断向量表和引导代码。这种操作极其危险,因为51单片机的中断向量表是硬编码在0x0003、0x000B等固定地址的,一旦main不在0x0000,复位向量(0x0000)指向的就不是你的代码,而是垃圾数据。所以0x6000在51语境下,往往是一个“错误示范”或“高级hack场景”,而非标准实践。它提醒我们:烧录地址必须与芯片的向量表布局严格匹配,否则中断永远无法响应

2.3 Bootloader:那个帮你“开门”的隐形管家

Bootloader是连接烧录工具和芯片硬件的翻译官。它不生产地址,但它决定了“你写的地址,最终会被解释成什么”。

  • 对于没有内置Bootloader的芯片(如早期AVR、部分Cortex-M0),烧录工具(如AVRDUDE、OpenOCD)直接通过SWD/JTAG接口,把二进制数据按字节写入Flash的物理地址。此时,烧录地址=执行地址=Flash物理地址。
  • 对于有强Bootloader的芯片(如STM32、ESP32、NXP Kinetis),烧录工具只是把固件“扔”到Flash某个位置,真正的“加载”由Bootloader完成。它会:
    1. 解析固件头(Header),获取入口地址(Entry Point)、校验和(Checksum)、分区信息(Partition Table);
    2. 验证签名(如果启用Secure Boot);
    3. 将代码从Flash拷贝到SRAM(如果需要XIP加速);
    4. 设置栈指针(SP)、跳转到Entry Point。

这就解释了为什么STM32CubeProgrammer里烧录地址可以填0x08000000,也可以填0x08004000(第二个APP区),只要Bootloader支持多APP切换;也解释了为什么ESP32的烧录地址是0x1000,但实际运行地址是0x10000——Bootloader在0x1000处读header,header里写着“我的代码从0x10000开始”。这个机制让OTA(空中升级)成为可能:新固件烧到0x20000,旧固件还在0x10000,Bootloader只需改一个标志位,下次重启就加载新版本。

注意:Bootloader本身也是代码,它有自己的烧录地址。STM32的系统存储器Bootloader在0x1FFFF000,ESP32的ROM Bootloader固化在芯片硅片里,不可修改。你无法“烧录”它,只能“触发”它。

3. 实操拆解:从STM32、ESP32到STC51,三类典型芯片的烧录地址配置全解析

3.1 STM32:基于链接脚本与启动模式的双重锁定

STM32的烧录地址,90%由链接脚本(.ld文件)和启动模式共同决定。我们以STM32F103RCT6(256KB Flash)为例,实操演示如何从零配置。

第一步:确认启动模式

  • 硬件上,将BOOT0接GND,BOOT1任意(通常悬空或接GND),确保芯片从主Flash启动。这是最常用模式。

第二步:编写/修改链接脚本Keil MDK或GCC项目中,STM32F103RCT6_FLASH.ld的核心段定义如下:

MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 256K RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 48K } SECTIONS { .isr_vector : { *(.isr_vector) } > FLASH .text : { *(.text) } > FLASH .rodata : { *(.rodata) } > FLASH .data : { *(.data) } > RAM AT > FLASH .bss : { *(.bss) } > RAM }

这里ORIGIN = 0x08000000是铁律。如果你强行改成ORIGIN = 0x08004000,编译器会把向量表(.isr_vector)放在0x08004000,但芯片上电后仍从0x08000000取第一条指令——结果就是读到0xFF,死机。所以,链接脚本的ORIGIN必须与芯片手册的Flash起始地址完全一致

第三步:在IDE中设置烧录地址

  • Keil MDK:Project → Options → Utilities → Settings →勾选“Use Debug Driver”,在“Flash Download”选项卡里,Flash Algorithm的“Start Address”自动读取链接脚本,无需手动改。
  • STM32CubeProgrammer:连接后,点击“Full Erase”,然后在“Download”页签,Address栏默认显示0x08000000,File Path选编译好的.bin或.hex文件,点击“Download”。

实测心得:我曾帮一个客户调试一个“烧进去不运行”的板子。查了半天代码,最后发现他们用的是STM32F103CBT6(128KB Flash),但链接脚本里写的是LENGTH = 256K,导致编译器把代码布局到了0x08020000之后,超出了实际Flash范围。烧录工具没报错,但超出部分写入的是保护区,读出来全是0。解决方法:严格按芯片型号修改链接脚本的LENGTH,并在STM32CubeProgrammer里勾选“Verify after programming”,它会逐字节比对Flash内容,立刻暴露越界问题。

3.2 ESP32:分区表驱动的动态地址分配

ESP32的烧录地址体系,核心在于“分区表(Partition Table)”。它是一个CSV文件,定义了Flash里每个功能区块的起始地址、大小和类型。

一个典型的partitions.csv如下:

# Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 1M,

这里Offset列就是烧录地址的关键:

  • nvs(非易失性存储)从0x9000开始,大小0x6000(24KB);
  • phy_init(射频校准数据)从0xf000开始;
  • factory(主应用程序)从0x10000开始。

当你用esptool.py烧录时,命令是:

esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 460800 \ write_flash -z 0x1000 build/bootloader/bootloader.bin \ 0x8000 build/partition_table/partition-table.bin \ 0x10000 build/app-template.bin
  • 0x1000:bootloader二进制写入Flash的偏移;
  • 0x8000:分区表写入偏移(必须与CSV里定义的0x8000一致);
  • 0x10000:主APP写入偏移,与CSV中factory的Offset完全对应。

为什么不是0x0000?因为0x0000~0x0fff是eFuse和ROM保留区,0x1000~0x7fff是bootloader和参数区,0x8000是分区表,0x10000才是APP的黄金位置。如果你把APP烧到0x0000,ROM Bootloader在0x1000处找不到合法的分区表,就会进入“download mode”,串口输出乱码。

实操技巧:ESP-IDF v4.0+默认使用“single app”分区表,APP从0x10000开始。但如果你要做OTA,必须用ota_data分区(大小0x2000),并生成两个APP:factory(0x10000)和ota_0(0x110000)。烧录时,先烧factory,再烧ota_0,Bootloader会根据ota_data里的标记决定加载哪个。这个机制让地址管理变得动态,但也增加了复杂度——分区表必须与烧录命令严格一一对应,差一个字节,整个系统就瘫痪

3.3 STC89C52RC:ISP协议下的地址博弈

STC51单片机的烧录,走的是UART ISP协议,地址概念与ARM完全不同。

STC-ISP软件界面里,有一个“地址”输入框,默认是0x0000。当你点击“下载”时,软件会:

  1. 发送同步命令,让单片机进入ISP模式;
  2. 按照Intel Hex格式解析你的.hex文件,提取每行的地址字段(如:020000040000FA中的0000);
  3. 将数据块写入单片机内部Flash的对应地址。

关键点:STC的Flash地址空间是0x0000~0x7FFF(32KB),但它的中断向量表是硬编码的

  • 复位向量:0x0000
  • 外部中断0:0x0003
  • 定时器0:0x000B
  • 串口中断:0x0023

所以,如果你的程序没有在0x0000处放置LJMP MAIN(长跳转到main),而是从0x0600开始,那么上电后CPU从0x0000取指令,读到的可能是未擦除的0xFF,执行NOPMOV A,#0FFH,程序彻底失控。

那么0x6000是怎么来的?两种可能:

  • 误操作:用户在STC-ISP里手输0x6000,软件会把hex文件的地址偏移强行加0x6000,导致向量表被覆盖;
  • 特殊应用:某些老式电磁炉程序,用0x6000作为自定义数据区(非代码区),利用STC的EEPROM模拟功能,把校准参数存在这里。但这与“烧录地址”无关,只是数据存储地址。

避坑指南:STC烧录失败90%的原因是波特率不匹配或冷启动没做好。务必在点击“下载”前,先给单片机断电,再按住ISP下载按键(通常是P3.0/P3.1),再上电,听到“嘀”一声后松手。此时单片机才真正进入ISP等待状态。任何跳过这一步的操作,都会导致烧录地址虽正确,但数据根本没写进去。

4. 常见问题与排查技巧实录:从“烧不进去”到“烧进去不跑”的全链路诊断

4.1 “烧录工具报错:Target not found” —— 启动模式与连接链路排查

这是最基础也最常被忽略的问题。现象:ST-Link/Nucleo连接电脑,Keil点击Download,弹出“Cannot access target”或“SWD connect failed”。

排查路径

  1. 硬件启动模式:STM32的BOOT0必须为低电平(GND),BOOT1可悬空。用万用表测BOOT0引脚对地电压,必须<0.8V。我见过太多案例,因为BOOT0上拉电阻没焊好,实际电压1.2V,芯片坚持从系统存储器启动,SWD接口被禁用。
  2. SWD连线:确认SWDIO(PA13)、SWCLK(PA14)、GND、VDD(可选)四根线全部连通。特别注意:VDD不是必须供电,但若目标板无电源,ST-Link需提供3.3V(勾选Keil里的“Power debug port”)。
  3. 复位信号:有些板子SWD接口与NRST共用,需在Keil里勾选“Connect under reset”,让ST-Link先拉低NRST再连接。
  4. 驱动与权限:Windows下检查设备管理器,ST-Link应显示为“STMicroelectronics STLink dongle”。Linux下需添加udev规则,否则普通用户无权访问/dev/ttyACM0

实操心得:我处理过一个“间歇性连接失败”的案子。查了一周,最后发现是SWD线太长(>20cm),信号反射导致时序紊乱。换成10cm杜邦线,问题消失。结论:高速调试接口,线材长度比你想象的更重要。

4.2 “烧录成功,但LED不亮/串口无输出” —— 执行地址与向量表验证

烧录进度条走完,提示“Programming completed”,但板子毫无反应。这是最折磨人的阶段。

诊断步骤

  1. 确认向量表位置:用objdump -d your_app.elf反汇编,看第一条指令是否在0x08000000(STM32)或0x10000(ESP32)。重点检查.isr_vector段的起始地址。
  2. 验证Flash内容:用ST-Link Utility或STM32CubeProgrammer,读取0x08000000开始的128字节,对比hex文件开头128字节。如果不同,说明烧录没生效(可能是Flash被写保护)。
  3. 检查写保护:STM32的Flash有OPT字节(Option Bytes),其中RDP(Readout Protection)和WPR(Write Protection)位可能被置位。用STM32CubeProgrammer的“Option Bytes”页签,将RDP设为“Level 0”,WPR全清零。
  4. 最小化测试:写一个只有while(1){GPIOA->ODR ^= 1;}的裸机程序,编译烧录。如果这个能跑,说明硬件没问题,问题出在你的原工程(如SysTick没初始化、中断没使能)。

独家技巧:对于STM32,可以用“Memory Browser”功能,在Keil调试时,右键Memory窗口→“Load Data from File”,加载你的.hex文件到0x08000000,然后F5全速运行。如果此时LED闪烁,证明代码逻辑正确,只是烧录环节出了问题。

4.3 “烧录地址填错,芯片变砖” —— 恢复与预防方案

填错地址本身不会“变砖”,但填错后执行非法指令,可能导致Flash锁死或RAM溢出。

恢复方法

  • STM32:短接BOOT0到3.3V,BOOT1到GND,上电。此时芯片从系统存储器启动,内置Bootloader激活。用ST-Link Utility,选择“Target → Connect → Connect to target”,然后“Erase chip”,全片擦除。完成后断电,恢复BOOT0=GND,重新烧录。
  • ESP32:按住GPIO0(下载键),再按住RESET(复位键),松开RESET,再松开GPIO0。此时进入下载模式,用esptool.py强制擦除:esptool.py --port /dev/ttyUSB0 erase_flash
  • STC51:STC-ISP软件里,勾选“强制擦除”,再点下载。它会发送特殊命令,绕过常规擦除流程。

预防清单

  • 永远在烧录前,用read_flash命令(ST-Link Utility)或esptool.py read_flash备份原始Flash;
  • 在项目文档里,明确记录芯片型号、启动模式、链接脚本ORIGIN、分区表Offset;
  • 使用Git管理链接脚本和分区表,每次修改都提交,避免“谁动了地址”这种甩锅现场。

4.4 “多个APP共存,如何切换烧录地址” —— 多启动区实战配置

工业设备常需双备份:一个稳定版(factory),一个测试版(test)。烧录地址管理是关键。

STM32双APP方案

  • 链接脚本分两份:app_factory.ld(ORIGIN=0x08000000),app_test.ld(ORIGIN=0x08020000);
  • Bootloader代码里,检测某个GPIO电平或Flash标志位,决定跳转到0x08000000还是0x08020000;
  • 烧录时,用STM32CubeProgrammer分别烧录两个bin文件到对应地址。

ESP32 OTA方案

  • 分区表里定义factory(0x10000)和ota_0(0x110000);
  • 编译时,用idf.py -D OTA_APP=1 build生成OTA版本;
  • 烧录命令:esptool.py write_flash 0x10000 factory.bin 0x110000 ota_0.bin
  • OTA升级时,新固件下载到ota_0,更新ota_data分区,重启后Bootloader自动加载。

经验之谈:双APP最大的坑是“地址冲突”。我曾在一个项目里,把test APP的链接脚本ORIGIN设为0x08020000,但忘了改其.data段的AT>FLASH地址,导致编译器把初始化数据写到了factory区,覆盖了关键参数。解决方案:在链接脚本里,为每个APP单独定义MEMORY区域,并用> REGION_NAME显式指定。

5. 地址选择的底层逻辑:从芯片手册到链接脚本的完整推导链

5.1 如何从芯片手册中精准定位烧录地址?

这不是靠记忆,而是靠一套标准化检索流程。以STM32F407VGT6为例:

  1. 打开官方数据手册(DS10792),搜索“memory map”或“address map”;
  2. 找到“Section 2.3 Memory mapping”章节,表格里明确写出:

    “The main Flash memory is mapped at address 0x0800 0000 on the code bus.”

  3. 交叉验证:打开参考手册(RM0090),搜索“system memory”,找到“Table 8. System memory boot modes”,确认BOOT0=0时,启动地址为0x08000000;
  4. 确认容量:数据手册“Features”页写明“Up to 1 MB Flash”,所以地址范围是0x08000000 ~ 0x080FFFFF;
  5. 检查外设映射:参考手册“Chapter 2 Memory organization”里,确认0x40000000是APB1外设基址,与Flash无关。

这套流程适用于所有芯片。NXP i.MX RT1064的手册里,Flash地址是0x60000000;GD32F303的手册里,是0x08000000;CH32V203(RISC-V)的手册里,是0x00000000。地址不是玄学,是手册里印着的白纸黑字

5.2 链接脚本里的地址,是如何与硬件一一对应的?

链接脚本(.ld)是编译器和硬件之间的契约。它的每一行,都在翻译硬件事实:

/* 这行声明:Flash这块物理资源,从0x08000000开始,长1MB,属性是可读可执行 */ FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1M /* 这行声明:向量表必须放在Flash最开头,因为CPU复位后PC=0x08000000,必须在这里找到SP和Reset_Handler */ .isr_vector : { *(.isr_vector) } > FLASH /* 这行声明:代码主体放后面,但必须紧挨着向量表,因为向量表末尾就是Reset_Handler的地址 */ .text : { *(.text) } > FLASH

如果ORIGIN写错,编译器会把向量表放在错误位置,硬件找不到入口;如果LENGTH写错,编译器可能把代码布局到不存在的地址,烧录时写入保护区。

计算实例:某项目用STM32F429,Flash总容量2MB,但客户要求预留512KB给OTA,只用前1.5MB。链接脚本应改为:

FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1536K /* 1536K = 0x180000 */

这样,.text段最大只能到0x08000000 + 0x180000 = 0x08180000。超过此地址的代码,链接器会报错region 'FLASH' overflowed,提前拦截风险。

5.3 为什么不能“统一用0x0000”?—— 架构差异的硬约束

有人问:“既然0x0000看起来最简单,为啥不全用它?”答案是:CPU架构的寻址机制根本不允许

  • ARM Cortex-M:复位向量必须位于0x00000000或0x08000000(取决于VTOR寄存器和启动模式),但0x00000000在Cortex-M中通常映射为“Alias of Flash”,内容与0x08000000相同。直接用0x0000烧录,硬件会把它当作Flash别名区,效果一样,但不符合规范。
  • RISC-V(如CH32V系列):复位向量固定在0x00000000,所以CH32V的烧录地址就是0x00000000;
  • 8051:复位向量固定在0x0000,所以STC/AT89C51的烧录地址必须是0x0000;
  • ESP32(Xtensa):复位后执行ROM代码,它自己决定从哪里读APP,所以用户烧录地址是0x1000,而非0x0000。

结论:烧录地址是芯片架构的DNA,不是软件可以随意定制的UI。你想“统一”,就得换芯片——但这显然不现实。真正的工程师,是读懂DNA,而不是抱怨它不够整齐。

6. 终极建议:建立你的“地址决策树”,告别盲目填数字

经过上千次烧录调试,我总结出一张决策树,贴在工位上,新人入职第一件事就是背熟:

开始 │ ├─ 芯片型号是什么? → 查官方数据手册 "Memory Map" 章节 │ │ │ ├─ Flash起始地址 = ? → 记为 ADDR_FLASH │ │ │ └─ 启动模式有哪些? → 确认硬件BOOT引脚接法 │ ├─ 开发环境是什么? │ │ │ ├─ Keil/IAR → 检查链接脚本 .ld/.icf,ORIGIN 必须 = ADDR_FLASH │ │ │ ├─ STM32CubeIDE → Project Properties → C/C++ Build → Settings → Tool Settings → │ │ Startup Code → Linker Script → 确认 MEMORY区域 │ │ │ └─ ESP-IDF → 检查 partitions.csv,factory 的 Offset 必须与 esptool.py 命令一致 │ ├─ 项目需求是什么? │ │ │ ├─ 单APP → 烧录地址 = ADDR_FLASH(STM32)或 0x10000(ESP32) │ │ │ ├─ 双APP/OTA → 在ADDR_FLASH基础上,按Flash容量划分多个区域,每个区域对应独立链接脚本 │ │ │ └─ Bootloader开发 → 烧录地址 = Bootloader在手册中定义的地址(如STM32系统存储器0x1FFFF000) │ └─ 烧录前必做三件事: │ ├─ 1. 用ST-Link Utility / esptool.py read_flash,备份当前Flash ├─ 2. 用objdump / hex2bin工具,确认你的bin文件起始地址与目标地址一致 └─ 3. 硬件复位一次,确保芯片处于已知状态

这张树没有捷径,每一步都必须动手查、动手试。我带过的实习生,最快掌握要领的,不是最聪明的,而是第一个把STM32F103手册第27页“Memory Map”表格抄在笔记本上的人。因为地址不是背出来的,是在一次次“烧错-排查-修正”中,刻进肌肉记忆里的。

最后分享一个小技巧:在你的工程目录里,建一个address_notes.md文件,里面只写三行:

Chip: STM32F103C8T6

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询