Zephyr 构建系统、Kconfig 与设备树

Zephyr 构建系统、Kconfig 与设备树

1. 构建入口

典型应用:

1
2
3
4
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 package 后,加载 cmake/modules/zephyr_default.cmake。该文件按严格顺序加载:

1
2
3
4
5
6
7
8
9
10
workspace/application configuration
→ extensions/version/basic_settings
→ west/ccache/root
→ zephyr_module
→ boards/snippets/arch/hwm
→ configuration_files/generated directories
→ dts
→ kconfig
→ arch/soc
→ kernel.cmake

DTS 先于 Kconfig 的主要原因是部分 Kconfig 默认值和依赖可读取设备树信息。

2. west build 到最终镜像

1
2
3
4
5
6
7
8
9
10
11
west build -b <board> <app>
└─ west 调用 CMake configure
├─ 定位 Zephyr package
├─ 发现 modules、board、SoC、arch
├─ 合并并编译 devicetree
├─ 运行 Kconfig
├─ 选择 CMake source
├─ 生成 linker script
├─ 编译 libraries + application
├─ pre0/pre1/final 多阶段链接
└─ objcopy 生成 bin/hex

常见输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
build/
├─ CMakeCache.txt
├─ build.ninja
├─ zephyr/
│ ├─ .config
│ ├─ zephyr.dts
│ ├─ include/generated/zephyr/autoconf.h
│ ├─ include/generated/zephyr/devicetree_generated.h
│ ├─ zephyr_pre0.elf / zephyr_pre1.elf
│ ├─ zephyr.elf
│ ├─ zephyr.map
│ ├─ zephyr.bin
│ └─ zephyr.hex
└─ modules/

3. CMake target 模型

CMakeLists.txt 创建:

  • zephyr_interface:承载全局 include、defines、compile/link options;
  • zephyr:通用 catch-all library;
  • 多个 zephyr_library_named() 子库;
  • app:应用源码 target;
  • zephyr_pre0zephyr_pre1zephyr_final:链接阶段。

Zephyr 不直接把所有源文件堆入一个 target。各目录使用:

1
2
3
4
zephyr_library()
zephyr_library_sources(foo.c)
zephyr_library_sources_ifdef(CONFIG_FEATURE bar.c)
zephyr_library_include_directories(...)

因此“配置为 n”通常意味着源码根本不进入编译。

4. 多阶段链接

CMakeLists.txt 最多执行三阶段:

  1. zephyr_pre0
    • section 地址仍可能变化;
  2. zephyr_pre1
    • section 大小和地址固定;
  3. zephyr_final
    • 最终 ELF。

需要额外阶段的典型功能:

  • CONFIG_DEVICE_DEPS:生成 device dependency handles;
  • CONFIG_GEN_ISR_TABLES:生成 ISR table;
  • CONFIG_USERSPACE:生成 kernel object hash table;
  • userspace application memory partitions。

生成器必须先观察预链接 ELF 中的符号/section,再产生 C/二进制对象参与下一次链接。

5. Kconfig

总入口 Kconfig 只加载 Kconfig.zephyr。后者继续聚合 arch、SoC、board、drivers、subsys、lib 和 application Kconfig。

配置来源通常按以下层次组合:

1
2
3
4
5
6
7
8
SoC/board defconfig
+ application prj.conf
+ CONF_FILE / EXTRA_CONF_FILE
+ shield/snippet/sysbuild 配置
+ 命令行 cache 配置
→ Kconfig 求解
→ build/zephyr/.config
→ autoconf.h

5.1 关键概念

  • config:定义 symbol;
  • default:满足条件时给默认值;
  • depends on:控制 symbol 可见/可选;
  • select:强制另一个 bool 为 y,不检查其依赖,需谨慎;
  • imply:弱建议;
  • choice:互斥选择;
  • menuconfig:可进入子菜单的配置项。

最终真相是 .config,不是 prj.conf。用户请求值可能因依赖不满足而被丢弃,并产生 warning。

5.2 代码使用

1
2
3
4
5
6
7
#if defined(CONFIG_FEATURE)
#endif

if (IS_ENABLED(CONFIG_FEATURE)) {
}

BUILD_ASSERT(CONFIG_VALUE >= 4);

autoconf.h 通过构建系统自动注入,无需应用手动 include。

6. Devicetree 输入合并

设备树输入大致为:

1
2
3
4
5
6
7
8
architecture/SoC .dtsi
→ board .dts
→ revision/shield overlays
→ application overlay
→ DTC preprocessing
→ binding 匹配与 schema 校验
→ zephyr.dts
→ devicetree_generated.h

核心构建逻辑在 cmake/modules/dts.cmake,解析器位于 scripts/dts/,bindings 位于 dts/bindings/

7. Binding 与 compatible

binding YAML 描述:

  • compatible
  • include 的基础 schema;
  • properties 类型、required、enum、default;
  • child-binding
  • bus/child-bus;
  • specifier cells,如 gpio-cellsinterrupt-cells

设备节点:

1
2
3
4
5
sensor@48 {
compatible = "vendor,my-sensor";
reg = <0x48>;
status = "okay";
};

驱动:

1
2
3
4
5
6
7
8
9
#define DT_DRV_COMPAT vendor_my_sensor

