杰理 SDK 文档中心
首页
首页
  • 项目概览

    • 项目概述与能力地图
    • 构建系统与编译流程
    • 芯片系列与规格
  • 应用示例

    • SPP 与 BLE 双模透传
    • AT 指令串口协议
    • HID 设备应用
    • 蓝牙 Mesh 应用
    • 公共组件与第三方协议
  • 芯片平台支持

    • 外设驱动
    • 电源与充电管理
    • 启动与链接脚本
    • 配置工具与 OTA 资源
  • 协议栈与系统库

    • 蓝牙控制器
    • BTStack 协议栈接口
    • 系统内核与服务
    • OTA 升级机制
  • 文档与参考

    • 蓝牙 AT 协议参考
    • 开发文档与认证信息

构建系统与编译流程

本页介绍 fw-AC630N_BT_SDK(AC630/1N 系列通用蓝牙固件 SDK)的构建系统与编译流程:从根 Makefile 的顶层编排、rule.mk 的通用编译/链接/归档规则,到 SoC 平台配置、Windows 构建入口以及库(lib)与应用(apps)的递归构建机制。

Purpose and Scope

本页聚焦"如何构建固件"这一条端到端链路,覆盖:

  • 根目录 Makefile 提供的全部构建目标(make、make clean、make libs、make debug、make cbp 等)及其编排逻辑;
  • rule.mk 中共享的编译、依赖生成、链接、静态库归档规则;
  • SoC 平台选择机制(tools/platform/Makefile.$(SoC))与预编译库接入(include_lib/Makefile.include);
  • Windows 下的构建环境(make_prompt.bat)以及 CodeBlocks 工程生成流程(cbp/winmake/winrelease);
  • 链接脚本 sdk.ld 与后处理脚本的预处理生成机制。

不在本页范围、由其他页面承载的内容:具体蓝牙协议栈功能(SPP/HID/Mesh 等应用逻辑)、各外设驱动的内部实现、工具链的安装与下载方式(见仓库 README.md 的 Toolchain 章节)、固件烧录工具的使用细节。

Overview

fw-AC630N_BT_SDK 是基于 Zephyr RTOS 的 Jieli 源码增强仓库,必须与 lib.a(预编译静态库)以及遵循相同命名约定的其他仓库组合后才能构建出可烧录固件(见 README.md)。因此构建系统需要同时处理三类输入:

  1. 源码:仓库内的 apps/(应用层)、lib/(库源码)、cpu/$(CPU)/(芯片相关代码);
  2. 预编译库:include_lib/liba/$(CPU)/ 下的 lib.a;
  3. 工具链:JL Toolchain(基于 clang/LLVM 的 pi32 交叉工具链)。

构建系统采用经典的 GNU Make 递归构建 架构:根 Makefile 负责平台检测、SoC 选择与目标分发;rule.mk 被各子目录 Makefile 包含,提供统一的编译/链接/归档规则;每个 lib/ 子目录与应用目录各自维护独立 Makefile,由顶层目标递归调用。关键设计意图:

  • 一套规则、多处复用:rule.mk 中的模式规则(pattern rule)对 .c/.s/.S/.cpp 统一处理,任何子工程包含该文件即可获得一致的编译行为;
  • 链接脚本动态生成:sdk.ld 不是手写文件,而是通过预处理器(-E -P)展开 cpu/$(CPU)/sdk_ld.c 生成,从而可以根据 $(CPU)、$(SoC) 等变量自动裁剪内存布局;
  • 双平台一致体验:同一套 Makefile 在 Linux 与 Windows 上均可工作,HOST_OS 自动检测差异(路径分隔符、依赖顺序、后处理脚本执行方式);
  • 增量构建:每个目标文件同时生成 .d 依赖文件并通过 -include $(deps) 纳入构建图,头文件变化可触发精确重编。

SDK 官方支持两种构建方式(见 README.md):

  • Codeblocks 构建:进入工程目录双击 .cbp 文件直接构建;
  • Makefile 构建:在 apps/app_cfg 选择目标,双击 make_prompt 打开构建环境后执行 make。

Architecture

构建系统的整体架构如下:

flowchart TD
    subgraph sg_Entry["构建入口"]
        Make["make(根 Makefile)"]
        Prompt["make_prompt.bat<br/>Windows 环境"]
    end

    subgraph sg_Top["顶层编排(Makefile)"]
        All["all / app"]
        Libs["libs / lib target=..."]
        Clean["clean / clean_libs"]
        Debug["debug(GDB)"]
        CBP["cbp / winmake / winrelease"]
    end

    subgraph sg_Rules["通用构建规则(rule.mk)"]
        Object["object(编译)"]
        Out["out(链接)"]
        Archive["archive(归档)"]
    end

    subgraph sg_Sub["递归子工程"]
        Apps["apps/ 各应用 Makefile"]
        Lib["lib/ 各库 Makefile"]
    end

    subgraph sg_Platform["平台与库配置"]
        PlatMK["tools/platform/Makefile.$(SoC)"]
        IncMK["include_lib/Makefile.include"]
    end

    subgraph sg_Tools["工具链与输出"]
        CC["clang(CC)"]
        LD["ld(LD)"]
        AR["ar(AR)"]
        ELF["OUTPUT_ELF 固件"]
        Script["POST_BUILD_SCRIPT 后处理"]
    end

    Prompt -->|"设置 PATH 并打开 cmd"| Make
    Make --> All
    Make --> Libs
    Make --> Clean
    Make --> Debug
    Make --> CBP
    All --> Apps
    Libs --> Lib
    Apps --> Object
    Lib --> Archive
    Object --> Out
    Archive --> Out
    Out --> CC
    Out --> LD
    Out --> Script
    Archive --> AR
    LD --> ELF
    PlatMK -->|"-include"| Make
    IncMK -->|"-include"| Make

