编译构建系统
本页介绍 fw-AC63_BT_SDK 的编译构建系统:从顶层 Makefile 的目标分发,到板级 Makefile 的工具链选择、编译参数与后处理下载脚本的完整构建链路。
Purpose and Scope
本页覆盖以下内容:
- 顶层
Makefile的目标(target)命名规则与芯片—板级工程映射关系 - 板级
Makefile(以apps/hid/board/br25/Makefile为例)中的工具链路径、编译参数、宏定义与链接设置 - Linux 与 Windows 两套构建环境的差异与切换逻辑
- 构建产物(
sdk.elf)的输出位置与后处理(下载/烧录)脚本 - 常见构建失败模式与排错建议
以下内容不在本页范围,由仓库其他部分或兄弟页面覆盖:
- 具体芯片(如 BR25/BR23)的硬件架构与外设寄存器,请参见对应芯片目录文档
- 具体应用(spp_and_le / hid / mesh)的业务逻辑,请参见各自的目录说明
- 固件烧录工具的使用细节,请参见
cpu/<chip>/tools/下的脚本与工具文档
Overview
fw-AC63_BT_SDK 是一个典型的"一个 SDK、多芯片、多应用"嵌入式蓝牙 SDK。为了让开发者用一条 make 命令即可完成任意"芯片 + 应用"组合的编译,仓库采用了两级 Makefile 结构:
- 顶层 Makefile(仓库根目录)—— 只做一件事:把形如
ac636n_hid的目标名翻译成apps/<app>/board/<board>/目录,并递归调用该目录下的 Makefile。 - 板级 Makefile(
apps/<app>/board/<board>/Makefile)—— 完成真正的编译工作:选择工具链、设置编译/链接参数、编译全部源码、链接生成sdk.elf,最后调用下载脚本。
工具链基于 LLVM/Clang 的 pi32v2 目标(杰理自研 32 位 DSP/MCU 内核),使用 LTO(链接时优化)与 -Oz 尺寸优化——这是嵌入式资源受限场景的标准选择:优先保证固件体积最小化。
flowchart TD
subgraph sg_Root["仓库根目录 Makefile"]
Root["Makefile<br/>目标分发器"]
end
subgraph sg_Board["板级 Makefile (apps/<app>/board/<board>/)"]
Board["Makefile<br/>工具链 + 编译参数"]
end
subgraph sg_Toolchain["工具链 (LLVM/Clang pi32v2)"]
CC["clang (CC/CXX)"]
LD["lto-wrapper (LD)"]
AR["lto-ar / llvm-ar (AR)"]
end
subgraph sg_Output["构建产物"]
ELF["sdk.elf"]
POST["download.bat / download.sh<br/>下载脚本"]
end
Root -->|"make ac636n_hid"| Board
Board -->|"CFLAGS -target pi32v2 -mcpu=r3 -flto -Oz"| CC
Board -->|"LDFLAGS"| LD
Board -->|"SYS_LIB_DIR 系统库"| AR
CC --> ELF
LD --> ELF
ELF --> POST
设计意图:将"目标名"与"构建细节"解耦。顶层 Makefile 只需维护一张目标名 → 目录的映射表,新增芯片/板级工程时无需改动任何编译逻辑;板级 Makefile 则集中了该板卡的全部构建知识(工具链、宏、库路径、下载脚本),保证"改板卡只改一处"。
构建目标与芯片映射
顶层 Makefile 支持 15 个构建目标,命名规则为 ac<芯片型号>_<应用>,同时为每个目标提供对应的 clean_<目标> 清理目标:
# 支持的目标
# make ac638n_spp_and_le
# make ac632n_spp_and_le
# make ac631n_spp_and_le
# make ac636n_spp_and_le
# make ac637n_spp_and_le
# make ac635n_spp_and_le
# make ac638n_hid
# ...
Source: Makefile
芯片型号与板级目录的对应关系(从顶层 Makefile 的目标实现中提取):
| 芯片目标 | 应用目录 | 板级目录 | 对应 Makefile |
|---|---|---|---|
| ac638n | spp_and_le / hid / mesh | apps/*/board/br34 | br34/Makefile |
| ac632n | spp_and_le / hid / mesh | apps/*/board/bd19 | bd19/Makefile |
| ac631n | spp_and_le / hid / mesh | apps/*/board/bd29 | bd29/Makefile |
| ac636n | spp_and_le / hid / mesh | apps/*/board/br25 | br25/Makefile |
| ac637n | spp_and_le / hid / mesh | apps/*/board/br30 | br30/Makefile |
| ac635n | spp_and_le / hid / mesh | apps/*/board/br23 | br23/Makefile |
每个目标的实现本质上只是一条递归 make 调用,例如:
ac636n_hid:
$(MAKE) -C apps/hid/board/br25 -f Makefile
clean_ac636n_hid:
$(MAKE) -C apps/hid/board/br25 -f Makefile clean
Source: Makefile
all 目标会依次构建全部 15 个目标,clean 目标则依次清理全部工程:
all: ac638n_spp_and_le ac632n_spp_and_le ... ac635n_mesh
@echo +ALL DONE
clean: clean_ac638n_spp_and_le ... clean_ac635n_mesh
@echo +CLEAN DONE
Source: Makefile
板级 Makefile:工具链与构建环境
板级 Makefile(如 apps/hid/board/br25/Makefile)是真正执行编译的入口。它以 $(MAKE) -C apps/hid/board/br25 -f Makefile 被顶层调用,自身并不关心是哪个应用在调用它——三个应用目录(spp_and_le / hid / mesh)下的同一板卡目录结构完全一致。
跨平台工具链选择(OS 分支)
Makefile 通过 $(OS) 变量区分 Windows 与 Linux,两套工具链路径、后处理脚本完全不同:
ifeq ($(OS), Windows_NT)
# Windows 下工具链位置
TOOL_DIR := C:/JL/pi32/bin
CC := clang.exe
CXX := clang.exe
LD := pi32v2-lto-wrapper.exe
AR := llvm-ar.exe
MKDIR := mkdir_win -p
RM := rm -rf
SYS_LIB_DIR := C:/JL/pi32/pi32v2-lib/r3
SYS_INC_DIR := C:/JL/pi32/pi32v2-include
EXT_CFLAGS := # Windows 下不需要 -D__SHELL__
export PATH:=$(TOOL_DIR);$(PATH)
## 后处理脚本
FIXBAT := ..\..\..\..\tools\utils\fixbat.exe # 用于处理 utf8->gbk 编码问题
POST_SCRIPT := ../../../../cpu/br25/tools/download.bat
RUN_POST_SCRIPT := ..\..\..\..\cpu\br25\tools\download.bat
else
# Linux 下工具链位置
TOOL_DIR := /opt/jieli/pi32v2/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/r3
SYS_INC_DIR := $(TOOL_DIR)/../include
EXT_CFLAGS := -D__SHELL__ # Linux 下需要这个保证正确处理 download.c
export PATH:=$(TOOL_DIR):$(PATH)
## 后处理脚本
FIXBAT := touch # Linux下不需要处理 bat 编码问题
POST_SCRIPT := ../../../../cpu/br25/tools/download.sh
RUN_POST_SCRIPT := bash $(POST_SCRIPT)
endif
Source: apps/hid/board/br25/Makefile
关键差异与设计意图:
- 工具链路径:Windows 固定为
C:/JL/pi32/bin,Linux 固定为/opt/jieli/pi32v2/bin。这是 SDK 的约定——Linux 用户需要从 pkgman.jieliapp.com 下载工具链并解压到/opt/jieli,保证/opt/jieli/common/bin/clang存在。 - 链接器:Windows 用
pi32v2-lto-wrapper.exe,Linux 用lto-wrapper——两者都是 LTO 包装器,负责把 LTO 位码链接成最终固件。 EXT_CFLAGS:Linux 下额外定义-D__SHELL__,保证download.c在 Linux 环境下被正确处理(源码中的条件编译依赖该宏)。FIXBAT:Windows 下用tools/utils/fixbat.exe修复.bat文件的 UTF-8→GBK 编码问题;Linux 下用touch占位,因为 shell 脚本无此问题。这体现了"同一构建逻辑、平台差异最小化"的封装思路。export PATH:将工具链目录注入 PATH,使后续所有命令(包括子 make、下载脚本)都能找到 clang 等工具。
构建产物与路径常量
# 输出文件设置
OUT_ELF := ../../../../cpu/br25/tools/sdk.elf
OBJ_FILE := $(OUT_ELF).objs.txt
# 编译路径设置
BUILD_DIR := objs
# 工程路径前缀
ROOT_PREFIX := ../../../..
Source: apps/hid/board/br25/Makefile
- 最终固件统一输出到
cpu/br25/tools/sdk.elf(即下载脚本所在目录),这样下载/烧录脚本可以直接引用同目录下的固件文件。 - 中间
.o文件输出到板级工程目录下的objs/,clean目标即删除该目录。 OBJ_FILE记录了链接时需要的所有目标文件清单,供 LTO 链接器使用。
编译参数(CFLAGS)
BR25 板级工程的 CFLAGS 集中体现了该 SDK 的编译约束:
CFLAGS := \
-target pi32v2 \
-mcpu=r3 \
-integrated-as \
-flto \
-Wuninitialized \
-Wno-invalid-noreturn \
-fno-common \
-Oz \
-g \
-fallow-pointer-null \
-fprefer-gnu-section \
-Wno-shift-negative-value \
-Wundef \
-Wframe-larger-than=256 \
-Wincompatible-pointer-types \
-Wreturn-type \
-Wimplicit-function-declaration \
-fms-extensions \
-w
Source: apps/hid/board/br25/Makefile
逐项解读:
| 参数 | 含义 |
|---|---|
-target pi32v2 | 指定编译目标为杰理 pi32v2 内核 |
-mcpu=r3 | CPU 变体为 r3(对应 BR25 芯片核) |
-integrated-as | 使用 Clang 内置汇编器,避免外部 as 版本不匹配 |
-flto | 开启链接时优化,配合 lto-wrapper 在链接阶段做跨文件优化 |
-Oz | 以"最小化代码尺寸"为优化目标——嵌入式 Flash 有限,这是默认策略 |
-g | 生成调试信息,便于用 objdump/调试器分析 |
-fno-common | 禁止公共块合并,避免未初始化全局变量跨文件意外合并 |
-fprefer-gnu-section | 偏好 GNU section 布局,配合 LTO 做函数级裁剪 |
-Wframe-larger-than=256 | 警告栈帧超过 256 字节的函数——嵌入式栈空间紧张,用于早期发现深栈风险 |
-fms-extensions | 允许 MSVC 扩展语法(匿名 struct/union 等),兼容部分历史代码 |
-w | 最后关闭警告(前面的 -W... 项与 -w 配合,实际只保留显式启用的诊断) |
设计意图:-Oz + -flto + -fprefer-gnu-section 三件套是嵌入式固件"压尺寸"的核心手段——编译器在链接期做全局优化、丢弃未引用的 section,从而让最终固件只包含实际用到的代码。-Wframe-larger-than 则是一个主动的栈安全哨兵。
宏定义(DEFINES)
板级 Makefile 还通过 DEFINES 注入芯片与功能开关宏:
DEFINES := \
-DSUPPORT_MS_EXTENSIONS \
-DCONFIG_RELEASE_ENABLE \
-DCONFIG_CPU_BR25 \
-DCONFIG_PRINT_IN_MASK \
-DCONFIG_EQ_SUPPORT_ASYNC \
-DCONFIG_MIXER_CYCLIC \
-DCONFIG_FREE_RTOS_ENABLE \
-DCONFIG_MMU_ENABLE \
-DCONFIG_SBC_CODEC_HW \
-DCONFIG_MSBC_CODEC_HW \
-DCONFIG_AEC_M=256 \
-DCONFIG_AUDIO_ONCHIP \
-DCONFIG_MEDIA_DEVELOP_ENABLE \
-D__GCC_PI32V2__ \
-DCONFIG_NEW_ECC_ENABLE \
-DEVENT_HANDLER_NUM_CONFIG=2 \
-DEVENT_TOUCH_ENABLE_CONFIG=0 \
-DEVENT_POOL_SIZE_CONFIG=256 \
-DCONFIG_EVENT_KEY_MAP_ENABLE=0 \
-DTIMER_POOL_NUM_CONFIG=10 \
...
Source: apps/hid/board/br25/Makefile
这些宏是"配置即代码"的体现:CONFIG_CPU_BR25 决定芯片相关代码分支;CONFIG_FREE_RTOS_ENABLE / CONFIG_MMU_ENABLE 开启 FreeRTOS 与 MMU;CONFIG_SBC_CODEC_HW / CONFIG_MSBC_CODEC_HW 选择硬件编解码器;EVENT_POOL_SIZE_CONFIG、TIMER_POOL_NUM_CONFIG 等则静态配置事件/定时器资源池大小——在无动态内存管理(或受限)的 MCU 场景,资源池大小必须在编译期确定。修改这些宏即可裁剪功能与内存占用,而无需改动业务源码。
核心构建流程
从执行 make ac636n_hid 到生成固件,完整链路如下:
sequenceDiagram
participant U as 开发者
participant R as 顶层 Makefile
participant B as 板级 Makefile (br25)
participant T as 工具链 (clang/lto-wrapper)
participant P as 后处理脚本 (download.sh/bat)
U->>R: make ac636n_hid
R->>B: $(MAKE) -C apps/hid/board/br25 -f Makefile
B->>B: 检测 OS → 选择工具链路径与脚本
B->>T: clang -target pi32v2 -mcpu=r3 -flto -Oz<br/>编译全部 .c/.cpp → objs/*.o
T->>T: lto-wrapper 链接 objs/*.o + SYS_LIB_DIR 系统库
T-->>B: 生成 cpu/br25/tools/sdk.elf
B->>P: RUN_POST_SCRIPT (download.sh / download.bat)
P-->>U: 固件下载/烧录完成
U->>R: make clean_ac636n_hid (可选)
R->>B: $(MAKE) -C apps/hid/board/br25 -f Makefile clean
B->>B: 删除 objs/ 等中间产物
流程要点:
- 目标解析:顶层 Makefile 把
ac636n_hid映射为apps/hid/board/br25,执行递归 make;clean_ac636n_hid则递归执行make clean。 - 环境选择:板级 Makefile 读取
$(OS),Windows_NT 走 Windows 分支(C:/JL/pi32/bin),否则走 Linux 分支(/opt/jieli/pi32v2/bin),并export PATH注入工具链。 - 编译:
clang以-target pi32v2 -mcpu=r3 -flto -Oz编译所有源文件,中间文件落入板级工程目录的objs/。 - 链接:
lto-wrapper(Windows 下pi32v2-lto-wrapper.exe)读取sdk.elf.objs.txt中的目标文件清单,结合SYS_LIB_DIR(pi32v2-lib/r3系统库)完成 LTO 链接,输出cpu/<chip>/tools/sdk.elf。 - 后处理:自动执行
download.bat(Windows)或bash download.sh(Linux)下载固件;Windows 下先用fixbat.exe修正 bat 编码,Linux 下用touch占位。
使用示例
编译指定芯片 + 应用组合
# 编译 HID 应用(AC636N → br25 板卡)
make ac636n_hid
# 编译 SPP 与 LE 双模应用
make ac638n_spp_and_le
# 编译 Mesh 应用
make ac635n_mesh
# 显示编译详细过程
make VERBOSE=1
Source: apps/hid/board/br25/Makefile
清理与全量构建
# 清理单个工程(删除 objs/ 中间产物)
make clean_ac636n_hid
# 全量清理所有 15 个工程
make clean
# 依次构建全部芯片×应用组合
make all
Source: Makefile
Linux 环境准备(首次使用)
# 1. 从 http://pkgman.jieliapp.com/doc/all 下载工具链并解压到 /opt/jieli
# 确保 /opt/jieli/common/bin/clang 存在(注意目录层次)
# 2. 调大文件描述符上限,防止链接时"打开文件太多"失败
ulimit -n 8096
# 3. 然后正常编译
make ac636n_hid
Source: Makefile
配置选项
构建系统的可配置项分为三层:
| 层级 | 配置项 | 取值示例 | 默认/说明 |
|---|---|---|---|
| 顶层目标 | 构建目标名 | ac636n_hid | 规则:ac<型号>_<应用>,见芯片映射表 |
| 顶层目标 | 清理目标名 | clean_ac636n_hid | 每个构建目标都有对应的 clean 目标 |
| 板级工具链 | TOOL_DIR | C:/JL/pi32/bin / /opt/jieli/pi32v2/bin | 按 $(OS) 自动选择 |
| 板级工具链 | CC / CXX / LD / AR | clang / lto-wrapper / lto-ar | Windows 下带 .exe 后缀 |
| 板级工具链 | SYS_LIB_DIR | C:/JL/pi32/pi32v2-lib/r3 | 系统运行库目录 |
| 板级工具链 | SYS_INC_DIR | C:/JL/pi32/pi32v2-include | 系统头文件目录 |
| 板级编译 | CFLAGS | -target pi32v2 -mcpu=r3 -flto -Oz | 编译参数,可追加定制 |
| 板级编译 | DEFINES | CONFIG_CPU_BR25、CONFIG_FREE_RTOS_ENABLE 等 | 功能开关与资源池大小宏 |
| 板级输出 | OUT_ELF | ../../../../cpu/br25/tools/sdk.elf | 固件输出路径 |
| 板级输出 | BUILD_DIR | objs | 中间文件目录 |
| 后处理 | POST_SCRIPT / RUN_POST_SCRIPT | download.sh / download.bat | 下载/烧录脚本,平台相关 |
| 后处理 | FIXBAT | fixbat.exe / touch | Windows 下修正 bat 文件编码 |
失败模式与边界情况
基于 Makefile 源码中的注释与实现,以下是已知的典型失败场景及原因:
| 失败现象 | 根因 | 解决建议 |
|---|---|---|
clang: command not found | Linux 工具链未安装或目录层次不对,/opt/jieli/common/bin/clang 不存在 | 按注释从 pkgman 下载并解压到 /opt/jieli,确认目录层次 |
| 链接阶段报 "too many open files" | ulimit -n 过小,LTO 链接需要打开大量文件 | 执行 ulimit -n 8096(或更大)后重试 |
| Windows 下 bat 脚本乱码/执行失败 | .bat 文件编码问题(UTF-8 与 GBK 混用) | 构建系统已通过 fixbat.exe 自动处理;手动修改脚本后需保持编码一致 |
download.c 行为异常(Linux) | 缺少 -D__SHELL__ 宏 | Linux 分支的 EXT_CFLAGS 已自动加上该宏,请勿删除 |
make all 中某个目标失败 | 该芯片工具链/库不匹配(如 SYS_LIB_DIR 指向的 r3 库与 -mcpu 不符) | 单独执行对应目标,检查板级 Makefile 的 SYS_LIB_DIR 与 -mcpu 是否匹配 |
| 栈溢出/运行期崩溃 | 深栈函数未被发现 | 关注 -Wframe-larger-than=256 警告,检查 EVENT_POOL_SIZE_CONFIG 等资源池宏是否过小 |
边界情况:
- 平台分支判定:仅依据
$(OS)是否为Windows_NT决定平台。若在 Windows 下使用 MinGW/MSYS 环境,$(OS)可能不精确匹配,导致误入 Linux 分支——建议在原生 Windows cmd 或符合该判定的环境中构建。 - 路径硬编码:Windows 工具链路径硬编码为
C:/JL/pi32/bin,Linux 硬编码为/opt/jieli/pi32v2/bin,不可自定义;更换工具链版本需整体替换目录内容。 make与make clean的组合:clean 只删除板级objs/中间产物,sdk.elf位于cpu/<chip>/tools/下,不会被 clean 删除,可避免误删已验证固件。
性能与运维注意事项
- LTO 编译较慢:
-flto在链接期做全程序优化,首次全量编译耗时明显高于普通编译。增量编译时 Makefile 依赖时间戳判定,改动单个文件只会重编相关目标文件。 - 并发构建:未在顶层 Makefile 中使用
-j,各目标串行执行;用户可自行传入make -jN ac636n_hid加速单工程编译,但需注意 LTO 链接阶段的内存占用。 - 产物位置约定:所有板卡的
sdk.elf统一输出到对应cpu/<chip>/tools/下,便于 CI 脚本与下载工具按固定路径取用。 - 磁盘空间:
objs/中每个源文件对应一个.o,15 个目标全量构建会产生较多中间文件;make clean可及时回收。
扩展点
构建系统为以下扩展场景预留了清晰的插入位置:
- 新增应用:在
apps/下复制现有应用目录结构(如apps/xxx/board/<board>/Makefile),顶层 Makefile 增加对应的<芯片>_<应用>与clean_<芯片>_<应用>目标即可,无需改动板级构建逻辑。 - 新增芯片/板卡:在
apps/*/board/下新增板级目录,参考现有板卡 Makefile 设置-mcpu、SYS_LIB_DIR、OUT_ELF与下载脚本;同时在顶层 Makefile 注册目标。 - 裁剪功能与内存:通过修改板级 Makefile 的
DEFINES(如关闭CONFIG_*功能宏、调小EVENT_POOL_SIZE_CONFIG/TIMER_POOL_NUM_CONFIG)即可调整固件体积与 RAM 占用,无需改动业务代码。 - 定制编译参数:追加
CFLAGS(如-Werror、额外 include 路径)或自定义DEFINES覆盖默认配置。
相关链接
- 根构建入口:Makefile
- 板级构建示例:apps/hid/board/br25/Makefile
- 其他板级 Makefile:apps/spp_and_le/board/br23/Makefile、apps/mesh/board/br30/Makefile
- 下载/烧录脚本目录:
cpu/<chip>/tools/(如 cpu/br25/tools/download.sh) - 工具链获取:pkgman.jieliapp.com
- 相关目录:应用入口请参见
apps/spp_and_le、apps/hid、apps/mesh各自的说明页面