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

基于树莓派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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


#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 
#include 
#include 
#include 
#include 
#include 

/** ./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 能实现灭灯
好的,实验完成

你可能感兴趣的:(arm,linux,c语言,驱动开发,物联网)