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 确认功能开关,最后看构建日志确认镜像与烧录目标。三者对应“硬件—功能—产物”三层,逐层收敛能快速定位大多数工程结构问题。把上述机制串起来看:应用源码与 CMakeLists 定义构建目标,Kconfig 决定功能集合,设备树描述硬件连接,BSP 提供板级默认值,四者汇入 CMake 构建图;当目标为 nRF5340 双核时,sysbuild 再将其拆分为 Application Core 与 Network Core 两个镜像分别产出。掌握这条链路,就能在自定义工程中稳定地增删外设、切换功能、适配新板并管理多镜像烧录。