4、设备驱动模型:设备驱动模型架构、设备结构体、驱动API、设备绑定
Zephyr RTOS 的设备驱动模型是一套统一的软件抽象层,它将硬件外设与上层应用解耦。理解该模型是进行驱动开发的核心前提。本章将深入剖析设备驱动模型的四层架构、核心数据结构 device、标准驱动 API 以及设备与驱动的绑定机制。
4.1 设备驱动模型架构
Zephyr 的设备驱动模型采用分层架构,从上至下依次为:应用层、API 层、驱动层和硬件层。这种分层设计保证了代码的可移植性和模块化。
下图展示了设备驱动模型的四层架构及其数据流关系:
4.2 设备结构体 (struct device)
在 Zephyr 中,每个硬件外设都由一个 struct device 实例表示。该结构体是驱动模型的核心,它聚合了设备的所有关键信息。
struct device 定义在 include/zephyr/device.h 中,主要成员如下:
| 成员 | 类型 | 描述 |
|---|---|---|
name |
const char * |
设备名称,用于在系统中唯一标识设备 |
config |
const void * |
指向设备配置数据的指针(编译时确定,通常来自设备树) |
api |
const void * |
指向驱动 API 结构体的指针 |
data |
void * |
指向设备运行时数据的指针(如状态、缓冲区) |
state |
atomic_t |
设备状态标志(初始化状态、电源状态等) |
config 和 api 是 const 类型,存储在只读区域(ROM/Flash),而 data 存储在可读写区域(RAM)。这种分离设计节省了 RAM 空间,并保证了配置和 API 的安全性。
设备实例通常通过宏 DEVICE_DEFINE 或 DEVICE_DT_DEFINE 在编译时静态定义。以下是一个典型的设备实例定义示例:
// 设备配置数据(来自设备树,存储在 ROM)
struct uart_nrfx_config uart0_config = {
.base = DT_REG_ADDR(DT_NODELABEL(uart0)),
.irq_num = DT_IRQN(DT_NODELABEL(uart0)),
};
// 设备运行时数据(存储在 RAM)
struct uart_nrfx_data uart0_data = {0};
// 驱动 API 结构体(存储在 ROM)
struct uart_driver_api uart_nrfx_api = {
.poll_in = uart_nrfx_poll_in,
.poll_out = uart_nrfx_poll_out,
.fifo_fill = uart_nrfx_fifo_fill,
.fifo_read = uart_nrfx_fifo_read,
};
// 定义设备实例
DEVICE_DEFINE(uart0, "UART_0", &uart0_init, NULL,
&uart0_data, &uart0_config,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&uart_nrfx_api);
4.3 驱动 API
驱动 API 是一组函数指针的集合,定义在 struct xxx_driver_api 中。它为上层应用提供了标准化的操作接口。每种设备类型(UART、GPIO、I2C、SPI 等)都有自己独立的 API 结构体。
以下以 UART 和 GPIO 为例,展示典型的驱动 API 结构:
| 设备类型 | API 结构体 | 关键函数指针 |
|---|---|---|
| UART | struct uart_driver_api |
poll_in, poll_out, fifo_fill, fifo_read, configure |
| GPIO | struct gpio_driver_api |
pin_configure, pin_set, pin_get, port_get_raw, port_set_masked_raw |
| I2C | struct i2c_driver_api |
transfer, configure, recover_bus |
| SPI | struct spi_driver_api |
transceive, configure |
应用层通过 device_get_binding() 获取设备句柄后,即可通过 API 结构体调用驱动函数:
// 获取 UART 设备句柄
const struct device *uart_dev = device_get_binding("UART_0");
if (uart_dev == NULL) {
return -ENODEV;
}
// 通过 API 结构体发送数据
const struct uart_driver_api *api = uart_dev->api;
api->poll_out(uart_dev, 'A');
NULL,并在调用时进行有效性检查。
4.4 设备绑定
设备绑定是指将设备树中描述的硬件节点与驱动代码中的 struct device 实例关联起来的过程。Zephyr 通过设备树(Devicetree)和宏展开机制实现自动绑定。
绑定流程分为以下步骤:
- 设备树描述: 在
.dts文件中定义硬件节点,包含寄存器地址、中断号、时钟等属性。 - 宏展开: 使用
DEVICE_DT_DEFINE宏,该宏会自动从设备树中提取配置信息,生成struct device实例。 - 编译时注册: 所有设备实例在编译时被放入一个特殊的链接器段(
.device段),形成一个设备列表。 - 运行时初始化: 系统启动时,内核按优先级遍历设备列表,调用每个设备的初始化函数。
- 应用获取: 应用通过
device_get_binding()按名称查找已初始化的设备。
下图展示了从设备树到设备实例的完整绑定流程:
DEVICE_DT_DEFINE 系列宏进行设备绑定,它们会自动处理设备树兼容性检查、配置提取和优先级排序,减少手动错误。
4.5 设备绑定示例:UART 驱动
以下是一个完整的 UART 驱动绑定示例,展示了从设备树到驱动实现的完整链路:
// 1. 设备树节点(board.dts)
// uart0: uart@40002000 {
// compatible = "nordic,nrf-uarte";
// reg = <0x40002000 0x1000>;
// interrupts = <39 1>;
// status = "okay";
// };
// 2. 驱动头文件(uart_nrfx.h)
struct uart_nrfx_config {
uint32_t base;
uint32_t irq_num;
};
struct uart_nrfx_data {
uint8_t rx_buffer[256];
uint32_t rx_count;
};
// 3. 驱动实现(uart_nrfx.c)
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
static int uart_nrfx_init(const struct device *dev)
{
const struct uart_nrfx_config *cfg = dev->config;
struct uart_nrfx_data *data = dev->data;
// 硬件初始化代码
printk("UART init at base 0x%x\n", cfg->base);
return 0;
}
static int uart_nrfx_poll_out(const struct device *dev, unsigned char c)
{
// 发送一个字节
return 0;
}
static int uart_nrfx_poll_in(const struct device *dev, unsigned char *c)
{
// 接收一个字节
return 0;
}
static const struct uart_driver_api uart_nrfx_api = {
.poll_out = uart_nrfx_poll_out,
.poll_in = uart_nrfx_poll_in,
};
// 4. 设备绑定宏
#define UART_NRFX_INIT(n) \
static const struct uart_nrfx_config uart##n##_config = { \
.base = DT_REG_ADDR(DT_NODELABEL(uart##n)), \
.irq_num = DT_IRQN(DT_NODELABEL(uart##n)), \
}; \
static struct uart_nrfx_data uart##n##_data; \
DEVICE_DT_DEFINE(DT_NODELABEL(uart##n), \
uart_nrfx_init, NULL, \
&uart##n##_data, \
&uart##n##_config, \
POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
&uart_nrfx_api)
// 5. 实例化设备
UART_NRFX_INIT(0);
- 设备结构体是驱动模型的核心,包含
config、data、api三个关键指针。 - 驱动 API 通过函数指针结构体实现多态,每种设备类型有独立的 API 定义。
- 设备绑定通过设备树和宏展开自动完成,开发者只需关注驱动逻辑实现。
- 使用
DEVICE_DT_DEFINE宏可以简化绑定过程,并确保与设备树的一致性。