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