基于树莓派4B的Linux驱动------内核定时器控制LED闪烁

基于树莓派4B的Linux驱动------内核定时器控制LED闪烁

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

一、实验说明

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

二、修改设备树文件

为了方便,我这里是直接在设备树的根目录添加一个节点
我修改的是arch/arm/boot/dts/bcm2711-rpi-4-b.dts文件
在该设备树文件的根节点添加以下内容
具体设备树的语法可以去看内核说明文档,我这里就不啰嗦了

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

三、编写驱动程序

我这里只列出了这里比较重要的函数,如果下面有哪些函数不懂的,可以自行百度
为了重要数据的稳定和安全,我这里还使用了自旋锁
为了方便与上层APP交互,我这里还实现了file_operations结构体里面的unlocked_ioctl函数,并且注册设备,创建设备节点
timer_setup 初始化定时器
msecs_to_jiffies 将毫秒转换为对应的 jiffies 类型
add_timer 添加定时器,并激活定时器
mod_timer 设置定时器,如果定时器没有激活,就会激活它
gpio_set_value 设置引脚输出的电平
spin_lock_irqsave 自旋锁,上锁
spin_unlock_irqrestore 自旋锁,解锁

设置好定时器后,到设置好的时间时,会调用定时器的服务函数,我们就可以在服务函数里添加我们要实现的功能,话不多说,代码如下

timer.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>
#include <linux/timer.h>
#include <linux/jiffies.h>


#define TIMER_NAME        "timer"
#define TIMER_COUNT        1
#define CLOSE_CMD          _IO(0XEF, 0x1)  /* 关闭定时器 */
#define OPEN_CMD           _IO(0XEF, 0x2)  /* 打开定时器 */
#define SETPERIOD_CMD      _IO(0XEF, 0x3)  /* 设置定时器周期命令 */


/* 设备结构体 */
struct timer_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编号 */
    struct timer_list timer;  /* 定时器 */
    int timePeriod;           /* 定时周期,单位为ms */
    spinlock_t lock;          /* 自旋锁 */
};

static struct timer_dev timerdev;

/* 定时器处理函数 */
static void timer_function(struct timer_list *t)
{
    struct timer_dev *dev = &timerdev;
    int timerPeriod;
    unsigned long flags;
    static int sta = 1;

    sta = !sta;
    gpio_set_value(dev->led_gpio, sta);

    spin_lock_irqsave(&dev->lock, flags);
	timerPeriod = dev->timePeriod;
	spin_unlock_irqrestore(&dev->lock, flags);
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(timerPeriod));
}

static int led_gpio_init(void)
{
    int ret;

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

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

    return 0;
}


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

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


static long timer_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    struct timer_dev *dev = filp->private_data;
    int timerPeriod;
    unsigned long flags;

    switch (cmd)
    {
        case CLOSE_CMD: /* 关闭定时器 */
            del_timer_sync(&dev->timer);
            break;
        case OPEN_CMD: /* 打开定时器 */
            spin_lock_irqsave(&dev->lock, flags);
			timerPeriod = dev->timePeriod;
			spin_unlock_irqrestore(&dev->lock, flags);
            mod_timer(&dev->timer, jiffies + msecs_to_jiffies(timerPeriod));
            break;
        case SETPERIOD_CMD: /* 设置定时器周期命令 */
            spin_lock_irqsave(&dev->lock, flags);
			dev->timePeriod = arg;
            spin_unlock_irqrestore(&dev->lock, flags);
            mod_timer(&dev->timer, jiffies + msecs_to_jiffies(arg));
            break;
        default: 
            printk("输入错误!\n");
            break;
    }
    return 0;
}


static const struct file_operations timer_fops = {
    .owner = THIS_MODULE,
    .open = timer_open,
    .release = timer_release,
    .unlocked_ioctl = timer_unlocked_ioctl,
};


