基于树莓派4B的Linux驱动------点亮LED灯_树莓派装linux led

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

基于树莓派4B的Linux驱动------点亮LED灯

美好的一天从点灯开始
学好Linux驱动从点灯大师做起

本人也是接触Linux不久可能有些问题也没考虑到以下仅是个人观点欢迎留言共同进步话不多说直接步入正题。

一、实验说明

本次实验采用设备树编程
开发板基于树莓派4B
linux内核版本linux-rpi-5.15.y
开发平台ubuntu交叉编译
LED为高电平点亮

二、修改设备树文件

为了方便我是直接在根节点下添加一个ledtest节点要不然放得太深不好找
首先在linux内核中找到对应的设备树文件
这里我是修改了bcm2711-rpi-4.dts文件
linux内核设备树文件路径在arch/arm/boot/dts目录下可以在该目录下找到该文件
设备树的语法我这里不细讲设备树的语法可以自己去搜网上一搜一大把
在根节点下添加以下节点

ledtest{
	#address-cells = <1>;
    #size-cells = <1>;
	compatible = "ledtest";
	pinctrl-names = "default";
	gpios = <&gpio 26 GPIO_ACTIVE_HIGH>;
    status = "okay";
};

属性#address-cells 和#size-cells 都为 1表示 reg 属性中起始地址占用一个字长(cell)地址长度也占用一个字长(cell)
属性 compatbile 设置 ledtest节点兼容性为“ledtest”
pinctrl-names 属性此属性描述 pinctrl 名字为“default”。
gpios 属性值表示此 LED 所使用的 GPIO 引脚的信息
属性 status 设置状态为“okay”。

本次我们使用的引脚编码是 BCM 26
可以使用gpio readall 命令查看树莓派引脚分布和状态设置自己想要设置的引脚不一定要是26
保存然后编译设备树文件使用命令make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
然后把编译好的.dtb文件放到板子里面

三、编写驱动程序

然后就可以写驱动程序了我们可以使用of函数来读取设备树里面的内容以下是我用到的of函数
of_find_node_by_path 用来打开设备节点
of_get_named_gpio 用来获取设备树中的某个属性
然后通过读取设备树属性获取到引脚编码再通过gpio_direction_output函数配置引脚为输出gpio_set_value函数来设置引脚输出的电平后面加上注册设备号注册字符设备自动创建设备节点这样编程的思路就大致理好了。
我这里是把控制LED的程序写在了gpioled_write函数里面目的是能让应用层来控制LED的亮灭为了方便我这里把printk的打印级别设置为最高了即KERN_EMERG。copy_from_user函数的作用从用户空间拷贝数据到内核空间话不多说直接上代码。
gpioled.c

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>


#define GPIOLED_NAME        "gpioled"
#define GPIOLED_COUNT        1
#define LEDOFF               0			/* 关灯 */
#define LEDON                1			/* 开灯 */


/* 设备结构体 */
struct gpioled_dev
{
    struct cdev cdev;         /* 字符设备 */
    dev_t devid;              /* 设备号 */
    struct class *class;      /* 类 */
    struct device *device;    /* 设备 */
    int major;                /* 主设备号 */
    int minor;                /* 次设备号 */
    struct device_node	*nd;  /* 设备节点 */
	int led_gpio;             /* led所使用的GPIO编号 */
};

static struct gpioled_dev gpioled;

static int gpioled_open(struct inode *inode, struct file *filp)
{
    printk(KERN_EMERG "gpioled_open\n");
    filp->private_data = &gpioled;
    return 0;
}

static int gpioled_release(struct inode *inode, struct file *filp)
{
    printk(KERN_EMERG "gpioled_release\n");
    return 0;
}

static ssize_t gpioled_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
    int retvalue;
	unsigned char databuf[1];
	unsigned char ledstat;
	struct gpioled_dev *dev = filp->private_data;

    printk(KERN_EMERG "gpioled_write\n");
	retvalue = copy_from_user(databuf, buf, count);
	if(retvalue < 0) {
		printk(KERN_EMERG "kernel write failed!\r\n");
		return -EFAULT;
	}

	ledstat = databuf[0];		/* 获取状态值 */

	if(ledstat == LEDON)
    {
        printk(KERN_EMERG "打开led灯\n");
		gpio_set_value(dev->led_gpio, 1);	/* 打开LED灯 */
	}
    else if(ledstat == LEDOFF)
    {
        printk(KERN_EMERG "关闭led灯\n");
		gpio_set_value(dev->led_gpio, 0);	/* 关闭LED灯 */
	}

    return 0;
}


static const struct file_operations gpioled_fops = {
    .owner = THIS_MODULE,
    .open = gpioled_open,
    .release = gpioled_release,
    .write = gpioled_write,
};