架构分层说明:

  • 入口层:用户执行 make(Linux 或 Windows cmd);Windows 下可先运行 make_prompt.bat 将 tools\utils 加入 PATH,保证 make 及辅助脚本可用。
  • 顶层编排层(根 Makefile):负责检测 HOST_OS、读取 SoC 变量、定义公共变量(ROOT、MAKE_RULE、AR_DIR 等),并通过 -include 引入平台 Makefile 与库配置,然后按目标分发到 apps/ 或 lib/。
  • 规则层(rule.mk):被各子工程 Makefile 包含,定义了 object(编译)、out(链接)、archive(归档)、version 等通用目标以及 .c/.s/.S/.cpp → .o 的模式规则。根 Makefile 通过 export MAKE_RULE = $(ROOT)/rule.mk 暴露该文件路径。
  • 子工程层:apps/ 下的应用工程与 lib/ 下的每个库各自提供 Makefile,它们定义本工程的源文件列表(objs、obj_ls、obj_bs、objs_cxx),并复用 rule.mk 完成实际构建。
  • 平台/库配置层:tools/platform/Makefile.$(SoC) 定义芯片相关的工具链路径、CPU、CC/LD/AR、OUTPUT_ELF、DIR_OUTPUT、POST_BUILD_SCRIPT 等;include_lib/Makefile.include 负责接入 include_lib/liba/$(CPU) 下的预编译静态库(SYS_LIBS/LIBS)。

顶层 Makefile:构建编排入口

根目录 Makefile 是构建系统的总控文件。文件开头以注释形式给出了全部常用用法(Makefile):

# -----------------Usage --------------------------

# 1.编译apps:		make;
# 2.清理apps:		make clean;
# 3.编译全部libs:	make libs;
# 4.清理全部libs:	make clean_libs;
# 5.编译单独lib:	make lib target=...;
# 6.清理单独lib:	make clean_lib target=...;
# 7.GDB 调试:		make debug;

来源:Makefile

主机平台检测与公共变量

构建系统在进入任何子工程前,先完成主机平台检测与公共变量导出(Makefile):

# --------------basic setting-----------------------
ifeq ($(shell uname),Linux)
export HOST_OS = linux
export SLASH=/
export ADDITION_DEP=libs
else
export HOST_OS = windows
export SLASH=\\
export ADDITION_DEP=
endif

# 默认注释编译信息(None/@)
export V=@
export jtag ?=n

# --------------common var begin-----------------------
export ROOT=$(abspath .)
export MAKE_RULE = $(ROOT)/rule.mk
export AR_DIR = \
  	$(ROOT)/include_lib/liba/$(CPU)
export AR_TARGET = $(shell find lib -maxdepth 1 -type d)
# --------------common var end-----------------------

关键设计点:

  • HOST_OS 自动检测:通过 uname 区分 Linux/Windows。SLASH 随之切换(/ 或 \),保证路径拼接在两种平台都正确。
  • ADDITION_DEP 的平台差异:Linux 上为 libs,Windows 上为空。这意味着 Linux 下执行 make 会先构建全部 libs 再构建 apps(因为 all: pre_make $(ADDITION_DEP)),而 Windows 下预编译库已随 SDK 提供、无需重编,因此依赖为空——避免在 Windows 上重复编译大量库源码。
  • V=@ 静默模式:默认用 @ 抑制命令回显,只有 + CC xxx / + AS xxx 这类摘要信息;需要完整输出时可覆盖 V 为空。
  • jtag ?=n:?= 赋值允许用户在命令行覆盖(make jtag=y),用于切换 GDB/JTAG 调试模式。
  • ROOT=$(abspath .):以仓库根目录为基准,所有子工程通过 $(ROOT) 引用绝对路径,避免递归 make 时相对路径错乱。
  • AR_TARGET:通过 find lib -maxdepth 1 -type d 自动发现 lib/ 下的全部库子目录,供 dry_run 等目标批量处理。

SoC 目标芯片选择

芯片型号通过 SoC 变量控制,默认 bd29(Makefile):

#配置下载目标SoC(br18/br21/br22/br23/br25/br26/bd29)
#export前面不要有空格,会导致文件sync异常
#export SoC?=br18
#export SoC?=br21
#export SoC?=br22
#export SoC?=br23
#export SoC?=br25
#export SoC?=br26
export SoC?=bd29
# export SoC?=br30
# export SoC?=br34
# export SoC?=br28

支持的芯片包括 br18/br21/br22/br23/br25/br26/bd29/br30/br34/br28。SoC 变量随后被 -include $(ROOT)/tools/platform/Makefile.$(SoC) 使用,即选择 tools/platform/Makefile.bd29 等平台文件。注释中特别强调"export 前面不要有空格,会导致文件 sync 异常"——这是 Makefile 语法陷阱(export 前空白会使该行不被识别为 export 指令),体现了该 SDK 对构建产物同步流程的敏感依赖。

