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

Zephyr RTOS 的设备驱动模型是其核心抽象层,它将硬件外设与上层应用解耦。理解设备驱动架构、设备树(Device Tree)以及驱动绑定与初始化流程,是开发传感器驱动的基石。本章将深入剖析这三个关键环节,帮助您建立完整的驱动开发知识体系。

3.1 设备驱动架构

Zephyr 的设备驱动模型采用分层与面向对象的设计思想,核心围绕 struct device 结构体展开。每个外设驱动都通过一个 struct device 实例来表示,该实例包含了驱动所需的全部信息。

3.1.1 核心数据结构:struct device

在 include/zephyr/device.h 中定义,struct device 是驱动模型的核心。其主要成员如下:

成员 类型 描述
name const char * 设备名称,用于在系统中唯一标识该设备。
config const void * 指向设备配置数据的指针,通常包含硬件地址、IRQ 号、I2C 地址等静态信息。
api const void * 指向驱动 API 结构体的指针,定义了该设备类型支持的操作(如读、写、配置)。
data void * 指向设备运行时数据的指针,用于存储设备状态、缓冲区等动态信息。
state atomic_t 设备状态标志,用于表示设备是否已初始化、是否就绪等。
关键概念: config 和 data 的分离是 Zephyr 驱动模型的重要设计。配置数据(如 I2C 地址)在编译时确定,存储在只读区域;运行时数据(如采样值、校准偏移)则存储在可读写的 RAM 中。这种设计提高了内存效率和安全性。

3.1.2 驱动 API 结构体

每个设备类(如传感器、GPIO、I2C)都定义了自己的 API 结构体。以传感器驱动为例,其 API 通常定义在 include/zephyr/drivers/sensor.h 中:

struct sensor_driver_api {
    int (*sample_fetch)(const struct device *dev,
                        enum sensor_channel chan);
    int (*channel_get)(const struct device *dev,
                       enum sensor_channel chan,
                       struct sensor_value *val);
    int (*attr_set)(const struct device *dev,
                    enum sensor_channel chan,
                    enum sensor_attribute attr,
                    const struct sensor_value *val);
    int (*attr_get)(const struct device *dev,
                    enum sensor_channel chan,
                    enum sensor_attribute attr,
                    struct sensor_value *val);
#ifdef CONFIG_SENSOR_ASYNC_API
    int (*trigger_set)(const struct device *dev,
                       const struct sensor_trigger *trig,
                       sensor_trigger_handler_t handler);
#endif
};

驱动开发者需要实现这些函数指针,并将其赋值给一个 sensor_driver_api 结构体实例。上层应用通过 device.h 提供的通用接口(如 sensor_sample_fetch())调用时,最终会通过 dev->api 跳转到具体的驱动实现。

3.1.3 驱动架构分层图

下图展示了 Zephyr 设备驱动模型的分层架构,从应用层到硬件层的完整调用链路:

Zephyr 设备驱动模型分层架构 应用层 (Application) 调用 sensor_sample_fetch() / sensor_channel_get() 等 API 设备核心层 (Device Core) 通过 dev->api 跳转到具体驱动实现 驱动 API 层 (Driver API) sensor_driver_api 结构体:sample_fetch / channel_get / attr_set 硬件抽象层 (HAL / 外设驱动) I2C / SPI / GPIO 总线操作,直接与寄存器交互 调用方向:应用层 → 设备核心层 → 驱动 API 层 → 硬件抽象层

3.2 设备树 (Device Tree) 基础

设备树是一种描述硬件配置的数据结构,在 Zephyr 中用于在编译时静态定义外设的属性和连接关系。它替代了传统的、冗长的宏定义和头文件配置,使得硬件描述更加清晰、可移植。

3.2.1 设备树文件体系

Zephyr 的设备树文件主要分为三类,它们共同构成了完整的硬件描述:

文件类型 扩展名 作用 示例
芯片级 DTSI .dtsi 定义 SoC 内部的外设、中断控制器、时钟等通用信息。 stm32f4.dtsi
板级 DTS .dts 定义具体开发板上的外设实例、引脚复用、I2C 地址等。 nucleo_f411re.dts
板级 DTSI .dtsi 被板级 DTS 包含,用于共享相同硬件配置的板卡。 nucleo_f4.dtsi

3.2.2 设备树节点与属性

设备树的基本单元是节点(node),每个节点代表一个硬件设备或总线。节点通过属性(property)来描述其特性。以下是一个 I2C 传感器节点的典型示例:

/ {
    soc {
        i2c1: i2c@40005400 {
            compatible = "st,stm32-i2c-v1";
            reg = <0x40005400 0x400>;
            clocks = <&rcc>;
            status = "okay";
            #address-cells = <1>;
            #size-cells = <0>;

            /* 挂载在 I2C1 总线上的传感器 */
            bme280: bme280@76 {
                compatible = "bosch,bme280";
                reg = <0x76>;
                status = "okay";
            };
        };
    };
};
关键属性解析:
  • compatible:驱动匹配的关键字符串,用于将设备节点与驱动绑定。
  • reg:设备的地址信息。对于 I2C 设备,表示 7 位从机地址(如 0x76);对于内存映射设备,表示基地址和大小。
  • status:设备状态,"okay" 表示启用,"disabled" 表示禁用。
  • #address-cells 和 #size-cells:定义子节点中 reg 属性的编码格式。

