在 Zephyr 中开发自定义 GPIO 驱动的完整指南

在嵌入式开发中,Zephyr RTOS 提供了强大的设备树和驱动模型,帮助开发者快速集成和管理硬件设备。本文将以一个实际的自定义 GPIO 驱动项目为例,详细讲解如何设计设备树、设备树绑定、实现驱动、编写设备树覆写文件,以及如何在应用程序中获取和调用驱动。

项目结构概览

项目结构如下:

hello_world/
├── boards/
│   └── app.overlay          # 设备树覆写文件
├── dts/
│   └── bendings/
│       └── my_driver.yaml   # 设备树绑定文件
├── src/
│   └── main.c              # 主应用程序
└── zephyr/
    ├── my_driver.c         # 驱动实现
    ├── my_driver.h         # 驱动头文件
    └── CMakeLists.txt      # 构建配置

1. 设备树的设计

设备树是 Zephyr 中用于描述硬件的关键部分。它以树状结构的形式定义了硬件资源及其属性。设备树的核心是设备节点,每个节点都描述了一个硬件设备。

实际工程中的设备树节点

在您的 app.overlay 文件中,定义了以下设备树节点:

#include <zephyr/dt-bindings/gpio/gpio.h>

/ {
    zephyr,user {
        status = "disabled";
        string = "led control\n";
        signal-gpios = <&gpioa_00_31 26 GPIO_ACTIVE_LOW>;
    };

    my_gpio {
        compatible = "my-gpio";
        status = "okay";
        label = "zephyr_my_gpio";
    };
};
  1. zephyr,user 节点

    • status = "disabled":该节点默认禁用,可通过覆写启用
    • string = "led control\n":自定义字符串属性,用于配置信息
    • signal-gpios = <&gpioa_00_31 26 GPIO_ACTIVE_LOW>:GPIO 引脚定义,使用 GPIOA 的第 26 引脚,低电平有效
  2. my_gpio 节点

    • compatible = "my-gpio":兼容性字符串,与驱动中的 DT_DRV_COMPAT 匹配
    • status = "okay":启用该设备节点
    • label = "zephyr_my_gpio":设备标签,用于代码中通过 DEVICE_DT_GET 获取设备

设备树节点设计原则

  1. 命名规范:节点名称应具有描述性,通常采用 <设备类型>@<地址> 格式
  2. 属性定义:所有硬件相关配置都应通过属性定义
  3. 状态管理:使用 status 属性控制设备的启用和禁用
  4. 标签使用:为需要代码访问的设备设置 label 属性

2. 设备树绑定文件的设计

设备树绑定文件是设备树的描述文件,用于定义设备节点的属性和约束条件。绑定文件通常以 .yaml 格式编写。

实际工程中的绑定文件

dts/bendings/my_driver.yaml 中:

description: My GPIO driver
compatible: "my-gpio"

properties:
  label:
    type: string
    required: true
    description: Device label for the GPIO driver
  
include: base.yaml
  1. description:对设备的简要描述,帮助开发者理解设备用途
  2. compatible:与设备树中的 compatible 属性匹配,确保驱动与设备正确绑定
  3. properties:定义设备的属性约束
    • label:必需属性,类型为字符串,用于设备标识
  4. include: base.yaml:继承 Zephyr 的基础绑定文件,包含通用属性定义

3. 驱动的实现

驱动是设备的核心逻辑,负责初始化硬件、提供 API 接口等。以下基于您的实际驱动代码进行分析。

驱动头文件设计

zephyr/my_driver.h 中:

#ifndef _MY_GPIO_H_
#define _MY_GPIO_H_

#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>

#ifdef __cplusplus
extern "C" {
#endif

// 驱动 API 定义

#ifdef __cplusplus
}
#endif

#endif /* ZEPHYR_DRIVERS_GPIO_MY_GPIO_H_ */

驱动源文件实现

zephyr/my_driver.c 中的核心实现:

#define DT_DRV_COMPAT my_gpio

#include <stdint.h>
#include <zephyr/sys/printk.h>
#include <zephyr/arch/cpu.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/gpio/gpio_utils.h>
#include "my_driver.h"

// 数据结构定义
struct my_gpio_data {
    struct gpio_driver_data common;
    gpio_port_pins_t output_state;
};

struct my_gpio_config {
    struct gpio_driver_config common;
};

// GPIO 配置函数
static int my_gpio_configure(const struct device *port, gpio_pin_t pin, gpio_flags_t flags)
{
    printk("my_gpio: configure pin %d with flags 0x%x\n", pin, flags);
    return 0;
}

// 获取 GPIO 端口原始值
static int my_gpio_port_get_raw(const struct device *port, gpio_port_value_t *value)
{
    struct my_gpio_data *data = port->data;
    *value = data->output_state;
    return 0;
}

