3、裸机开发环境搭建:基于ARM Cortex-M的MCU选型、Keil/IAR/GCC工具链配置、工程模板建立

好,咱们进入第三章。这一章说白了就是“把家伙事儿备齐”。你想想看,写UART驱动也好,调DMA也罢,总得有个地方跑代码吧?我见过不少新手,一上来就急着敲代码,结果开发环境没配好,折腾半天连个灯都点不亮。嗯,咱们别走那个弯路。

3.1 MCU选型:为什么我推荐STM32F4系列

市面上ARM Cortex-M的芯片太多了,M0、M3、M4、M7,还有最近火起来的M33。咱们这个课程选什么?我个人习惯用STM32F407VGT6。为什么?三个理由:

  • UART外设丰富:F407有8个串口,足够你折腾各种波特率、数据位、校验位组合。
  • DMA控制器成熟:它有2个DMA控制器,每个控制器8个数据流,每个数据流8个通道。说白了就是DMA资源管够。
  • 调试生态好:J-Link、ST-Link、DAP-Link全支持,你手头有什么用什么。

我在项目中遇到过用M0做UART接收的坑——DMA通道太少,串口一多就冲突。所以这次咱们直接上M4,省心。

对比项 STM32F103 (M3) STM32F407 (M4) STM32G070 (M0+)
UART数量 3 6 + 2 4
DMA数据流 7 (单控制器) 16 (双控制器) 5 (单控制器)
最高主频 72 MHz 168 MHz 64 MHz
调试接口 SWD/JTAG SWD/JTAG SWD

选型建议:如果你手头只有F103,也能跟着学。但DMA那部分有些差异,我会在对应章节标注。F407是“标准答案”,跟着它走最顺畅。

3.2 工具链配置:Keil、IAR、GCC三选一

工具链这东西,说白了就是“编译器+调试器+工程管理”。我三种都用过,各有千秋。咱们一个一个说。

3.2.1 Keil MDK-ARM

Keil是ARM的亲儿子,用的人最多。我个人习惯用Keil做快速原型验证,因为它的工程向导太方便了。

配置步骤:

  1. 安装MDK-ARM 5.38或更高版本
  2. 安装对应芯片的Pack包(STM32F4xx_DFP)
  3. 新建工程,选择STM32F407VG
  4. 在Manage Run-Time Environment里勾选CMSIS-Core和Device-Startup
  5. 配置Debug选项为ST-Link或J-Link

小技巧:Keil的工程文件是.uvprojx,本质是XML。我曾经手改过这个文件来批量修改编译选项,比在IDE里一个个点快多了。

3.2.2 IAR Embedded Workbench

IAR的编译器优化做得特别好,代码密度比Keil低10%-15%。如果你做产品量产,IAR是首选。

配置步骤:

  1. 安装IAR EWARM 8.50或更高
  2. 创建新工程,选择芯片型号
  3. 添加CMSIS核心文件和启动文件
  4. 配置Linker文件(.icf)——这个和Keil的.sct不一样,别搞混了
  5. 设置调试器为J-Link或I-jet

注意:IAR的工程文件是.ewp和.eww。如果你从Keil迁移过来,启动文件要换成IAR版本的。我记得有一次直接拿Keil的启动文件放到IAR里编译,报了一堆汇编语法错误——因为IAR的汇编器是自研的,不是ARMCC。

3.2.3 GCC + Makefile

GCC是开源方案,免费、跨平台、可定制。我个人的主力开发环境就是VS Code + GCC + Makefile。为什么?因为自动化方便,CI/CD直接集成。

配置步骤:

  1. 安装ARM GCC工具链(gcc-arm-none-eabi)
  2. 安装OpenOCD(开源调试器)
  3. 编写Makefile,指定芯片型号、编译选项、链接脚本
  4. 编写链接脚本(.ld文件)
  5. 用VS Code + Cortex-Debug插件调试
# Makefile 核心片段
TARGET = uart_demo
MCU = cortex-m4
CC = arm-none-eabi-gcc
LD = arm-none-eabi-ld