核心目标定义

根 Makefile 声明了全部 phony 目标并定义了主要目标(Makefile):

.PHONY: all clean lib clean_lib libs clean_libs dry_run debug usage cbp winmake app

all: pre_make $(ADDITION_DEP)
	@$(MAKE) -C apps || exit 1

app: pre_make
	@$(MAKE) -C apps || exit 1

libs:
	@$(MAKE) -C lib || exit 1

clean:
	@echo "Clean ..."
	@find . -name "*.d" -delete
	@find . -name "*.o" -delete

uboot_bt:
	@$(MAKE) clean -C lib/btctrler -f Makefile_uboot || exit
	@$(MAKE) archive -C lib/btctrler -f Makefile_uboot || exit 1
  • all 是默认目标:先执行 pre_make(由平台 Makefile 提供的准备步骤),再按需构建 libs(仅 Linux),最后递归进入 apps/ 执行 make;任何一步失败都通过 || exit 1 立即中断并传递错误码。
  • app 只构建应用层,跳过库依赖。
  • libs 递归进入 lib/ 构建全部库;clean 用 find 全仓删除 .d 与 .o,实现全量清理。
  • uboot_bt 是特例:用 -f Makefile_uboot 指定备用 makefile 构建 lib/btctrler 的 uboot 版本(先 clean 后 archive),说明同一库可以通过不同 makefile 产出不同变体。

调试与工程生成目标

TERMINAL :=gnome-terminal

debug:
	$(SHELL) $(DIR_OUTPUT)/run_jtag.sh $(OUTPUT_ELF)

debug 目标直接执行平台 Makefile 生成的 run_jtag.sh 脚本,把 $(OUTPUT_ELF)(ELF 可执行文件)传给 GDB 调试环境;被注释掉的 gnome-terminal 行保留了旧版启动远程 GDB 的方式,供参考。

cbp、winmake、winrelease 三个目标用于为 Windows/CodeBlocks 用户生成独立工程(Makefile):

cbp:
	$(V) python3 /opt/utils/gen_cbp.py --cc_path $(CC) --ar_path $(AR) \
		--ld_path $(LD) --make_log make_log.txt \
		--prefix `realpath .` \
		--ignore_dirs `realpath lib` \
		--gen_ld gen_sdk_ld.bat \
		--gen_ld_cc C:\\JL\\pi32\\bin\\clang.exe \
		--gen_ld_incs C:\\JL\\pi32\\pi32v2-include \
		--outdir `realpath ../cbp_out` \
		--cbp_title $(CBP_TITLE) \
		--cbp_compiler $(CBP_COMPILER) \
		$(CBP_ADD_OPT) \
		--cbp_position $(CBP_POSITION) \
		--post_build $(DIR_OUTPUT)/download.bat \
		--other_files $(DIR_OUTPUT) $(OTHER_FILES)
	$(V) python3 /opt/utils/gen_winmake.py --cc_path $(CC) --ar_path $(AR) \
		--ld_path $(LD) --make_log make_log.txt \
		--prefix `realpath .` \
		--ignore_dirs `realpath lib` \
		--outdir `realpath ../cbp_out` \
		--other_files $(DIR_OUTPUT)/uboot.bin \
			$(DIR_OUTPUT)/download.bat	\
			$(OTHER_FILES) \
		--generate_entry make_prompt.bat \
		--entry_template make_prompt.bat \
		--other_dirs tools/utils

这些目标调用 /opt/utils/gen_cbp.py 与 /opt/utils/gen_winmake.py 两个 Python 生成器:

  • gen_cbp.py 依据当前编译参数(CC/AR/LD 路径、头文件、OTHER_FILES)生成 CodeBlocks 工程(.cbp),并附带 gen_sdk_ld.bat(Windows 下重新生成 sdk.ld 的批处理)、download.bat(烧录脚本)等配套文件,输出到 ../cbp_out;
  • gen_winmake.py 进一步生成 Windows 版 make 环境(make_prompt.bat 入口、uboot.bin、download.bat 等),输出到 ../winmake_out;
  • winrelease 将 cbp 与 winmake 的输出合并到 ../merge_out,形成分发给最终用户的完整工程包。

OTHER_FILES 变量集中声明了必须随工程同步的额外文件(Makefile):

OTHER_FILES = apps/config/eq_tab.h
OTHER_FILES += apps/config/eq_tab_coeff.h

ifeq ($(APP_CASE),dongle)
	OTHER_FILES += \

else
	OTHER_FILES += apps/$(APP_CASE)/include/bt_ble.h \

endif

OTHER_FILES 会原样传给 Python 生成器,保证 eq_tab.h(EQ 参数表)、bt_ble.h(蓝牙配置头)等"生成/同步文件"也进入导出工程。APP_CASE 变量区分不同应用场景(如 dongle 与普通 app 的头文件差异),说明同一 SDK 可以通过该变量裁剪应用变体。

平台与库配置的挂载

根 Makefile 的末尾通过 -include 挂载平台与库配置(Makefile):