// 设置 GPIO 端口掩码值
static int my_gpio_port_set_masked_raw(const struct device *port,
           gpio_port_pins_t mask,
           gpio_port_value_t value)
{
    struct my_gpio_data *data = port->data;
    data->output_state = (data->output_state & ~mask) | (value & mask);
    printk("my_gpio: set masked 0x%x to 0x%x, new state: 0x%x\n", mask, value, data->output_state);
    return 0;
}

// 设置 GPIO 端口位
static int my_gpio_port_set_bits_raw(const struct device *port, gpio_port_pins_t pins)
{
    struct my_gpio_data *data = port->data;
    data->output_state |= pins;
    printk("my_gpio: set bits 0x%x, new state: 0x%x\n", pins, data->output_state);
    return 0;
}

// 清除 GPIO 端口位
static int my_gpio_port_clear_bits_raw(const struct device *port, gpio_port_pins_t pins)
{
    struct my_gpio_data *data = port->data;
    data->output_state &= ~pins;
    printk("my_gpio: clear bits 0x%x, new state: 0x%x\n", pins, data->output_state);
    return 0;
}

// 切换 GPIO 端口位
static int my_gpio_port_toggle_bits(const struct device *port, gpio_port_pins_t pins)
{
    struct my_gpio_data *data = port->data;
    data->output_state ^= pins;
    printk("my_gpio: toggle bits 0x%x, new state: 0x%x\n", pins, data->output_state);
    return 0;
}

// GPIO 中断配置
static int my_gpio_pin_interrupt_configure(const struct device *port,
               gpio_pin_t pin,
               enum gpio_int_mode mode,
               enum gpio_int_trig trig)
{
    printk("my_gpio: pin %d interrupt configure mode=%d trig=%d\n", pin, mode, trig);
    return 0;
}

// 定义驱动 API 结构
static const struct gpio_driver_api my_gpio_api = {
    .pin_configure = my_gpio_configure,
    .port_get_raw = my_gpio_port_get_raw,
    .port_set_masked_raw = my_gpio_port_set_masked_raw,
    .port_set_bits_raw = my_gpio_port_set_bits_raw,
    .port_clear_bits_raw = my_gpio_port_clear_bits_raw,
    .port_toggle_bits = my_gpio_port_toggle_bits,
    .pin_interrupt_configure = my_gpio_pin_interrupt_configure,
};

// 驱动初始化函数
static int my_gpio_init(const struct device *dev)
{
    struct my_gpio_data *data = dev->data;
    data->output_state = 0;
    printk("my_gpio: initialized\n");
    return 0;
}

// 设备实例定义宏
#define MY_GPIO_DEFINE(n) \
    static const struct my_gpio_config my_gpio_config_##n = { \
               .common = {0} \
    };\
    static struct my_gpio_data my_gpio_data_##n;\
    DEVICE_DT_INST_DEFINE(n, my_gpio_init, NULL, \
                         &my_gpio_data_##n, &my_gpio_config_##n, \
                         POST_KERNEL, CONFIG_GPIO_INIT_PRIORITY, \
                         &my_gpio_api);

// 为所有兼容的设备节点生成设备实例
DT_INST_FOREACH_STATUS_OKAY(MY_GPIO_DEFINE)

驱动实现关键技术点分析

1. 设备树兼容性匹配
#define DT_DRV_COMPAT my_gpio
  • 与设备树中的 compatible = "my-gpio" 精确匹配
  • 确保驱动只处理指定的设备节点
2. 驱动数据结构设计
struct my_gpio_data {
    struct gpio_driver_data common;
    gpio_port_pins_t output_state;
};
  • 继承标准 GPIO 驱动数据结构
  • 添加自定义状态管理字段
3. 完整的 GPIO API 实现

驱动实现了标准的 GPIO 驱动 API,包括:

  • 引脚配置 (pin_configure)
  • 端口读写操作 (port_get_raw, port_set_masked_raw)
  • 位操作 (port_set_bits_raw, port_clear_bits_raw, port_toggle_bits)
  • 中断配置 (pin_interrupt_configure)
4. 设备实例化机制
DT_INST_FOREACH_STATUS_OKAY(MY_GPIO_DEFINE)
  • 自动为设备树中所有状态为 okay 的兼容设备生成实例
  • 简化了多设备支持的管理

4. 设备树覆写文件的设计

设备树覆写文件(app.overlay)用于为特定的硬件平台添加或修改设备树节点。在实际工程中,这是配置硬件的关键文件。

实际工程中的覆写文件分析

#include <zephyr/dt-bindings/gpio/gpio.h>