CFLAGS = -mcpu=$(MCU) -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16
CFLAGS += -O2 -g -Wall -ffunction-sections -fdata-sections

LDFLAGS = -T stm32f407vg.ld --specs=nosys.specs

all: $(TARGET).elf $(TARGET).bin

$(TARGET).elf: main.o uart.o startup_stm32f407xx.o
	$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)

$(TARGET).bin: $(TARGET).elf
	arm-none-eabi-objcopy -O binary $< $@

clean:
	rm -f *.o *.elf *.bin

我的建议:初学者先用Keil,上手快。等你把UART和DMA的逻辑搞明白了,再转GCC做自动化。别一开始就折腾Makefile,容易劝退。

3.3 工程模板建立:一套通用的“骨架”

不管用哪个工具链,工程模板的结构都差不多。我总结了一套“三明治”结构,用了好几年,稳定可靠。

目录结构:

project/
├── src/                # 用户源代码
│   ├── main.c
│   ├── uart.c
│   ├── uart.h
│   ├── dma.c
│   └── dma.h
├── startup/            # 启动文件和链接脚本
│   ├── startup_stm32f407xx.s
│   └── stm32f407vg.ld
├── cmsis/              # CMSIS核心文件
│   ├── core_cm4.h
│   ├── core_cmFunc.h
│   └── system_stm32f4xx.c
├── hal/                # 外设驱动(可选)
│   └── stm32f4xx_hal.h
├── build/              # 编译输出
└── Makefile            # 或 .uvprojx / .ewp

为什么要这么分?说白了就是“关注点分离”。启动文件你基本不动,CMSIS是ARM官方的,用户代码全在src里。我见过有人把所有文件堆在一个目录下,找文件找到崩溃——别学那个。

3.4 核心流程:从复位到main()

这个流程我画了一张图,你一看就明白。

ARM Cortex-M 复位启动流程 1. 复位向量 2. 取栈顶指针 SP 3. 跳转 Reset_Handler 4. SystemInit() 5. main() 说明:启动文件中的 Reset_Handler 会先调用 SystemInit() 配置时钟, 然后跳转到 main()。UART和DMA的初始化代码写在 main() 开头。

这个流程你心里要有数。我见过有人把UART初始化写在SystemInit里——结果时钟还没配好,串口就开了,数据全是乱的。

3.5 验证环境:点个灯,发个串口

环境搭好之后,第一件事不是写UART驱动,而是验证工具链能不能正常工作。我习惯写一个“最小验证程序”:

// main.c - 环境验证程序
#include "stm32f4xx.h"

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

int main(void) {
    // 使能GPIOA时钟
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
    // 配置PA5为推挽输出(板载LED)
    GPIOA->MODER |= (1 << 10);  // 输出模式
    GPIOA->OTYPER &= ~(1 << 5); // 推挽
    GPIOA->OSPEEDR |= (3 << 10); // 高速

    while(1) {
        GPIOA->BSRR = (1 << 5);   // 亮
        delay(1000000);
        GPIOA->BSRR = (1 << 21);  // 灭
        delay(1000000);
    }
}

验证清单:

  • LED闪烁 → 证明GPIO和时钟配置正确
  • 用调试器单步执行 → 证明调试链路正常
  • 查看反汇编 → 确认编译优化没把delay优化掉

我曾经踩过的坑:用Keil默认配置编译,结果LED死活不亮。查了半天发现是HSE晶振配置不对,SystemInit里默认用的HSI,频率只有16MHz。delay参数没调,闪得太快肉眼看不出来。所以验证环境时,delay值给大一点,别抠抠搜搜的。

3.6 本章小结

环境搭建这事儿,说难不难,说简单也不简单。核心就三点:

  • 芯片选型:STM32F407是“万金油”,资源够、资料多、坑少
  • 工具链:Keil入门快,IAR优化好,GCC自动化强——选一个你顺手的
  • 工程模板:目录结构清晰,启动流程心里有数,验证程序跑通