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

Zephyr RTOS 的设备驱动模型是其核心架构之一,它通过设备树(Devicetree)实现硬件描述与驱动代码的解耦,并定义了清晰的API 分层结构与设备初始化流程。理解这一模型是进行 I2C 从机适配与工程移植的基础。

3.1 设备树(Devicetree)概念

设备树是一种描述硬件资源的数据结构,在 Zephyr 中用于替代传统的板级头文件(如 board.h)。它以 .dts(Devicetree Source)和 .dtsi(Devicetree Source Include)文件形式存在,位于 boards/ 和 soc/ 目录下。

核心作用:设备树将硬件配置(如外设地址、中断号、时钟频率)从 C 代码中剥离,使得同一份驱动代码可以适配不同硬件平台,只需更换设备树文件即可。

3.1.1 设备树节点与属性

设备树由节点(node)和属性(property)组成。每个节点代表一个硬件设备或总线,属性描述其具体参数。以下是一个典型的 I2C 控制器节点示例:

/ {
    soc {
        i2c0: i2c@40003000 {
            compatible = "nordic,nrf-twim";
            reg = <0x40003000 0x1000>;
            interrupts = <3 1>;
            status = "okay";
            clock-frequency = <100000>;
            #address-cells = <1>;
            #size-cells = <0>;

            temp_sensor: tmp102@48 {
                compatible = "ti,tmp102";
                reg = <0x48>;
                status = "okay";
            };
        };
    };
};
属性 含义 示例值
compatible 驱动匹配字符串,用于绑定驱动 "nordic,nrf-twim"
reg 寄存器地址与长度 <0x40003000 0x1000>
interrupts 中断号与触发方式 <3 1>
status 设备启用状态 "okay" 或 "disabled"
clock-frequency 总线时钟频率 <100000> (100kHz)

3.1.2 设备树与驱动绑定的关系

设备树中的 compatible 属性是连接硬件节点与驱动代码的桥梁。Zephyr 的驱动注册宏(如 I2C_DEVICE_DT_DEFINE)会使用 DT_DRV_COMPAT 宏来匹配设备树中的 compatible 字符串,从而将驱动实例绑定到对应的硬件节点上。

设备树与驱动绑定关系图 设备树节点 compatible = "ti,tmp102" reg = <0x48> 匹配 驱动绑定层 DT_DRV_COMPAT = ti_tmp102 I2C_DEVICE_DT_DEFINE(...) 实例化 驱动实例 tmp102 设备对象 说明: 1. 设备树中的 compatible 字符串使用下划线替换斜杠(如 "ti,tmp102" → "ti_tmp102")。 2. 驱动通过 DT_INST_FOREACH_STATUS_OKAY 宏遍历所有状态为 "okay" 的节点并初始化。

3.2 驱动绑定机制

Zephyr 的驱动绑定机制通过宏和链接脚本实现,将设备树节点与驱动代码在编译时静态关联。核心流程包括:

  1. 定义兼容宏:在驱动源文件中使用 #define DT_DRV_COMPAT ti_tmp102 声明本驱动支持的 compatible 字符串。
  2. 使用绑定宏:调用 I2C_DEVICE_DT_DEFINE 或 DEVICE_DT_DEFINE 宏,传入设备树节点标识符(如 DT_NODELABEL(temp_sensor))。
  3. 生成实例:宏展开后生成一个 struct device 实例,并填充其 API 函数指针、配置数据、运行时数据。
  4. 链接到设备表:生成的实例通过 __device_section 链接到系统的设备表中,供内核在启动时遍历初始化。
关键宏对比:
宏名称 用途 参数示例
DEVICE_DT_DEFINE 通用设备定义 DEVICE_DT_DEFINE(node_id, init_fn, NULL, data, config, level, prio, api)
I2C_DEVICE_DT_DEFINE I2C 设备专用定义 I2C_DEVICE_DT_DEFINE(node_id, init_fn, data, config, level, prio, api)
DT_INST_FOREACH_STATUS_OKAY 遍历所有状态为 ok 的实例 DT_INST_FOREACH_STATUS_OKAY(I2C_DEVICE_DT_DEFINE)

3.3 API 分层结构

Zephyr 的设备驱动 API 采用三层架构:应用层、设备驱动层、硬件抽象层(HAL)。这种分层设计提高了代码的可移植性和复用性。

