在嵌入式开发过程中,从源代码编写到最终程序烧录涉及多个关键步骤,这些步骤在手动方式和自动化方式下有所不同。本文详细介绍了8个嵌入式项目处理流程:源代码编写、预处理、编译、汇编、链接、生成二进制和 HEX 文件、烧录以及清理。每个流程都有其独立的操作命令,并对不同的开发工具链进行详细讲解。
├── src/ # 源代码文件夹
│ ├── main.c # 主程序
│ ├── drivers.c # 驱动程序
│ ├── startup.s # 启动文件
│ ├── utils.c # 工具函数
│ └── common_defs.h # 公共定义头文件
├── build/ # 编译输出文件夹
│ ├── main.i # 预处理后的主程序
│ ├── drivers.i # 预处理后的驱动程序
│ ├── startup.i # 预处理后的启动文件
│ ├── main.s # 汇编代码 - 主程序
│ ├── drivers.s # 汇编代码 - 驱动程序
│ ├── startup.s # 汇编代码 - 启动文件
│ ├── main.o # 编译后的目标文件 - 主程序
│ ├── drivers.o # 编译后的目标文件 - 驱动程序
│ ├── startup.o # 编译后的目标文件 - 启动文件
│ ├── utils.o # 编译后的目标文件 - 工具函数
│ ├── main.elf # 可执行文件
│ ├── main.bin # 烧录用二进制文件
│ └── main.hex # 烧录用 HEX 文件
├── linker_script.ld # 链接脚本
└── Makefile # Makefile 构建系统(可选)main.c:主程序代码,包含应用的主逻辑。drivers.c:硬件相关驱动文件,例如 LED、按键等设备控制。startup.s:启动代码,负责设置堆栈指针、跳转到主程序入口。utils.c:辅助工具函数,如延时、其他常用功能。common_defs.h:公共定义的头文件,包含宏定义、常量、外部函数声明等。.i 文件:预处理文件,包含头文件展开、宏替换等处理后的代码。.s 文件:汇编代码文件。.o 文件:目标文件,是编译过程中生成的机器码,尚未链接。.elf 文件:最终的可执行文件,包含所有链接后的代码,可以用于调试。.bin 文件:将 .elf 文件转换后的二进制文件,用于烧录到硬件中。.hex 文件:可选的 HEX 格式文件,同样用于烧录。在此阶段,你编写并组织源代码文件和头文件。代码包括 .c 文件和 .h 文件,涉及的文件为 main.c, drivers.c, startup.s, 和 common_defs.h。
预处理操作会展开宏定义和包含的头文件,生成 .i 文件。可以通过以下命令生成:
arm-none-eabi-gcc -E -o build/main.i src/main.c
arm-none-eabi-gcc -E -o build/drivers.i src/drivers.c
arm-none-eabi-gcc -E -o build/startup.i src/startup.s生成的文件:
build/main.i:预处理后的主程序代码。build/drivers.i:预处理后的驱动程序代码。build/startup.i:预处理后的启动文件代码。编译操作会将 .c 或 .s 文件转化为汇编语言文件 .s。生成文件:
arm-none-eabi-gcc -S -o build/main.s src/main.c
arm-none-eabi-gcc -S -o build/drivers.s src/drivers.c
arm-none-eabi-gcc -S -o build/startup.s src/startup.s汇编文件会生成目标文件 .o,目标文件是编译过程中的机器代码,准备链接成最终的可执行文件。
arm-none-eabi-gcc -c -o build/main.o src/main.c
arm-none-eabi-gcc -c -o build/drivers.o src/drivers.c
arm-none-eabi-gcc -c -o build/startup.o src/startup.s
arm-none-eabi-gcc -c -o build/utils.o src/utils.c生成的文件:
build/main.obuild/drivers.obuild/startup.obuild/utils.o链接阶段将所有目标文件 .o 链接成一个可执行文件 .elf。你需要提供一个链接脚本 linker_script.ld,它会指定程序在内存中的位置。
arm-none-eabi-ld -T linker_script.ld -o build/main.elf build/main.o build/drivers.o build/startup.o build/utils.o生成的文件:
build/main.elf:最终的可执行文件。将可执行文件 .elf 转换为 .bin 和 .hex 格式,用于烧录到设备。
arm-none-eabi-objcopy -O binary build/main.elf build/main.bin
arm-none-eabi-objcopy -O ihex build/main.elf build/main.hex生成的文件:
build/main.binbuild/main.hex将 .bin 文件烧录到目标硬件中。可以使用烧录工具,如 st-flash。
st-flash write build/main.bin 0x8000000在调试过程中,可以使用 GDB 等工具进行调试,加载 .elf 文件进行符号调试。
arm-none-eabi-gdb build/main.elf -ex "target remote :3333"步骤 | 说明 | 生成文件类型 | 是否继续使用 |
|---|---|---|---|
1. 代码编写 | 编写源代码与配置文件 | .c, .h, .s, .ld | ✅ |
2. 预处理 | 宏展开、头文件替换 | .i | ❌ |
3. 编译 | C 代码 → 汇编代码 | .s | ❌ |
4. 汇编 | 汇编代码 → 机器码 | .o | ✅ |
5. 链接 | 合并所有目标文件 | .elf | ✅ |
6. 格式转换 | 可执行文件 → 烧录文件 | .bin, .hex | ✅ |
7. 烧录 | 将程序写入 MCU | - | - |
8. 调试 | 远程在线调试 | .elf | ✅ |
以上就是嵌入式项目的完整手动编译流程,包含了详细的文件结构、文件生成步骤和命令。
接下来是如何通过 Makefile 来自动化构建和管理这个项目的过程。
# 设置编译器、汇编器、链接器等工具
CC = arm-none-eabi-gcc
AS = arm-none-eabi-as
LD = arm-none-eabi-ld
OBJCOPY = arm-none-eabi-objcopy
OBJCPYFLAGS = -O binary
OBJCOPYHEXFLAGS = -O ihex
# 编译选项
CFLAGS = -g -Wall -mcpu=cortex-m3 -mthumb
AFLAGS = -g
LDFLAGS = -T linker_script.ld -nostartfiles -ffreestanding
# 源文件和目标文件
SRC = src/main.c src/drivers.c src/startup.s src/utils.c
OBJ = build/main.o build/drivers.o build/startup.o build/utils.o
DEPS = $(OBJ:.o=.d)
# 编译输出
OUTPUT = build/main.elf
BIN = build/main.bin
HEX = build/main.hex
# 默认目标
all: $(BIN)
# 编译源文件为目标文件
build/main.o: src/main.c
$(CC) $(CFLAGS) -MMD -MF build/main.d -c $< -o $@
build/drivers.o: src/drivers.c
$(CC) $(CFLAGS) -MMD -MF build/drivers.d -c $< -o $@
build/startup.o: src/startup.s
$(AS) $(AFLAGS) -c $< -o $@
build/utils.o: src/utils.c
$(CC) $(CFLAGS) -MMD -MF build/utils.d -c $< -o $@
# 生成可执行文件
$(OUTPUT): $(OBJ)
$(LD) $(LDFLAGS) -o $@ $^
# 生成二进制文件
$(BIN): $(OUTPUT)
$(OBJCOPY) $(OBJCPYFLAGS) $< $@
# 生成 HEX 文件
$(HEX): $(OUTPUT)
$(OBJCOPY) $(OBJCOPYHEXFLAGS) $< $@
# 清理文件
clean:
rm -f build/*.o build/*.d $(OUTPUT) $(BIN) $(HEX)
# 生成依赖文件
-include $(DEPS)CC = arm-none-eabi-gcc
AS = arm-none-eabi-as
LD = arm-none-eabi-ld
OBJCOPY = arm-none-eabi-objcopy
OBJCPYFLAGS = -O binary
OBJCOPYHEXFLAGS = -O ihexarm-none-eabi-gcc,适用于 ARM 架构的交叉编译器。arm-none-eabi-as。arm-none-eabi-ld。CFLAGS = -g -Wall -mcpu=cortex-m3 -mthumb
AFLAGS = -g
LDFLAGS = -T linker_script.ld -nostartfiles -ffreestanding-g:生成调试信息。-Wall:启用所有警告。-mcpu=cortex-m3:指定目标 CPU 为 Cortex-M3。-mthumb:启用 Thumb 模式,适合嵌入式编程。-T linker_script.ld:指定链接脚本。-nostartfiles:告诉链接器不要自动链接启动文件。-ffreestanding:用于裸机编程,禁止标准库的调用。SRC = src/main.c src/drivers.c src/startup.s src/utils.c
OBJ = build/main.o build/drivers.o build/startup.o build/utils.o
DEPS = $(OBJ:.o=.d).o 文件路径。$(OUTPUT): $(OBJ)
$(LD) $(LDFLAGS) -o $@ $^$(OUTPUT):生成的最终可执行文件。
$(OBJ):依赖的目标文件列表。
$(BIN): $(OUTPUT)
$(OBJCOPY) $(OBJCPYFLAGS) $< $@$(BIN):二进制文件。$(OBJCPYFLAGS):转换为二进制文件的标志。$<:第一个依赖文件,即可执行文件。$@:目标文件,即二进制文件。$(HEX): $(OUTPUT)
$(OBJCOPY) $(OBJCOPYHEXFLAGS) $< $@$(HEX):HEX 文件。$(OBJCOPYHEXFLAGS):转换为 HEX 文件的标志。clean:
rm -f build/*.o build/*.d $(OUTPUT) $(BIN) $(HEX)-include $(DEPS)-include 使得 make 在找不到依赖文件时不会报错,而是跳过。了解了 Makefile 的内容后,我们可以专注于 Makefile 中的 命令行 部分。接下来,我会逐步解释命令行中的各个命令,以及它们如何在嵌入式开发的构建过程中被使用。
make 命令make 是一个自动化工具,它根据 Makefile 中的定义来编译和链接源代码。最常用的命令行是 make 或 make <target>。
make)makemake 时,make 会查找 Makefile 并默认执行 Makefile 中的 第一个目标,一般是 all。Makefile 中没有定义 all,make 会选择第一个目标作为默认目标。make <target>)make <target>make 会执行该目标的规则。make clean 会执行 clean 目标的规则,清理所有编译生成的文件。在 Makefile 中定义的每个目标(如 all, build/main.o, clean)都可以通过命令行传递给 make 来执行。make 根据这些目标执行相关的规则。
all:all: $(BIN)make 时,它默认执行 all 目标的规则。all 目标依赖于 $(BIN),即 build/main.bin。clean:clean:
rm -f build/*.o build/*.d $(OUTPUT) $(BIN) $(HEX)make clean 会执行 clean 目标的规则,删除构建过程中生成的所有文件,包括 .o 文件、.d 文件、最终的可执行文件和二进制文件。make 还支持一些常用的命令行选项,用来控制编译和构建过程。以下是一些常用的选项:
-f 选项:指定 Makefile 文件make -f Makefile.custom-f 选项允许你指定一个自定义的 Makefile 文件。例如,make -f Makefile.custom 会使用 Makefile.custom 文件来构建项目,而不是默认的 Makefile。-j 选项:并行构建make -j4-j 选项允许你指定并行构建的任务数。例如,make -j4 会启动 4 个并行进程来加速构建过程。-n 选项:仅显示命令而不执行make -n-n 选项会告诉 make 显示每个目标的执行命令,但不执行实际操作。这对于调试 Makefile 非常有用,可以查看 make 将会执行哪些命令。-B 选项:强制重新构建make -B-B 选项强制 make 即使没有检测到依赖文件的变化,也重新构建所有目标。这个选项通常用于清理旧的文件并强制重新构建。-k 选项:忽略错误并继续构建make -k-k 选项在构建过程中遇到错误时,继续执行剩余的目标。这对于构建多个目标时检查不同的错误非常有帮助。Makefile 中的命令行根据 Makefile 的结构,你可以执行不同的目标。以下是如何使用命令行与 Makefile 配合工作的详细示例:
假设 Makefile 中有一个默认目标 all,当你在命令行中运行 make 时,它将自动执行该目标。
makemake 命令时,make 会编译所有源文件,并生成 main.bin 文件。如果你只想编译某个单独的源文件,而不是整个项目,可以执行相应的目标,如 build/main.o。
make build/main.omake 编译 main.c 文件,并生成 main.o 文件。运行 clean 目标来删除所有中间文件和最终生成的文件:
make cleanbuild/ 目录下的 .o 文件、.d 文件、main.elf、main.bin 和 main.hex 文件。如果你的项目包含多个目标文件,并且你有一个多核处理器,使用 -j 选项可以加速构建过程:
make -j4在调试 Makefile 时,你可能只想查看 make 会执行哪些命令,而不实际执行它们:
make -nmake 是一个非常强大的工具,可以根据 Makefile 中的规则自动化构建过程。clean)来实现不同的功能。-j 来并行构建,-n 来仅显示命令)可以让构建过程更加灵活和高效。如果你在使用 make 命令时遇到问题,查看 Makefile 里的规则和目标,确保你正在执行正确的目标,并理解每个目标的操作。
在嵌入式开发中,项目从源代码编写到最终烧录的过程涉及多个步骤,可以分为手动和自动两种方式。以下是8个主要的处理流程,概述了每个流程如何操作及其在整个开发过程中的角色。
main.c、drivers.c 等),并定义系统中各个模块的功能。.i 文件(如 main.i),这一步会展开宏定义、包含头文件等。Makefile 中配置相应的编译规则,运行 make 命令时自动进行预处理。gcc)将源代码(.c 文件)编译成目标文件(.o 文件),例如 main.o。startup.s),开发人员手动运行汇编器(如 as)将汇编文件编译成目标文件(.o 文件)。Makefile 的自动规则,使用 $(AS) 命令自动汇编汇编文件,生成 .o 文件。ld)将目标文件链接成可执行文件(.elf)。Makefile 中定义了链接规则,运行 make 时,自动调用链接器将所有目标文件链接成最终的可执行文件。objcopy)将 .elf 文件转换为二进制文件(.bin)或 HEX 文件(.hex),供烧录到设备。Makefile 的规则,运行 $(OBJCOPY) 自动生成二进制文件和 HEX 文件,准备烧录。.bin 或 .hex 文件烧录到目标嵌入式设备。Makefile 中添加自动烧录命令,通过连接设备并运行 make 完成烧录。.o、.d、.elf 文件等),以保持工作目录整洁。make clean 命令,Makefile 自动清理所有临时文件和构建输出,减少手动干预。嵌入式项目的处理流程可以通过手动或者自动化方式完成。使用 Makefile 可以实现大部分步骤的自动化,包括源代码编译、目标文件生成、链接、二进制和 HEX 文件生成等。通过自动化流程,能够显著提高开发效率并减少人为错误,尤其适用于较为复杂和重复的项目构建过程。