3. Zephyr 构建系统:CMake与west的协作、Kconfig配置系统、设备树(Devicetree)基础
Zephyr RTOS 的构建系统是驱动开发的核心基础设施。它由三个紧密协作的子系统组成:west(元构建工具)、CMake(构建生成器)、Kconfig(配置系统)和 Devicetree(硬件描述)。理解它们的分工与协作,是编写可移植、可配置驱动的第一步。
3.1 构建系统全景:west 与 CMake 的协作
Zephyr 使用 west(Zephyr 元工具)作为顶层构建协调器,底层依赖 CMake 生成具体的构建文件(如 Makefile 或 Ninja)。west 负责项目管理、多仓库同步、构建命令封装;CMake 负责编译、链接、生成目标文件。
下图展示了 west 与 CMake 在构建流程中的协作关系:
3.1.1 west 命令与工作流
west 是 Zephyr 项目的瑞士军刀。驱动开发中最常用的命令如下:
| 命令 | 功能 | 驱动开发场景 |
|---|---|---|
west init |
初始化 Zephyr 工作区 | 首次搭建开发环境 |
west update |
同步所有模块仓库 | 拉取最新驱动框架 |
west build -b <board> |
配置并编译应用 | 编译驱动测试程序 |
west flash |
烧录固件到目标板 | 验证驱动功能 |
west build -t menuconfig |
启动图形化 Kconfig 配置 | 调整驱动参数 |
west build -b <board> -p always 会强制重新配置并编译,适合在修改 Kconfig 或 Devicetree 后使用。
3.1.2 CMake 在驱动中的角色
每个 Zephyr 驱动目录下都有一个 CMakeLists.txt,它告诉构建系统:
- 哪些源文件需要编译(
target_sources) - 依赖哪些头文件路径(
zephyr_include_directories) - 是否依赖其他驱动模块(
depends_on)
一个典型的驱动 CMakeLists.txt 示例:
# drivers/sensor/my_sensor/CMakeLists.txt
zephyr_library()
zephyr_library_sources(my_sensor.c)
zephyr_library_sources(my_sensor_trigger.c)
zephyr_library_include_directories(.)
zephyr_library_link_libraries(interface__sensor)
3.2 Kconfig 配置系统
Kconfig 是 Zephyr 的编译时配置系统,用于开关驱动功能、设置参数、定义依赖关系。驱动开发者通过 Kconfig 符号控制驱动是否被编译、使用哪种模式、配置缓冲区大小等。
3.2.1 Kconfig 符号结构
每个 Kconfig 符号由 类型、提示、默认值 和 依赖 组成。驱动中常见的符号类型:
| 类型 | 含义 | 驱动示例 |
|---|---|---|
bool |
开关选项(y/n) | CONFIG_MY_SENSOR=y |
int |
整数参数 | CONFIG_MY_SENSOR_BUF_SIZE=256 |
hex |
十六进制地址 | CONFIG_MY_SENSOR_BASE_ADDR=0x4000 |
string |
字符串配置 | CONFIG_MY_SENSOR_MODEL="v2" |
下面是一个驱动 Kconfig 文件的典型结构:
# drivers/sensor/my_sensor/Kconfig
menuconfig MY_SENSOR
bool "My Sensor Driver"
depends on I2C
help
Enable support for My Sensor.
if MY_SENSOR
config MY_SENSOR_TRIGGER
bool "Enable trigger support"
depends on GPIO
default n
config MY_SENSOR_BUF_SIZE
int "Buffer size"
range 64 1024
default 256
endif # MY_SENSOR
3.2.2 Kconfig 依赖与菜单层次
Kconfig 使用 depends on 表达依赖关系,使用 menuconfig / if ... endif 组织菜单层次。驱动开发者必须确保:
- 驱动符号依赖正确的总线(如 I2C、SPI)
- 子功能符号依赖父功能符号
- 默认值符合硬件规格
depends on 会导致驱动在未使能总线时被编译,引发链接错误。始终检查驱动 Kconfig 的依赖链。
下图展示了 Kconfig 符号之间的依赖与菜单关系:
3.3 设备树 (Devicetree) 基础
Devicetree 是 Zephyr 中描述硬件拓扑的标准方式。它将 SoC 外设、总线、引脚、中断、时钟等硬件信息以树状结构表达,驱动通过 Devicetree API 获取硬件参数,实现与板级无关的代码。
3.3.1 Devicetree 文件体系
Zephyr 的 Devicetree 文件分为三个层次:
| 层次 | 文件位置 | 作用 |
|---|---|---|
| SoC 级 | dts/<arch>/<soc>.dtsi |
定义 SoC 内置外设、地址、中断 |
| 板级 | boards/<arch>/<board>.dts |
定义板级引脚、外设实例、时钟 |
| 应用覆盖 | app.overlay |
应用层修改或添加设备节点 |
3.3.2 节点与属性语法
Devicetree 使用节点(node)表示硬件设备,属性(property)描述设备参数。一个典型的 I2C 传感器节点:
// 节点名: 传感器@地址
my_sensor: my-sensor@48 {
compatible = "vendor,my-sensor"; // 驱动匹配字符串
reg = <0x48>; // I2C 地址
interrupt-parent = <&gpio0>; // 中断控制器
interrupts = <5 1>; // GPIO 引脚 5, 上升沿触发
label = "MY_SENSOR"; // 设备标签
status = "okay"; // 启用状态
};
关键属性说明:
- compatible:驱动通过此字符串匹配设备节点
- reg:总线地址或寄存器基址
- interrupts:中断号与触发类型
- status:
"okay"启用,"disabled"禁用
3.3.3 驱动如何读取 Devicetree
Zephyr 提供一组 Devicetree API 宏,驱动在 C 代码中通过节点标识符获取硬件信息:
#include <zephyr/devicetree.h>
// 获取节点标识符
#define MY_SENSOR_NODE DT_NODELABEL(my_sensor)
// 读取属性
#define MY_SENSOR_I2C_ADDR DT_REG_ADDR(MY_SENSOR_NODE)
#define MY_SENSOR_IRQ_PIN DT_IRQN(MY_SENSOR_NODE)
#define MY_SENSOR_LABEL DT_LABEL(MY_SENSOR_NODE)
// 检查状态
#if DT_NODE_HAS_STATUS(MY_SENSOR_NODE, okay)
// 设备已启用,执行初始化
#endif
下图展示了 Devicetree 节点与驱动代码之间的数据流:
3.4 三系统协作实例:添加一个虚拟传感器驱动
下面通过一个完整示例,展示 CMake、Kconfig、Devicetree 如何协同工作:
- Devicetree:在板级 .dts 中添加传感器节点
- Kconfig:定义驱动开关和参数
- CMake:添加源文件到构建
- 驱动代码:通过 Devicetree API 读取配置
步骤 1:在 boards/arm/my_board.dts 中添加节点:
/ {
soc {
i2c0: i2c@40003000 {
my_sensor: my-sensor@48 {
compatible = "vendor,my-sensor";
reg = <0x48>;
label = "MY_SENSOR";
status = "okay";
};
};
};
};
步骤 2:在 drivers/sensor/my_sensor/Kconfig 中定义符号:
config MY_SENSOR
bool "My Sensor Driver"
depends on I2C
help
Enable My Sensor I2C driver.
步骤 3:在 drivers/sensor/my_sensor/CMakeLists.txt 中添加源文件:
zephyr_library()
zephyr_library_sources(my_sensor.c)
zephyr_library_include_directories(.)
步骤 4:在驱动 C 文件中读取 Devicetree 信息:
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/devicetree.h>
#define MY_SENSOR_NODE DT_NODELABEL(my_sensor)
#if DT_NODE_HAS_STATUS(MY_SENSOR_NODE, okay)
static const struct i2c_dt_spec dev_i2c =
I2C_DT_SPEC_GET(MY_SENSOR_NODE);
int my_sensor_init(const struct device *dev)
{
if (!device_is_ready(dev_i2c.bus)) {
return -ENODEV;
}
// 使用 dev_i2c.addr 进行 I2C 通信
return 0;
}
#endif
I2C_DT_SPEC_GET() 宏自动从 Devicetree 节点中提取 I2C 总线实例和地址,驱动代码无需硬编码任何硬件参数。
3.5 本章小结
| 子系统 | 核心职责 | 驱动开发者关注点 |
|---|---|---|
| west | 项目管理和构建命令封装 | 使用 west build -b <board> 编译 |
| CMake | 编译规则和源文件管理 | 编写 CMakeLists.txt 添加源文件 |
| Kconfig | 编译时配置和参数定义 | 定义驱动开关、依赖、默认值 |
| Devicetree | 硬件拓扑描述 | 定义节点、属性、compatible 匹配 |