-include $(ROOT)/tools/platform/Makefile.$(SoC)
-include $(ROOT)/include_lib/Makefile.include
  • Makefile.$(SoC):按芯片型号加载平台定义(工具链路径、CPU、编译参数 CC_ARGS/LD_ARGS、输出路径 DIR_OUTPUT/OUTPUT_ELF、后处理脚本 POST_BUILD_SCRIPT 等),这也是 pre_make、run_jtag.sh 等目标的定义来源;
  • Makefile.include:接入预编译库,定义 SYS_LIBS/LIBS 供链接阶段使用。

-include 前缀表示文件不存在时静默忽略,避免在缺少平台文件时直接报错中断。

rule.mk:通用编译与链接规则

rule.mk 是被各子工程(apps/ 与每个 lib/ 子目录)包含的共享规则文件,是"编译流程"的心脏。它假定调用方已定义 objs(C 目标)、objs_cxx(C++ 目标)、obj_ls(小写 .s 汇编目标)、obj_bs(大写 .S 汇编目标)四类源文件列表。

依赖文件推导

文件开头将所有目标转换为绝对路径,并由 .o 推导出 .d 依赖文件(rule.mk):

objs:= $(abspath $(objs))
objs_cxx:= $(abspath $(objs_cxx))
obj_ls:= $(abspath $(obj_ls))
obj_bs:= $(abspath $(obj_bs))
deps = $(objs:.o=.d)
deps += $(objs_cxx:.o=.d)
deps += $(obj_ls:.o=.d)
deps += $(obj_bs:.o=.d)

deps 是四类目标文件对应 .d 文件的并集。文件末尾的 -include $(deps)(rule.mk)将这些依赖文件纳入构建图——这是增量构建的基础:头文件变更时,make 依据 .d 中记录的依赖关系精确重编受影响的源文件。

目标依赖链

.PHONY: out archive clean dry_run

out: object 
	...(链接阶段,见下文)

archive: object 
	...(归档阶段,见下文)

run: object
	@echo "dry run for YCM server"

object: version $(obj_ls) $(obj_bs) $(objs) $(objs_cxx) rm_lib

依赖链为 object → version + 全部目标文件 + rm_lib,即:先处理版本文件,再编译所有源码,最后按需清理旧库;out(链接)与 archive(归档)都依赖 object,保证任何产物生成前源码已是最新。

版本文件生成

version:
	@[ -f cpu/$(CPU)/dual_uvc_version.z ] && $(VER) cpu/$(CPU)/dual_uvc_version.z cpu/$(CPU)/dual_uvc_version.S || true

如果存在 dual_uvc_version.z(版本描述文件),则用 $(VER) 工具把它展开成汇编文件 dual_uvc_version.S,使固件内嵌版本信息;文件不存在时 || true 容忍跳过。

编译模式规则

四类源文件对应四条模式规则,以 C 规则为例(rule.mk):

$(objs):%.o:%.c
	@$(CC) $(CC_ARGS) $(CC_DEFINE) -MM -MT "$(<:.c=.o)" $(SYS_INCLUDES) $(includes) $< > $(@:.o=.d)
	@echo + CC $<
ifeq ($(GEN_LIB),y)
	$(V) -$(CC) $(SYS_INCLUDES)  $(includes) $(CC_ARGS) $(CC_DEFINE) -c $< -o $@
else
	$(V) $(CC) $(SYS_INCLUDES)  $(includes) $(CC_ARGS) $(CC_DEFINE) -c $< -o $@
endif

汇编规则(.s/.S)与 C++ 规则(.cpp)结构相同,差异仅在预处理宏与回显前缀(+ AS、+ CXX)(rule.mk)。每个编译步骤的设计意图:

  1. 先产依赖后编译:-MM -MT 生成 .d 文件,-MT "$(<:.c=.o)" 明确指定依赖文件描述的目标,避免路径推导歧义;
  2. -D__ASSEMBLY__:汇编规则额外定义 __ASSEMBLY__ 宏,供头文件区分 C/汇编语境;
  3. GEN_LIB=y 容错编译:库构建模式下命令前缀加 -(-$(CC)),编译失败不中断 make——配合后续 rm_lib 删除旧库,宁可产出缺库也不让构建系统在中间库上继续链接,同时避免误报导致整个 libs 目标失败;
  4. 静默回显:@echo + CC $< 在 V=@ 下输出人类可读的编译进度。

链接流程(out 目标)

out 是应用工程的最终产物目标,完成链接脚本生成、后处理脚本生成与链接三步(rule.mk):

out: object 
	@$(CC) -MM $(SYS_INCLUDES) $(includes) -D__LD__ $(CC_DEFINE) $(ROOT)/cpu/$(CPU)/sdk_ld.c > $(ROOT)/cpu/$(CPU)/sdk_ld.d
	@[ -f $(ROOT)/cpu/$(CPU)/tools/download.c ] && \
	$(CC) -MM $(SYS_INCLUDES) $(includes) -D__LD__ $(CC_DEFINE) $(ROOT)/cpu/$(CPU)/tools/download.c > $(ROOT)/cpu/$(CPU)/tools/download.d || true
