3. Zephyr 设备驱动模型:设备树、驱动绑定与初始化流程
Zephyr RTOS 的设备驱动模型是其核心架构之一,它通过设备树(Devicetree)实现硬件描述与驱动代码的解耦,并定义了清晰的API 分层结构与设备初始化流程。理解这一模型是进行 I2C 从机适配与工程移植的基础。
3.1 设备树(Devicetree)概念
设备树是一种描述硬件资源的数据结构,在 Zephyr 中用于替代传统的板级头文件(如 board.h)。它以 .dts(Devicetree Source)和 .dtsi(Devicetree Source Include)文件形式存在,位于 boards/ 和 soc/ 目录下。
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 字符串,从而将驱动实例绑定到对应的硬件节点上。
3.2 驱动绑定机制
Zephyr 的驱动绑定机制通过宏和链接脚本实现,将设备树节点与驱动代码在编译时静态关联。核心流程包括:
- 定义兼容宏:在驱动源文件中使用
#define DT_DRV_COMPAT ti_tmp102声明本驱动支持的 compatible 字符串。 - 使用绑定宏:调用
I2C_DEVICE_DT_DEFINE或DEVICE_DT_DEFINE宏,传入设备树节点标识符(如DT_NODELABEL(temp_sensor))。 - 生成实例:宏展开后生成一个
struct device实例,并填充其 API 函数指针、配置数据、运行时数据。 - 链接到设备表:生成的实例通过
__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)。这种分层设计提高了代码的可移植性和复用性。
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 初始化流程步骤
- 链接阶段:编译器将所有
DEVICE_DT_DEFINE生成的设备实例按初始化级别排序,放入__device_start到__device_end的内存区域。 - 启动阶段:内核启动后,依次遍历每个初始化级别的设备列表,调用其
init_fn函数。 - 设备注册:初始化函数执行成功后,设备实例被标记为
DEVICE_STATE_INITIALIZED,并注册到系统的设备管理器中。 - 应用访问:应用通过
device_get_binding()或DEVICE_DT_GET()获取设备句柄,然后调用 API 函数。
3.5 实践要点: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宏,传入设备树节点标识符,自动完成绑定。
#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 从机适配工作只需关注设备树配置和驱动逻辑实现,无需修改内核或板级支持包的其他部分。