工程与构建系统
本文介绍 fw-AD15N_GP-MCU_SDK 的工程组织方式与 Makefile 构建系统,涵盖顶层 Makefile 的目标分发机制、各芯片/产品工程的构建配置、交叉编译工具链、编译/链接/后处理完整流程以及常用构建参数。
Purpose and Scope
本页面向希望在 Linux 或 Windows 环境下编译、烧录 SDK 固件的开发者,以及需要新增目标工程或修改构建参数的平台工程师,说明:
- SDK 仓库中
sdk/目录下多个 Makefile 的分工与调用关系; Makefile.ad15n_mcu等目标工程 Makefile 的内部结构:工具链路径、编译参数、宏定义、头文件搜索路径、源文件列表、链接脚本生成与下载脚本生成;- 从
make到生成sdk.elf再到执行烧录脚本的完整构建链路; - 常用构建目标、
VERBOSE、LINK_AT等参数的含义与默认值。
以下主题属于其他目录页的范畴,本文不展开:芯片外设驱动 API(见芯片 BSP 相关页面)、include_lib 静态库的接口说明(见库 API 页面)、USB 升级工具的安装与烧录细节(见烧录与升级页面)。
Overview
该 SDK 是杰理(Jieli)AD1NN 系列 MCU 的固件开发套件,采用 Makefile + Clang/LLVM 交叉编译工具链 构建。整个构建系统由两层组成:
- 顶层聚合 Makefile(
sdk/Makefile):本身不直接编译任何代码,只负责把make <目标名>请求转发给对应的目标工程 Makefile(例如make ad15n_mcu等价于make -f Makefile.ad15n_mcu)。 - 目标工程 Makefile(如
Makefile.ad15n_mcu、Makefile.ad15n_voice_toy、Makefile.ad17n_mcu等):每个文件对应一个芯片型号(AC104N/AD14N/AD15N/AD17N/AD18N)与一种产品形态(mcu纯 MCU 应用 /voice_toy语音玩具应用),完整定义该目标的编译、链接、后处理规则。
构建流程分三个阶段:
- pre_build(预构建):用 clang 的预处理器把
app_ld.c展开生成链接脚本app.ld,把download_bat.c展开生成下载脚本(Windows 为download.bat,Linux 为download.sh); - compile + link(编译链接):逐文件编译(
clang -target pi32 ... -Oz -flto)生成目标文件,再由lto-wrapper一次性链接所有目标文件、静态库与系统库,输出sdk.elf与app.map; - post_build(后处理):执行下载脚本完成烧录或固件后处理。
选择这套设计的原因:所有目标共享同一套 clang/LLVM 工具链与 pi32 指令集目标,通过“一个工程一个 Makefile + 顶层统一入口”的方式,既能按产品快速切换构建配置,又能保持多目标并行编译(make all)与统一清理(make clean)的便利性。
Architecture
flowchart TD
subgraph sg_Top["sdk/Makefile(顶层入口)"]
Top["make ad15n_mcu"]
end
subgraph sg_Target["目标工程 Makefile"]
MF["Makefile.ad15n_mcu"]
CC["clang(pi32 交叉编译)"]
LD["lto-wrapper(链接)"]
AR["lto-ar / llvm-ar"]
end
subgraph sg_Sources["源码与依赖"]
SRC["app/src/mcu + app/bsp/*.c"]
INC["include_lib 头文件"]
LIB["include_lib/liba 静态库"]
SYS["系统 libc.a / libm.a"]
end
subgraph sg_Out["构建产物"]
LD_FILE["app.ld(由 app_ld.c 预处理生成)"]
ELF["app/post_build/sh55/sdk.elf"]
MAP["app.map"]
POST["download.bat / download.sh"]
end
Top --> MF
MF --> CC
MF --> LD
MF --> AR
CC --> SRC
CC --> INC
LD --> LIB
LD --> SYS
LD --> LD_FILE
LD --> ELF
LD --> MAP
MF --> POST
POST --> ELF
各节点的职责:
sdk/Makefile:顶层聚合入口,声明all/clean及 9 个目标的.PHONY规则,逐个转发子目标。它的设计意图是让用户只记make <产品名>一个命令,而不需要关心具体 Makefile 文件名。- 目标工程 Makefile:每个文件是自包含的构建配方——定义工具链、编译参数、宏、头文件路径、源文件清单、链接参数与产物路径。
Makefile.ad15n_mcu是所有目标中结构最典型的一份。 - clang / lto-wrapper / lto-ar:杰理工具链中的编译器、链接器与归档器。所有目标文件用
-flto生成 LLVM 位码,链接阶段做全程序优化,这与后文LFLAGS里大量--plugin-opt=-pi32-*参数一一对应。 - 源码与依赖:应用源码(
app/src/mcu、app/bsp)、发布库include_lib/liba/sh55/mcu/*.a、系统库libc.a/libm.a/libcompiler-rt.a共同构成链接输入。 - 构建产物:
app.ld(链接脚本)、sdk.elf(固件)、app.map(内存映射,用于排查内存占用)、download.bat/sh(下载/后处理脚本)。脚本本身也是由 C 文件预处理生成的,这是该 SDK 构建系统的一个显著特点。
顶层 Makefile:多目标分发机制
sdk/Makefile 位于仓库根目录的 sdk/ 子目录中,是所有构建的入口。它的注释明确列出了支持的 9 个目标,并在 .PHONY 声明中同时声明了每个目标的 clean_ 版本:
.PHONY: all clean ac104n_mbox_mg ad17n_mcu ad15n_mcu ad14n_mcu ad15n_voice_toy ad18n_voice_toy ad14n_voice_toy ad17n_voice_toy ad18n_mcu clean_ac104n_mbox_mg clean_ad17n_mcu clean_ad15n_mcu clean_ad14n_mcu clean_ad15n_voice_toy clean_ad18n_voice_toy clean_ad14n_voice_toy clean_ad17n_voice_toy clean_ad18n_mcu
all: ac104n_mbox_mg ad17n_mcu ad15n_mcu ad14n_mcu ad15n_voice_toy ad18n_voice_toy ad14n_voice_toy ad17n_voice_toy ad18n_mcu
@echo +ALL DONE
clean: clean_ac104n_mbox_mg clean_ad17n_mcu clean_ad15n_mcu clean_ad14n_mcu clean_ad15n_voice_toy clean_ad18n_voice_toy clean_ad14n_voice_toy clean_ad17n_voice_toy clean_ad18n_mcu
@echo +CLEAN DONE
Source: sdk/Makefile
每个目标的规则只是递归调用对应的工程 Makefile,例如:
ad15n_mcu:
$(MAKE) -C . -f Makefile.ad15n_mcu
clean_ad15n_mcu:
$(MAKE) -C . -f Makefile.ad15n_mcu clean
Source: sdk/Makefile
设计意图:
- 统一入口:用户只需执行
make ad15n_mcu,不必记忆Makefile.ad15n_mcu这个文件名; - 批量构建:
make all会依次构建全部 9 个目标,适合 CI 或发布前全量验证; - 递归 make 的代价:每个目标都是独立进程调用子 make,
-j并行度需要由用户在外层指定(如make -j4),顶层文件本身没有传递并行参数。
目标工程 Makefile 结构(以 Makefile.ad15n_mcu 为例)
跨平台工具链设置
构建系统同时支持 Windows 与 Linux,通过 $(OS) 变量区分。Windows 下工具链位于 C:/JL/pi32/bin,Linux 下位于 /opt/jieli/pi32/bin:
ifeq ($(OS), Windows_NT)
# Windows 下工具链位置
TOOL_DIR := C:/JL/pi32/bin
CC := clang.exe
CXX := clang.exe
LD := lto-wrapper.exe
AR := llvm-ar.exe
MKDIR := mkdir_win -p
RM := rm -rf
SYS_LIB_DIR := C:/JL/pi32/libc
SYS_INC_DIR := C:/JL/pi32/include/libc
EXT_CFLAGS := # Windows 下不需要 -D__SHELL__
export PATH:=$(TOOL_DIR);$(PATH)
else
# Linux 下工具链位置
TOOL_DIR := /opt/jieli/pi32/bin
CC := clang
CXX := clang
LD := lto-wrapper
AR := lto-ar
MKDIR := mkdir -p
RM := rm -rf
export OBJDUMP := $(TOOL_DIR)/objdump
export OBJCOPY := $(TOOL_DIR)/objcopy
export OBJSIZEDUMP := $(TOOL_DIR)/objsizedump
SYS_LIB_DIR := $(TOOL_DIR)/../lib
SYS_INC_DIR := $(TOOL_DIR)/../include
EXT_CFLAGS := -D__SHELL__ # Linux 下需要这个保证正确处理 download.c
export PATH:=$(TOOL_DIR):$(PATH)
endif
Source: sdk/Makefile.ad15n_mcu
两个平台的关键差异:
| 差异点 | Windows | Linux | 原因 |
|---|---|---|---|
| 工具链根目录 | C:/JL/pi32 | /opt/jieli/pi32 | 安装约定不同 |
AR | llvm-ar.exe | lto-ar | LTO 模式下归档器需支持 LLVM 位码 |
EXT_CFLAGS | 空 | -D__SHELL__ | 保证 download.c 等脚本源文件在 Linux 下被正确处理 |
| 后处理脚本 | download.bat(fixbat.exe 转 GBK 编码) | download.sh(bash 执行,touch 占位) | 平台脚本差异,fixbat 仅 Windows 需要 |
编译参数(CFLAGS)
CFLAGS 面向 pi32 目标做了大量针对性优化,核心是 -Oz(极致体积优化)与 -flto(全程序链接期优化):
CFLAGS := \
-target pi32 \
-integrated-as \
-fno-builtin \
-mllvm -pi32-memreg-opt \
-mllvm -pi32-mem-offset-adj-opt \
-mllvm -pi32-const-spill \
-mllvm -pi32-enable-jz \
-mllvm -pi32-tailcall-opt \
-mllvm -inline-threshold=5 \
-mllvm -pi32-enable-itblock=1 \
-Oz \
-flto \
-g \
-fprefer-gnu-section \
-fms-extensions \
-Wno-empty-body \
-Wcast-align \
-Wundef
Source: sdk/Makefile.ad15n_mcu
要点解读:
-target pi32:指定交叉编译目标架构为 pi32(杰理私有 32 位 DSP/MCU 内核,SH55 等 CPU 核基于它);-mllvm -pi32-*:一系列 pi32 后端的机器级优化开关(内存寄存器优化、偏移调整、常量 spill、条件跳转、尾调用、IT 块),-inline-threshold=5控制内联阈值,兼顾代码体积;-Oz与-flto配合:MCU 片内 Flash/RAM 资源紧张,体积优先;LTO 让链接器能看到全部程序后做跨模块优化;-fprefer-gnu-section:每个函数/数据放入独立 section,配合链接时的--gc-sections删除未使用代码。
宏定义(DEFINES)
DEFINES 按产品形态裁剪功能,Makefile.ad15n_mcu 是纯 MCU 形态,因此大量外设功能被关闭:
DEFINES := \
-DD_TOY_SDK=1 \
-DD_APP_TOY=1 \
-DFPGA=0 \
-DCPU_SH55=1 \
-DFLASH_CACHE_ENABLE=0 \
-DMKEY_CHECK_ENABLE=1 \
-DAUDIO_ADC_EN=0 \
-DHAS_USB_EN=0 \
-DHAS_UPDATE_EN=0 \
-DSIMPLE_FATFS_ENABLE=0 \
-DSYS_VM_EN=0 \
-DNOFLOAT
DEFINES += $(EXT_CFLAGS) # 额外的一些定义
Source: sdk/Makefile.ad15n_mcu
这些宏在 app_config.c、app_ld.c、download_bat.c 等源文件中驱动条件编译:例如 HAS_USB_EN=0 关闭 USB 升级相关代码、SYS_VM_EN=0 关闭 VM 掉电存储、NOFLOAT 禁用浮点运算以减小代码体积。对比 Makefile.ad15n_voice_toy 会看到另一组宏(如音频、Flash 缓存使能),这正是“一个工程一个 Makefile”所承载的产品差异化。
源文件清单与头文件路径
源文件显式列出,全部位于 app/ 目录下:
c_SRC_FILES := \
app/bsp/common/rtc/rtc.c \
app/bsp/common/vm/vm_api.c \
app/bsp/cpu/sh55/clock.c \
app/bsp/cpu/sh55/port_init.c \
app/bsp/cpu/sh55/power_api.c \
app/bsp/cpu/sh55/uart.c \
app/bsp/lib/common.c \
app/src/mcu/app_config.c \
app/src/mcu/mcu_app.c \
app/src/mcu/sh55/init.c \
app/src/mcu/sh55/main.c
Source: sdk/Makefile.ad15n_mcu
头文件搜索路径覆盖 app/bsp/cpu/sh55、include_lib 及其子目录(common/cpu/fs/msg/device 等)、app/post_build/sh55 等,最后追加系统头文件目录 $(SYS_INC_DIR)(见 sdk/Makefile.ad15n_mcu#L116-L141)。include_lib 是发布库的公共头文件目录,应用代码通过这些头文件调用库 API,库的二进制实现放在 include_lib/liba/ 下,编译时由链接器引用。
链接参数(LFLAGS)
链接阶段把 LTO 位码与静态库、系统库合并,并通过 @$(OBJ_FILE) 传递目标文件列表以规避命令行长度限制:
LFLAGS := \
--plugin-opt=save-temps \
--plugin-opt=-dont-used-symbol-list=malloc,free,sprintf,printf,puts,putchar,getchar \
--gc-sections \
--start-group \
include_lib/liba/sh55/mcu/cpu_lib.a \
include_lib/liba/sh55/mcu/efuse_trim_value_lib.a \
--end-group \
-Tapp/post_build/sh55/app.ld \
-M=app/post_build/sh55/app.map \
--plugin-opt=-pi32-memreg-opt \
--plugin-opt=-pi32-mem-offset-adj-opt \
--plugin-opt=-pi32-const-spill \
--plugin-opt=-pi32-enable-jz \
--plugin-opt=-pi32-tailcall-opt \
--plugin-opt=-inline-threshold=5 \
--plugin-opt=-pi32-enable-itblock=1
Source: sdk/Makefile.ad15n_mcu
要点:
--start-group/--end-group:允许cpu_lib.a与efuse_trim_value_lib.a之间循环引用符号;-T app.ld:使用 pre_build 阶段生成的链接脚本;-M=app.map:输出完整内存映射文件,用于排查 RAM/Flash 占用;--plugin-opt=-dont-used-symbol-list=...:保留malloc/free/printf等符号,避免--gc-sections误删库中仍被动态使用的实现;LIBS在 Windows 下显式给出C:/JL/pi32/lib/下的libc.a、libm.a、libcompiler-rt.a,配合LIBPATHS的-L使用。
构建流水线:pre_build → 编译 → 链接 → post_build
all 目标把整条流水线串起来,并在完成后执行下载脚本:
all: pre_build $(OUT_ELF)
$(info +POST-BUILD)
$(QUITE) $(RUN_POST_SCRIPT) sdk
pre_build:
$(info +PRE-BUILD)
$(QUITE) $(CC) $(CFLAGS) $(DEFINES) $(INCLUDES) -D__LD__ -E -P app/post_build/sh55/mcu/app_ld.c -o app/post_build/sh55/app.ld
$(QUITE) $(CC) $(CFLAGS) $(DEFINES) $(INCLUDES) -D__LD__ -E -P app/post_build/sh55/mcu/download_bat.c -o $(POST_SCRIPT)
$(QUITE) $(FIXBAT) $(POST_SCRIPT)
Source: sdk/Makefile.ad15n_mcu
pre_build 是这套系统最有特色的机制:链接脚本 app.ld 和下载脚本 download.bat/sh 不是手写的静态文件,而是由 C 源文件(app_ld.c、download_bat.c)经过 clang -E -P -D__LD__ 预处理生成。这样做的好处是脚本内容可以直接引用 DEFINES 里的宏(如 Flash 起始地址、分区大小、是否使能 USB 升级),当产品配置变化时脚本自动随之调整,避免脚本与配置漂移。
编译规则采用模式规则,每种源文件后缀对应一条规则,统一加上 -MMD -MF 生成 .d 依赖文件:
$(BUILD_DIR)/%.c.o : %.c
$(info +CC $<)
$(QUITE) $(MKDIR) $(@D)
$(QUITE) $(CC) $(CFLAGS) $(DEFINES) $(INCLUDES) -MMD -MF $(@:.o=.d) -c $< -o $@
Source: sdk/Makefile.ad15n_mcu
同一 Makefile 还提供了 .S、.s(汇编)、.cpp、.cxx、.cc(C++)共 6 条模式规则(见 sdk/Makefile.ad15n_mcu#L279-L302),其中 .S/.s 走汇编流程、C++ 源文件追加 $(CXXFLAGS)。-include $(DEP_FILES) 在文件末尾引入所有 .d 文件,实现头文件变更后的增量重编译。
链接规则把目标文件列表写入临时文件再传给 lto-wrapper,避免参数过长:
ifeq ($(LINK_AT), 1)
$(OUT_ELF): $(OBJS)
$(info +LINK $@)
$(shell $(MKDIR) $(@D))
$(file >$(OBJ_FILE), $(OBJS))
$(QUITE) $(LD) -o $(OUT_ELF) @$(OBJ_FILE) $(LFLAGS) $(LIBPATHS) $(LIBS)
else
$(OUT_ELF): $(OBJS)
$(info +LINK $@)
$(shell $(MKDIR) $(@D))
$(QUITE) $(LD) -o $(OUT_ELF) $(OBJS) $(LFLAGS) $(LIBPATHS) $(LIBS)
endif
Source: sdk/Makefile.ad15n_mcu
LINK_AT 默认值为 1(见 sdk/Makefile.ad15n_mcu#L233),使用 GNU make 的 $(file >...) 函数。注释说明:部分旧版 make 不支持 file 函数,此时需用 make LINK_AT=0 回退到直接传参方式。目标文件统一输出到 ad15n_mcu_objs/(BUILD_DIR),与源码目录隔离,make clean 只需删除 $(OUT_ELF) 与 $(BUILD_DIR) 即可(见 sdk/Makefile.ad15n_mcu#L254-L256)。
产物路径约定:OUT_ELF := app/post_build/sh55/sdk.elf,即所有目标最终固件都落在对应 CPU 核(SH55)的 app/post_build/sh55/ 目录下,与链接脚本、下载脚本、map 文件同目录,方便烧录工具统一查找。
核心构建流程
sequenceDiagram
participant Dev as 开发者
participant Top as sdk/Makefile
participant MF as Makefile.ad15n_mcu
participant CC as clang(pi32)
participant LD as lto-wrapper
participant Script as 下载/后处理脚本
Dev->>Top: make ad15n_mcu(或 make all)
Top->>MF: $(MAKE) -C . -f Makefile.ad15n_mcu
MF->>CC: pre_build:-E -P app_ld.c → app.ld
MF->>CC: pre_build:-E -P download_bat.c → download.bat/sh
loop 每个 c_SRC_FILES / S_SRC_FILES
MF->>CC: -MMD -Oz -flto -c 源文件
CC-->>MF: ad15n_mcu_objs/*.c.o + *.d
end
MF->>LD: 写 @objs.txt,链接 sdk.elf
LD-->>MF: sdk.elf + app.map
MF->>Script: 执行 $(RUN_POST_SCRIPT) sdk
Script-->>Dev: 烧录/后处理完成
完整构建过程中的关键决策点:
- 入口选择:直接
make ad15n_mcu或先进入sdk/目录执行make -f Makefile.ad15n_mcu all效果等价;顶层make all则串行构建 9 个目标。 - pre_build 生成物:
app.ld与下载脚本都依赖$(DEFINES),任何宏变更都会触发脚本重新生成(因为 pre_build 始终执行、无增量判断)。 - 增量编译:模式规则依赖
.d文件,头文件变化只重编受影响的目标文件;链接阶段始终全量执行。 - post_build:
RUN_POST_SCRIPT在 Windows 为app\post_build\sh55\download.bat,在 Linux 为bash $(POST_SCRIPT);执行download时传入参数sdk,脚本据此找到sdk.elf完成烧录。
使用示例
示例 1:Linux 命令行编译 AD15N MCU 工程
README 中给出了推荐的命令行编译方式(进入 sdk/ 目录后执行):
# 选择对应的 Makefile 进行编译
make -f Makefile.ad15n_voice_toy all -j4
# 或者 AD15N MCU 工程
make -f Makefile.ad15n_mcu all -j4
Source: README.md
示例 2:通过顶层 Makefile 按目标名编译
不指定 -f,直接用顶层入口(在 sdk/ 目录下):
make ad15n_mcu # 编译 AD15N MCU 工程并执行下载脚本
make VERBOSE=1 ad15n_mcu # 显示每条编译/链接命令的详细过程
make clean_ad15n_mcu # 清理该工程的临时文件与产物
make all # 依次编译全部 9 个目标
make clean # 清理全部目标的产物
Source: sdk/Makefile
示例 3:Windows 下的工程构建
Windows 推荐使用 Code::Blocks IDE 打开对应的 .cbp 工程文件(Build → Build,Ctrl+F9)编译(见 README.md#L151-L157);若使用命令行,工具链会自动切换为 C:/JL/pi32/bin 下的 clang.exe 等可执行文件,后处理脚本为 download.bat,并用 fixbat.exe 修正脚本编码。两种平台下 Makefile 会通过 ifeq ($(OS), Windows_NT) 自动选择正确的工具与路径,无需手工修改。
配置选项
make 变量
| 变量 | 类型 | 默认值 | 说明 |
|---|---|---|---|
VERBOSE | int | 0 | 置为 1 时显示完整编译/链接命令(去掉 @ 静默前缀),用于排查编译错误 |
LINK_AT | int | 1 | 使用 make file 函数把目标文件列表写入临时文件后以 @file 方式链接;旧版 make 不支持时置 0 回退为直接传参 |
OS | string | 环境变量 | Windows_NT 时走 Windows 工具链分支,否则走 Linux 分支 |
TOOL_DIR | path | 平台相关 | Windows:C:/JL/pi32/bin;Linux:/opt/jieli/pi32/bin |
BUILD_DIR | path | ad15n_mcu_objs | 编译中间产物目录(各目标 Makefile 各不相同) |
OUT_ELF | path | app/post_build/sh55/sdk.elf | 最终固件输出路径 |
顶层 make 目标
| 目标 | 作用 | 对应工程 Makefile |
|---|---|---|
all | 构建全部 9 个目标 | 依次调用各目标 |
clean | 清理全部目标 | 依次调用各 clean 目标 |
ad15n_mcu / clean_ad15n_mcu | 构建/清理 AD15N MCU 工程 | Makefile.ad15n_mcu |
ad15n_voice_toy / clean_ad15n_voice_toy | 构建/清理 AD15N 语音玩具工程 | Makefile.ad15n_voice_toy |
ad14n_mcu、ad17n_mcu、ad18n_mcu | 构建 AD14N/AD17N/AD18N MCU 工程 | 对应 Makefile.adXXn_mcu |
ad14n_voice_toy、ad17n_voice_toy、ad18n_voice_toy | 构建对应语音玩具工程 | 对应 Makefile.adXXn_voice_toy |
ac104n_mbox_mg | 构建 AC104N 音箱(mbox)管理工程 | Makefile.ac104n_mbox_mg |
关键宏定义(Makefile.ad15n_mcu)
| 宏 | 值 | 作用 |
|---|---|---|
CPU_SH55 | 1 | 目标 CPU 核为 SH55 |
D_TOY_SDK / D_APP_TOY | 1 | 玩具 SDK / 玩具应用模式 |
FPGA | 0 | 非 FPGA 验证平台 |
FLASH_CACHE_ENABLE | 0 | 关闭 Flash 缓存 |
MKEY_CHECK_ENABLE | 1 | 使能按键(MKEY)检测 |
AUDIO_ADC_EN | 0 | 关闭音频 ADC(纯 MCU 形态) |
HAS_USB_EN | 0 | 关闭 USB 功能 |
HAS_UPDATE_EN | 0 | 关闭升级功能 |
SIMPLE_FATFS_ENABLE | 0 | 关闭简易 FATFS 文件系统 |
SYS_VM_EN | 0 | 关闭 VM 掉电存储 |
NOFLOAT | — | 禁用浮点运算,减小代码体积 |
环境准备与前置条件
Makefile 注释(sdk/Makefile.ad15n_mcu#L6-L11)与 README(README.md#L89-L92)明确了 Linux 环境要求:
- 从
http://pkgman.jieliapp.com/doc/all下载杰理编译工具链,解压到/opt/jieli,保证/opt/jieli/common/bin/clang存在(注意目录层次); - 确认
ulimit -n足够大(建议大于 8096),否则链接可能因打开文件过多而失败,可用ulimit -n 8096调大; - Windows 环境则需安装杰理工具链(默认路径
C:/JL/pi32),并推荐使用 Code::Blocks IDE。
失败模式与边界情况
基于 Makefile 源码可以确认以下常见失败场景及其规避方式:
- 链接期“打开文件过多”失败:LTO 全程序链接会把所有目标文件同时读入内存并打开大量文件句柄,当
ulimit -n偏小时链接会失败。Makefile 注释明确建议ulimit -n 8096。这是构建系统最典型的运维坑,属于系统资源限制而非代码错误。 - 旧版 make 不支持
file函数:LINK_AT=1(默认)依赖 GNU make 的$(file >...);若报语法错误,需以make LINK_AT=0回退到把目标文件直接放在命令行上的方式(命令行长度受限,目标文件较多时可能溢出,但可作为兜底)。 - 平台差异导致脚本编码错误:Windows 下
download.bat由clang -E -P生成时输出为 UTF-8,而 cmd 需要 GBK 编码,因此用fixbat.exe转换;若跳过pre_build或手动编辑脚本,可能出现中文乱码或无法执行。Linux 下无此问题(FIXBAT := touch占位)。 -D__SHELL__缺失:Linux 分支通过EXT_CFLAGS注入该宏,保证download.c(及其生成脚本)在 Linux 下行为正确;若自行裁剪 CFLAGS 时遗漏,生成的download.sh可能不兼容。--gc-sections误删运行时符号:dont-used-symbol-list显式保留malloc/free/sprintf/printf/puts/putchar/getchar,防止这些被库间接使用的符号被回收导致链接或运行期问题。- 并行构建的资源竞争:顶层
make all串行调用各子 make;但若用户直接对多个目标并行执行(如make -j4于顶层),各子工程使用各自独立的BUILD_DIR,产物互不覆盖,因此并行是安全的;真正共享的app/post_build/sh55/目录下各目标以sdk.elf同名输出,串行调用时后构建者覆盖先构建者,属预期行为(烧录哪个目标取决于最后一次构建)。
性能与运维注意事项
- 增量编译:
.d依赖文件机制保证头文件变更后只重编受影响单元;但pre_build无增量判断、每次make都重新生成app.ld与下载脚本——这是有意为之,因为脚本内容依赖宏配置,无法可靠判断“未变化”。 - LTO 链接耗时:
-flto让链接阶段承担全程序优化,目标文件越多链接越慢;LINK_AT=1的@file方式避免了命令行过长,但无法缩短链接本身耗时。构建机建议具备足够内存(LTO 需要同时持有全部位码)。 -Oz与-g并存:产物包含调试信息,objdump/objcopy/objsizedump等工具被导出到环境供后处理与大小分析使用;正式发布时可评估是否需要裁剪调试段。- 清理策略:
make clean只删除sdk.elf与BUILD_DIR,app.map、app.ld、下载脚本等生成物保留在app/post_build/sh55/下;彻底重置需手动删除这些生成物。 - 文件句柄与路径长度:Windows 下工具链路径
C:/JL/pi32/bin较短,但深层嵌套的BUILD_DIR目录(镜像了源码相对路径)可能触达路径长度限制;如遇“路径太长”错误,可考虑缩短源码目录深度或迁移工具链安装位置。
扩展点
构建系统为新增目标/产品提供了清晰的扩展模式:
- 新增产品目标:仿照现有文件复制一份
Makefile.<target>,修改四类内容——(1)BUILD_DIR与产物路径;(2)DEFINES宏按产品形态裁剪(参考mcu与voice_toy两套宏的差异);(3)c_SRC_FILES/S_SRC_FILES源文件清单;(4) 链接参数中include_lib/liba/<cpu>/<app>/下的静态库。然后在顶层sdk/Makefile的.PHONY、all、clean及各目标规则中加入新目标名即可。 - 修改链接脚本/下载脚本:不直接编辑
app.ld或download.bat,而是编辑其生成源app/post_build/sh55/mcu/app_ld.c与download_bat.c,让宏驱动的条件编译继续生效。 - 切换 CPU 核:
INCLUDES、c_SRC_FILES中大量引用sh55目录(app/bsp/cpu/sh55、include_lib/cpu/sh55),换核需要同步替换这些路径,并调整-DCPU_SH55=1等宏。 - 调试辅助:
make VERBOSE=1输出完整命令;生成的app.map用于分析内存布局;Linux 下OBJDUMP/OBJCOPY/OBJSIZEDUMP已导出,可直接用于反汇编与固件大小统计。
测试与验证
仓库以示例工程形式交付(README 中说明“包含 SDK Release 版本代码及示例工程”),构建系统的正确性验证方式是端到端编译 + 烧录:make <target> all 成功生成 sdk.elf 并执行下载脚本,即视为通过。make clean 后再构建可验证干净环境下的可复现性。由于 app_ld.c 与 download_bat.c 由同一套宏驱动,修改配置后应重新执行完整构建(而非仅增量编译),以确保脚本与固件一致。
相关链接
- sdk/Makefile(顶层构建入口)
- sdk/Makefile.ad15n_mcu(AD15N MCU 工程构建配置)
- sdk/Makefile.ad15n_voice_toy(AD15N 语音玩具工程构建配置)
- README.md(SDK 总览与编译/烧录指南)
- 环境搭建与工具链安装:杰理官方开发环境文档(doc.zh-jieli.com)
- 烧录与升级细节:见“烧录与升级”页面