ifeq ($(NEED_USED_LIST),y)
	@$(CC) -MM $(SYS_INCLUDES) $(includes) -D__LD__ $(CC_DEFINE) $(ROOT)/apps/$(APP_CASE)/sdk_used_list.c > $(ROOT)/apps/$(APP_CASE)/sdk_used_list.d
	$(V) $(CC) $(SYS_INCLUDES) $(includes) -E -D__LD__ $(CC_DEFINE) -P $(ROOT)/apps/$(APP_CASE)/sdk_used_list.c -o $(ROOT)/apps/$(APP_CASE)/sdk_used_list.used
endif
	$(V) $(CC) $(SYS_INCLUDES) $(includes) -E -D__LD__ $(CC_DEFINE) -P $(ROOT)/cpu/$(CPU)/sdk_ld.c -o $(ROOT)/cpu/$(CPU)/sdk.ld
ifneq (,$(wildcard $(ROOT)/cpu/$(CPU)/tools/download.c))
	$(V) $(CC) $(SYS_INCLUDES) $(includes) -E -D__LD__ $(CC_DEFINE) -P $(ROOT)/cpu/$(CPU)/tools/download.c -o $(POST_BUILD_SCRIPT) || true 
endif
	$(V) $(LD) $(LD_ARGS) -o $(OUTPUT_ELF) $(objs) $(obj_ls) $(obj_bs) $(SYS_LIBS) $(LIBS) $(LINKER) 
  • 链接脚本生成:sdk_ld.c 是一个"用 C 语法书写的链接脚本模板",通过 -E -P(预处理、去行号)展开为真正的 sdk.ld;-D__LD__ 宏让同一文件在"作为链接脚本模板"与"作为普通 C 文件"两种语境下取不同分支。这是本 SDK 构建系统最有特色的设计:内存布局可以用 C 的宏与条件编译动态推导,按 CPU/SoC 自动适配。
  • 后处理脚本生成:cpu/$(CPU)/tools/download.c 以同样的预处理方式生成 $(POST_BUILD_SCRIPT)(即下载/烧录脚本,如 download.bat)。文件不存在时用 || true/wildcard 双重保护,保证可裁剪平台也能通过。
  • NEED_USED_LIST=y 可选环节:为 app 生成 sdk_used_list.used(被使用的 SDK 符号清单),供裁剪/审计使用。
  • 链接:$(LD) $(LD_ARGS) 将四类目标文件与 $(SYS_LIBS)(系统库)、$(LIBS)(预编译 lib.a)以及 $(LINKER)(脚本文件)链接为 $(OUTPUT_ELF)。

链接后的收尾动作(rule.mk):

ifneq ($(cibuild),y)
ifneq ($(HOST_OS),windows)
	$(V) /opt/utils/check-mix-diff-cpu $(OUTPUT_ELF).0.5.precodegen.bc || (/opt/utils/view-target-cpu $(LD_ARGS) -o $(objs) $(obj_ls) $(obj_bs) $(SYS_LIBS) $(LIBS) $(LINKER) && exit 1)
ifeq ($(jtag),n)
	@cd $(DIR_OUTPUT) && bash $(POST_BUILD_SCRIPT) $(ELF)
else
	@cd $(DIR_OUTPUT) && bash $(POST_BUILD_SCRIPT) $(ELF) "_jtag"
endif
endif
endif
ifeq ($(HOST_OS),windows)
	@cd $(DIR_OUTPUT) && $(POST_BUILD_SCRIPT) $(ELF)
endif
  • CPU 混用检查:check-mix-diff-cpu 校验 precodegen.bc(LLVM 位码)的 CPU 目标;不一致时用 view-target-cpu 打印实际目标并 exit 1 中止——防止把为其他芯片编译的中间产物混入链接;
  • 后处理执行:Linux 下用 bash 执行 $(POST_BUILD_SCRIPT) 并传入 $(ELF) 参数(jtag 模式下附加 "_jtag" 参数,生成带调试符号的变体);Windows 下直接执行;
  • cibuild=y:CI 构建模式跳过本机后处理,说明该规则同时服务本地开发与 CI 流水线。

静态库归档(archive 目标)

lib/ 下的库工程使用 archive 目标产出 .a(rule.mk):

archive: object 
	$(V) $(AR) $(AR_ARGS) $(AR_OUT) $(objs) $(obj_ls) $(obj_bs) $(objs_cxx)
ifeq ($(OVERRIDE),y)
	$(V) $(OVERRIDE_SEG) --input $(AR_OUT) --output $(AR_OUT_NEW) --code_seg ".$(MOUDLE_NAME).$(ORSEG_NAME).text"
endif

归档时把 C/C++/汇编目标一并打包进 $(AR_OUT)。OVERRIDE=y 时额外调用 $(OVERRIDE_SEG) 工具按 --code_seg ".$(MOUDLE_NAME).$(ORSEG_NAME).text" 重排代码段——这是 Jieli 的"段覆盖"机制:允许库模块的代码段独立命名,链接时可用同名段覆盖默认实现(典型用于 ROM 化/补丁场景)。

配套的 rm_lib 目标(rule.mk)在 GEN_LIB=y 时先删除旧归档文件,避免增量编译残留的过期目标污染新库:

rm_lib:
ifeq ($(GEN_LIB),y)
	@[ -f $(AR_OUT) ] && rm $(AR_OUT) || true
endif

Windows 构建环境与入口

make_prompt.bat 是 Windows 用户进入构建环境的入口(make_prompt.bat):

