基于树莓派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的闪烁频率
好的,实验完成