/* 入口 */
static int __init timer_init(void)
{
    int ret;
    unsigned long flags;

    printk("timer_init\n");
    timerdev.major = 0;

    /* 初始化自旋锁 */
	spin_lock_init(&timerdev.lock);

    /* 初始化led */
    ret = led_gpio_init();
    if(ret < 0)
    {
        return ret;
    }

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

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

    /* 自动创建设备节点 */
    timerdev.class = class_create(THIS_MODULE, TIMER_NAME);
    if(IS_ERR(timerdev.class))
    {
        ret = PTR_ERR(timerdev.class);
        goto fail_class;
    }
    timerdev.device = device_create(timerdev.class, NULL, timerdev.devid, NULL, TIMER_NAME);
    if(IS_ERR(timerdev.device))
    {
        ret = PTR_ERR(timerdev.device);
        goto fail_device;
    }
    
    /* 初始化定时器 */
    timer_setup(&timerdev.timer, timer_function, 0);
    spin_lock_irqsave(&timerdev.lock, flags);
    timerdev.timePeriod = 500;
    spin_unlock_irqrestore(&timerdev.lock, flags);
    timerdev.timer.expires = jiffies + msecs_to_jiffies(timerdev.timePeriod);
    add_timer(&timerdev.timer);  /* 添加到系统 */


    return 0;

fail_device:
    class_destroy(timerdev.class);
fail_class:
    cdev_del(&timerdev.cdev);
fail_cdev:
    unregister_chrdev_region(timerdev.devid, TIMER_COUNT);
fail_devid:
    return ret;
}


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

    /* 删除 timer */
    del_timer_sync(&timerdev.timer);

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

    /* 注销设备号 */
    unregister_chrdev_region(timerdev.devid, TIMER_COUNT);

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

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


/* 注册驱动加载和卸载 */
module_init(timer_init);
module_exit(timer_exit);

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

四、编写MakeFile程序

Makefile

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

CURRENT_PATH := $(shell pwd)

obj-m := timer.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程序

timerAPP.c

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


#define CLOSE_CMD          _IO(0XEF, 0x1)  /* 关闭定时器 */
#define OPEN_CMD           _IO(0XEF, 0x2)  /* 打开定时器 */
#define SETPERIOD_CMD      _IO(0XEF, 0x3)  /* 设置定时器周期命令 */

/** ./timerAPP /dev/timer
  * 
  * 
*/
int main(int argc, char *argv[])
{
    char *pathname;
    int fd, ret;
    unsigned int cmd, arg;

    pathname = argv[1];

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

    fd = open(pathname, O_RDWR);
    if(fd < 0)
    {
        printf("open %s error!\n", pathname);
    }
    while (1)
    {
        printf("输入命令:");
        ret = scanf("%d", &cmd);
        if (ret != 1) 
        {
            /* 参数输入错误 */
            printf("参数输入错误!\n");
            while('\n' != getchar());  /* 防止卡死 */
        }
        switch(cmd)
        {
            case 1: /* 关闭 LED 灯 */
                cmd = CLOSE_CMD;
                break;
            case 2: /* 打开 LED 灯 */
                cmd = OPEN_CMD;
                break;
            case 3: /* 设置周期值 */
                cmd = SETPERIOD_CMD; 
                printf("设置周期:");
                ret = scanf("%d", &arg);
                if (ret != 1)
                { 
                    /* 参数输入错误 */
                    printf("参数输入错误!\n");
                    while('\n' != getchar());  /* 防止卡死 */
                }
                break;
            default: 
                printf("输入错误!\n");
                break;
        }
        
        ioctl(fd, cmd, arg); /* 控制定时器的打开和关闭 */
    }

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

    return 0;
}

六、编译测试

把编译好的设备树拷贝到树莓派中
make 编译驱动文件,并拷贝到树莓派中
arm-linux-gnueabihf-gcc timerAPP.c -o timerAPP 编译APP应用程序,并拷贝到树莓派中
sudo insmod timer.ko 加载驱动程序
sudo ./timerAPP /dev/timer 打开应用程序
输入1灯会闪烁
输入2灯就不会闪烁
输入3可以调整LED的闪烁频率
好的,实验完成

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值