基于 Zephyr 的 SF32 自定义驱动开发教程
在 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";
};
};
-
zephyr,user节点:status = "disabled":该节点默认禁用,可通过覆写启用string = "led control\n":自定义字符串属性,用于配置信息signal-gpios = <&gpioa_00_31 26 GPIO_ACTIVE_LOW>:GPIO 引脚定义,使用 GPIOA 的第 26 引脚,低电平有效
-
my_gpio节点:compatible = "my-gpio":兼容性字符串,与驱动中的DT_DRV_COMPAT匹配status = "okay":启用该设备节点label = "zephyr_my_gpio":设备标签,用于代码中通过DEVICE_DT_GET获取设备
设备树节点设计原则
- 命名规范:节点名称应具有描述性,通常采用
<设备类型>@<地址>格式 - 属性定义:所有硬件相关配置都应通过属性定义
- 状态管理:使用
status属性控制设备的启用和禁用 - 标签使用:为需要代码访问的设备设置
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
description:对设备的简要描述,帮助开发者理解设备用途compatible:与设备树中的compatible属性匹配,确保驱动与设备正确绑定properties:定义设备的属性约束label:必需属性,类型为字符串,用于设备标识
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";
};
};
详细技术解析
-
头文件包含
#include <zephyr/dt-bindings/gpio/gpio.h>- 引入 GPIO 相关的宏定义,如
GPIO_ACTIVE_LOW - 确保设备树配置的标准化
- 引入 GPIO 相关的宏定义,如
-
自定义用户节点
zephyr,user { status = "disabled"; string = "led control\n"; signal-gpios = <&gpioa_00_31 26 GPIO_ACTIVE_LOW>; };- 状态管理:默认禁用,可根据需要启用
- 字符串属性:提供配置信息,可在代码中通过
DT_PROP读取 - GPIO 配置:具体引脚定义,使用 GPIOA 的第 26 引脚
-
自定义 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;
}
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)