#define MY_INIT(inst) \
DEVICE_DT_INST_DEFINE(inst, init_fn, NULL, \
&data_##inst, &config_##inst, \
POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, \
&api);

DT_INST_FOREACH_STATUS_OKAY(MY_INIT)

匹配不是运行时字符串扫描。compatible 经生成头转换为 C 宏,实例宏在编译期展开成静态对象。

8. 常用 DTS 宏

节点定位:

1
2
3
4
5
DT_NODELABEL(name)
DT_ALIAS(name)
DT_CHOSEN(name)
DT_PATH(...)
DT_DRV_INST(inst)

属性读取:

1
2
3
4
5
6
DT_PROP(node, prop)
DT_PROP_OR(node, prop, default)
DT_REG_ADDR(node)
DT_REG_SIZE(node)
DT_IRQN(node)
DT_ENUM_IDX(node, prop)

遍历与条件:

1
2
3
4
DT_NODE_HAS_STATUS(node, okay)
DT_HAS_COMPAT_STATUS_OKAY(compat)
DT_INST_FOREACH_STATUS_OKAY(fn)
DT_FOREACH_CHILD_STATUS_OKAY(node, fn)

设备规格:

1
2
3
4
5
GPIO_DT_SPEC_GET(...)
I2C_DT_SPEC_GET(...)
SPI_DT_SPEC_GET(...)
ADC_DT_SPEC_GET(...)
PWM_DT_SPEC_GET(...)

spec 结构把 controller struct device *、pin/address/channel 和 flags 打包,应用无需重复解析 phandle cells。

9. Board、SoC 与 Arch 选择

-b <board> 触发:

  1. 从 board roots 查找 board.yml
  2. 解析 board、revision、SoC/qualifier;
  3. 加载 board DTS 和 defconfig;
  4. SoC 描述选择 architecture;
  5. 工具链和 linker 根据 architecture/SoC 配置;
  6. board runner 决定 flash/debug 命令。

Hardware model v2 可用类似:

1
board[/soc[/cpucluster]]

一个 board.yml 可以声明多个 board 或多个 SoC 变体。

10. Zephyr modules

west manifest 中的 project 可通过 zephyr/module.yml 注册:

  • CMake;
  • Kconfig;
  • DTS root;
  • board/SoC root;
  • samples/tests;
  • sysbuild。

cmake/modules/zephyr_module.cmake 和脚本扫描 workspace,生成模块列表。模块不一定在 Zephyr 主树中,例如 MCUboot、HAL、crypto 库通常位于相邻目录。

11. Sysbuild

普通 build 只构建一个 Zephyr image;sysbuild 管理多个相互依赖 image:

1
2
3
4
5
sysbuild
├─ bootloader
├─ application
├─ network/radio core image
└─ signing/merge dependencies

每个 image 有独立:

  • CMake cache;
  • .config
  • DTS;
  • build 目录。

SB_CONFIG_* 控制 sysbuild 域,不能与 image 内 CONFIG_* 混用。MCUboot 联合构建会把 slot/footer、签名和依赖参数传递给应用 image。

12. Linker 与 iterable sections

Zephyr linker script 由 arch/SoC linker fragments、公共 section 定义和应用 snippets 组合。

关键用途:

  • .text/.rodata/.data/.bss/noinit
  • init entry;
  • device objects;
  • shell commands;
  • logging metadata;
  • ztest suites;
  • network L2;
  • userspace objects。

STRUCT_SECTION_ITERABLE(type, name) 把对象放入命名 section;运行时使用 STRUCT_SECTION_FOREACH() 遍历。该模式减少显式注册代码和动态分配。

13. 常用构建命令

1
2
3
4
5
6
7
8
west build -b <board> -d build/<name> <app>
west build -d build/<name> -t menuconfig
west build -d build/<name> -t guiconfig
west build -d build/<name> -t rom_report
west build -d build/<name> -t ram_report
west build -d build/<name> -t pristine
west flash -d build/<name>
west debug -d build/<name>

需要完全重新求解 board/DTS/Kconfig 时使用:

1
west build -p always ...

不要通过手改 build/zephyr/.config 或生成头文件保存配置;下次 configure 会覆盖。

14. 构建问题定位

Kconfig warning

检查:

  • symbol 是否真实存在;
  • 依赖是否满足;
  • 是否被 choice 排除;
  • .config 中最终值;
  • menuconfig 中 help 和 dependency。

DTS error

检查:

  • build/zephyr/zephyr.dts
  • 对应 binding;
  • status = "okay"
  • reg、interrupt、clock、pinctrl cells;
  • overlay 是否实际被 CMake 发现。

__device_dts_ord_N

devicetree_generated.h 查 ordinal 对应节点,然后确认:

  • 节点 enabled;
  • 驱动 Kconfig 为 y;
  • 驱动 CMake 加入源文件;
  • compatible 与 DT_DRV_COMPAT 一致;
  • bus/controller device 也已创建。

链接溢出

查看 zephyr.maprom_reportram_report,区分:

  • flash payload;
  • RAM .data/.bss/noinit
  • thread stack;
  • heap;
  • logging buffers;
  • MCUboot header/trailer 保留区。

文章互动

阅读 --

留言

0 条留言

正在加载留言…