SET SCRIPT_PATH=%~dp0%
set PATH=%SCRIPT_PATH%\tools\utils;%PATH%

cmd

脚本仅做两件事:把自身所在目录下的 tools\utils 加入 PATH(保证 make、Python 辅助脚本等工具可被直接调用),然后打开一个交互式 cmd 窗口。用户在 README 指引下于该窗口执行 make 即可构建(见 README.md)。tools\utils 同时被 cbp/winmake 目标通过 --other_dirs tools/utils 复制进导出工程,保证导出的 Windows 工程自包含。

核心流程:从 make 到固件

一次典型的 make(Linux 环境)端到端时序如下:

sequenceDiagram
    participant Dev as 开发者
    participant Top as 根 Makefile
    participant Rule as rule.mk
    participant CC as clang(CC)
    participant LD as 链接器(LD)
    participant Post as POST_BUILD_SCRIPT

    Dev->>Top: make
    Top->>Top: 检测 HOST_OS(uname)
    Top->>Top: 读取 SoC=bd29 → include Makefile.bd29
    Top->>Top: include include_lib/Makefile.include
    Top->>Top: all → pre_make + libs(Linux)
    Top->>Rule: $(MAKE) -C lib(libs 目标)
    Rule->>Rule: object → version + 编译全部源文件
    Rule->>CC: 模式规则编译 .c/.s/.S/.cpp → .o + .d
    Rule->>Rule: archive → ar 打包 lib.a
    Top->>Rule: $(MAKE) -C apps
    Rule->>Rule: object 重新编译应用层
    Rule->>CC: 预处理 sdk_ld.c → sdk.ld(-E -P -D__LD__)
    Rule->>CC: 预处理 download.c → POST_BUILD_SCRIPT
    Rule->>LD: 链接 objs + SYS_LIBS + LIBS → OUTPUT_ELF
    Rule->>Post: check-mix-diff-cpu 校验
    Rule->>Post: bash download.bat $(ELF)
    Post-->>Dev: 生成可烧录固件 / 下载到目标板

流程要点解读:

  1. 环境准备:根 Makefile 先确定 HOST_OS 与 SoC,据此加载 tools/platform/Makefile.bd29 和 include_lib/Makefile.include,得到工具链路径、编译/链接参数与库列表;
  2. 库优先(Linux):all 的 $(ADDITION_DEP)=libs 使 lib/ 全部库先被编译归档,之后应用链接时才能引用最新 lib.a;
  3. 应用编译:$(MAKE) -C apps 进入应用工程,应用 Makefile 包含 rule.mk,走 object → out 依赖链;
  4. 脚本生成:out 中用预处理方式从 sdk_ld.c 生成 sdk.ld、从 download.c 生成烧录脚本——构建配置(内存布局、下载流程)本身由源码驱动;
  5. 链接与后处理:链接产出 OUTPUT_ELF,经 CPU 混用校验后执行 POST_BUILD_SCRIPT,最终得到可烧录镜像。

构建目标的分发关系可归纳为:

flowchart TD
    M["make"] --> Q{"ADDITION_DEP?"}
    Q -->|"linux: libs"| L["$(MAKE) -C lib<br/>(全部库 archive)"]
    Q -->|"windows: 空"| A["$(MAKE) -C apps"]
    L --> A
    A --> O["object 编译"]
    O --> U["out 链接"]
    U --> P["后处理 + 烧录"]
    M2["make lib target=xxx"] --> L2["仅编译指定库"]
    M3["make clean"] --> C["删除全仓 .d/.o"]
    M4["make debug"] --> D["run_jtag.sh + GDB"]
    M5["make cbp/winmake"] --> G["Python 生成器导出工程"]

使用示例

基本用法:全量构建

按 README 指引,先在 apps/app_cfg 选择目标,再执行 make(README.md):

# 在仓库根目录执行(Windows 下先在 make_prompt 中打开 cmd)
make

等价于执行根 Makefile 的 all 目标(Makefile):

all: pre_make $(ADDITION_DEP)
	@$(MAKE) -C apps || exit 1

清理与库操作

make clean          # 删除全仓 *.d 与 *.o(增量构建痕迹)
make libs           # 编译 lib/ 下全部库
make clean_libs     # 清理全部库
make lib target=btctrler   # 只编译指定库
make clean_lib target=btctrler
make usage          # 打印全部目标用法

工程导出(为 Windows/CodeBlocks 用户)

make cbp           # 生成 CodeBlocks 工程到 ../cbp_out
make winmake       # 生成 Windows make 工程到 ../winmake_out
make winrelease    # 合并两者到 ../merge_out

cbp 目标调用 gen_cbp.py 时会把当前编译环境参数固化为工程配置(Makefile):

cbp:
	$(V) python3 /opt/utils/gen_cbp.py --cc_path $(CC) --ar_path $(AR) \
		--ld_path $(LD) --make_log make_log.txt \
		--prefix `realpath .` \
		--ignore_dirs `realpath lib` \
		--gen_ld gen_sdk_ld.bat \
		--gen_ld_cc C:\\JL\\pi32\\bin\\clang.exe \
		--gen_ld_incs C:\\JL\\pi32\\pi32v2-include \
		--outdir `realpath ../cbp_out` \
		--post_build $(DIR_OUTPUT)/download.bat \
		--other_files $(DIR_OUTPUT) $(OTHER_FILES)

