第一阶段:编写源代码
这是你作为开发者直接参与的阶段。你使用文本编辑器(如 VS Code、CLion)或 IDE(如 Keil、STM32CubeIDE)创建和编写以下两类文件:
「应用程序代码」:
main.c、stm32f1xx_it.c、uart_driver.c等,包含了你的业务逻辑和外设驱动代码。「启动文件」:
startup_stm32f103xb.s。这是一段汇编代码,是 MCU 上电后执行的「第一条指令」。它的核心任务是:设置堆栈指针(SP)。
初始化
.data段(将 Flash 中的初始值复制到 RAM)。清零
.bss段。调用
SystemInit()函数配置系统时钟。跳转到
main()函数。
「链接脚本」:
STM32F103_FLASH.ld。就是你现在正在学习的这个文件,它定义了芯片的内存布局(Flash 和 RAM 的起始地址、大小),以及各个段(.text、.data、.bss等)在内存中的位置。MEMORY命令:定义物理存储区域MEMORY { ROM (rx) : ORIGIN = 0x08000000, LENGTH = 1024K /* STM32F103的Flash */ RAM (rw) : ORIGIN = 0x20000000, LENGTH = 64K /* STM32F103的SRAM */ }
SECTIONS命令:定义输出段布局
SECTIONS { .段名 : [AT(LMA地址)] { [输入文件列表(输入段名)] [符号赋值] } > 内存区域名 }链接器在处理各段的放置顺序时,会按照 SECTIONS 命令中段定义的先后顺序进行
「这一阶段产出的是.c、.s、.ld等文本文件」。
第二阶段:编译(Compilation)
这一阶段由「交叉编译器」arm-none-eabi-gcc和arm-none-eabi-g++完成。注意,这里的“交叉”是指编译器运行在 PC(x86 架构)上,但生成的是给 ARM 架构使用的机器码。
编译器会逐个处理每个.c和.s文件,执行以下步骤:
「预处理」:处理
#include、#define、#ifdef等预处理指令,生成一个纯净的、已展开的源代码文件。「编译」:将 C/C++ 源代码翻译成「汇编语言」。
「汇编」:将汇编语言翻译成「机器码」,并打包成「目标文件」(Object File)。
「这一阶段产出的是.o文件」。例如,main.c会生成main.o,stm32f1xx_it.c会生成stm32f1xx_it.o。
「关键点」:此时,每个.o文件内的地址都是相对的(从 0 开始),它们不知道彼此的存在,也不知道最终的 Flash 地址。
第三阶段:链接(Linking)
这一阶段由「链接器」arm-none-eabi-ld完成(通常通过arm-none-eabi-gcc间接调用)。链接器的工作是「将多个.o文件合并成一个可执行文件」,并解析它们之间的符号引用。
链接器会严格按照你提供的「链接脚本(.ld文件)」来执行:
「合并段」:将所有
main.o中的.text段、stm32f1xx_it.o中的.text段等,全部合并到输出文件的一个大的.text段中。.data、.bss等段也同理。「分配绝对地址」:根据链接脚本中
MEMORY命令的定义,将合并后的.text段分配到 Flash 的起始地址0x08000000,将.data段分配到 RAM 的起始地址0x20000000,并为每个函数和全局变量分配一个「绝对地址」。「重定位」:将代码中所有需要跳转或访问的地址,从相对地址修正为刚刚分配的绝对地址。
「解析符号」:如果一个函数在
main.c中被调用,但定义在stm32f1xx_it.c中,链接器会找到它的地址,并填入BL指令中。
「这一阶段产出的是.elf(Executable and Linkable Format)文件」。这个文件包含了完整的机器码、地址信息以及调试信息(如符号表)。
「你的 CMake 命令make或cmake --build完成后,在你指定的构建目录下就能找到这个.elf文件」。
第四阶段:烧录(Flashing/Programming)
这一阶段由「烧录工具」配合「调试器硬件」完成。你使用的「JLink」就是调试器的一种。
「格式转换」:虽然
.elf文件包含所有信息,但很多烧录工具(如 JLink 的JFlash或JLinkExe)可以直接识别.elf。也可以将其转换为更精简的.hex或.bin文件。「建立连接」:通过 USB 线连接 PC 和 JLink 调试器,再通过 SWD(Serial Wire Debug)或 JTAG 线连接 JLink 和 STM32 目标板。
「传输数据」:烧录工具(如
JLinkExe或OpenOCD)将.elf文件中的机器码数据,通过 SWD/JTAG 协议,逐字节地写入到 STM32 的 Flash 存储器中。写入的起始地址就是链接脚本中定义的0x08000000。「验证」:烧录完成后,工具通常会回读 Flash 中的数据,并与原始文件进行比对,以确保写入正确无误。
「这一阶段的结果是,STM32 芯片的 Flash 中已经存储了你的程序机器码」。
第五阶段:运行(Execution)
「上电复位」:给 STM32 板子上电或按下复位键。
「硬件自动加载」:CPU 内核自动从地址
0x00000000(映射到 Flash 地址0x08000000)读取栈顶指针(_estack)的值,设置到MSP寄存器。「读取复位向量」:CPU 自动从地址
0x00000004(映射到 Flash 地址0x08000004)读取复位向量(Reset_Handler)的地址,并跳转到该地址执行。「执行启动文件」:
Reset_Handler开始执行你之前写的汇编启动代码,完成系统初始化(复制.data、清零.bss、配置时钟等)。「跳转到
main()」:启动代码最后调用main()函数,你的程序开始正式运行。