树莓派3B点灯(3)-- 自写驱动(闪烁版)

越来越发现树莓派很友好了,用来学习真的太棒了!在板子上可以建立整套编译环境,不管是应用还是内核模块,都可以即编即用,效率高的像飙车。。。

建立环境很简单

sudo apt update
sudo apt install raspberrypi-kernel-headers build-essential

之后就可以gcc这些玩意了。。

这次打算做多个版本。V1就是最简单的普通版,实现LED闪烁。V2增加内核节点,可以在用户空间进行控制。V3则是增加设备树的匹配或者其他。提供IOCTL的接口这次就不写了,因为很久之前写过。。。

V1:

my_blink_led.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/kthread.h>

#define LED_GPIO_PIN 538 // GPIO26

static struct task_struct *blink_thread;

// LED控制线程
static int blink_led_thread(void *data) {
    int state = 0;

    // 让LED持续闪烁
    while (!kthread_should_stop()) {
        gpio_set_value(LED_GPIO_PIN, state);
        state = !state;  // 切换LED状态
        msleep(500);     // 闪烁频率:500ms开,500ms关
    }

    // 离开时确保关闭LED
    gpio_set_value(LED_GPIO_PIN, 0);
    return 0;
}

// 模块初始化
static int __init my_led_init(void) {
    int ret;

    // 申请GPIO26
    ret = gpio_request(LED_GPIO_PIN, "LED_GPIO");
    if (ret) {
        pr_err("Failed to request GPIO %d\n", LED_GPIO_PIN);
        return ret;
    }

    // 设置GPIO26为输出模式
    ret = gpio_direction_output(LED_GPIO_PIN, 0);
    if (ret) {
        pr_err("Failed to set GPIO %d as output\n", LED_GPIO_PIN);
        gpio_free(LED_GPIO_PIN);
        return ret;
    }

    // 创建线程来控制LED闪烁
    blink_thread = kthread_run(blink_led_thread, NULL, "led_blink_thread");
    if (IS_ERR(blink_thread)) {
        pr_err("Failed to create LED blink thread\n");
        gpio_free(LED_GPIO_PIN);
        return PTR_ERR(blink_thread);
    }

    pr_info("LED blink module loaded\n");
    return 0;
}

// 模块退出
static void __exit my_led_exit(void) {
    // 停止线程
    if (blink_thread) {
        kthread_stop(blink_thread);
    }

    // 释放GPIO
    gpio_free(LED_GPIO_PIN);
    pr_info("LED blink module unloaded\n");
}

module_init(my_led_init);
module_exit(my_led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fanged");
MODULE_DESCRIPTION("Simple GPIO LED Blink Module");

调用的是Linux的GPIO库,主要是gpio_request,gpio_direction_output,gpio_set_value这几个函数。

这里有个注意的地方,在定义GPIO口的时候,要定义成内部的那个538。不能直接写26,否则要报错。之前有讲过,两个具体差异后面有时间再看看(TODO)。

Makefile

obj-m += my_blink_led.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

之后直接make,然后sudo insmod my_blink_led.ko,卸载则是sudo rmmod my_blink_led。

基本上insmod之后,马上就能看到LED闪了,就像天上的星星。

V2:

my_blink_led.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/sysfs.h>
#include <linux/fs.h>

#define LED_GPIO_PIN 538  // 使用 GPIO26 控制LED
static struct task_struct *blink_thread;
static int blink_delay = 500;  // LED 闪烁的时间间隔(毫秒)
static int led_state = 1;      // LED 状态(1=开启,0=关闭)
static int is_blinking = 1;    // 是否允许闪烁

// LED 控制线程
static int blink_led_thread(void *data) {
    while (!kthread_should_stop()) {
        if (is_blinking) {
            gpio_set_value(LED_GPIO_PIN, led_state);
            led_state = !led_state;
            msleep(blink_delay);
        } else {
            gpio_set_value(LED_GPIO_PIN, led_state);  // 设置当前LED状态
            msleep(100);
        }
    }
    gpio_set_value(LED_GPIO_PIN, 0);  // 模块卸载时关闭LED
    return 0;
}

// sysfs 节点:用于配置LED是否闪烁
static ssize_t blink_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) {
    return sprintf(buf, "%d\n", is_blinking);
}

static ssize_t blink_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) {
    int blink;
    if (kstrtoint(buf, 10, &blink))
        return -EINVAL;

    is_blinking = blink;
    return count;
}

// sysfs 节点:用于配置闪烁间隔
static ssize_t delay_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) {
    return sprintf(buf, "%d\n", blink_delay);
}

static ssize_t delay_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) {
    int delay;
    if (kstrtoint(buf, 10, &delay))
        return -EINVAL;

    blink_delay = delay;
    return count;
}

// 定义 sysfs 接口属性
static struct kobj_attribute blink_attr = __ATTR(blink, 0664, blink_show, blink_store);
static struct kobj_attribute delay_attr = __ATTR(delay, 0664, delay_show, delay_store);

