Zephyr 设备模型与驱动 API

Zephyr 设备驱动模型是一种用于定义通用 API(应用程序接口)与设备驱动实现之间的关联关系的架构。如下图所示,该模型可分为三个不同的组成部分。

zdm

  • Device driver APIs 设备驱动程序 API
  • Device driver instances 设备驱动程序实例
  • Device driver implementation 设备驱动程序实现

应用程序通过通用类型 API 进行交互。这些通用 API 通过获取目标硬件的设备指针,与设备驱动实例建立接口连接。设备指针的结构与内容由设备驱动实现定义,该实现位于设备驱动模型的最底层。

本文主要描述设备驱动程序的 API,其它两个部分另外文章进行说明。

设备驱动程序 API 链接到标题

设备驱动 API 是设备驱动和 Zephyr 应用之间的界面,完成设备设备驱动和应用之间的解耦。本文无意介绍所有驱动 API 的用法,具体请参考设备驱动 API 头文件和设备文档。

驱动 API 链接到标题

在 Zephyr 中,每种驱动程序有自己的标准 API,这些 API 被定义在zephyr/include/zephyr/drivers下的头文件中,对于相同类型的设备的驱动,其 API 形式都是一致的,在官方文档 Peripherals 中可以找到这些设备驱动的描述。

应用与驱动 API 链接到标题

应用通过设备驱动程序为了与硬件外设或系统模块进行交互,设备驱动是一种处理底层细节的软件,按照需求配置和驱动硬件。在 Zephyr 中驱动程序的实现与其 API 高度解耦,无论使用的是哪种 SOC 或者外设,Zephyr 的应用看到的都是通用的设备驱动 API,这意味着 Zephyr 下可以在无需修改应用程序的情况下替换底层驱动程序的实现。 这种解耦带来了许多好处,其中包括高度的可移植性,它可以在无需手动修改底层的驱动程序实现的情况下让相同的应用代码可以在不同的开发板上运行。

所有设备驱动都由统一的设备描述符struct device *来访问,应用程序通过通用 API 与硬件进行交互,具体方式是使用 DEVICE_DT_GET() 或相关宏来获取目标硬件的设备指针struct device *

例如 Devicetree 中有一个 label 为adc0的节点

&adc0 {
	status = "okay";
	#address-cells = <1>;
	#size-cells = <0>;
};

以下代码片段将获取由 DT_NODELABEL(label) 返回的设备树节点标识符,并通过DEVICE_DT_GET(node_id)返回设备对象的指针。然后 device_is_ready() 检查设备是否处于可以使用其的状态。

const struct device *dev;
    dev = DEVICE_DT_GET(DT_NODELABEL(adc0));

    if (!device_is_ready(dev)) {
		printk("device can't ready\r\n");
        return;
    }

早期通常是使用device_get_binding()获取设备指针,现在更推荐使用DEVICE_DT_GET,主要原因是:如果设备未被驱动程序分配(例如设备树中不存在该设备或其状态为禁用),在构建阶段就可以检查出来。这种编译期检查机制能更早暴露配置错误,避免将设备初始化问题遗留到运行时而增加调试难度。 例如当adc0在 Devicetree 中被禁用后,构建时会提示:

error: '__device_dts_ord_54' undeclared (first use in this function); did you mean '__device_dts_ord_57'?
   10 |     dev = DEVICE_DT_GET(DT_NODELABEL(adc0));
      |             ^~~~~~~~~~~~~~~~~~~
      |             __device_dts_ord_57

而当访问一个 Devicetree 中不存在的节点时会提示

error: '__device_dts_ord_DT_N_NODELABEL_adc3_ORD' undeclared (first use in this function)
   10 |     dev = DEVICE_DT_GET(DT_NODELABEL(adc3));

在得到设备指针后就可以通过通用的设备驱动 API 访问设备,这些 API 可以通过对应的头文件找到,例如对于 ADC 来说,常用的 API 有:

__syscall int adc_channel_setup(const struct device *dev,
				const struct adc_channel_cfg *channel_cfg);

__syscall int adc_read(const struct device *dev,
		       const struct adc_sequence *sequence);

__syscall int adc_read_async(const struct device *dev,
			     const struct adc_sequence *sequence,
			     struct k_poll_signal *async);

大部分外设都提供与DEVICE_DT_GETdevice_is_ready等效的专用 API,例如对于 ADC 来说就是 ADC_DT_SPEC_GETadc_is_ready_dt, 前面的示例代码就可以改为

const struct adc_dt_spec *spec;
    spec = ADC_DT_SPEC_GET(DT_NODELABEL(adc0));

    if (!adc_is_ready_dt(spec)) {
		printk("device can't ready\r\n");
        return;
    }

struct adc_dt_spec从 devicetree 中获取更多关于外设的配置信息,可减少在应用代码中手动添加外设配置的需求

struct adc_dt_spec {
	const struct device *dev;

	uint8_t channel_id;
	bool channel_cfg_dt_node_exists;
	struct adc_channel_cfg channel_cfg;
	uint16_t vref_mv;
	uint8_t resolution;
	uint8_t oversampling;
};

因此使用特定外设的操作方法也是默认的推荐方式。类似的也提供的特定设备指针的操作 API,例如

static inline int adc_channel_setup_dt(const struct adc_dt_spec *spec)
static inline int adc_read_dt(const struct adc_dt_spec *spec,
			      const struct adc_sequence *sequence)

参考 链接到标题

https://docs.zephyrproject.org/4.0.0/kernel/drivers/index.html