如何使用 Makefile 构建固件?

发布: (2025年12月21日 GMT+8 20:46)
5 min read
原文: Dev.to

Source: Dev.to

请提供您希望翻译的完整文本(除代码块和 URL 之外),我将按照要求将其翻译成简体中文并保留原有的格式。

项目布局

$ tree
.
├── bin/
├── drivers/
   ├── CMSIS/
   ├── STM32F446RETX_FLASH.ld
   └── STM32F4xx_HAL_Driver/
├── lib/
├── Makefile
└── src/
    ├── it.c
    ├── main.c
    ├── main.h
    └── peripheral.c

必需的库和工具

  • HAL库 – 外设驱动。
  • CMSIS库 – Cortex‑M 核心支持。
  • 链接脚本 – MCU 的内存布局。
  • 启动 / 系统代码 – 汇编复位处理程序和系统初始化。
  • arm‑none‑eabi‑* 工具链(gcc、objcopy、size,…)。

所有必需的库和脚本都放在 drivers 目录下。您可以使用以下命令获取它们:

git clone --recurse-submodules https://github.com/STMicroelectronics/STM32CubeF4.git
sudo apt install gcc-arm-none-eabi

HAL库用于编程外设,而 CMSIS 提供核心定义。

Makefile 逐步讲解

以下是完整的 Makefile,逐段进行说明。

1. 工具链定义

CC      := arm-none-eabi-gcc
OBJCOPY := arm-none-eabi-objcopy
SIZE    := arm-none-eabi-size
RM      := rm -f
FILE    := file

2. MCU 特定定义

DEVICE_FAMILY  := STM32F4xx
DEVICE_MODEL   := STM32F446xx
DEVICE_VARIANT := STM32F446RETx

3. 编译器和链接器标志

CORTEX_FLAGS := -mthumb -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard
COMMON_FLAGS := -g3 -Os -ffunction-sections -fdata-sections -Wall
AS_FLAGS     := -x assembler-with-cpp

说明

  • -mthumb      使用 Thumb 指令集。
  • -mcpu=cortex-m4  目标为 Cortex‑M4 核心。
  • -mfpu=fpv4-sp-d16-mfloat-abi=hard 启用硬件浮点。

4. 包含路径

MAIN_INC      := ./src
CMSIS_INC     := ./drivers/CMSIS/Include
CMSIS_DEV_INC := ./drivers/CMSIS/Device/ST/STM32F4xx/Include
HAL_INC       := ./drivers/STM32F4xx_HAL_Driver/Inc
CMSIS_DSP_INC := ./drivers/CMSIS/DSP/Include

这些目录包含源代码引用的头文件。

5. 源文件和特殊文件

