在嵌入式开发过程中,从源代码编写到最终程序烧录涉及多个关键步骤,这些步骤在手动方式和自动化方式下有所不同。本文详细介绍了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.o
build/drivers.o
build/startup.o
build/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.bin
build/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 ihex
arm-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
)make
make
时,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
时,它将自动执行该目标。
make
make
命令时,make
会编译所有源文件,并生成 main.bin
文件。如果你只想编译某个单独的源文件,而不是整个项目,可以执行相应的目标,如 build/main.o
。
make build/main.o
make
编译 main.c
文件,并生成 main.o
文件。运行 clean
目标来删除所有中间文件和最终生成的文件:
make clean
build/
目录下的 .o
文件、.d
文件、main.elf
、main.bin
和 main.hex
文件。如果你的项目包含多个目标文件,并且你有一个多核处理器,使用 -j
选项可以加速构建过程:
make -j4
在调试 Makefile
时,你可能只想查看 make
会执行哪些命令,而不实际执行它们:
make -n
make
是一个非常强大的工具,可以根据 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 文件生成等。通过自动化流程,能够显著提高开发效率并减少人为错误,尤其适用于较为复杂和重复的项目构建过程。