2. 开发环境搭建:MDK/IAR安装、工程模板建立、第一个点灯程序

说实话,我见过不少初学者,拿到开发板第一件事就是往 Keil 里点「Build」——结果报错一堆,连芯片型号都没选对。环境搭建这事儿,看着简单,但坑不少。今天咱们就把 MDK 和 IAR 这两个主流工具链捋一遍,再手把手建个工程模板,最后让 LED 亮起来。

2.1 工具链选择:MDK vs IAR

先说说这两个工具。MDK(Keil)和 IAR 是 ARM Cortex-M 开发的两大主流 IDE。我个人习惯用 MDK,因为它的工程管理更直观,调试器集成度高。但 IAR 的编译优化确实更狠,代码密度能压得更低。

对比项 MDK (Keil) IAR Embedded Workbench
编译器 ARMCC / ARMCLANG IAR C/C++ Compiler
调试器支持 J-Link, ST-Link, ULINK J-Link, ST-Link, I-Jet
工程模板 自带 Pack Installer 需手动配置
代码优化 中等 较高(尤其 -Oz)
学习曲线 较平缓 稍陡

我的建议是:初学者先用 MDK,等熟悉了寄存器操作和调试流程,再尝试 IAR。我在项目中遇到过用 IAR 编译出的固件比 MDK 小 15% 的情况,但那是后期优化阶段的事了。

2.2 MDK 安装与配置

安装 MDK 其实不复杂,但有几个关键点容易忽略。

2.2.1 安装步骤

  1. 下载 MDK-ARM:从 Keil 官网获取最新版本(目前是 5.38a)。
  2. 安装主程序:一路 Next,注意安装路径不要有中文。
  3. 安装器件包:运行 Pack Installer,搜索 STM32F103 并安装对应的 DF Pack。
  4. 注册 License:用正版或评估版(评估版有 32KB 代码限制,点灯够用)。
注意: 安装完成后,务必检查一下 TOOLS.INI 文件中的路径配置。我曾经因为系统用户名是中文,导致编译器找不到头文件,折腾了半小时才发现是路径编码问题。

2.2.2 配置调试器

我用的是 ST-Link,便宜又稳定。在 MDK 里点 Project → Options for Target → Debug,选择 ST-Link Debugger,然后点 Settings 确认 SWD 模式、速度选 4MHz 就行。

2.3 工程模板建立

每次新建工程都从头配置?太累了。我习惯做一个「最小工程模板」,以后直接复制改改就能用。

2.3.1 模板目录结构

STM32F103_Template/
├── Core/
│   ├── startup_stm32f103xb.s    // 启动文件
│   ├── system_stm32f103xe.c     // 系统时钟配置
│   └── stm32f1xx.h              // 外设寄存器定义
├── Drivers/
│   └── CMSIS/
│       ├── core_cm3.h
│       └── cmsis_compiler.h
├── User/
│   ├── main.c
│   └── stm32f1xx_it.c           // 中断服务函数
├── Project/
│   └── template.uvprojx         // MDK 工程文件
└── Output/                      // 编译输出目录

这个结构是我从实际项目中提炼出来的。核心文件就三个:启动文件、系统初始化、主程序。其他外设驱动按需添加。

2.3.2 启动文件选择

启动文件要根据芯片型号选。STM32F103C8T6 是 64KB Flash,用 startup_stm32f103xb.s。F103ZET6 是 512KB Flash,用 startup_stm32f103xe.s。选错了,程序跑不起来。

小技巧: 启动文件里的堆栈大小可以改。默认 Heap 512 字节、Stack 1KB。如果你要用 printf 或 malloc,记得把 Heap 改大点。我一般设 Stack 2KB、Heap 4KB。

2.4 第一个点灯程序

好了,环境搭好了,模板建好了,该让硬件动起来了。点灯是嵌入式界的「Hello World」,但背后涉及的知识点可不少。