/* 入口 */
static int __init gpioled_init(void)
{
    int ret;

    printk(KERN_EMERG "gpioled_init\n");
    gpioled.major = 0;

    /* 设置LED所使用的GPIO */
	/* 获取设备节点gpioled */
	gpioled.nd = of_find_node_by_path("/ledtest");
	if(gpioled.nd == NULL)
    {
		printk(KERN_EMERG "ledtest node not find!\r\n");
		return -EINVAL;
	}

	/* 获取设备树中的gpio属性得到LED所使用的LED编号 */
	gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "gpios", 0);
	if(gpioled.led_gpio < 0)
    {
		printk(KERN_EMERG "can't get gpios");
		return -EINVAL;
	}
	printk(KERN_EMERG "gpios num = %d\n", gpioled.led_gpio);
	ret = gpio_direction_output(gpioled.led_gpio, 1);
	if(ret < 0)
    {
		printk(KERN_EMERG "can't set gpio!\n");
	}

    /* 注册设备号 */
    if(gpioled.major)
    {
        gpioled.devid = MKDEV(gpioled.major, 0);
        ret = register_chrdev_region(gpioled.devid, GPIOLED_COUNT, GPIOLED_NAME);
    }
    else
    {
        ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_COUNT, GPIOLED_NAME);
        gpioled.major = MAJOR(gpioled.devid);
        gpioled.minor = MINOR(gpioled.devid);
    }
    if(ret < 0)
    {
        printk(KERN_EMERG "gpioled chrdev_region err!\n");
        goto fail_devid;
    }
    printk(KERN_EMERG "gpioled major:%d, minor:%d\n", gpioled.major, gpioled.minor);

    /* 注册字符设备 */
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &gpioled_fops);
    ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_COUNT);
    if(ret < 0)
    {
        goto fail_cdev;
    }

    /* 自动创建设备节点 */
    gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    if(IS_ERR(gpioled.class))
    {
        ret = PTR_ERR(gpioled.class);
        goto fail_class;
    }
    gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
    if(IS_ERR(gpioled.device))
    {
        ret = PTR_ERR(gpioled.device);
        goto fail_device;
    }

    return 0;

fail_device:
    class_destroy(gpioled.class);
fail_class:
    cdev_del(&gpioled.cdev);
fail_cdev:
    unregister_chrdev_region(gpioled.devid, GPIOLED_COUNT);
fail_devid:
    return ret;

}


/* 出口 */
static void __exit gpioled_exit(void)
{
    printk(KERN_EMERG "gpioled_exit\n");

    /* 删除字符设备 */
    cdev_del(&gpioled.cdev);

    /* 注销设备号 */
    unregister_chrdev_region(gpioled.devid, GPIOLED_COUNT);

    /* 摧毁设备 */
    device_destroy(gpioled.class, gpioled.devid);

    /* 摧毁类 */
    class_destroy(gpioled.class);
}


/* 注册驱动加载和卸载 */
module_init(gpioled_init);
module_exit(gpioled_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxx");

四、编写MakeFile程序

MakeFile

KERNELDIR := /home/pi/linux/pi4/linux-rpi-5.15.y

CURRENT_PATH := $(shell pwd)

obj-m := gpioled.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

五、编写APP程序

gpioledAPP.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

/** ./gpioledAPP /dev/gpioled <1:2>
  * ./gpioledAPP /dev/gpioled 0 灭灯
  * ./gpioledAPP /dev/gpioled 1 亮灯
*/
int main(int argc, char *argv[])
{
    char *pathname;
    char data;
    int fd;

    pathname = argv[1];

    if(argc != 3)
    {
        printf("%s 输入错误!\n", pathname);
        return -1;
    }
    if(argv[2][1] != '\0')
    {
        printf("%s 输入错误!\n", pathname);
        return -1;
    }

    fd = open(pathname, O_RDWR);
    if(fd < 0)
    {
        printf("open %s error!\n", pathname);
    }

    data = (char)atoi(argv[2]);
    if(write(fd, &data, sizeof(data)) < 0)
    {
        printf("write %s error!\n", pathname);
    }

    if(close(fd) == -1)
    {
        printf("close %s error!\n", pathname);
    }

    return 0;
}

六、编译测试

在工作目录下
命令行输入make编译驱动程序
命令行输入arm-linux-gnueabihf-gcc gpioledAPP.c -o gpioledAPP 编译APP应用程序
传到板子上去使用insmod命令加载驱动模块
sudo ./gpioledAPP /dev/gpioled 1 能实现亮灯
sudo ./gpioledAPP /dev/gpioled 0 能实现灭灯
好的实验完成

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux

“基于树莓派4B的Linux驱动------点亮LED灯_树莓派装linux led” 的相关文章