3.2.3 设备树到 C 宏的转换

在编译过程中,设备树编译器(DTC)将 .dts 文件转换为 .h 头文件。Zephyr 的构建系统会生成类似 zephyr.dts_compiled.h 的文件,其中包含大量的宏定义。例如,上述 bme280 节点会生成:

#define DT_N_S_soc_S_i2c_40005400_S_bme280_76_BUS_NAME   "I2C_1"
#define DT_N_S_soc_S_i2c_40005400_S_bme280_76_REG_ADDR   0x76
#define DT_N_S_soc_S_i2c_40005400_S_bme280_76_COMPATIBLE "bosch,bme280"
#define DT_N_S_soc_S_i2c_40005400_S_bme280_76_STATUS     okay

驱动开发中,通常使用 DT_INST 系列宏来简化访问,例如 DT_INST_REG_ADDR(0) 获取第 0 个实例的 reg 地址。

3.3 驱动绑定与初始化流程

驱动绑定是将设备树节点与具体的驱动实现关联起来的过程。Zephyr 通过 DEVICE_DT_DEFINE 宏完成这一工作,并在系统启动时按顺序初始化所有设备。

3.3.1 驱动绑定宏:DEVICE_DT_DEFINE

这是驱动注册的核心宏,通常在驱动源文件的末尾调用。其原型如下:

#define DEVICE_DT_DEFINE(node_id, init_fn, pm_device, data_ptr, cfg_ptr, level, prio, api_ptr)
参数 描述
node_id 设备树节点的标识符,通常使用 DT_DRV_INST(n) 宏。
init_fn 设备初始化函数指针,在系统启动时被调用。
pm_device 电源管理设备指针,通常为 NULL。
data_ptr 指向运行时数据结构体的指针。
cfg_ptr 指向配置数据结构体的指针,通常从设备树中提取。
level 初始化优先级级别,如 POST_KERNEL、APPLICATION。
prio 同级别内的初始化优先级,数值越小越先执行。
api_ptr 指向驱动 API 结构体的指针。

一个典型的传感器驱动绑定示例如下:

/* 定义运行时数据结构 */
struct bme280_data {
    int16_t temp;
    uint16_t press;
    uint16_t hum;
};

/* 定义配置数据结构,从设备树提取 */
struct bme280_config {
    uint16_t i2c_addr;
    const struct device *bus;
};

/* 定义驱动 API 实例 */
static const struct sensor_driver_api bme280_api = {
    .sample_fetch = bme280_sample_fetch,
    .channel_get = bme280_channel_get,
};

/* 使用 DEVICE_DT_DEFINE 注册驱动 */
DEVICE_DT_DEFINE(DT_DRV_INST(0),
                 bme280_init,
                 NULL,
                 &bme280_data_0,
                 &bme280_config_0,
                 POST_KERNEL,
                 CONFIG_SENSOR_INIT_PRIORITY,
                 &bme280_api);

3.3.2 初始化流程

Zephyr 的启动过程会按照 level 和 prio 的顺序,依次调用所有已注册设备的 init_fn。初始化流程分为以下几个阶段:

  1. EARLY:早期初始化,用于架构相关代码和时钟。
  2. PRE_KERNEL_1:内核初始化第一阶段,启动调度器前。
  3. PRE_KERNEL_2:内核初始化第二阶段,启动调度器后。
  4. POST_KERNEL:内核初始化后,大多数外设驱动在此阶段初始化。
  5. APPLICATION:应用层初始化,用于依赖外设的应用组件。
注意事项: 传感器驱动通常选择 POST_KERNEL 级别,并设置合理的优先级(如 CONFIG_SENSOR_INIT_PRIORITY,默认 80)。如果传感器依赖 I2C 总线,必须确保 I2C 控制器驱动的初始化优先级高于传感器驱动。

3.3.3 驱动初始化流程图

下图展示了从设备树解析到驱动初始化完成的完整流程:

Zephyr 驱动初始化流程 1. 构建系统解析 .dts 文件 2. 生成 .h 宏定义头文件 3. DEVICE_DT_DEFINE 注册设备 4. 系统启动,按 level/prio 排序 5. 调用 init_fn 完成初始化 关键点:设备树 → 宏定义 → 驱动注册 → 排序 → 初始化函数执行

3.4 本章小结

本章详细介绍了 Zephyr 设备驱动模型的三大核心要素:

  • 设备驱动架构:基于 struct device 的分层设计,实现了配置与数据的分离,以及 API 的标准化。
  • 设备树基础:通过 .dts 文件描述硬件,利用 compatible 属性实现驱动与设备的绑定。
  • 驱动绑定与初始化流程:使用 DEVICE_DT_DEFINE 宏注册驱动,系统启动时按优先级顺序调用初始化函数。
实战要点: 在开发传感器驱动时,请务必先确认设备树中 I2C 节点的 status 为 "okay",并正确设置传感器的 reg 地址。初始化函数中应优先获取总线设备指针(通过 DEVICE_DT_GET),再进行传感器复位和配置。