i.MX6ULL 裸机开发启动流程笔记
一、整体启动流程概述
在 i.MX6ULL 这类 ARM Cortex-A7 处理器的裸机开发中,程序启动分为三个关键阶段:
- 汇编初始化:完成处理器模式切换和栈指针设置。
- 硬件寄存器定义:通过头文件映射硬件寄存器地址。
- C 语言执行:跳转到
main函数,开始业务逻辑开发。
二、汇编启动代码分析(start.s)
.global __start
_start: /* 设置处理器进入 SVC 模式 */ mrs r0, cpsr /* 读取当前状态寄存器 CPSR 到 r0 */ bic r0, r0, #0x1f /* 清除 CPSR 的低 5 位(模式位 M[4:0])*/ orr r0, r0, #0x13 /* 将模式位设置为 0x13,对应 SVC 管理模式 */ msr cpsr, r0 /* 将修改后的值写回 CPSR,切换处理器模式 */
/* 设置 SP 指针 */ ldr sp, =0x80200000 /* 将栈指针指向 DDR 内存的 0x80200000 地址 */
/* 跳转到 C 语言 main 函数 */ b main /* 无条件跳转,进入 C 语言执行阶段 */关键知识点
- SVC 模式:这是 ARM 处理器的特权模式,用于操作系统内核或裸机程序,拥有所有硬件访问权限。
- 栈指针 SP:C 语言函数调用依赖栈来保存局部变量和返回地址。此处栈指向外部 DDR 内存,大小为 2MB,栈向下增长。
b指令:这是无条件跳转指令,直接跳转到main函数,标志着程序从汇编进入 C 语言阶段。
三、硬件寄存器定义(main.h)
这个头文件的核心是将芯片物理寄存器地址映射为 C 语言可访问的变量,以实现硬件控制。
#ifndef __MAIN_H#define __MAIN_H
/* 定义时钟控制寄存器(CCM) */#define CCM_CCGR0 (*((volatile unsigned long*)0X020C4068))#define CCM_CCGR1 (*((volatile unsigned int*)0X020C406C))// ... 其他 CCGR 寄存器
/* 定义 IOMUX 配置寄存器 */#define SW_MUX_GPIO1_IO03 (*((volatile unsigned int*)0X020E0068))#define SW_PAD_GPIO1_IO03 (*((volatile unsigned int*)0X020E02F4))
/* 定义 GPIO1 控制寄存器 */#define GPIO1_DR (*((volatile unsigned int*)0X0209C000))#define GPIO1_GDIR (*((volatile unsigned int*)0X0209C004))// ... 其他 GPIO 寄存器
#endif关键知识点
volatile关键字:防止编译器对寄存器读写进行优化,确保每次访问都是真实的硬件操作。- 寄存器映射原理:通过指针直接访问物理内存地址,实现对硬件寄存器的直接读写。
- 功能分类:
- CCM 寄存器:用于开启/关闭外设时钟,例如给 GPIO1 模块提供时钟。
- IOMUX 寄存器:用于配置引脚的功能和电气属性(如上下拉、速度)。
- GPIO 寄存器:用于控制引脚的输入输出方向和电平状态。
- 可以模仿stm32使用结构体访问寄存器
/* led初始化 */void led_init(void){IOMUXC_SetPinMux(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01, 0); /*复用为GPIO*/IOMUXC_SetPinConfig(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01, 0X10B0);}
四、从汇编到 C 语言的衔接
- 汇编初始化是基础:如果没有正确设置处理器模式和栈指针,C 语言程序会直接崩溃。
- 头文件是硬件接口:
main.h是 C 语言与硬件之间的桥梁,所有硬件操作都依赖这些寄存器定义。 - 启动流程的意义:这个流程是所有 ARM 裸机开发的通用范式,掌握它可以快速适配其他 Cortex-A 系列处理器。
五、常见问题与注意事项
- 栈溢出风险:栈大小设置要合理,避免局部变量过多导致栈溢出。
- 寄存器地址错误:必须严格参考芯片手册,错误的地址会导致硬件无响应或系统崩溃。
- 模式切换错误:如果未进入 SVC 模式,后续硬件操作可能因权限不足而失败。
i.MX6ULL 裸机开发:链接脚本与 Makefile 联动笔记
一、核心作用概述
这两份文件是 i.MX6ULL 裸机程序编译链接的核心配置:
imx6ul.lds:链接脚本,定义程序在内存中的布局,规定代码、数据段的存放位置与地址。Makefile:编译构建脚本,自动化完成编译、链接、二进制转换、反汇编等流程。
二、链接脚本(imx6ul.lds)详解
SECTIONS{ . = 0x87800000; /* 整个程序的起始地址(DDR 范围内) */ .text: { start.o /* 启动代码必须放在最前面,保证执行入口 */ *(.text) /* 所有文件的代码段 */ } .rodata ALIGN(4) : {*(.rodata*)} /* 只读数据段(4字节对齐) */ .data ALIGN(4) : {*(.data)} /* 已初始化数据段(4字节对齐) */ bss_start = .; /* BSS 段起始地址符号 */ .bss ALIGN(4) : {*(.bss) *(COMMON)} /* 未初始化数据段 */ bss_end = .; /* BSS 段结束地址符号 */}关键要点
- 地址定位:
0x87800000是程序加载地址,必须在 DDR 内存范围(0x80000000~0xFFFFFFFF)内,且与栈指针(0x80200000)无地址冲突。 - 段布局顺序:代码段 → 只读数据段 → 初始化数据段 → 未初始化数据段(BSS),符合 ARM 裸机程序的标准内存布局。
- BSS 段管理:
bss_start和bss_end用于在启动代码中对 BSS 段清零,保证未初始化全局变量默认值为 0。 - 入口保证:
start.o优先链接,确保处理器上电后先执行汇编初始化代码。
三、Makefile 详解
objs = start.o main.o
ledc.bin : $(objs) # 使用链接脚本链接,生成 ELF 文件 arm-linux-gnueabihf-ld -Timx6ul.lds $^ -o ledc.elf # 转换为纯二进制文件(用于烧录) arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@ # 生成反汇编文件(用于调试) arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis
# 编译 .c 文件%.o : %.c arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<
# 编译 .S 汇编文件%.o : %.S arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<
clean: rm -rf *.o ledc.bin ledc.elf ledc.dis关键要点
- 链接阶段:
-Timx6ul.lds指定使用自定义链接脚本,替代-Ttext选项,实现更精细的内存布局控制。 - 编译选项:
-Wall:开启所有编译警告,便于排查代码问题。-nostdlib:不链接标准 C 库(裸机环境无操作系统支持)。-O2:开启二级优化,减小代码体积、提升执行效率。-c:只编译不链接,生成.o目标文件。
- 二进制转换:
objcopy将 ELF 格式转为.bin纯二进制文件,用于烧录到开发板。 - 反汇编:
objdump生成.dis反汇编文件,方便调试和查看指令执行流程。 - 清理规则:
clean目标删除所有编译生成的中间文件,便于重新构建。
四、两者联动逻辑
- 编译阶段:Makefile 先将
start.S和main.c编译为start.o和main.o。 - 链接阶段:
ld工具根据imx6ul.lds的布局规则,将.o文件链接为ledc.elf,确保代码和数据段按指定地址存放。 - 输出阶段:
objcopy生成可烧录的ledc.bin,objdump生成调试用的反汇编文件。 - 调试支持:链接脚本定义的
bss_start/bss_end可在启动代码中使用,配合 Makefile 构建出符合 C 语言规范的可执行程序。
五、关键注意事项
- 地址一致性:链接脚本的起始地址
0x87800000必须与 DDR 内存范围、栈指针地址无冲突。 - 入口文件顺序:
start.o必须在链接脚本中优先链接,否则程序无法从正确的初始化入口执行。 - BSS 段清零:需在启动汇编中添加清零 BSS 段的代码,利用
bss_start和bss_end符号完成初始化。 - 工具链版本:
arm-linux-gnueabihf交叉编译工具链需与 i.MX6ULL 架构匹配,避免编译错误。
CCM 寄存器核心解析(i.MX6ULL 裸机开发必备)
CCM 是 Clock Control Module 的缩写,直译是时钟控制模块,它是 i.MX6ULL 芯片的「时钟总控中心」,核心作用是为芯片内部所有外设(GPIO、UART、SPI、I2C 等)和核心模块分配、开启/关闭、配置时钟频率,是裸机开发中操作硬件的第一步必配寄存器(外设时钟未开启时,无论怎么配置寄存器,硬件都无响应)。
一、CCM 寄存器的核心作用
i.MX6ULL 芯片上电后,内部时钟不会默认给所有外设供电,原因有二:
- 节能:未使用的外设关闭时钟,减少芯片功耗;
- 稳定:按需开启时钟,避免无用时钟信号干扰硬件工作。
而 CCM 寄存器就是用来精准控制每个外设的时钟开关和频率,简单说:操作任何外设前,必须先通过 CCM 寄存器给该外设「打开时钟」(比如操作 GPIO1 之前,要先开启 GPIO1 对应的时钟)。
二、i.MX6ULL 中 CCM 的关键寄存器:CCM_CCGRx 系列
你在之前的 main.h 中看到的 CCM_CCGR0、CCM_CCGR1、CCM_CCGR2 等是 CCM 最常用的子寄存器,全称是 Clock Control Gate Register(时钟控制门寄存器),也是裸机开发中唯一高频使用的 CCM 寄存器(其他 CCM 寄存器用于配置系统时钟频率,入门阶段无需修改)。
1. CCGRx 寄存器的工作原理
每个 CCM_CCGRx 都是 32 位的寄存器,每 2 个二进制位对应一个外设的时钟控制位,通过配置这 2 位的取值,决定对应外设的时钟状态,核心配置规则(通用):
| 2 位取值 | 时钟状态 | 说明 |
|---|---|---|
00 | 关闭 | 外设无时钟,完全休眠,无法操作 |
01 | 运行模式开启 | 芯片运行时开启时钟,休眠时关闭 |
10 | 保留 | 芯片厂商预留,开发中禁止使用 |
11 | 始终开启 | 无论芯片运行/休眠,时钟一直开启(裸机开发首选配置,简单直接) |
2. 入门核心操作:一键开启所有外设时钟
裸机开发(比如 LED 点灯、UART 打印)的入门阶段,无需精准配置单个外设的时钟,最简便的方式是直接将所有 CCGRx 寄存器赋值为 0XFFFFFFFF(32 位全 1),实现「所有外设时钟始终开启」,避免逐个配置的麻烦,代码如下(C 语言):
// 引用之前定义的 CCM 寄存器CCM_CCGR0 = 0XFFFFFFFF;CCM_CCGR1 = 0XFFFFFFFF;CCM_CCGR2 = 0XFFFFFFFF;CCM_CCGR3 = 0XFFFFFFFF;CCM_CCGR4 = 0XFFFFFFFF;CCM_CCGR5 = 0XFFFFFFFF;CCM_CCGR6 = 0XFFFFFFFF;这行代码是 i.MX6ULL 裸机程序 main 函数的第一行必写代码,写完再配置 GPIO、UART 等外设。
三、CCM 寄存器在裸机开发中的使用流程(以 GPIO1 点灯为例)
结合之前的知识,完整的硬件操作流程会包含 CCM 配置,步骤如下:
- 汇编初始化:设置 SVC 模式、初始化栈指针、清零 BSS 段,跳转到 C 语言
main函数; - 开启时钟:在
main函数开头,通过 CCM_CCGRx 寄存器开启 GPIO1 时钟(或全外设时钟); - 配置引脚:通过 IOMUX 寄存器将 GPIO1_IO03 配置为 GPIO 功能;
- 配置 GPIO:通过 GPIO1_GDIR 设为输出模式,GPIO1_DR 控制电平高低;
四、关键注意点
- 地址固定:i.MX6ULL 的 CCM 寄存器基地址为
0x020C4000,你之前定义的CCM_CCGR0 = 0X020C4068就是该基地址下的偏移地址,必须严格参考芯片手册,地址错误会导致时钟配置无效; - 先时钟后外设:这是裸机开发的「铁律」,如果跳过 CCM 配置直接操作外设,无论寄存器配置多正确,硬件都不会有任何响应;
- 入门无需深入:CCM 模块还有时钟分频、倍频、源时钟选择等复杂功能(对应其他寄存器),入门阶段(点灯、串口)只需掌握「开启时钟」这一个操作即可,后续开发复杂外设时再深入。
总结
- CCM 是芯片的时钟总控中心,核心负责外设时钟的开启/关闭和频率配置;
- 裸机开发中最常用的是
CCM_CCGRx系列寄存器,入门直接赋值0XFFFFFFFF全开启即可; - 操作任何外设前,必须先通过 CCM 开启对应时钟,这是硬件操作的前提;
- CCM 寄存器地址是固定的,需严格对照 i.MX6ULL 芯片手册。
用于BSP工程管理的makefile文件
这份Makefile是ARM裸机开发标准化编译脚本,适配模块化BSP工程结构,实现多目录汇编/.c文件的自动化编译、链接、镜像生成,同时提供调试、清理功能。笔记将按代码结构+核心语法+关键规则拆解,兼顾记忆和实际使用,所有语法均标注适用场景和作用。
一、文件整体概述
核心功能
- 自动检索分散在不同目录的
.S(汇编)、.c(C语言)源文件; - 统一将编译后的
.o目标文件存至obj目录,保持工程整洁; - 结合链接脚本
imx6u.lds链接生成elf格式文件; - 转换
elf为可直接烧录的bin二进制镜像,生成dis反汇编文件(调试用); - 提供
clean(清理编译产物)、print(打印变量调试)辅助功能。
适用场景
ARM裸机开发(如IMX6U开发板)、交叉编译环境、模块化BSP工程(驱动/主程序/头文件分离)。
核心语法体系
Makefile基础变量、内置函数、自动变量、静态模式规则、伪目标,是嵌入式Makefile的通用核心语法,可直接复用至其他ARM裸机项目。
二、分模块笔记(代码+语法+释义)
模块1:交叉编译工具链定义(基础变量)
# 变量定义(?= 若未定义则赋值,:= 直接赋值)CROSS_COMPILE ?= arm-linux-gnueabihf-TARGET ?= ledcCC := $(CROSS_COMPILE)gccLD := $(CROSS_COMPILE)ldOBJCOPY := $(CROSS_COMPILE)objcopyOBJDUMP := $(CROSS_COMPILE)objdump核心语法
- 变量赋值符(Makefile核心,区别于普通编程)
?=:条件赋值,仅当变量未被定义时才赋值(适合预留可外部修改的变量,如工具链前缀、目标名);:=:直接赋值(立即展开),赋值后变量值固定,后续修改原变量不影响此变量(嵌入式开发首选,避免变量嵌套歧义); 补充:=是延迟赋值(使用时才展开),易出嵌套问题,裸机开发极少用。
- 变量引用:
$(变量名),如$(CC)会展开为arm-linux-gnueabihf-gcc,是Makefile引用变量的唯一标准方式。
代码释义
CROSS_COMPILE:交叉编译工具链前缀,ARM架构专用,可外部修改;TARGET:最终编译目标名,所有生成文件(ledc.elf/ledc.bin)均基于此;CC/LD/OBJCOPY/OBJDUMP:封装交叉编译工具,分别为C编译器、链接器、格式转换工具、反汇编工具。
模块2:工程目录定义(纯变量,为后续检索做准备)
# 头文件目录(续行符\:实现多行变量定义)INCUDIRS := imx6u \ bsp/clk \ bsp/led \ bsp/delay# 源文件目录SRCDIRS := project \ bsp/clk \ bsp/led \ bsp/delay核心语法
- 续行符
\:Makefile中一行写不完时,用\结尾表示续行,注意\后无空格(否则会报错); - 多目录定义:按功能拆分目录(头文件/源文件、芯片级/板级驱动),适配BSP模块化思想。
代码释义
INCUDIRS:所有头文件(.h)所在目录,后续编译器会通过此目录搜索头文件;SRCDIRS:所有源文件(.S/.c)所在目录,后续会自动检索这些目录下的源文件。
模块3:源文件/头文件自动检索(内置函数核心)
# 生成编译器头文件搜索参数:-I 目录1 -I 目录2INCLUDE := $(patsubst %, -I %, $(INCUDIRS))# 检索所有.S(汇编)、.c(C)源文件SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))# 去除文件路径,仅保留文件名(如bsp/clk/clk.c → clk.c)SFILENDIR := $(notdir $(SFILES))CFILENDIR := $(notdir $(CFILES))# 生成obj目录下的目标文件(如clk.S → obj/clk.o)SOBJS := $(patsubst %, obj/%, $(SFILENDIR:.S=.o))COBJS := $(patsubst %, obj/%, $(CFILENDIR:.c=.o))OBJS := $(SOBJS)$(COBJS)# 设置源文件搜索路径,Make自动在这些目录找源文件VPATH := $(SRCDIRS)核心语法(Makefile内置函数,嵌入式开发高频使用)
所有函数格式:$(函数名 参数1, 参数2, ...),参数间用逗号+空格分隔,是Makefile批量处理的核心。
patsubst 匹配模式, 替换模式, 处理对象:模式替换函数,按规则批量替换字符串;- 例:
$(patsubst %, -I %, $(INCUDIRS))→ 给INCUDIRS中每个目录加-I(编译器头文件搜索参数); - 例:
$(SFILENDIR:.S=.o)→ 把所有.S后缀替换为.o(简写形式,等价于patsubst %.S, %.o, $(SFILENDIR))。
- 例:
foreach 循环变量, 遍历集合, 执行操作:循环函数,遍历集合中每个元素并执行操作;- 例:
$(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))→ 遍历SRCDIRS每个目录,检索其中所有.S文件。
- 例:
wildcard 匹配规则:通配符函数,检索指定目录下符合规则的文件,替代系统通配符*(Makefile中直接用*易失效);- 例:
$(wildcard $(dir)/*.c)→ 检索dir目录下所有.c文件。
- 例:
notdir 文件路径:路径处理函数,去除文件的绝对/相对路径,仅保留文件名,方便统一存放目标文件。- VPATH 路径集合:Makefile内置搜索路径变量,指定后Make会自动在这些目录中检索源文件(无需写完整路径),解决源文件分散问题。
代码释义
INCLUDE:最终传给编译器的头文件参数,如-I imx6u -I bsp/clk;SFILES/CFILES:所有汇编/C源文件的完整路径,如bsp/led/led.c;SFILENDIR/CFILENDIR:仅保留源文件文件名,如led.c;SOBJS/COBJS:obj目录下的目标文件,是最终编译的中间产物;OBJS:所有目标文件集合,后续链接时直接使用。
模块4:伪目标声明
# 声明clean为伪目标,避免与同名文件冲突.PHONY:clean核心语法
.PHONY: 目标名:声明伪目标,伪目标不是实际的文件,而是一个「命令标识」;- 作用:避免工程中存在与目标名(如
clean)同名的文件时,Make误判目标已完成,导致命令不执行; - 规则:所有非文件生成类目标(如clean、print)都要声明为伪目标,嵌入式开发必加。
- 作用:避免工程中存在与目标名(如
模块5:核心编译规则(静态模式规则+自动变量)
Makefile的核心是「规则」,规则格式:目标: 依赖 \n 制表符 命令(命令前必须用制表符\t,不能用空格,嵌入式开发高频报错点)。
规则1:最终镜像生成规则(主规则)
# 目标:ledc.bin 依赖:所有.o文件(OBJS)$(TARGET).bin : $(OBJS) $(LD) -Timx6u.lds -o $(TARGET).elf $^ # 链接生成elf文件 $(OBJCOPY) -O binary -S $(TARGET).elf $@ # 转换为bin文件 $(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis # 生成反汇编文件规则2:汇编文件编译规则
# 静态模式规则:为SOBJS中每个obj/%.o匹配对应的%.S源文件$(SOBJS) : obj/%.o : %.S $(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<规则3:C文件编译规则
# 静态模式规则:为COBJS中每个obj/%.o匹配对应的%.c源文件$(COBJS) : obj/%.o : %.c $(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<核心语法1:静态模式规则(嵌入式裸机开发最核心规则)
格式:目标集合 : 目标模式 : 依赖模式,专门用于批量处理同类型文件(如所有汇编/所有C文件),替代逐行写规则的冗余操作。
- 目标集合:要处理的所有目标文件(如
$(SOBJS)所有汇编目标文件); - 目标模式:匹配目标的通用格式,用
%作为通配符(代表任意长度字符串,区别于*,仅在模式中生效); - 依赖模式:根据
%自动推导目标对应的依赖文件,实现「目标-依赖」的批量匹配。 - 例:
$(SOBJS) : obj/%.o : %.S→ 遍历SOBJS中的obj/start.o,匹配obj/%.o得到%=start,自动推导依赖为start.S。
核心语法2:自动变量(规则中专用,批量编译的关键)
Makefile自动将「目标/依赖」赋值给内置变量,无需手动写文件名,仅在规则的命令行中生效,嵌入式开发高频使用3个:
| 自动变量 | 含义 | 适用场景 |
|---|---|---|
$@ | 代表当前规则的目标文件(完整名) | 所有规则的输出文件指定 |
$< | 代表当前规则的第一个依赖文件 | 单依赖规则(如编译.o文件) |
$^ | 代表当前规则的所有依赖文件(去重) | 多依赖规则(如链接所有.o文件) |
编译器参数释义(嵌入式裸机专用)
-Wall:开启所有编译警告,便于排查代码问题;-nostdlib:不使用标准C库(裸机开发无操作系统,无标准库,必加);-c:只编译不链接,生成.o目标文件(编译阶段核心参数);-O2:二级代码优化,平衡编译效率和运行效率;-T:指定链接脚本(如imx6u.lds),裸机开发必须通过链接脚本指定程序运行地址;-O binary:将elf文件转换为二进制格式;-S:去掉符号信息,减小bin文件体积;-D -m arm:反汇编时指定架构为ARM,生成可读的反汇编代码。
模块6:辅助规则(清理+调试)
# 清理规则:删除所有编译产物clean: rm -rf $(TARGET).elf $(TARGET).bin $(TARGET).dis $(OBJS)# 调试规则:打印变量值,排查Makefile语法问题print: @echo INCLUDE = $(INCLUDE) @echo SFILES = $(SFILES) @echo CFILES = $(CFILES) # 其余变量打印略核心语法
rm -rf:Linux命令,-r递归删除,-f强制删除,用于清理所有编译生成的文件(elf/bin/dis/obj/*.o);@echo:@表示不打印命令本身,仅打印输出结果,若无@,Make会先打印echo 变量=值,再打印变量内容,影响调试可读性;
用法
- 清理:终端执行
make clean,一键删除所有编译产物; - 调试:终端执行
make print,查看所有变量的实际展开值,排查变量定义/函数使用的错误(如源文件检索失败、路径错误)。
三、关键执行流程(Makefile运行逻辑)
Makefile会从主目标开始,自动推导依赖并执行规则,本文件的执行流程为:
- 终端执行
make,Make默认执行第一个非变量/非伪目标的规则(即$(TARGET).bin : $(OBJS)); - 检查依赖
$(OBJS)(所有.o文件)是否存在,若不存在,执行对应的编译规则(汇编/C文件编译); - 编译规则通过静态模式规则+自动变量,批量将.S/.c编译为obj/%.o;
- 所有.o文件生成后,执行链接命令生成
$(TARGET).elf; - 转换elf为bin镜像,生成dis反汇编文件;
- 最终在工程根目录生成
ledc.elf/ledc.bin/ledc.dis,obj目录生成所有.o文件。
四、高频易错点&避坑技巧
- 命令前必须用制表符\t:Makefile的语法要求,若用空格,会报
*** missing separator. Stop.错误,是最高频的报错; - 续行符\后无空格:
\结尾后若有空格,会导致变量定义错误,检索不到文件; - VPATH必须指定源文件目录:否则Make无法找到分散在不同目录的源文件,报
No rule to make target错误; - 伪目标必须声明.PHONY:否则工程中若有同名文件(如clean.txt),
make clean会失效; - 裸机开发必加-nostdlib:若无此参数,编译器会尝试链接标准C库,裸机环境无标准库,会报
undefined reference to main等链接错误; - **变量引用必须用(CC)`。
五、核心语法速查(记忆版)
1. 变量赋值
?=:条件赋值(未定义则赋值)→ 预留可外部修改的变量;:=:直接赋值(立即展开)→ 嵌入式开发首选;
2. 内置函数(高频)
$(patsubst 匹配, 替换, 对象):模式替换;$(foreach 变量, 集合, 操作):循环遍历;$(wildcard 规则):检索文件;$(notdir 路径):去除路径;
3. 自动变量(必记)
$@:当前目标文件;$<:第一个依赖文件;$^:所有依赖文件;
4. 核心规则
- 静态模式规则:
目标集合 : 目标模式 : 依赖模式→ 批量编译; - 伪目标:
.PHONY: 目标名→ 非文件类目标必加;
5. 关键标记
\:续行;@:不打印命令本身;%:模式通配符;$( ):变量/函数引用。
Some information may be outdated