static struct attribute *attrs[] = {
    &blink_attr.attr,
    &delay_attr.attr,
    NULL,
};

// sysfs 属性组
static struct attribute_group attr_group = {
    .attrs = attrs,
};

static struct kobject *blink_kobj;  // sysfs kobject

// 模块初始化
static int __init my_led_init(void) {
    int ret;

    // 请求 GPIO26
    ret = gpio_request(LED_GPIO_PIN, "LED_GPIO");
    if (ret) {
        pr_err("Failed to request GPIO %d\n", LED_GPIO_PIN);
        return ret;
    }

    // 设置 GPIO26 为输出模式
    ret = gpio_direction_output(LED_GPIO_PIN, 0);
    if (ret) {
        pr_err("Failed to set GPIO %d as output\n", LED_GPIO_PIN);
        gpio_free(LED_GPIO_PIN);
        return ret;
    }

    // 创建 sysfs 节点
    blink_kobj = kobject_create_and_add("blink_led", kernel_kobj);
    if (!blink_kobj) {
        pr_err("Failed to create sysfs kobject\n");
        gpio_free(LED_GPIO_PIN);
        return -ENOMEM;
    }

    // 创建 sysfs 属性组
    ret = sysfs_create_group(blink_kobj, &attr_group);
    if (ret) {
        pr_err("Failed to create sysfs group\n");
        kobject_put(blink_kobj);
        gpio_free(LED_GPIO_PIN);
        return ret;
    }

    // 创建 LED 闪烁控制线程
    blink_thread = kthread_run(blink_led_thread, NULL, "led_blink_thread");
    if (IS_ERR(blink_thread)) {
        pr_err("Failed to create LED blink thread\n");
        sysfs_remove_group(blink_kobj, &attr_group);
        kobject_put(blink_kobj);
        gpio_free(LED_GPIO_PIN);
        return PTR_ERR(blink_thread);
    }

    pr_info("LED blink module loaded\n");
    return 0;
}

// 模块退出
static void __exit my_led_exit(void) {
    // 停止 LED 控制线程
    if (blink_thread) {
        kthread_stop(blink_thread);
    }

    // 移除 sysfs 节点
    sysfs_remove_group(blink_kobj, &attr_group);
    kobject_put(blink_kobj);

    // 释放 GPIO
    gpio_free(LED_GPIO_PIN);
    pr_info("LED blink module unloaded\n");
}