MAIN_SRC      := $(wildcard ./src/*.c)
HAL_SRC       := $(wildcard ./drivers/STM32F4xx_HAL_Driver/Src/*.c)
SYSTEM_SRC    := ./drivers/CMSIS/Device/ST/STM32F4xx/Source/Templates/system_stm32f4xx.c
STARTUP_CODE  := ./drivers/CMSIS/Device/ST/STM32F4xx/Source/Templates/gcc/startup_stm32f446xx.S
LINKER_SCRIPT := ./drivers/STM32F446RETX_FLASH.ld

6. 简写和构建定义

DEFINES := -D$(DEVICE_FAMILY) -D$(DEVICE_MODEL) -D$(DEVICE_VARIANT) \
           -DUSE_HAL_DRIVER

SOURCES  := $(MAIN_SRC) $(HAL_SRC) $(SYSTEM_SRC)
OBJECTS  := $(notdir $(patsubst %.c,%.o,$(SOURCES))) startup_stm32f446xx.o
INCLUDES := -I$(MAIN_INC) -I$(CMSIS_DEV_INC) -I$(CMSIS_INC) -I$(HAL_INC) \
            -I$(CMSIS_DSP_INC)

CFLAGS  := $(CORTEX_FLAGS) $(COMMON_FLAGS) $(DEFINES) $(INCLUDES)
AFLAGS  := $(CORTEX_FLAGS) $(AS_FLAGS) $(DEFINES) $(INCLUDES)
LDFLAGS := $(CORTEX_FLAGS) -T $(LINKER_SCRIPT) \
           -Wl,--gc-sections,--relax --specs=nano.specs --specs=nosys.specs \
           -Wl,--start-group -lc -lm -lnosys -Wl,--end-group

关键点

  • SOURCES 收集所有 .c 文件;OBJECTS 将它们转换为对应的 .o 名称。
  • CFLAGSAFLAGSLDFLAGS 分别用于编译、汇编和链接。
  • LDFLAGS 链接 newlib‑nano C 库 (-lc)、数学库 (-lm) 和 nosys 存根库 (-lnosys),因为固件运行在裸金属环境(无操作系统)。

7. 输出文件

FIRMWARE_ELF := firmware.elf
FIRMWARE_BIN := firmware.bin
  • ELF – 包含机器码、调试信息、符号表等;用于烧录和调试。
  • BIN – 原始二进制镜像,适合直接写入闪存。

8. 构建规则

.PHONY: all clean flash

all: $(FIRMWARE_ELF) $(FIRMWARE_BIN)

$(FIRMWARE_ELF): $(OBJECTS)
	$(CC) $(LDFLAGS) -o $@ $^

$(FIRMWARE_BIN): $(FIRMWARE_ELF)
	$(OBJCOPY) -O binary $< $@

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

startup_stm32f446xx.o: $(STARTUP_CODE)
	$(CC) $(AFLAGS) -c -o $@ $<

clean:
	$(RM) *.o $(FIRMWARE_ELF) $(FIRMWARE_BIN)

# 示例 flash 目标(根据需要调整编程器/命令)
flash: $(FIRMWARE_BIN)
	st-flash write $(FIRMWARE_BIN) 0x08000000

工作原理

  1. Compilation – 每个 .c 文件使用 $(CFLAGS) 编译为目标文件。
  2. Assembly – 启动汇编文件使用 $(AFLAGS) 进行汇编。
  3. Linking – 所有目标文件使用 $(LDFLAGS) 并结合提供的链接脚本进行链接,生成 firmware.elf
  4. Binary conversionobjcopy 将 ELF 文件转换为原始二进制文件(firmware.bin)。
  5. Flashing – 可选的 flash 目标演示如何对 MCU 编程(此处使用 st-flash;如有需要请替换为你的编程器)。

结论

  • 编译、链接并生成 ELF 和原始二进制映像。
  • 保持构建过程完全透明——没有隐藏的 IDE 魔法。
  • 轻松调整文件列表、编译器标志或目标 MCU。

理解每一步让你对构建过程拥有完全控制,这在调试低层问题或在没有完整 IDE 的环境中工作时极其宝贵。祝你玩得开心!

机器码
如果你查看两者的大小,你会发现 ELF 可执行文件要大得多!

构建配方

.PHONY: all

all:
	@echo "-------------------------------------"
	@echo "----- Building the source files -----"
	@echo "-------------------------------------"
	@$(CC) $(AFLAGS) -c $(STARTUP_CODE)
	@$(CC) $(CFLAGS) -c $(SOURCES)

	@echo "\n------------------------------------"
	@echo "----- Linking the object files -----"
	@echo "------------------------------------"
	@$(CC) $(LDFLAGS) $(OBJECTS) -o $(FIRMWARE_ELF)
	@$(OBJCOPY) -O binary $(FIRMWARE_ELF) $(FIRMWARE_BIN)
	@$(RM) $(OBJECTS)

	@echo "\n-------------------------------------"
	@echo "----- The firmware memory usage -----"
	@echo "-------------------------------------"
	@$(SIZE) $(FIRMWARE_ELF)

	@echo "\n-------------------------------------"
	@echo "----- The firmware binary format ----"
	@echo "-------------------------------------"
	@$(FILE) $(FIRMWARE_ELF)

构建固件

$ make

就这样 🥳🥳🥳。您已经构建了 firmware.elffirmware.bin

烧录固件

$ openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \
    -c "program firmware.elf verify reset exit"

现在您可以将已构建的固件烧录到微控制器中(使用 ST‑Link 连接或类似工具)。

Back to Blog

相关文章

阅读更多 »

解码 ARM Cortex-Mx 异常入口与退出

引言 – 为什么要写这篇文章 在 ARM Cortex‑M 系列上进行中断处理在理论上看起来很简单,但一打开调试器就会变得令人困惑。 - PC v...