2.4.1 硬件原理

STM32F103C8T6 最小系统板上,LED 通常接在 PC13 引脚(高电平熄灭,低电平点亮)。为什么是低电平点亮?因为 PC13 内部上拉到 VCC,输出 0 时电流从 VCC 经 LED 流入引脚。

2.4.2 寄存器操作

点灯需要配置两个寄存器组:RCC(时钟控制)和 GPIO(通用输入输出)。

// 1. 使能 GPIOC 时钟(RCC_APB2ENR 寄存器)
RCC->APB2ENR |= (1 << 4);   // 第4位对应 GPIOC 时钟使能

// 2. 配置 PC13 为推挽输出(GPIOx_CRH 寄存器)
GPIOC->CRH &= ~(0xF << 20); // 先清除 CNF13 和 MODE13 位
GPIOC->CRH |= (0x2 << 20);  // MODE13 = 10(输出模式,最大速度 2MHz)
GPIOC->CRH |= (0x0 << 22);  // CNF13 = 00(通用推挽输出)

// 3. 点亮 LED(GPIOx_BSRR 寄存器)
GPIOC->BSRR = (1 << 29);    // BR13 = 1,复位引脚(输出低电平)

你可能会问:为什么不用库函数?因为寄存器编程能让你真正理解硬件是怎么工作的。我在调试一个 SPI 通信问题时,就是靠直接读寄存器值才找到时钟相位配置错误的。

2.4.3 完整代码

#include "stm32f1xx.h"

void delay(uint32_t count) {
    while (count--);
}

int main(void) {
    // 使能 GPIOC 时钟
    RCC->APB2ENR |= (1 << 4);

    // 配置 PC13 为推挽输出
    GPIOC->CRH &= ~(0xF << 20);
    GPIOC->CRH |= (0x2 << 20);  // 输出模式,2MHz

    while (1) {
        GPIOC->BSRR = (1 << 29);  // 点亮 LED
        delay(500000);
        GPIOC->BSRR = (1 << 13);  // 熄灭 LED(BS13 = 1)
        delay(500000);
    }
}
关键点: BSRR 寄存器是「写 1 有效」的。低 16 位写 1 置位引脚(输出高),高 16 位写 1 复位引脚(输出低)。同时操作不会冲突,因为硬件保证了原子性。

2.5 调试与验证

代码写完了,下载到板子上。如果 LED 没亮,别慌。我遇到过的情况:

  • LED 不亮:检查 RCC 时钟使能位是否正确。用调试器看 RCC->APB2ENR 寄存器的值。
  • LED 常亮不闪:delay 函数优化掉了。把 delay 循环里的变量声明为 volatile。
  • 下载失败:检查 SWD 接线,或者按住复位键再点下载。

嗯,这里要注意:调试器不要带电插拔。我烧过一个 ST-Link,就是因为调试过程中拔了线,导致电平冲突烧了接口芯片。

2.6 知识结构总览

下面这张图把本章的核心逻辑串起来了。从工具链选择到寄存器操作,每一步都是后面章节的基础。

开发环境搭建与点灯程序知识结构 开发环境搭建与点灯程序知识结构 工具链选择 MDK (Keil) - 推荐入门 IAR - 优化更强 安装与配置 工程模板建立 启动文件 系统初始化 主程序框架 输出目录 第一个点灯程序 RCC 时钟使能 GPIO 模式配置 BSRR 寄存器操作

这张图把本章的知识点串成了流水线:选工具 → 装环境 → 建模板 → 写代码 → 调硬件。每一步都踩实了,后面学外设驱动、中断系统、低功耗模式时,你才知道底层是怎么跑的。

好了,LED 已经在你手上闪烁了。虽然只是点亮一个灯,但你已经完成了从零到一的关键一步——你亲手配置了时钟、操作了寄存器、控制了硬件引脚。这比任何库函数调用都更有价值。