1. ARM裸机开发环境搭建

各位同学,咱们正式开始。第一章说白了就是「把家伙事儿备齐」。做嵌入式裸机开发,跟写桌面程序完全是两码事——你没法在开发板上直接敲代码、点运行。你得在 PC 上写好代码,用交叉编译器翻译成 ARM 能懂的机器码,再通过烧录工具塞进板子。

我刚开始学的时候,光装工具链就折腾了两天。后来带项目,发现新人卡在第一周的十有八九都是环境问题。所以这一章咱们稳扎稳打,把地基打牢。

1.1 交叉编译工具链安装

什么是交叉编译?说白了,就是在 x86 的 PC 上编译出 ARM 架构的可执行文件。你的开发板跑不了 Visual Studio,也跑不了 GCC 本体,它只能跑编译好的二进制。

我个人习惯用 arm-none-eabi-gcc 这套工具链。它是专门针对裸机(无操作系统)ARM 开发的,轻量、稳定,社区支持也好。

工具 用途
arm-none-eabi-gcc C 编译器,把 .c 转成 .o
arm-none-eabi-ld 链接器,把多个 .o 合成 .elf
arm-none-eabi-objcopy 格式转换,.elf → .bin / .hex
arm-none-eabi-objdump 反汇编,调试时看机器码
arm-none-eabi-gdb 调试器,配合 JTAG/SWD 单步
安装建议:Linux 用户直接 apt install gcc-arm-none-eabi。Windows 用户去 ARM 官网下载 GNU Arm Embedded Toolchain,解压后把 bin 目录加到 PATH 里。别用虚拟机里的 Ubuntu 交叉编译 Windows 版——我踩过这个坑,路径问题能让你怀疑人生。

1.2 Makefile 编写

裸机项目文件不多,但每次敲 gcc 命令也够烦的。Makefile 就是帮你自动化编译的脚本。你只需要写一次规则,以后 make 一下全搞定。

一个最小可用的 Makefile 长这样:

# 编译器
CC = arm-none-eabi-gcc
LD = arm-none-eabi-ld
OBJCOPY = arm-none-eabi-objcopy

# 编译选项
CFLAGS = -mcpu=cortex-m4 -mthumb -Wall -O0 -g
LDFLAGS = -T link.ld

# 源文件
SRCS = main.c uart.c gpio.c
OBJS = $(SRCS:.c=.o)

# 目标
TARGET = firmware

all: $(TARGET).bin

$(TARGET).elf: $(OBJS)
	$(LD) $(LDFLAGS) -o $@ $^

$(TARGET).bin: $(TARGET).elf
	$(OBJCOPY) -O binary $< $@

%.o: %.c
	$(CC) $(CFLAGS) -c -o $@ $<

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

这里有几个关键点:

  • -mcpu=cortex-m4:指定目标 CPU 内核。你用 M3 就改 cortex-m3,用 A7 就改 cortex-a7。别搞错,否则编译出来的指令集不兼容。
  • -mthumb:ARM 有两种指令集——ARM 模式和 Thumb 模式。Cortex-M 系列只支持 Thumb,所以必须加这个。
  • -T link.ld:指定链接脚本,下一节细讲。
我曾经犯过的错:忘了加 -mthumb,编译出来的代码烧进 STM32 直接跑飞。查了两天才发现是指令集不匹配。嗯,从那以后我写 Makefile 第一行就检查 CPU 和指令集选项。

1.3 链接脚本解析

链接脚本(.ld 文件)告诉链接器:你的代码该放哪,数据该放哪,堆栈从哪开始。裸机没有操作系统帮你管理内存布局,全得自己定。

一个典型的 Cortex-M 链接脚本:

ENTRY(Reset_Handler)

MEMORY
{
    FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 512K
    RAM   (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}

SECTIONS
{
    .text :
    {
        *(.isr_vector)    /* 中断向量表 */
        *(.text)          /* 代码段 */
        *(.rodata)        /* 只读数据 */
        _etext = .;
    } > FLASH

    .data :
    {
        _sdata = .;
        *(.data)
        _edata = .;
    } > RAM AT > FLASH

    .bss :
    {
        _sbss = .;
        *(.bss)
        _ebss = .;
    } > RAM
}

解释一下:

  • ENTRY(Reset_Handler):指定入口函数。上电后 CPU 第一条指令就是 Reset_Handler。
  • MEMORY:定义物理存储区域。FLASH 从 0x08000000 开始,RAM 从 0x20000000 开始——这是 STM32 的典型地址映射。
  • .text 段放 FLASH:代码和只读数据烧在 Flash 里,掉电不丢。
  • .data 段:初始化的全局变量。运行时在 RAM 里,但初始值存在 Flash 里,启动时由启动代码拷贝过去。
  • .bss 段:未初始化的全局变量。启动时清零。
为什么 .data 要 AT > FLASH? 因为 RAM 掉电就没了。你写在代码里的 int a = 100; 这个 100 必须存在 Flash 里,上电后再搬到 RAM。链接脚本里的 AT 就是干这个的。

1.4 硬件开发板介绍

咱们课程用的板子是 STM32F407VET6 最小系统板。选它的原因很简单:

  • Cortex-M4 内核,带 FPU(浮点运算单元)
  • 外设丰富:UART、SPI、I2C、USB、CAN、DMA……够咱们折腾 30 章
  • 价格便宜,某宝几十块包邮
  • 文档齐全,社区活跃,遇到问题好查

核心参数:

参数 值
CPU ARM Cortex-M4 @ 168MHz
Flash 512KB
SRAM 192KB
GPIO 82 个可编程 I/O
调试接口 SWD(2 线)

你还需要一个 ST-Link 调试器(或者 J-Link OB),用来烧录和调试。连接方式很简单:

  • ST-Link 的 SWDIO → 板子的 SWDIO
  • ST-Link 的 SWCLK → 板子的 SWCLK
  • ST-Link 的 GND → 板子的 GND
  • ST-Link 的 3.3V → 板子的 3.3V(可选,如果板子不自带供电)
避坑:有些便宜的 ST-Link 拷贝版接线顺序是反的。拿到手先拿万用表量一下 VCC 和 GND,别直接怼上去。我曾经烧过一个板子,就是因为山寨调试器的 VCC 和 GND 标反了。
ARM裸机开发环境搭建 — 知识体系 开发环境搭建 交叉编译工具链 Makefile 自动化编译 链接脚本 .ld 硬件开发板 STM32F407 arm-none-eabi-gcc objcopy / objdump MEMORY / SECTIONS .text / .data / .bss 核心思路:PC 端编写 → 交叉编译 → 烧录 → 开发板运行 调试手段:串口打印 + LED 指示 + SWD 单步

好了,第一章的内容就是这些。工具链装好、Makefile 写好、链接脚本配好、板子连上电——下一件事就是点亮那颗 LED。咱们第二章见真章。