Heng30的博客
搜索 分类 关于 订阅

如何通过设备树初始化raspi3b的gpio?

2025-03-08

点亮第一个LED灯是嵌入式开发的Hello World程序。下面我们学习通过Linux驱动来点亮LED灯。通过设备树来获取LED的信息,使用Linux内核的GPIO子系统提供的接口来控制LED灯。

实验环境

raspberry pi3b

Linux raspberrypi 6.1.21-v8+ #1642 SMP PREEMPT Mon Apr  3 17:24:16 BST 2023 aarch64 GNU/Linux

设备树文件

// testoverlay.dts

/dts-v1/;
/plugin/;
/ {
    compatible = "raspberrypi,3-model-bbrcm,bcm2837";
    fragment@0 {
        target-path = "/";
        __overlay__ {
            my_device {
                compatible = "brightlight,mydev";
                status = "okay";
                label = "test";
                my_value = <12>;
                green-led-gpio = <&gpio 21 0>;
            };
        };
    };
};

驱动代码

// simple.c

#include <linux/gpio/consumer.h>
#include <linux/init.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/proc_fs.h>
#include <linux/property.h>

static int led_status = 0;

static struct gpio_desc *my_led = NULL;
static struct proc_dir_entry *proc_file = NULL;

static struct of_device_id my_driver_ids[] = {
    {
        .compatible = "brightlight,mydev",
    },
    {},
};
MODULE_DEVICE_TABLE(of, my_driver_ids);

static ssize_t _read(struct file *file, char __user *userbuf, size_t size,
                     loff_t *offset) {
    int remain_len = 0;
    pr_info("simple_read\n");

    remain_len = copy_to_user(userbuf, &led_status, sizeof(led_status));
    if (remain_len > 0) {
        pr_err("copy_to_user failed. remove length is %d\n", remain_len);
    }

    return sizeof(led_status);
}

static ssize_t _write(struct file *file, const char __user *userbuf,
                      size_t size, loff_t *offset) {
    pr_info("simple_write\n");

    led_status = userbuf[0] - '0';
    switch (led_status) {
    case 0:
    case 1:
        gpiod_set_value(my_led, led_status);
    default:
        break;
    }

    return size;
}

static struct proc_ops fops = {
    .proc_read = _read,
    .proc_write = _write,
};

static int _dt_probe(struct platform_device *pdev) {
    struct device *dev = &pdev->dev;
    const char *label = NULL;
    int my_value = 0, ret = 0;

    pr_info("simple_dt_probe\n");

    if (!device_property_present(dev, "label")) {
        pr_err("device_property_present 'lable' failed\n");
        return -1;
    }

    if (!device_property_present(dev, "my_value")) {
        pr_err("device_property_present 'my_value' failed\n");
        return -1;
    }

    if (!device_property_present(dev, "green-led-gpio")) {
        pr_err("device_property_present 'green-led-gpio' failed\n");
        return -1;
    }

    ret = device_property_read_string(dev, "label", &label);
    if (ret) {
        pr_err("device_property_read_string 'label' failed\n");
        return -1;
    }
    pr_info("lable = %s", label);

    ret = device_property_read_u32(dev, "my_value", &my_value);
    if (ret) {
        pr_err("device_property_read_u32 'my_value' failed\n");
        return -1;
    }
    pr_info("my_value = %d", my_value);

    my_led = gpiod_get(dev, "green-led", GPIOD_OUT_LOW);
    if (IS_ERR(my_led)) {
        pr_err("gpiod_get failed\n");
        return -IS_ERR(my_led);
    }

    proc_file = proc_create("my-led", 0666, NULL, &fops);
    if (!proc_file) {
        pr_err("proc_create /proc/my-led file failed\n");
        gpiod_put(my_led);
        return -ENOMEM;
    }

    return 0;
}

static int _dt_remove(struct platform_device *pdev) {
    pr_info("simple_dt_remove\n");
    proc_remove(proc_file);

    return 0;
}

static struct platform_driver my_dirver = {
    .probe = _dt_probe,
    .remove = _dt_remove,
    .driver =
        {
            .name = "my_device_driver",
            .of_match_table = my_driver_ids,
        },
};

static int __init simple_init(void) {
    pr_info("simple_init\n");

    if (platform_driver_register(&my_dirver)) {
        pr_err("platform_driver_register error");
        return -1;
    }

    return 0;
}

static void __exit simple_exit(void) {
    pr_info("simple_exit\n");

    proc_remove(proc_file);
    gpiod_put(my_led);
    platform_driver_unregister(&my_dirver);
}

module_init(simple_init);
module_exit(simple_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("heng30");
MODULE_VERSION("v0.0.1");
MODULE_DESCRIPTION("A simple kernel module");

编译脚本

#!/bin/sh

top-dir = $(shell pwd)
kernel-version = $(shell uname -r)
kernel-dir ?= /lib/modules/$(kernel-version)/build

obj-m += simple.o

all: dt driver

driver:
        make -C $(kernel-dir) modules M=$(top-dir)

dt:
        dtc -@ -I dts -O dtb -o testoverlay.dtbo testoverlay.dts

clean:
        rm -f *.o *.ko *.mod *.mod.c *.order *.symvers *.dtbo
        make -C $(kernel-dir) clean m=$(top-dir)

测试

  • 安装gpio工具:sudo apt install gpiod

  • 编译程序:make

  • 安装设备树节点:sudo dtoverlay testoverlay.dtbo

  • 查看设备树节点信息:ls /proc/device-tree/my_device/

compatible  green-led-gpio  label  my_value  name  status
  • 查看gpio 21引脚状态:gpioget 0 21
0
  • 安装驱动:insmod simple.ko

  • 设置gpio 21引脚为高电平: echo 1 > /proc/my-led

  • 移除驱动:rmmod simple.ko

  • 移除设备树节点:sudo dtoverlay -r testoverlay

  • 查看gpio 21引脚状态:gpioget 0 21

1

参考