裸机 Makefile:交叉编译、链接脚本与烧录目标
一句话回答
裸机 Makefile 的核心是:用变量集中定义交叉工具链前缀、CPU 选项和链接脚本,用模式规则把每个 .c 编成 .o,再统一链接成 ELF、用 objcopy 转出 bin/hex,并额外写 clean 和 flash 目标来清理和烧录。 变量化之后换芯片或换工具链只改几行。
一、一个可工作的模板
makefile
# 工具链与选项
PREFIX = arm-none-eabi-
CC = $(PREFIX)gcc
OBJCOPY = $(PREFIX)objcopy
CPU = -mcpu=cortex-m3 -mthumb
CFLAGS = $(CPU) -Wall -O2 -ffreestanding
LDFLAGS = $(CPU) -T linker.ld -nostartfiles
# 源文件与目标
SRCS = startup.c main.c uart.c
OBJS = $(SRCS:.c=.o)
all: firmware.bin
# .c -> .o($< 第一个依赖,$@ 目标)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# 链接成 ELF($^ 全部依赖)
firmware.elf: $(OBJS) linker.ld
$(CC) $(LDFLAGS) $(OBJS) -o $@
# 转成可烧录 bin
firmware.bin: firmware.elf
$(OBJCOPY) -O binary $< $@
# 烧录(按你的调试器替换命令)
flash: firmware.bin
st-flash write firmware.bin 0x08000000
clean:
rm -f $(OBJS) firmware.elf firmware.bin
.PHONY: all flash clean二、关键概念
- 变量集中管理:
PREFIX、CPU、LDFLAGS提到顶部,换工具链/芯片只改一处。 - 模式规则
%.o: %.c:一条规则匹配所有同名.c → .o。 - 自动变量:
$@指目标、$<指第一个依赖、$^指全部依赖。 $(SRCS:.c=.o):把源文件名批量替换成目标文件名。.PHONY:声明all/flash/clean不是真实文件,避免和同名文件冲突。- 链接规则把 linker.ld 也列为依赖:脚本一变就重新链接。
三、头文件依赖
简单 Makefile 里 .o 只依赖同名 .c,你改了被包含的 .h 不会触发重编,可能用到旧目标文件。两种解决:
- 手工在规则里补上头文件:
main.o: main.h regs.h; - 工程化做法用编译器生成依赖:编译加
-MMD -MP,再-include $(SRCS:.c=.d),让依赖自动跟踪头文件。
常见坑
- 命令行不是 Tab:Makefile 每条配方必须以真实 Tab 开头,空格会报错。
- 没用交叉前缀:误用本机 gcc 产出 x86 代码,见 交叉编译入门。
- 链接脚本没列入依赖:改了
.ld不重链,烧的是旧布局。 - 头文件依赖缺失:改
.h后不重编,跑的还是旧目标文件。 - clean/flash 没设
.PHONY:目录里若有同名文件会导致目标不执行。 - 烧录地址写错:
flash目标的基地址(如0x08000000)要和芯片 Flash 起始地址及链接脚本一致。
总结
裸机 Makefile 用变量集中定义交叉前缀、CPU 选项和链接脚本,模式规则加自动变量把 .c 编成 .o,再链接成 ELF、objcopy 转 bin,并配 flash/clean 目标;配方必须用 Tab,链接脚本和头文件要正确纳入依赖(可用 -MMD -MP 自动跟踪),烧录基地址须与链接脚本一致。