/ {
    zephyr,user {
        status = "disabled";
        string = "led control\n";
        signal-gpios = <&gpioa_00_31 26 GPIO_ACTIVE_LOW>;
    };

    my_gpio {
        compatible = "my-gpio";
        status = "okay";
        label = "zephyr_my_gpio";
    };
};
详细技术解析
  1. 头文件包含

    #include <zephyr/dt-bindings/gpio/gpio.h>
    
    • 引入 GPIO 相关的宏定义,如 GPIO_ACTIVE_LOW
    • 确保设备树配置的标准化
  2. 自定义用户节点

    zephyr,user {
        status = "disabled";
        string = "led control\n";
        signal-gpios = <&gpioa_00_31 26 GPIO_ACTIVE_LOW>;
    };
    
    • 状态管理:默认禁用,可根据需要启用
    • 字符串属性:提供配置信息,可在代码中通过 DT_PROP 读取
    • GPIO 配置:具体引脚定义,使用 GPIOA 的第 26 引脚
  3. 自定义 GPIO 设备节点

    my_gpio {
        compatible = "my-gpio";
        status = "okay";
        label = "zephyr_my_gpio";
    };
    

5. 获取和调用驱动

在主程序中,通过设备树 API 获取设备实例,并调用驱动提供的功能。基于您的实际代码进行分析。

实际工程中的驱动调用

src/main.c 中的关键实现:

#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.h>
#include <stdio.h> 
#include <zephyr/devicetree.h>

// 线程栈定义
#define MY_STACK_SIZE 512
K_THREAD_STACK_DEFINE(my_thread_stack, MY_STACK_SIZE)

// 设备树别名定义
#define KEY1_NODE DT_ALIAS(sw0) 
#define KEY2_NODE DT_ALIAS(sw1)

// GPIO 设备规格定义
static const struct gpio_dt_spec button1 = GPIO_DT_SPEC_GET(KEY1_NODE, gpios);
static const struct gpio_dt_spec button2 = GPIO_DT_SPEC_GET(KEY2_NODE, gpios);

// 自定义用户节点属性读取
#define ZEPHYR_USER_NODE DT_PATH(zephyr_user)
char *text = DT_PROP(ZEPHYR_USER_NODE, string);

// LED 设备规格定义
static const struct gpio_dt_spec led0 = GPIO_DT_SPEC_GET(ZEPHYR_USER_NODE, signal_gpios);

// 自定义 GPIO 设备获取
#if DT_NODE_EXISTS(DT_PATH(my_gpio))
const struct device *my_gpio_device = DEVICE_DT_GET(DT_PATH(my_gpio));
#endif

int main(void)
{
    printf("Hello World with buttons and key!\n");

    // 检查自定义 GPIO 设备状态
#if DT_NODE_EXISTS(DT_PATH(my_gpio))
    if (!device_is_ready(my_gpio_device)) {
        printf("Error: my_gpio device not ready\n");
    } else {
        printf("my_gpio device ready\n");
    }
#endif

    // 检查按钮设备状态
    if (!gpio_is_ready_dt(&button1)) {
        printf("Error: btn1 not ready\n");
        return 0;
    }
    if (!gpio_is_ready_dt(&button2)) {
        printf("Error: btn2 not ready\n");
        return 0;
    }
    
    // 检查 LED 设备状态
    if (!gpio_is_ready_dt(&led0)) {
        printf("Error: led0 not ready\n");
        return 0;
    }

    // 配置按钮为输入模式
    int ret = gpio_pin_configure_dt(&button1, GPIO_INPUT);
    if (ret != 0) {
        printf("Error: failed to configure button1\n");
        return 0;
    }

    ret = gpio_pin_configure_dt(&button2, GPIO_INPUT);
    if (ret != 0) {
        printf("Error: failed to configure button2\n");
        return 0;
    }

    // 配置 LED 为输出模式
    ret = gpio_pin_configure_dt(&led0, GPIO_OUTPUT);
    if (ret != 0) {
        printf("Error: failed to configure led0\n");
        return 0;
    }

    printf("All devices configured successfully\n");
    printf("Custom text from device tree: %s", text);

    // 主循环
    while (1) {
        // 读取按钮状态并控制 LED
        int btn1_state = gpio_pin_get_dt(&button1);
        int btn2_state = gpio_pin_get_dt(&button2);
        
        // 根据按钮状态控制 LED
        if (btn1_state == 0) { // 按钮按下(假设低电平有效)
            gpio_pin_set_dt(&led0, 1); // 点亮 LED
        } else if (btn2_state == 0) {
            gpio_pin_set_dt(&led0, 0); // 熄灭 LED
        }
        
        k_msleep(100); // 延时 100ms
    }
    
    return 0;
}
Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