4. nRF Connect SDK与Zephyr RTOS工程结构

nRF Connect SDK(NCS)本质上是 Zephyr RTOS 的一个下游发行版,它在 Zephyr 之上叠加了 Nordic 的协议栈、驱动与示例。理解 NCS 工程结构,关键不是记住目录名字,而是理解四套机制如何协同:CMake 决定“编译什么、怎么编译”,Kconfig 决定“编译进哪些功能”,设备树(Devicetree) 决定“硬件长什么样、外设如何连接”,板级支持包(BSP) 决定“这块板子的默认硬件描述与配置从哪来”。四者共同作用,最终生成一个可烧录的镜像。

先看一个典型自定义应用工程的目录结构。它通常不放在 NCS 仓库内部,而是独立于 SDK 之外,通过 west 工作区引用 SDK:

my_app/
├── CMakeLists.txt          # 顶层构建入口
├── prj.conf                # 应用级 Kconfig 配置
├── boards/
│   └── nrf5340dk_nrf5340_cpuapp.overlay   # 板级设备树覆盖
├── src/
│   └── main.c
└── sysbuild.cfg            # 多镜像构建配置(可选)

顶层 CMakeLists.txt 的最小内容只有几行,它把应用注册进 Zephyr 的构建系统:

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(my_app)
target_sources(app PRIVATE src/main.c)

这里 find_package(Zephyr) 是分水岭:它加载 Zephyr 的构建框架,之后 app 这个目标才存在。target_sources 把源文件挂到应用目标上。构建命令通常为:

west build -b nrf5340dk/nrf5340/cpuapp -p always
west flash

-p always 表示每次完整重跑 CMake,避免配置缓存导致的“改了 prj.conf 却不生效”问题,这在调试 Kconfig 时非常有用。

Kconfig:功能开关的层级与优先级

Kconfig 是一套配置系统,每个配置项形如 CONFIG_XXX=y。它的值来自多个来源,优先级从低到高大致为:SoC/板级默认值 → 应用 prj.conf → 构建时 -DCONFIG_XXX=y。因此当某个功能“明明在 prj.conf 里开了却没生效”,第一反应应是检查它是否被更高优先级覆盖,或是否依赖项未满足。

例如启用日志与浮点打印:

CONFIG_LOG=y
CONFIG_LOG_MODE_IMMEDIATE=y
CONFIG_CBPRINTF_FP_SUPPORT=y
CONFIG_NEWLIB_LIBC=y

Kconfig 的依赖关系是强约束。若某选项 depends on 的条件不成立,它在 menuconfig 中根本不可见,写进 prj.conf 也会被忽略。可用 west build -t menuconfig 交互查看,或在构建输出目录的 zephyr/.config 中确认最终生效值。

设备树:把硬件连接写成可编译的数据

设备树用节点和属性描述硬件。以 nRF5340 DK 上通过 I2C 连接一颗传感器为例,应用层 overlay 文件可以这样写:

&i2c1 {
    status = "okay";
    clock-frequency = <I2C_BITRATE_FAST>;

    bme280: bme280@76 {
        compatible = "bosch,bme280";
        reg = <0x76>;
        status = "okay";
    };
};

要点有三:&i2c1 是引用 SoC 设备树中已定义的节点,overlay 只做增量修改;reg 是 I2C 从地址;compatible 是驱动匹配的字符串,必须与驱动源码中的 DT_DRV_COMPAT 一致,否则设备不会被实例化。编译后可在 build/zephyr/zephyr.dts 查看合并后的完整设备树,这是排查“节点没生效”的最直接依据。

在 C 代码中通过设备树获取硬件信息,避免硬编码:

#define SENSOR_NODE DT_NODELABEL(bme280)
static const struct i2c_dt_spec sensor = I2C_DT_SPEC_GET(SENSOR_NODE);

板级支持包与多镜像构建

BSP 由板级目录下的 board.cmake、Kconfig.defconfig、*.dts 等组成,定义了该板默认的引脚、外设状态与烧录方式。应用通过 boards/<board>.overlay 和 boards/<board>.conf 在 BSP 默认值之上做覆盖,从而做到“同一份应用代码适配多块板”。

nRF5340 是双核架构:Application Core 与 Network Core 各需一个镜像。多镜像构建由 sysbuild 管理。在 sysbuild.cfg 中可指定各镜像的配置:

SB_CONFIG_BOOTLOADER_MCUBOOT=y
SB_CONFIG_NETCORE_APP_UPDATE=y

构建时 west build -b nrf5340dk/nrf5340/cpuapp --sysbuild 会分别产出 app core 与 net core 镜像,并统一烧录。若只构建单核,则省略 --sysbuild。判断多镜像是否正确的依据是构建日志中出现的镜像列表,以及 build/ 下各子目录是否都生成了 zephyr.hex。

排查顺序建议:先看 zephyr.dts 确认硬件描述,再看 .config 确认功能开关,最后看构建日志确认镜像与烧录目标。三者对应“硬件—功能—产物”三层,逐层收敛能快速定位大多数工程结构问题。
NCS 工程四要素协同与多镜像构建 应用源码 src/main.c CMakeLists.txt Kconfig prj.conf .config 最终值 设备树 *.overlay zephyr.dts 合并结果 板级支持包 board.cmake Kconfig.defconfig CMake / west 构建系统 find_package(Zephyr) → 生成构建图 sysbuild 多镜像调度 SB_CONFIG_* 控制各镜像 Application Core 镜像 cpuapp · zephyr.hex MCUboot + 应用 Network Core 镜像 cpunet · zephyr.hex 蓝牙/802.15.4 协议栈 验证 zephyr.dts .config 构建日志

把上述机制串起来看:应用源码与 CMakeLists 定义构建目标,Kconfig 决定功能集合,设备树描述硬件连接,BSP 提供板级默认值,四者汇入 CMake 构建图;当目标为 nRF5340 双核时,sysbuild 再将其拆分为 Application Core 与 Network Core 两个镜像分别产出。掌握这条链路,就能在自定义工程中稳定地增删外设、切换功能、适配新板并管理多镜像烧录。