调试

make jtag=y debug   # 通过 run_jtag.sh 启动 GDB 调试(可附加 jtag 变体)

debug 目标(Makefile):

debug:
	$(SHELL) $(DIR_OUTPUT)/run_jtag.sh $(OUTPUT_ELF)

配置选项

变量类型默认值说明
SoCstringbd29目标芯片型号,可选 br18/br21/br22/br23/br25/br26/bd29/br30/br34/br28;决定加载 tools/platform/Makefile.$(SoC)
HOST_OSstring自动检测(uname)linux 或 windows;决定 SLASH、ADDITION_DEP 与后处理执行方式
SLASHstring/(Linux)/ \(Windows)路径分隔符
ADDITION_DEPstringlibs(Linux)/ 空(Windows)all 目标的额外前置依赖,控制是否先编译全部库
Vstring@命令回显控制;置空可显示完整编译命令
jtagstringny 时后处理脚本附加 _jtag 参数,产出 JTAG 调试变体
APP_CASEstring由应用 Makefile 定义应用场景标识(如 dongle),影响 OTHER_FILES 等
GEN_LIBstring未设置y 时编译失败不中断(容错),且归档前删除旧 AR_OUT
OVERRIDEstring未设置y 时启用 OVERRIDE_SEG 代码段覆盖机制
NEED_USED_LISTstring未设置y 时生成 sdk_used_list.used 符号使用清单
cibuildstring未设置y 时跳过本机后处理,适配 CI 环境
CPUstring由 Makefile.$(SoC) 定义芯片内核标识,决定 cpu/$(CPU) 目录与 AR_DIR 路径
CC / LD / ARpath由 Makefile.$(SoC) 定义工具链可执行文件路径(clang / ld / ar)
OUTPUT_ELFpath由 Makefile.$(SoC) 定义链接输出的 ELF 文件路径
DIR_OUTPUTpath由 Makefile.$(SoC) 定义构建输出目录
POST_BUILD_SCRIPTpath由 Makefile.$(SoC) 定义由 download.c 预处理生成的后处理脚本
LIBS / SYS_LIBSlist由 include_lib/Makefile.include 定义链接阶段使用的预编译静态库
ROOTpath$(abspath .)仓库根目录绝对路径
MAKE_RULEpath$(ROOT)/rule.mk共享规则文件路径(export 给子工程)
AR_TARGETlistfind lib -maxdepth 1 -type dlib/ 下全部库子目录
OTHER_FILESlisteq_tab.h、eq_tab_coeff.h、bt_ble.h随工程导出/同步的额外文件

API 参考:Make 目标

目标命令行为
allmake默认目标:pre_make + 按 ADDITION_DEP 构建 libs,再递归 $(MAKE) -C apps;失败即 exit 1
appmake app仅编译应用层(跳过 libs)
libsmake libs递归 $(MAKE) -C lib 编译全部库
cleanmake cleanfind 删除全仓 *.d 与 *.o
libmake lib target=NAME编译单个指定库(target 指定库目录)
clean_libmake clean_lib target=NAME清理单个指定库
clean_libsmake clean_libs清理全部库
debugmake debug执行 $(DIR_OUTPUT)/run_jtag.sh $(OUTPUT_ELF) 启动 GDB
uboot_btmake uboot_bt以 -f Makefile_uboot 构建 lib/btctrler 的 uboot 版本
dry_runmake dry_run对所有库与应用执行编译干跑(供 YCM 服务器索引)
cbpmake cbp调用 gen_cbp.py/gen_winmake.py 生成 CodeBlocks 工程到 ../cbp_out
winmakemake winmake生成 Windows make 工程到 ../winmake_out
winreleasemake winrelease合并 cbp 与 winmake 输出到 ../merge_out
usagemake usage打印全部目标用法说明

rule.mk 内部目标(子工程使用):

目标前置依赖行为
objectversion + 全部源文件 + rm_lib编译所有目标文件并生成 .d 依赖
outobject生成 sdk.ld 与后处理脚本,链接产出 OUTPUT_ELF,执行后处理
archiveobject用 ar 打包目标文件为 AR_OUT,可选 OVERRIDE_SEG 段覆盖
version—用 $(VER) 将 dual_uvc_version.z 展开为 dual_uvc_version.S
runobjectYCM 干跑占位
rm_lib—GEN_LIB=y 时删除旧归档

失败模式、边界情况与并发

编译失败的中断语义

  • 应用构建是严格失败的:$(MAKE) -C apps || exit 1 与 $(MAKE) -C lib || exit 1 保证任何子工程失败都会向上传递错误码,make 立即中止(Makefile)。
  • 库构建是容错失败的:GEN_LIB=y 时编译命令加 - 前缀(-$(CC)),单文件编译失败不中断(rule.mk)。此时配合 rm_lib 删除旧库,避免"半新半旧"的库被后续链接误用——代价是链接阶段可能因缺库报错,但错误信号更真实。