Zephyr I2C 驱动 API 分层结构 应用层 (Application Layer) i2c_write() / i2c_read() / i2c_transfer() 设备驱动层 (Device Driver Layer) struct i2c_driver_api → 函数指针表 硬件抽象层 (HAL Layer) 寄存器读写 / 中断处理 / DMA 配置

3.3.1 各层职责

层级 职责 典型 API / 结构体
应用层 提供用户友好的高层接口,隐藏底层细节 i2c_write(), i2c_read(), i2c_burst_read()
设备驱动层 实现具体外设的驱动逻辑,维护设备状态 struct i2c_driver_api, struct i2c_device_config
硬件抽象层 直接操作寄存器,处理硬件中断和 DMA nrf_twim_xfer(), nrf_twim_isr()

3.4 设备初始化流程

Zephyr 的设备初始化在系统启动时按初始化级别(Initialization Level)顺序执行。每个设备驱动可以指定其初始化函数在哪个阶段被调用。

3.4.1 初始化级别

级别宏 数值 典型用途
PRE_KERNEL_1 0 CPU 内核、中断控制器、定时器
PRE_KERNEL_2 1 SoC 外设初始化(如 GPIO、UART)
POST_KERNEL 2 板级外设、I2C/SPI 总线控制器
APPLICATION 3 应用层设备、传感器驱动

3.4.2 初始化流程步骤

  1. 链接阶段:编译器将所有 DEVICE_DT_DEFINE 生成的设备实例按初始化级别排序,放入 __device_start 到 __device_end 的内存区域。
  2. 启动阶段:内核启动后,依次遍历每个初始化级别的设备列表,调用其 init_fn 函数。
  3. 设备注册:初始化函数执行成功后,设备实例被标记为 DEVICE_STATE_INITIALIZED,并注册到系统的设备管理器中。
  4. 应用访问:应用通过 device_get_binding() 或 DEVICE_DT_GET() 获取设备句柄,然后调用 API 函数。
设备初始化流程 步骤1:链接排序 按级别排列设备 步骤2:遍历初始化 调用 init_fn() 步骤3:注册设备 标记初始化完成 步骤4 应用访问 初始化函数原型: int my_i2c_init(const struct device *dev); 返回值:0 表示成功,负值表示失败。 典型操作:配置时钟、复位外设、注册中断、设置默认参数。 注意:初始化函数中不应调用可能阻塞的 API(如睡眠、信号量等待)。

3.5 实践要点:I2C 从机适配中的模型应用

常见陷阱:在 I2C 从机适配时,设备树中的 reg 属性代表从机地址(7 位或 10 位),而非寄存器地址。务必确认地址格式与硬件手册一致,否则驱动绑定将失败。

在 I2C 从机适配工程中,设备驱动模型的应用体现在以下方面:

  • 设备树修改:在板级 .dts 文件中添加从机设备节点,指定 compatible、reg(从机地址)、status = "okay"。
  • 驱动实现:编写符合 struct i2c_driver_api 接口的驱动,实现 transfer、slave_read、slave_write 等回调函数。
  • 初始化级别选择:I2C 从机驱动通常使用 POST_KERNEL 级别,确保 I2C 控制器驱动已先初始化完成。
  • 设备绑定:使用 I2C_DEVICE_DT_DEFINE 宏,传入设备树节点标识符,自动完成绑定。
示例代码片段:I2C 从机驱动绑定
#define DT_DRV_COMPAT my_company_my_i2c_slave

static const struct i2c_driver_api my_i2c_slave_api = {
    .transfer = my_i2c_slave_transfer,
    .slave_read = my_i2c_slave_read,
    .slave_write = my_i2c_slave_write,
};

static int my_i2c_slave_init(const struct device *dev)
{
    const struct my_i2c_slave_config *cfg = dev->config;
    /* 初始化硬件寄存器 */
    return 0;
}

I2C_DEVICE_DT_DEFINE(DT_NODELABEL(my_slave),
                     my_i2c_slave_init,
                     NULL,
                     &my_i2c_slave_data,
                     &my_i2c_slave_config,
                     POST_KERNEL,
                     CONFIG_I2C_INIT_PRIORITY,
                     &my_i2c_slave_api);

通过以上机制,Zephyr 实现了硬件描述与驱动代码的完全解耦,使得 I2C 从机适配工作只需关注设备树配置和驱动逻辑实现,无需修改内核或板级支持包的其他部分。