3. Zephyr 设备驱动模型:设备驱动架构、设备树基础与驱动绑定初始化流程
Zephyr RTOS 的设备驱动模型是其可移植性与硬件抽象能力的核心。本章将深入剖析设备驱动的分层架构、设备树(Device Tree)的配置机制,以及驱动从编译到运行的完整绑定与初始化流程。理解这些内容,是进行 I2C 与 SPI 总线驱动开发的前提。
3.1 设备驱动架构:分层与抽象
Zephyr 的设备驱动模型采用经典的分层架构,从上到下依次为:应用层、API 层、驱动核心层 和 硬件抽象层(HAL)。这种设计使得应用代码与底层硬件解耦,提高了代码的可重用性和可移植性。
i2c_write())调用驱动,驱动核心层负责查找设备实例并调用具体的驱动函数,而硬件抽象层则封装了寄存器操作和中断处理。
3.2 设备树 (Device Tree) 基础
设备树是一种描述硬件配置的数据结构,Zephyr 使用设备树来静态定义 SoC 外设、板级引脚、时钟、中断等资源。它替代了传统的宏定义和头文件配置,使得硬件描述更加清晰和标准化。
3.2.1 设备树文件体系
Zephyr 的设备树文件主要分为三类,它们通过 #include 和 /include/ 机制组合成一个完整的硬件描述。
| 文件类型 | 扩展名 | 作用域 | 描述 |
|---|---|---|---|
| SoC 级 DTSI | .dtsi |
芯片系列 | 定义 SoC 内部外设(如 UART、I2C、SPI 控制器)、中断控制器、时钟等。 |
| 板级 DTS | .dts |
具体开发板 | 包含 SoC DTSI,并定义板级引脚复用(pinctrl)、外部设备、内存布局等。 |
| Overlay 文件 | .overlay |
应用/项目 | 在不修改原始 DTS 文件的前提下,为特定应用添加或覆盖硬件配置。 |
3.2.2 设备树节点与属性
设备树由节点(node)和属性(property)组成。每个节点代表一个硬件设备或总线,属性则描述其配置参数。以下是一个典型的 I2C 设备节点示例:
// 在板级 DTS 或 Overlay 文件中
&i2c1 {
status = "okay"; // 启用该 I2C 控制器
clock-frequency = <100000>; // 设置总线频率为 100kHz
// 挂载在 I2C1 总线上的从设备
temperature_sensor: sensor@48 {
compatible = "ti,tmp112"; // 驱动匹配字符串
reg = <0x48>; // 7 位 I2C 从设备地址
label = "TEMP_SENSOR_0"; // 设备标签,用于应用层查找
};
};
关键属性说明:
- compatible:驱动与设备绑定的关键字符串,驱动通过
DEVICE_DT_DEFINE宏注册此字符串。 - reg:对于 I2C 设备,表示从设备地址;对于 SPI 设备,表示片选(CS)引脚编号。
- status:
"okay"表示启用,"disabled"表示禁用。 - label:逻辑名称,应用可通过
device_get_binding()获取设备指针。
3.3 驱动绑定与初始化流程
Zephyr 的驱动初始化是一个高度自动化的过程,它依赖于设备树和链接器脚本。整个过程可以分为三个阶段:编译时绑定、启动时排序 和 运行时初始化。
3.3.1 编译时绑定:宏与链接器
驱动开发者使用 DEVICE_DT_DEFINE 宏来注册一个驱动实例。这个宏在编译时做了三件关键事情:
- 生成设备实例结构体:创建一个
struct device类型的全局变量。 - 填充设备数据:从设备树中提取
reg、label、compatible等信息,并关联驱动 API 和配置数据。 - 放入链接器段:将设备实例放入特定的链接器段(如
.device段),以便启动时遍历。
// 驱动注册宏示例 (简化)
#define I2C_DEVICE_DT_DEFINE(node_id) \
DEVICE_DT_DEFINE(node_id, \
i2c_init, /* 初始化函数 */ \
NULL, /* PM 函数 */ \
&i2c_data, /* 运行时数据 */ \
&i2c_config, /* 配置数据 */ \
POST_KERNEL, /* 初始化级别 */ \
CONFIG_I2C_INIT_PRIORITY, /* 优先级 */ \
&i2c_api) /* API 结构体 */
3.3.2 启动时初始化顺序
Zephyr 内核启动时,会按照 初始化级别(level) 和 优先级(priority) 依次调用所有注册驱动的初始化函数。级别决定了初始化的宏观阶段,优先级则在同一级别内进行微调。
| 初始化级别 | 宏定义 | 典型用途 |
|---|---|---|
| 早期 | PRE_KERNEL_1 |
CPU 内核、中断控制器、系统时钟等最基础的硬件初始化。 |
| 内核就绪前 | PRE_KERNEL_2 |
内存控制器、基础外设(如 UART 用于早期调试)。 |
| 内核启动后 | POST_KERNEL |
大多数设备驱动,包括 I2C、SPI、GPIO 等。这是总线驱动初始化的主要阶段。 |
| 应用 | APPLICATION |
应用层初始化,此时所有内核服务和设备驱动均已就绪。 |
POST_KERNEL 级别初始化,而挂载在其上的从设备驱动(如传感器)应在此之后初始化。如果优先级设置不当,可能导致从设备初始化时总线尚未就绪。
3.3.3 运行时设备查找
初始化完成后,应用可以通过设备标签(label)或设备树路径来获取设备指针。Zephyr 提供了两种主要方式:
- 基于标签的查找:
device_get_binding("TEMP_SENSOR_0"),返回const struct device *。 - 基于设备树的查找:
DEVICE_DT_GET(DT_NODELABEL(temperature_sensor)),在编译时直接获取设备指针,效率更高。
3.4 本章小结
本章介绍了 Zephyr 设备驱动模型的三大支柱:
- 分层架构:通过 API 层、核心层和 HAL 层实现硬件抽象与代码复用。
- 设备树:作为硬件配置的“数据库”,通过
compatible和reg等属性实现驱动与设备的静态绑定。 - 初始化流程:从编译时宏展开,到启动时按级别/优先级排序执行,最终将设备注册到内核供应用使用。
compatible 字符串与驱动宏中的字符串完全一致;同时,合理设置初始化优先级,确保总线控制器先于从设备初始化。