module_init(my_led_init);
module_exit(my_led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fanged");
MODULE_DESCRIPTION("Simple GPIO LED Blink Module with sysfs control");

代码通过kobject_create_and_add创建了sysfs节点,sysfs_create_group创建群组。群组就是下面的控制点,在struct kobj_attribute这个结构中进行定义。这个结构最后有两个回调函数,第一个用来get,后面一个用来set。(这个也是老套路中的老套路了)

Makefile,编译方法,insmod方法和上面一样。之后马上就能在/sys/kernel、下面看到节点。

控制是否闪烁:

开
echo 1 | sudo tee /sys/kernel/blink_led/blink
关
echo 0 | sudo tee /sys/kernel/blink_led/blink

控制闪烁频率(单位毫秒):

echo 200 | sudo tee /sys/kernel/blink_led/delay

至于为什么这里不用echo 200 > /sys/kernel/blink_led/delay这样看起来要简单一点的写法呢?还是权限,如果是root,那么两个没有区别。如果不是root,那么后面这个会遇到权限不足的报错。

V3及以后

后面单独写吧。。

参考:

Android新增LED设备--从底层到上层理解安卓架构_安卓增加led灯节点-CSDN博客

要自己实现一个不使用内核 `leds` 模块的简单 LED 驱动程序,并通过设备树配置,可以按照以下步骤来完成:

1. **编写设备树配置**:定义 GPIO 和 LED 的相关信息。
2. **编写自定义内核模块(.ko 文件)**:通过 `GPIO` 控制 LED 的亮灭。
3. **通过 sysfs 暴露控制接口**:允许用户通过 `/sys` 文件系统控制 LED 的开关。
4. **设备树绑定**:让设备树来配置 GPIO 引脚。

### 1. 设备树配置
首先,编写设备树配置,用于指定 LED 所使用的 GPIO 引脚和相关信息。

**`my_leds.dts`** 文件:

```dts
/dts-v1/;
/plugin/;

/ {
    compatible = "my_led_driver";

    fragment@0 {
        target = <&gpio>;
        __overlay__ {
            my_led {
                compatible = "my-led";
                gpios = <&gpio 26 0>;  // 使用GPIO26,active high
            };
        };
    };
};
```

- `compatible = "my-led";`:用于匹配自定义的 LED 驱动。
- `gpios = <&gpio 26 0>;`:GPIO26 是用于控制 LED 的引脚,`0` 表示 "active high"(高电平点亮 LED)。

### 2. 编写内核模块
接下来,编写一个简单的 LED 驱动模块,这个模块通过 sysfs 暴露 LED 的控制接口。

**`my_led_driver.c`** 文件:

```c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/sysfs.h>

// 设备结构体
struct my_led_data {
    int gpio_num;
};

// 打开和关闭 LED 的函数
static ssize_t led_on(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
    struct my_led_data *led_data = dev_get_drvdata(dev);
    int state;

    // 将用户输入转换为整数
    if (kstrtoint(buf, 10, &state))
        return -EINVAL;

    // 控制 GPIO 引脚
    gpio_set_value(led_data->gpio_num, state);

    return count;
}

// 定义 sysfs 接口
static DEVICE_ATTR(state, 0220, NULL, led_on);

// 设备树匹配表
static const struct of_device_id my_led_of_match[] = {
    { .compatible = "my-led", },
    {},
};
MODULE_DEVICE_TABLE(of, my_led_of_match);

// 平台驱动的 probe 函数
static int my_led_probe(struct platform_device *pdev)
{
    struct my_led_data *led_data;
    struct device *dev = &pdev->dev;
    int ret;

    // 分配数据结构
    led_data = devm_kzalloc(dev, sizeof(*led_data), GFP_KERNEL);
    if (!led_data)
        return -ENOMEM;

    // 获取设备树中配置的 GPIO 引脚
    led_data->gpio_num = of_get_named_gpio(dev->of_node, "gpios", 0);
    if (led_data->gpio_num < 0)
        return led_data->gpio_num;

    // 申请 GPIO
    ret = devm_gpio_request_one(dev, led_data->gpio_num, GPIOF_OUT_INIT_LOW, "my_led");
    if (ret) {
        dev_err(dev, "Failed to request GPIO\n");
        return ret;
    }

    // 在 sysfs 中创建 "state" 文件,用于控制 LED
    ret = device_create_file(dev, &dev_attr_state);
    if (ret) {
        dev_err(dev, "Failed to create sysfs entry\n");
        return ret;
    }

    dev_set_drvdata(dev, led_data);
    return 0;
}

// 平台驱动的 remove 函数
static int my_led_remove(struct platform_device *pdev)
{
    device_remove_file(&pdev->dev, &dev_attr_state);
    return 0;
}

// 定义平台驱动结构
static struct platform_driver my_led_driver = {
    .probe = my_led_probe,
    .remove = my_led_remove,
    .driver = {
        .name = "my_led",
        .of_match_table = my_led_of_match,
    },
};

module_platform_driver(my_led_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Simple GPIO LED driver");
```

### 3. 驱动实现的关键点

#### a) **设备树支持**
- `of_device_id` 表中的 `compatible` 字符串 (`my-led`) 与设备树中定义的 `compatible = "my-led"` 进行匹配。
- `of_get_named_gpio` 函数用于从设备树中获取 GPIO 引脚编号。

#### b) **GPIO 控制**
- 驱动中使用 `gpio_set_value()` 来控制 GPIO 引脚的电平,从而点亮或熄灭 LED。
- `devm_gpio_request_one()` 用于向内核请求控制指定的 GPIO 引脚。

#### c) **sysfs 接口**
- 通过 `device_create_file()` 创建了一个 `state` 文件,允许用户通过 `/sys/class/` 来控制 LED 的状态(亮灭)。
- 当用户写入 `1` 或 `0` 到 `state` 文件时,驱动程序会根据输入改变 GPIO 的电平,从而控制 LED 的亮灭。

### 4. 编译驱动

首先,编译内核模块(确保你已设置好内核编译环境)。

```bash
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
```

### 5. 设备树加载

将编译好的设备树文件(`my_leds.dts`)转换为设备树覆盖文件:

```bash
dtc -@ -I dts -O dtb -o my_leds.dtbo my_leds.dts
sudo cp my_leds.dtbo /boot/overlays/
```

在 `/boot/config.txt` 中添加以下行以加载设备树覆盖文件:

```bash
dtoverlay=my_leds
```

### 6. 加载内核模块

加载编译好的驱动模块:

```bash
sudo insmod my_led_driver.ko
```

检查内核日志,确认驱动已正确加载并找到设备树中的配置:

```bash
dmesg | grep my_led
```

### 7. 控制 LED

现在可以通过 `sysfs` 控制 LED:

```bash
# 开启 LED
echo 1 > /sys/devices/platform/my_led/state

# 关闭 LED
echo 0 > /sys/devices/platform/my_led/state
```

### 8. 总结

- 设备树配置 (`my_leds.dts`) 用于定义 LED 使用的 GPIO 引脚。
- 自定义内核驱动 (`my_led_driver.c`) 实现了基本的 LED 控制逻辑,并通过 `sysfs` 暴露给用户控制。
- 可以通过设备树和 sysfs 文件系统来控制 LED 的亮灭。

这是一个简单的例子,展示了如何通过设备树和自定义内核模块来控制 GPIO 引脚上的 LED。如果需要扩展功能,比如支持更多 LED 或者复杂的控制逻辑,可以在此基础上进行进一步的开发。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值