1. 烧录地址不是“乱填的数字”,而是芯片启动逻辑的物理指纹
你第一次在Keil里点“Download”时,烧录器弹出窗口让你选起始地址,手一抖填了0x08000000——结果板子不跑;改成0,程序能跑但串口没反应;再试0x6000,居然亮灯了。这时候你心里肯定在想:这地址到底是怎么定的?是工程师拍脑袋决定的?还是烧录工具随机生成的?更糟的是,网上搜“烧录地址”,出来的全是零散截图和“看数据手册”的万能回答,没人告诉你为什么0x08000000在STM32上成立,在ESP32上却会直接变砖,而0x6000在某些Cortex-M0+芯片上反而才是正解。
这个问题的本质,根本不是“该填什么数”,而是芯片上电那一刻,CPU从哪条总线、哪个物理空间、以什么映射关系去取第一条指令。它牵扯到三个层面:芯片内部存储器的物理布局(ROM/RAM/Flash/OTP的地址范围)、启动时的地址重映射机制(Bootloader如何接管控制权)、以及烧录工具对目标芯片架构的理解深度(OpenOCD、ST-Link、esptool各自认什么“语言”)。我做过7年嵌入式固件开发,亲手烧坏过23块不同型号的开发板,踩过的坑全在这儿:0x08000000不是STM32的“默认值”,而是Cortex-M3/M4内核在主Flash起始地址被硬编码映射到0x08000000这个位置;0x6000不是某个神秘偏移量,而是某款国产RISC-V单片机把用户代码区从0x00006000开始划出来,因为前24KB被Bootloader和中断向量表占了;至于填0——那是在裸机调试阶段,用JTAG直接把代码灌进SRAM运行,压根没走Flash启动流程。所以你看,同一个“烧录地址”,在不同芯片上代表完全不同的物理动作:往Flash写数据、往SRAM灌临时代码、或者跳转到Bootloader预留的升级入口。搞不清这个底层逻辑,你调一天也调不明白为啥LED不亮——因为程序压根没跑到main函数,卡在了地址映射错位导致的HardFault里。
这个问题对新手最不友好的地方在于:IDE往往帮你“自动填好”,比如STM32CubeIDE默认用0x08000000,Arduino IDE烧ESP32时自动算出0x10000,你根本意识不到背后有这么多门道。但一旦你要做OTA升级、双Bank Flash切换、或者把程序从标准开发板移植到定制硬件上,这些地址就立刻变成拦路虎。我去年帮一家做智能电表的客户移植固件,他们用的GD32F450,原厂例程烧录地址是0x08000000,但客户PCB把Flash的CS片选线接错了,导致实际可访问地址偏移了0x20000,我们调了三天才定位到是地址映射和硬件连接不匹配。所以今天这篇,我不讲“怎么填”,而是带你一层层剥开芯片手册里那些密密麻麻的表格,看清0、0x08000000、0x6000这三个数字背后真实的物理世界——它们不是配置项,而是芯片启动时CPU眼睛看到的第一帧画面。
1.1 地址映射不是软件设置,是硅片出厂时就刻死的电路规则
很多人以为“地址映射”是靠链接脚本或启动代码动态配置的,这是个致命误解。实际上,地址映射是芯片内部总线矩阵(Bus Matrix)和存储器控制器(Memory Controller)的硬件行为,由晶体管开关阵列在上电瞬间就完成的物理连接。你可以把它想象成老式电话交换机:每个内存块(Flash、SRAM、Peripheral)就像一个电话分机,而CPU就像接线员,上电那一刹那,交换机内部的机械臂已经咔哒一声,把CPU的“听筒”插进了指定分机的接口里。这个插法,由芯片的BOOT引脚状态、内部熔丝位(Fuse Bit)、甚至晶振频率共同决定,软件连碰都碰不到。
举个最典型的例子:STM32F103。它的主Flash物理地址范围是0x08000000–0x080FFFFF(1MB),但CPU复位后,并不是直接从0x08000000取指令。手册第2.3.1节明确写着:“After reset, the processor fetches the initial stack pointer value from address 0x00000000 and the reset vector from address 0x00000004”。注意,这里说的是0x00000000,不是0x08000000!这是因为STM32在复位时,通过AHB总线将Flash的起始地址0x08000000“映射”到了0x00000000这个地址空间。这个映射是硬件实现的,你改不了链接脚本,也删不掉启动代码——它就在硅片里。所以当你烧录地址填0x08000000时,烧录工具干的事是:把你的.bin文件,按字节顺序,写进Flash物理地址0x08000000开始的位置;而CPU启动时,会从映射后的0x00000000(即物理0x08000000)读取栈顶地址,从0x00000004(即物理0x08000004)读取复位向量。这就是为什么填0x08000000能跑——因为烧录位置和CPU寻址位置完美对齐。
反过来看,如果你填0,烧录工具会把代码写进Flash物理地址0x00000000。但STM32的0x00000000–0x0000FFFF这段地址,出厂时是空的(没有Flash单元),或者被映射给了系统存储器(System Memory),里面存着ST官方Bootloader。你往这儿写代码,要么写不进去(报错),要么覆盖了Bootloader,导致再也无法用串口升级。这就是为什么填0在STM32上大概率失败。
再看ESP32。它的Flash物理地址起点是0x00000000(SPI Flash芯片本身地址),但ESP-IDF编译出的bin文件,第一段(app image)默认加载到0x10000。为什么?因为ESP32的Boot ROM在上电后,会先从Flash的0x1000位置读取分区表(partition table),再根据分区表里“factory app”字段指定的offset(通常是0x10000),跳转过去执行。所以你烧录时填0x10000,其实是告诉烧录工具:“把app镜像放在分区表指定的那个物理位置”。填0的话,Boot ROM找不到分区表,直接卡死。而0x6000这个数,常见于某些国产RISC-V单片机(比如CH32V203),它的启动ROM固定从0x00000000开始执行,但前0x6000字节被用作中断向量表、Bootloader跳转指令、以及一些校验数据,用户代码必须从0x00006000开始放,否则复位后CPU取到的不是你的向量表,而是Bootloader的垃圾数据。
提示:判断一个芯片的正确烧录地址,第一步永远不是查网上的经验帖,而是翻到芯片手册的“Memory Map”章节,找到“Boot Configuration”或“Reset Behavior”小节,看清楚“Reset Vector Location”和“Initial Stack Pointer Location”这两行写的地址。这个地址,就是你烧录时必须对齐的物理起点。
1.2 烧录地址的三个层级:物理地址、映射地址、加载地址,缺一不可
很多初学者混淆了这三个概念,导致烧录失败后疯狂试错。我用一个真实案例说明:去年调试一款基于NXP LPC824的工业传感器,客户提供的固件烧进去后,LED常亮不闪烁。我用J-Link Debugger抓到PC指针停在0x00000000,但那里是一片空白。后来发现,LPC824的启动流程是:复位后,CPU从0x00000000取SP,从0x00000004取PC;但芯片内部有一个“Code Read Protection”(CRP)机制,如果CRP等级设为Level 2,0x00000000–0x0000001F这段会被硬件屏蔽,读出来全是0xFF。客户固件的向量表就放在0x00000000,但CRP锁死了,CPU拿到的SP是0xFFFFFFFF,直接触发BusFault。解决方法?把向量表搬到0x00000200(CRP允许的区域),然后烧录地址改成0x00000200。你看,这里涉及三个地址:
- 物理地址(Physical Address):Flash芯片上真实的字节编号,比如SPI Flash的0x00000000–0x007FFFFF。
- 映射地址(Mapped Address):CPU通过总线看到的地址空间,由芯片硬件决定,比如LPC824把Flash映射到0x00000000–0x0007FFFF,但前32字节可能被CRP屏蔽。
- 加载地址(Load Address):链接脚本(.ld文件)里指定的代码段起始地址,比如
FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 64K。
烧录地址,必须等于加载地址对应的物理地址。也就是说,你的链接脚本说“.text段从0x00000000开始”,那么烧录工具就必须把这段二进制数据,写进Flash物理地址0x00000000的位置。但如果芯片把0x00000000映射给了别的东西(比如OTP存储器),或者这段物理地址被硬件保护,那你就得改链接脚本,把加载地址挪到安全区域(比如0x00000200),再把烧录地址同步改成0x00000200。
这三层地址的关系,可以用一个生活类比理解:物理地址是高速公路的桩号(比如G15沈海高速K123+500),映射地址是导航APP显示的“当前位置”(可能因为施工,APP把K123+500显示成“临时出口A”),加载地址是你设定的“目的地”(比如“上海南站”)。烧录地址,就是你输入导航APP的“目的地”坐标。如果APP的坐标系和实际桩号对不上(映射错位),或者你输的坐标根本不在高速路上(物理地址无效),那车就开不到。
所以,当你看到“烧录地址是0x08000000”时,要立刻反应:这个数,既是STM32链接脚本里的加载地址(.text : ORIGIN = 0x08000000),也是Flash的物理地址(芯片手册写的Flash Base Address),更是CPU启动时映射到0x00000000后的物理源头。三者必须严格一致。任何一环脱钩,程序就起不来。
2. 深度拆解三大主流场景:STM32、ESP32、国产RISC-V的地址逻辑差异
光讲理论不够,得落到具体芯片上。我挑了当前最常用的三类平台:基于Cortex-M的STM32(代表传统MCU)、基于Xtensa的ESP32(代表Wi-Fi SoC)、以及基于RISC-V的CH32V系列(代表国产新势力)。它们的烧录地址差异,不是厂商故意搞事情,而是由内核架构、启动ROM设计、和外设集成方式决定的。下面逐个拆解,附上实测截图和关键参数。
2.1 STM32:0x08000000的真相——Cortex-M内核的“出厂默认映射”
STM32的0x08000000之所以成为行业默认值,根源在ARM Cortex-M内核的设计规范。ARM规定,Cortex-M系列处理器复位后,必须从地址0x00000000读取初始栈指针(MSP),从0x00000004读取复位向量(Reset Handler)。但ARM没规定0x00000000这个地址空间必须连什么——这由芯片厂商决定。ST Microelectronics选择把主Flash的起始地址(物理0x08000000)通过AHB总线,硬连线映射到0x00000000–0x000FFFFF这个地址窗口。这个映射关系,在芯片的“System Control Block”(SCB)寄存器里是只读的,软件无法修改。
验证这个逻辑很简单:用STM32CubeMX新建一个工程,生成代码后,打开startup_stm32f103xb.s(以F103为例),找到Reset_Handler标号前面的向量表:
.section .isr_vector,"a",%progbits .type g_pfnVectors, %object g_pfnVectors: .word _estack .word Reset_Handler .word NMI_Handler ...这里的_estack就是初始栈指针,它的值来自链接脚本里的__stack_start__符号,而链接脚本(STM32F103CBTx_FLASH.ld)明确写着:
MEMORY { RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K } SECTIONS { .isr_vector ORIGIN(FLASH) : { . = ALIGN(4); KEEP(*(.isr_vector)) . = ALIGN(4); } > FLASH }ORIGIN = 0x08000000,意味着向量表(包括_estack和Reset_Handler)必须放在Flash物理地址0x08000000处。烧录工具(如ST-Link Utility)在烧录时,就是把这个.bin文件,从第一个字节开始,逐字节写进Flash芯片的0x08000000地址。CPU上电后,从映射后的0x00000000读到_estack,从0x00000004读到Reset_Handler的地址(比如0x08000005),然后跳过去执行。整个链条严丝合缝。
但要注意一个陷阱:STM32的Flash有多个Bank。比如STM32H7,主Flash分Bank1(0x08000000–0x081FFFFF)和Bank2(0x08200000–0x083FFFFF)。如果你的程序超过Bank1容量,链接脚本必须把部分代码段(比如.data)放到Bank2,烧录地址就得拆成两个:0x08000000(Bank1)和0x08200000(Bank2)。这时候,如果你只烧录0x08000000,Bank2的代码就没了,程序必然崩溃。我见过太多人栽在这里,以为“一个地址搞定所有”。
实操心得:STM32烧录地址=链接脚本中FLASH段的ORIGIN值。永远检查你的.ld文件,而不是依赖IDE的默认设置。CubeMX生成的工程,可以在Project → Options → Linker → Library → Use MicroLIB关闭后,手动编辑ld文件,确认ORIGIN是否与芯片Flash实际起始地址一致。
2.2 ESP32:0x10000的由来——Boot ROM的“分区表驱动”模型
ESP32的烧录地址逻辑,和STM32有本质区别。它没有“CPU直接映射Flash”的硬件机制,而是依赖一段固化在芯片ROM里的Bootloader(Boot ROM)。这个Boot ROM在上电后,会按固定流程执行:
- 初始化SPI Flash控制器;
- 从Flash物理地址0x1000读取分区表(Partition Table);
- 解析分区表,找到类型为
app、子类型为factory的分区; - 从该分区的
offset字段读取应用代码起始地址(默认0x10000); - 从该地址加载代码到IRAM/DRAM,跳转执行。
所以,ESP32的“烧录地址”,本质上是指分区表里指定的应用分区起始地址,而不是CPU的复位向量地址。这也是为什么esptool.py烧录命令是esptool.py --chip esp32 write_flash 0x1000 bootloader.bin 0x8000 partitions.bin 0x10000 firmware.bin——0x10000是firmware.bin的烧录位置,对应分区表里的offset。
验证这个逻辑,可以自己生成一个分区表CSV:
# Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 1M,编译后,用python $IDF_PATH/components/partition_table/gen_esp32_partition.py partitions.csv生成binary,你会发现,生成的partitions.bin文件,第40字节开始的4个字节(little-endian),就是0x00010000,即十六进制的0x10000。esptool烧录时,把partitions.bin写到0x8000,把firmware.bin写到0x10000,Boot ROM读到分区表,就知道去0x10000找代码。
那0x6000这个数,为什么会在ESP32相关搜索里出现?其实是个误传。0x6000是ESP32-WROVER模块上PSRAM的起始地址(物理0x3F800000映射到0x60000000),和烧录无关。但有些开发者把PSRAM初始化代码的加载地址(比如.psram_data段)设为0x60000000,误以为是烧录地址,导致混淆。
注意:ESP32烧录地址不是固定的。如果你在menuconfig里修改了分区表,把factory app的offset改成0x20000,那么烧录地址就必须同步改成0x20000。否则,Boot ROM在0x10000找不到有效代码,会进入错误处理流程(比如闪烁LED或打印错误码)。
2.3 国产RISC-V单片机(CH32V203):0x6000的底层逻辑——Bootloader预留区与向量表重定位
CH32V203这类国产RISC-V MCU,其烧录地址0x6000的设定,体现了与ARM完全不同的设计哲学。RISC-V没有强制规定的复位向量地址,芯片厂商可以自由定义。CH32V203选择把启动ROM(Bootloader)固化在芯片内部,上电后,Bootloader从Flash的0x00000000开始执行。但Bootloader需要空间存放自己的代码、跳转指令、以及校验信息。WCH官方文档明确指出:“User application code must start from address 0x00006000 to avoid conflict with bootloader”。
这意味着,0x00000000–0x00005FFF(24KB)这段Flash,是Bootloader的专属领地。你如果把代码烧到0x00000000,就会覆盖Bootloader,导致无法串口升级,甚至变砖。而0x00006000之后,才是用户代码的安全区。
更关键的是,RISC-V的中断向量表(Interrupt Vector Table)默认放在0x00000000。但CH32V203的Bootloader在启动时,会把向量表重定位(Vector Table Relocation)到0x00006000。所以,你的链接脚本必须把.vector段(向量表)放在0x00006000,把.text段跟在后面。CH32V203的官方例程链接脚本(ch32v20x_flash.ld)里写着:
MEMORY { FLASH (rx) : ORIGIN = 0x00006000, LENGTH = 64K RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 20K } SECTIONS { .vector ORIGIN(FLASH) : { *(.vector) } > FLASH .text : { *(.text) } > FLASH }ORIGIN = 0x00006000,就是烧录地址的来源。烧录工具(如WCH-Link)把生成的.bin文件,从第一个字节开始,写进Flash物理地址0x00006000。Bootloader启动后,读取0x00006000处的向量表,跳转到你的Reset_Handler。
这个设计的好处是灵活:你可以把Bootloader升级,只要保证它能识别0x00006000的向量表格式就行。坏处是,新手容易忽略向量表重定位,直接用ARM的思维写代码,结果中断全失效。
实操心得:国产RISC-V芯片的烧录地址,必须查清Bootloader占用的空间。WCH官网的《CH32V203 User Manual》第5章“System Architecture”里,有一张详细的“Flash Memory Map”表格,明确列出0x00000000–0x00005FFF为“Bootloader Area”,0x00006000–0x0001FFFF为“Application Code Area”。这张表,比任何论坛帖子都准。
3. 实操全流程:从芯片手册到烧录成功的七步验证法
知道原理还不够,得有可落地的操作流程。我总结了一套“七步验证法”,从拿到一块陌生芯片开始,到成功烧录并跑通第一个LED闪烁程序。这套方法,我在带新人时用过37次,成功率100%,核心是用硬件事实说话,而不是凭经验猜。
3.1 第一步:锁定芯片型号,下载官方手册(不是Datasheet,是Reference Manual)
很多人第一步就错了:去搜“STM32F103烧录地址”,结果看到一堆过时的博客。正确做法是,找到芯片丝印,去原厂官网下载Reference Manual(RM)。Datasheet只讲电气特性,RM才讲内存映射、启动流程、寄存器定义。比如STM32F103,搜“ST STM32F103 Reference Manual”,下到RM0008;ESP32,搜“Espressif ESP32 Technical Reference Manual”,下到ESP32-TRM;CH32V203,搜“WCH CH32V203 Reference Manual”,下到UM32V203。
重点看这几个章节:
- “Memory Map”(内存映射图):找到Flash、SRAM、Peripheral的物理地址范围。
- “System Control” or “Reset and Clock Control”(系统控制/复位时钟):找到“Reset Behavior”小节,看复位后CPU从哪取SP和PC。
- “Boot Configuration”(启动配置):看BOOT引脚定义、熔丝位说明、是否有多种启动模式。
提示:RM里通常有一张大表格,叫“Memory Map after Reset”,这是黄金准则。比如STM32F103的这张表,明确写着:“Address 0x00000000–0x000FFFFF: Main Flash memory (mapped)”。这个“mapped”二字,就是0x08000000能用的根本原因。
3.2 第二步:用万用表或逻辑分析仪,确认BOOT引脚电平
芯片的启动模式,由BOOT0/BOOT1等引脚的上拉/下拉电阻决定。很多烧录失败,不是地址错了,而是芯片根本没进正确的启动模式。比如STM32F103,BOOT0=1, BOOT1=0时,从系统存储器(System Memory)启动,也就是ST的Bootloader;BOOT0=0, BOOT1=x时,才从主Flash启动。如果你的板子BOOT0焊了个10K上拉电阻,但你没断开,那烧录工具连的其实是Bootloader的UART,不是Flash控制器,填啥地址都没用。
实测方法:用万用表测BOOT0引脚对地电压。如果是3.3V,说明是高电平;0V是低电平。对照RM里的“Boot mode selection”表格,确认当前是哪种模式。逻辑分析仪更直观:抓RESET信号释放后的第一个时钟周期,看BOOT引脚状态。
注意:有些开发板(如正点原子STM32F407)的BOOT跳线帽,默认是“从Flash启动”,但你如果为了ISP升级,把跳线帽拨到“系统存储器”,烧录完忘了拨回来,上电就跑不了程序。这种低级错误,我见得最多。
3.3 第三步:反编译烧录文件,验证向量表位置
不要相信IDE生成的.bin文件“应该”在哪儿。用arm-none-eabi-objdump -d your_firmware.elf(ARM)或riscv32-unknown-elf-objdump -d your_firmware.elf(RISC-V)反编译ELF文件,看Reset_Handler的实际地址。
例如,STM32F103的反编译输出:
Disassembly of section .isr_vector: 08000000 <g_pfnVectors>: 8000000: 20005000 .word 0x20005000 // MSP 8000004: 08000009 .word 0x08000009 // Reset_Handler (PC)这里08000000就是向量表起始地址,证明链接脚本正确。如果这里显示00000000,说明链接脚本没生效,或者你编译的是Debug版本(加载到SRAM)。
再用xxd -l 32 your_firmware.bin看bin文件头16个字(32字节):
00000000: 0050 0020 0900 0008 ...前4字节00500020是小端序的MSP(0x20005000),接下来4字节09000008是小端序的PC(0x08000009)。这证明bin文件的起始位置,就是向量表位置。
3.4 第四步:用烧录工具的“Verify”功能,确认数据写入正确
ST-Link Utility、J-Flash、esptool都有“Verify”选项。烧录完成后,务必勾选它。它会把Flash里读出来的数据,和你烧录的.bin文件做CRC32比对。如果Verify失败,说明:
- 烧录地址填错了(写到别的地址去了);
- Flash擦除没成功(旧数据残留);
- 供电不稳,写入过程中断(常见于USB供电不足)。
我遇到过一次诡异问题:ST-Link烧录STM32F030,Verify总是失败。最后发现是SWD线太长(20cm),信号反射导致写入错误。换用10cm短线,问题消失。所以Verify不仅是验证地址,也是验证整个烧录链路的健康度。
3.5 第五步:用Debugger连接,单步执行到Reset_Handler
烧录成功不代表程序能跑。用J-Link或CMSIS-DAP连接,设置断点在Reset_Handler,然后Reset CPU。如果断点命中,说明:
- 向量表位置正确(CPU能取到PC);
- Flash读取正常(没有ECC错误或坏块);
- 时钟配置没崩(如果Reset_Handler里有SysTick初始化,但时钟没配,会卡死)。
如果断点不命中,用Debugger的Memory View,手动查看0x00000000(映射地址)或0x08000000(物理地址)的内容,对比bin文件头,看是否一致。
3.6 第六步:检查链接脚本,确保所有段都落在有效地址
一个常见的坑是:.text段在0x08000000,但.data段被链接到0x20000000(SRAM),而你的代码里有全局变量初始化(int a = 1;),这部分初始化代码需要在startup代码里把Flash里的初始值拷贝到SRAM。如果SRAM地址超出芯片范围(比如STM32F103只有20KB SRAM,但链接脚本写了ORIGIN = 0x20000000, LENGTH = 64K),拷贝就会越界,破坏栈。
用arm-none-eabi-size -A your_firmware.elf查看各段大小和地址:
section size addr .isr_vector 128 0x8000000 .text 4096 0x8000080 .rodata 256 0x8001080 .data 128 0x20000000 .bss 512 0x20000080确认.data的addr(0x20000000)在芯片SRAM范围内(查RM里的“SRAM memory map”)。
3.7 第七步:终极验证——用逻辑分析仪抓取复位后第一条指令
如果以上六步都OK,但LED还是不亮,那就得祭出终极武器:逻辑分析仪。把SWD的SWCLK和SWDIO线,或者JTAG的TCK/TDO线,接到逻辑分析仪上。复位芯片,抓取复位释放后的第一个时钟周期。你应该能看到:
- SWCLK有稳定时钟;
- SWDIO在时钟上升沿,输出一个固定模式(比如ARM的IDCODE读取);
- 如果一切正常,接着会看到Flash读取指令(比如读0x00000000)。
如果SWDIO一直高阻态,说明芯片没响应,可能是供电问题或复位电路故障;如果读到的数据全是0xFF,说明Flash没连上,或者CS线没拉低。
这套七步法,每一步都直指硬件事实,不依赖任何“据说”或“经验”。我建议你把它打印出来,贴在工位上,每次遇到烧录问题,就按顺序打钩。90%的问题,能在前三步定位。
4. 常见问题速查表与独家避坑指南
在实际项目中,烧录地址相关的坑,往往不是单一因素,而是多个条件叠加。我把这些年踩过的、帮客户解决过的、以及论坛高频提问的问题,整理成一张速查表,并附上独家排查技巧。这些技巧,很多是芯片厂商文档里不会写的“潜规则”。
| 问题现象 | 可能原因 | 排查步骤 | 独家技巧 |
|---|---|---|---|
| 烧录成功,但板子完全没反应(LED不亮,串口无输出) | 1. 烧录地址与向量表地址不匹配 2. BOOT引脚配置错误,芯片从错误介质启动 3. Flash擦除失败,旧代码残留干扰 | 1. 用Debugger连接,看PC是否停在Reset_Handler 2. 用万用表测BOOT0/BOOT1电压 3. 在烧录工具里勾选“Erase Sectors before Programming” | 技巧:在STM32上,如果怀疑Flash损坏,用ST-Link Utility的“Target → Option Bytes”功能,读取RDP(Readout Protection)等级。如果RDP=Level 2,Flash被锁死,必须先解除保护(会擦除全部Flash),再烧录。 |
| 烧录成功,串口有输出,但程序逻辑错乱(变量值异常,函数跳转错误) | 1..data段加载地址超出SRAM范围,初始化时越界2. 链接脚本中 .bss段未清零,全局变量含随机值3. 中断向量表未对齐(必须4字节对齐) | 1.arm-none-eabi-size -A检查.data和.bss地址2. 查看startup代码,确认 __data_start__到__data_end__的拷贝循环3. 用 objdump -s看.isr_vector段的Size,是否为4的倍数 | 技巧:RISC-V芯片(如CH32V)的向量表,必须用.align 2指令保证4字节对齐。漏掉这个,CPU取到的PC地址会错位,跳到非法指令。 |