3. 开发环境搭建:Keil/IAR环境配置、编译器选项设置、Linker脚本编写基础

好,咱们进入第三章。说实话,很多做Bootloader的新手,代码写得挺漂亮,结果栽在环境配置上。我见过太多人花三天调一个链接错误,最后发现是编译器选项没设对。所以这一章,咱们把环境搭建这件事彻底讲透。

3.1 选择Keil还是IAR?我的建议

先说说这两个工具。Keil MDK和IAR Embedded Workbench,是ARM Cortex-M开发的两大主流。我个人习惯用Keil多一些,因为它的界面更直观,调试器集成得也好。但IAR的代码优化确实更狠,尤其对代码尺寸的控制,IAR能比Keil再小10%-15%。

怎么选?我建议这样:

  • 如果你刚入门,或者团队里其他人都在用Keil,那就用Keil。遇到问题好找人问。
  • 如果你对代码尺寸有极致要求,比如Flash只剩2KB空间了,那IAR是更好的选择。
  • 如果你做车规级产品,很多Tier1要求用IAR,因为它的编译器认证更全。
我的小经验: 我在一个项目中同时用过Keil和IAR——开发阶段用Keil,因为调试方便;量产前用IAR编译一遍,代码能小不少。当然,前提是你的代码在两个编译器下都能通过。

3.2 Keil环境配置要点

Keil的配置,说白了就是三个地方:Target、C/C++、Linker。咱们一个一个说。

3.2.1 Target选项卡

这里最关键的是选对芯片型号和浮点单元。我见过有人把STM32F103选成F407,编译通过了,下载进去直接跑飞。

选项 推荐设置 说明
Device 你的具体芯片型号 比如STM32G070RBT6
Xtal 实际晶振频率 别乱填,会影响串口波特率
Use Cross-Module Optimization 勾选 能优化代码尺寸
Use MicroLIB 强烈建议勾选 减少代码体积,Bootloader必备
注意: MicroLIB虽然小,但它不支持浮点printf。如果你需要在Bootloader里打印浮点数,得自己实现。我一般用整数打印,省事。

3.2.2 C/C++选项卡

这里我重点说几个选项:

  • Optimization:Bootloader建议选 -O1 或 -Os。别选 -O0,代码太大。也别选 -O3,有时候优化过头会把你的延时函数优化掉。
  • Define:这里要定义你的芯片宏,比如 STM32G070xx。不定义的话,很多外设驱动编译不过。
  • Language C:选 C99 或 C11。C89太老了,很多语法不支持。

避坑指南: 我曾经在 -O3 优化下,一个简单的 for 循环延时被编译器直接优化没了,导致Bootloader启动时序全乱。后来我加了 volatile 关键字才解决。所以,Bootloader里所有跟硬件时序相关的变量,记得加 volatile。

3.3 IAR环境配置要点

IAR的配置逻辑跟Keil差不多,但名字不一样。我刚开始用IAR时,找那个链接脚本找了半小时。

3.3.1 General Options

这里主要设置芯片型号和浮点单元。IAR的芯片支持包叫"device files",记得更新到最新版。

3.3.2 C/C++ Compiler

IAR的编译器选项在"Options > C/C++ Compiler"里:

  • Optimization:选 High (Balanced) 或 High (Size)。IAR的尺寸优化确实厉害。
  • Language conformance:选 C99 或 C11。
  • Preprocessor:这里定义宏,跟Keil的Define一样。
小技巧: IAR有个"Multi-file compilation"选项,勾上之后能把多个.c文件一起编译,优化效果更好。但要注意,如果你的文件之间有宏定义冲突,就别勾。

3.4 Linker脚本编写基础

Linker脚本,说白了就是告诉编译器:你的代码放哪,数据放哪。Bootloader的Linker脚本尤其重要,因为你要把Bootloader和应用程序放在不同的Flash区域。

我以STM32G070为例,写一个最简单的Linker脚本:

/* Linker script for STM32G070 Bootloader */
MEMORY
{
  FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 32K  /* Bootloader占用前32KB */
  RAM   (rwx) : ORIGIN = 0x20000000, LENGTH = 36K  /* RAM 36KB */
}

SECTIONS
{
  .text :
  {
    KEEP(*(.isr_vector))    /* 中断向量表,必须保留 */
    *(.text*)               /* 代码段 */
    *(.rodata*)             /* 只读数据 */
    . = ALIGN(4);
    _etext = .;             /* 代码结束地址 */
  } > FLASH

  .data :
  {
    _sdata = .;             /* 数据段起始地址 */
    *(.data*)
    . = ALIGN(4);
    _edata = .;             /* 数据段结束地址 */
  } > RAM AT> FLASH

  .bss :
  {
    _sbss = .;              /* BSS段起始地址 */
    *(.bss*)
    . = ALIGN(4);
    _ebss = .;              /* BSS段结束地址 */
  } > RAM
}

这段脚本里,有几个关键点:

  • FLASH起始地址0x08000000,长度32KB。这意味着Bootloader最大只能32KB。
  • 中断向量表必须放在最前面,用KEEP关键字保护,防止被优化掉。
  • .data段在RAM里运行,但初始值存在Flash里。所以用了"AT> FLASH"。
  • _sdata、_edata这些符号,是给启动代码用的。启动代码会把这些数据从Flash拷贝到RAM。
重要提醒: 应用程序的Linker脚本,Flash起始地址要改成0x08008000(32KB之后),长度改成剩下的空间。不然应用程序会覆盖Bootloader。我见过有人忘了改这个,结果Bootloader升级一次就变砖了。

3.5 编译器选项对比总结

最后,我整理了一个对比表,方便你参考:

配置项 Keil IAR 我的建议
优化等级 -O1 或 -Os High (Balanced) Bootloader用尺寸优化
微库 Use MicroLIB 无对应选项 Keil必勾,IAR默认就小
链接脚本 .sct文件 .icf文件 语法不同,但逻辑一样
调试器 JLink/ST-Link JLink/I-jet JLink最通用

嗯,环境搭建这部分就这些。说白了,工具只是工具,关键是你得理解它背后在干什么。下一章咱们开始写Bootloader的启动代码,那才是真正有意思的地方。

课后作业: 打开你的Keil或IAR,创建一个空工程,配置好芯片型号和Linker脚本。然后写一个空的main函数,编译通过。看看生成的.map文件,确认代码段和数据段的地址对不对。