3. Zephyr 设备驱动模型:设备驱动架构、设备树基础与驱动绑定初始化流程

Zephyr RTOS 的设备驱动模型是其可移植性与硬件抽象能力的核心。本章将深入剖析设备驱动的分层架构、设备树(Device Tree)的配置机制,以及驱动从编译到运行的完整绑定与初始化流程。理解这些内容,是进行 I2C 与 SPI 总线驱动开发的前提。

3.1 设备驱动架构:分层与抽象

Zephyr 的设备驱动模型采用经典的分层架构,从上到下依次为:应用层、API 层、驱动核心层 和 硬件抽象层(HAL)。这种设计使得应用代码与底层硬件解耦,提高了代码的可重用性和可移植性。

架构核心思想: 应用通过标准化的 API(如 i2c_write())调用驱动,驱动核心层负责查找设备实例并调用具体的驱动函数,而硬件抽象层则封装了寄存器操作和中断处理。
Zephyr 设备驱动分层架构 应用层 (Application) 调用标准 API: i2c_write(), spi_transceive() API 层 (Device Driver API) 定义通用接口结构体: struct i2c_driver_api 驱动核心层 (Driver Core) 设备查找、初始化、电源管理、设备绑定 硬件抽象层 (HAL / SoC Layer) 寄存器操作、中断服务、DMA 配置 物理硬件 (Physical Hardware)

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 文件的前提下,为特定应用添加或覆盖硬件配置。
开发提示: 在开发 I2C/SPI 驱动时,最常修改的是板级 DTS 或 Overlay 文件,用于配置总线频率、引脚号和设备地址。

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() 获取设备指针。
设备树节点结构示例 / (根节点) soc (SoC 节点) i2c1 (I2C 控制器节点) sensor@48 (设备节点) compatible = "ti,tmp112"; reg = <0x48>;

3.3 驱动绑定与初始化流程

Zephyr 的驱动初始化是一个高度自动化的过程,它依赖于设备树和链接器脚本。整个过程可以分为三个阶段:编译时绑定、启动时排序 和 运行时初始化。

3.3.1 编译时绑定:宏与链接器

驱动开发者使用 DEVICE_DT_DEFINE 宏来注册一个驱动实例。这个宏在编译时做了三件关键事情:

  1. 生成设备实例结构体:创建一个 struct device 类型的全局变量。
  2. 填充设备数据:从设备树中提取 reg、label、compatible 等信息,并关联驱动 API 和配置数据。
  3. 放入链接器段:将设备实例放入特定的链接器段(如 .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 应用层初始化,此时所有内核服务和设备驱动均已就绪。
注意事项: I2C 和 SPI 总线控制器驱动通常应在 POST_KERNEL 级别初始化,而挂载在其上的从设备驱动(如传感器)应在此之后初始化。如果优先级设置不当,可能导致从设备初始化时总线尚未就绪。

3.3.3 运行时设备查找

初始化完成后,应用可以通过设备标签(label)或设备树路径来获取设备指针。Zephyr 提供了两种主要方式:

  • 基于标签的查找:device_get_binding("TEMP_SENSOR_0"),返回 const struct device *。
  • 基于设备树的查找:DEVICE_DT_GET(DT_NODELABEL(temperature_sensor)),在编译时直接获取设备指针,效率更高。
Zephyr 驱动初始化流程 1. 编译时绑定 DEVICE_DT_DEFINE 生成设备实例 2. 启动时排序 按级别 (POST_KERNEL) 和优先级排序 3. 调用初始化函数 执行 i2c_init() 配置寄存器 4. 设备注册到内核 设备实例加入全局设备链表 5. 应用层查找与使用 device_get_binding() 获取设备指针 关键点:初始化顺序由 INIT_LEVEL 和 INIT_PRIORITY 共同决定 总线控制器 (I2C/SPI) 必须在从设备之前完成初始化

3.4 本章小结

本章介绍了 Zephyr 设备驱动模型的三大支柱:

  • 分层架构:通过 API 层、核心层和 HAL 层实现硬件抽象与代码复用。
  • 设备树:作为硬件配置的“数据库”,通过 compatible 和 reg 等属性实现驱动与设备的静态绑定。
  • 初始化流程:从编译时宏展开,到启动时按级别/优先级排序执行,最终将设备注册到内核供应用使用。
实战要点: 在编写 I2C/SPI 驱动时,务必确认设备树中 compatible 字符串与驱动宏中的字符串完全一致;同时,合理设置初始化优先级,确保总线控制器先于从设备初始化。