2. 开发环境搭建:Keil/IAR工程配置、链接脚本修改、启动文件定制

好,咱们正式开始动手。这一章我带你把开发环境搭起来。别小看这一步,我见过太多人代码写好了,结果工程配置不对,链接脚本没改,启动文件还是原厂的——最后Bootloader跑不起来,还以为是代码有bug。

说白了,环境搭对了,后面就顺了。搭不对,你查三天都查不出问题。

2.1 开发工具选择与版本说明

我个人习惯用Keil MDK做ARM Cortex-M系列的开发。IAR也不错,但咱们课程以Keil为主,IAR的配置我会在关键地方提一下差异。

工具 推荐版本 说明
Keil MDK 5.38 及以上 支持AC6编译器,代码密度更好
IAR EWARM 8.50 及以上 优化能力强,调试体验好
STM32CubeMX 6.10 及以上 用于生成初始化代码和链接脚本模板
我的建议:如果你刚开始接触Bootloader,先用Keil。它的工程管理更直观,出错提示也更友好。IAR的链接脚本语法有点绕,新手容易踩坑。

2.2 Keil工程配置要点

创建一个Bootloader工程,和普通应用工程不一样。你需要关注几个关键点。

2.2.1 目标芯片与Flash分区

先确定你的芯片型号。我用STM32F103C8T6举例,它有个特点:Flash只有64KB,但实际能用到128KB(嗯,这是ST的"福利")。不过咱们按64KB来规划。

  • Bootloader区:0x08000000 ~ 0x08003FFF,16KB
  • 应用区:0x08004000 ~ 0x0800FFFF,48KB
  • 参数区:0x0800FC00 ~ 0x0800FFFF,1KB(存升级标志和校验值)
注意:Flash分区必须和链接脚本一致。我曾经帮一个客户排查问题,他Bootloader和应用都写对了,就是分区没对齐,结果每次升级完应用就跑飞。查了两天才发现是链接脚本里Flash起始地址写错了。

2.2.2 编译器选项设置

在Keil里,点开Options for Target,这几个地方必须改:

  1. Target标签:ROM1起始地址填0x08000000,大小0x4000(16KB)。RAM不用动。
  2. C/C++标签:Optimization选-O2或-Oz。我建议用-Oz,代码更小。
  3. Linker标签:取消勾选"Use Memory Layout from Target Dialog",改用自定义链接脚本。

关键一步:取消"Use Memory Layout from Target Dialog"后,Keil会使用工程目录下的.sct文件作为链接脚本。这个文件你得自己改。

2.3 链接脚本修改(.sct / .icf)

链接脚本决定了你的代码和数据怎么放。Bootloader的链接脚本和普通应用不一样,因为它的起始地址不是0x08000000吗?不对,Bootloader的起始地址就是0x08000000,但应用的要偏移。

咱们先看Bootloader的链接脚本。

2.3.1 Keil的.sct文件

;  Bootloader链接脚本 - STM32F103C8T6
;  Flash: 0x08000000 ~ 0x08003FFF (16KB)
;  RAM:   0x20000000 ~ 0x20004FFF (20KB)

LR_IROM1 0x08000000 0x00004000  {    ; load region
  ER_IROM1 0x08000000 0x00004000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 0x20000000 0x00005000  {  ; RW data
   .ANY (+RW +ZI)
  }
}

这段脚本干了三件事:

  • 把代码放在0x08000000开始的16KB空间
  • 中断向量表放在最前面(RESET段)
  • 可读写数据放在RAM里

2.3.2 IAR的.icf文件

IAR的语法不太一样,但逻辑相同:

define symbol __ICFEDIT_region_ROM_start__  = 0x08000000;
define symbol __ICFEDIT_region_ROM_end__    = 0x08003FFF;
define symbol __ICFEDIT_region_RAM_start__  = 0x20000000;
define symbol __ICFEDIT_region_RAM_end__    = 0x20004FFF;

place at address mem:__ICFEDIT_region_ROM_start__ { readonly section .intvec };
place in ROM_region   { readonly };
place in RAM_region   { readwrite, block HEAP, block CSTACK };
避坑指南:我曾经在IAR里忘了把.intvec段放在起始地址,结果芯片上电后直接跑飞。因为CPU复位后第一件事就是去0x00000000取栈指针,然后去0x00000004取复位向量。如果你的中断向量表不在那里,它就找不到入口。

2.4 启动文件定制

启动文件(startup_xxx.s)是芯片上电后执行的第一段代码。它做了三件事:

  1. 设置栈指针
  2. 初始化中断向量表
  3. 跳转到main函数

对于Bootloader,启动文件基本不用改。但有一个地方要注意:堆栈大小。

Bootloader的堆栈不需要太大,因为它的功能简单。我一般把堆设成0x200(512字节),栈设成0x400(1KB)。省下来的RAM可以给应用用。

; 修改启动文件中的堆栈大小
Stack_Size      EQU     0x400
Heap_Size       EQU     0x200
注意:别把栈设得太小。Bootloader里如果用了printf或者复杂的Flash擦写函数,栈不够会直接HardFault。我建议至少留0x400。

2.5 知识结构总览

下面这张图把整个环境搭建的脉络理清楚了。你照着这个顺序做,不会乱。

开发环境搭建知识结构 工具选择与版本 Keil工程配置 链接脚本修改 启动文件定制 Flash分区规划 Boot: 0x08000000 App: 0x08004000 .sct / .icf 语法 定义加载域和执行域 指定中断向量表位置 堆栈大小调整 Stack: 0x400 Heap: 0x200 编译器优化选项 自定义链接脚本 中断向量表对齐 关键:分区、链接脚本、启动文件三者必须一致 否则Bootloader无法正常启动

2.6 验证环境是否搭对

工程配好了,怎么知道对不对?我教你一个快速验证的方法。

  1. 编译工程,看生成的.hex文件大小。Bootloader应该在16KB以内。
  2. 用调试器下载,看芯片是否停在main函数入口。
  3. 查看反汇编,确认复位向量指向的是你的Reset_Handler。

快速检查:在Keil里按Ctrl+D进入调试模式,打开Disassembly窗口。看PC指针是不是停在0x08000000附近的Reset_Handler里。如果是,说明启动文件和链接脚本都对了。