平台差异导致的边界行为

  • Windows 上 all 的 ADDITION_DEP 为空,不会预编译 libs,直接构建 apps;若用户修改了库源码却未重编库,链接将使用旧 lib.a。这是设计取舍:Windows 默认假设预编译库是交付物的一部分。
  • Windows 上跳过 check-mix-diff-cpu CPU 校验(ifneq ($(HOST_OS),windows)),而 Linux 上强制校验(rule.mk)。
  • sdk_ld.c/download.c 的依赖文件生成使用 && ... || true 与 wildcard 双保险:文件不存在时静默跳过,保证裁剪平台(无 tools/download.c)也能通过编译。

并发构建

Makefile 未在内部强制 -j 并行度,依赖 .d 的增量机制天然支持用户自行执行 make -jN 并行编译。需要关注的并发风险:

  • object 依赖链中 version 会写 cpu/$(CPU)/dual_uvc_version.S,而该文件若同时是其他目标的源文件,并行构建下存在竞争写入风险;SDK 通过把 version 置于所有源文件之前(object: version $(obj_ls) ...)串行化此步骤来规避;
  • out 目标中多个 -MM/-E 步骤共享 sdk.ld 输出路径,同一时刻仅允许一个 out 在执行——链接与脚本生成天然串行;
  • rm_lib 删除 AR_OUT 与 archive 打包之间若被并行调用会互相干扰,因此 archive: object 的依赖顺序保证清理先于打包完成。

其他已知边界

  • export SoC?=bd29 注释明确警告"export 前面不要有空格,会导致文件 sync 异常":Makefile 语法要求 export 必须是行首指令,否则变量不会导出到子 make,构建产物同步流程将失效;
  • OTHER_FILES 在 APP_CASE=dongle 时条件为空(OTHER_FILES += \ 后无内容),此时若 make cbp 导出工程,EQ 头文件不会被同步——这是面向 dongle 变体的显式裁剪;
  • dry_run 目标对每个库依次执行 clean → dry_run → clean,其目的是为 YCM 生成索引数据库,不是真实构建;中途失败不会中断(循环体内无 exit),符合"干跑"语义。

性能与运维注意

  • 增量构建:每文件生成 .d,头文件变更触发精确重编,避免全量重编;make clean 删除全部 .d/.o 即回到全量构建状态。
  • 静默输出:V=@ 默认仅打印 + CC/+ AS 摘要,大量文件构建时输出量小、日志可读;排查问题时可 make V= clean 观察完整命令。
  • CPU 混用防线:check-mix-diff-cpu 在链接后校验 LLVM 位码的 CPU 目标,防止 include_lib 中混入错误芯片的预编译产物导致固件无法启动——这是固件类项目特有的可靠性闸门。
  • CI 适配:cibuild=y 跳过本机后处理脚本,服务器上只需产出 OUTPUT_ELF 与镜像,烧录步骤留给后续流水线。
  • make debug 依赖 $(DIR_OUTPUT)/run_jtag.sh(由平台 Makefile 提供),GDB 调试前需确保 JTAG 代理已启动。

扩展点

构建系统为二次开发预留了清晰的自定义入口:

  1. 新增芯片:在 tools/platform/ 下新增 Makefile.$(新SoC),定义 CPU、工具链、输出路径与后处理脚本;在根 Makefile 的 SoC 列表中启用即可。
  2. 新增库:在 lib/ 下新建目录并放置 Makefile(定义 objs 等变量、包含 $(MAKE_RULE)、声明 archive 目标);AR_TARGET 的 find 自动发现机制与 libs 目标会将其纳入构建。
  3. 新增应用:在 apps/ 下新增应用目录,复用 rule.mk 的 out 目标完成链接;APP_CASE 变量区分应用变体并驱动 OTHER_FILES 同步。
  4. 段覆盖(patch/ROM 化):库 Makefile 设置 OVERRIDE=y 并配合 OVERRIDE_SEG 的 --code_seg ".$(MOUDLE_NAME).$(ORSEG_NAME).text",可将模块代码段独立命名,供链接期覆盖。
  5. 工程导出定制:cbp/winmake 支持 CBP_TITLE、CBP_COMPILER、CBP_ADD_OPT、CBP_POSITION 等变量,以及 OTHER_FILES 追加同步文件,可定制导出的 IDE 工程。
  6. 链接脚本模板:修改 cpu/$(CPU)/sdk_ld.c 即可用 C 宏/条件编译调整内存布局,无需手工维护 sdk.ld。

测试与验证

仓库根目录未提供独立的构建测试套件;构建系统的"测试"即对 apps/spp_and_le、apps/hid、apps/mesh 三个官方示例工程的编译验证(见 README.md):

  • apps/spp_and_le:SPP + LE 双模示例;
  • apps/hid:HID 设备示例;
  • apps/mesh:蓝牙 Mesh 示例。

对任何自定义修改,建议至少执行 make clean && make 全量重编并确认 check-mix-diff-cpu 通过、POST_BUILD_SCRIPT 正常产出烧录镜像。

相关链接

  • README.md(构建说明与 Toolchain 获取)
  • 根 Makefile(顶层编排与全部目标)
  • rule.mk(通用编译/链接/归档规则)
  • make_prompt.bat(Windows 构建环境入口)
  • 平台配置:tools/platform/Makefile.$(SoC)(如 Makefile.bd29)
  • 库配置:include_lib/Makefile.include 与 include_lib/liba/$(CPU)/
  • 官方示例工程:apps/spp_and_le、apps/hid、apps/mesh
Prev
项目概述与能力地图
Next
芯片系列与规格