前言
本篇文章将给大家介绍一下SR501驱动程序的编写。
一、SR501模块介绍
SR501是一种基于红外线感应原理的人体感应模块,通常被用于安防等一系列自动控制场景中。它主要通过红外线传感器检测感应区域内的人体热辐射,当检测到人体进入这个区域时,输出高电平信号;当人体离开这个区域时,输出低电平信号。
SR501模块整体封装在一块小板子上,板子上有两个旋钮,可以通过旋转它们来调节感应灵敏度和输出信号类型,以适应不同的应用场景。此外,模块还具有自动感应和手动感应两种模式,可以通过调节模式选择开关来进行调节。
SR501模块拥有许多优点,例如可以灵敏地探测人体,响应速度快、稳定性好、安装简单等等,因此被广泛应用于各种人体感应应用场景中。
SR501接线:
VCC----电源
GND----GND
OUT-----GPIO(设置为输入模式)
二、设备树编写
这里我将SR501模块接到了GPIO4_19:
这里主要需要注意的就是compatible属性,使用这个属性来和我们编写的驱动进行匹配。
sr501{ compatible = "my,sr501"; gpios = <&gpio4 19 GPIO_ACTIVE_HIGH>; };
三、驱动编写
1.确定主设备号
主设备设置为0让系统自动帮我们分配主设备号。
static int major=0;/*主设备号*/
2.编写file_operations结构体
我们需要提供这个结构体并且编写其中的open和read函数,供应用程序使用。
static ssize_t sr501_read (struct file *file, char __user *buf, size_t size, loff_t *off) { int err; wait_event_interruptible(sr501_wq, sr501_data); err = copy_to_user(buf, &sr501_data, 4); sr501_data = 0; return 0; } static int sr501_open (struct inode *inode, struct file *file) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); return 0; } static struct file_operations sr501_ops={ .owner = THIS_MODULE, .open = sr501_open, .read = sr501_read, };
3.注册file_operations结构体
在Linux中注册其实就是指在Linux内核中添加我们自己编写的这个file_operations结构体。这个注册的工作在入口函数中完成。
static int __init sr501_init(void) { int err; printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); /*确定主设备号*/ major=register_chrdev(major, "mysr501", &sr501_ops); /*创建类*/ sr501_class=class_create(THIS_MODULE, "sr501"); if (IS_ERR(sr501_class)) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); unregister_chrdev(major, "mysr501"); return PTR_ERR(sr501_class); } init_waitqueue_head(&sr501_wq);//初始化队列 err=platform_driver_register(&sr501); return 0; }
4.出口函数编写
有入口函数就会有出口函数,在入口函数中做的是设备的注册等工作,那么出口函数就是做相反的工作,将设备注销。
static void __exit sr501_exit(void) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); platform_driver_unregister(&sr501); class_destroy(sr501_class); unregister_chrdev(major, "mysr501"); } module_init(sr501_init); module_exit(sr501_exit); MODULE_LICENSE("GPL");
5.probe函数和remove函数编写
创建platform_driver结构体和of_device_id结构体,使用of_device_id结构体中的compatible 属性和设备树进行匹配,匹配完成后会调用到probe函数。
static const struct of_device_id my_sr501[] = { { .compatible = "my,sr501" }, { }, }; static struct platform_driver sr501={ .driver = { .name = "sr501", .of_match_table = my_sr501, }, .probe = sr501_probe, .remove = sr501_remove, };
static int sr501_probe(struct platform_device *pdev) { int err; printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); /*1.获取硬件信息*/ sr501_gpio=gpiod_get(&pdev->dev, NULL, GPIOD_IN); if (IS_ERR(sr501_gpio)) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); } /*得到irq*/ irq = gpiod_to_irq(sr501_gpio); /*申请中断并设置为双边沿触发*/ err = request_irq(irq, sr501_isr, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "sr501", NULL); if (err != 0) { printk("request_irq is err\n"); } /*2.创建设备节点*/ device_create(sr501_class, NULL, MKDEV(major, 0), NULL, "sr501"); return 0; } static int sr501_remove(struct platform_device *pdev) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); device_destroy(sr501_class, MKDEV(major, 0)); free_irq(irq, NULL); gpiod_put(sr501_gpio); return 0; }
6.中断编写
这里我们使用中断来实现SR501的核心功能,因为当检测到有人的时候SR501表现为高电平,没有人的时候SR501则表现为低电平。那么这样我们就可以把SR501的引脚配置为中断引脚,当电平发生变化的时候就会产生中断。
这里使用到了wait_queue_head_t 定义了一个等待队列,当有人的时候使用wake_up函数唤醒等待队列读取数据。
static wait_queue_head_t sr501_wq;/*等待队列*/ static irqreturn_t sr501_isr(int irq, void *dev_id) { int val = gpiod_get_value(sr501_gpio); if(val) { sr501_data = 1; wake_up(&sr501_wq); } return IRQ_HANDLED; // IRQ_WAKE_THREAD; }
7.测试程序编写
有了驱动程序后相应的也需要一个应用程序来测试驱动代码是否正确,在应用程序中使用read函数来读取数据。
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <poll.h> #include <signal.h> #include <unistd.h> /* * ./sr501_test /dev/sr501 * */ int main(int argc, char **argv) { int fd; int data; /* 1. 判断参数 */ if (argc != 2) { printf("Usage: %s <dev>\n", argv[0]); return -1; } /* 2. 打开文件 */ // fd = open(argv[1], O_RDWR | O_NONBLOCK); fd = open(argv[1], O_RDWR); if (fd == -1) { printf("can not open file %s\n", argv[1]); return -1; } while (1) { read(fd, &data, 4); if (data) { printf("have people\n"); } else { printf("no people\n"); } sleep(1); } close(fd); return 0; }
8.全部驱动程序
#include <linux/module.h> #include <linux/poll.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/miscdevice.h> #include <linux/kernel.h> #include <linux/major.h> #include <linux/mutex.h> #include <linux/proc_fs.h> #include <linux/seq_file.h> #include <linux/stat.h> #include <linux/init.h> #include <linux/device.h> #include <linux/tty.h> #include <linux/kmod.h> #include <linux/gfp.h> #include <linux/gpio/consumer.h> #include <linux/platform_device.h> #include <linux/of_gpio.h> #include <linux/of_irq.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/slab.h> #include <linux/fcntl.h> #include <linux/timer.h> #include <linux/workqueue.h> #include <asm/current.h> #include <linux/delay.h> #include <linux/timex.h> static int major=0;/*主设备号*/ static struct class *sr501_class; static struct gpio_desc *sr501_gpio;/*sr501 gpio*/ static int sr501_data = 0; static int irq;/*中断号*/ static wait_queue_head_t sr501_wq;/*等待队列*/ static ssize_t sr501_read (struct file *file, char __user *buf, size_t size, loff_t *off) { int err; wait_event_interruptible(sr501_wq, sr501_data); err = copy_to_user(buf, &sr501_data, 4); sr501_data = 0; return 0; } static int sr501_open (struct inode *inode, struct file *file) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); return 0; } static struct file_operations sr501_ops={ .owner = THIS_MODULE, .open = sr501_open, .read = sr501_read, }; static irqreturn_t sr501_isr(int irq, void *dev_id) { int val = gpiod_get_value(sr501_gpio); if(val) { sr501_data = 1; wake_up(&sr501_wq); } return IRQ_HANDLED; // IRQ_WAKE_THREAD; } static int sr501_probe(struct platform_device *pdev) { int err; printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); /*1.获取硬件信息*/ sr501_gpio=gpiod_get(&pdev->dev, NULL, GPIOD_IN); if (IS_ERR(sr501_gpio)) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); } /*得到irq*/ irq = gpiod_to_irq(sr501_gpio); /*申请中断并设置为双边沿触发*/ err = request_irq(irq, sr501_isr, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "sr501", NULL); if (err != 0) { printk("request_irq is err\n"); } /*2.创建设备节点*/ device_create(sr501_class, NULL, MKDEV(major, 0), NULL, "sr501"); return 0; } static int sr501_remove(struct platform_device *pdev) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); device_destroy(sr501_class, MKDEV(major, 0)); free_irq(irq, NULL); gpiod_put(sr501_gpio); return 0; } static const struct of_device_id my_sr501[] = { { .compatible = "my,sr501" }, { }, }; static struct platform_driver sr501={ .driver = { .name = "sr501", .of_match_table = my_sr501, }, .probe = sr501_probe, .remove = sr501_remove, }; static int __init sr501_init(void) { int err; printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); /*确定主设备号*/ major=register_chrdev(major, "mysr501", &sr501_ops); /*创建类*/ sr501_class=class_create(THIS_MODULE, "sr501"); if (IS_ERR(sr501_class)) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); unregister_chrdev(major, "mysr501"); return PTR_ERR(sr501_class); } init_waitqueue_head(&sr501_wq);//初始化队列 err=platform_driver_register(&sr501); return 0; } static void __exit sr501_exit(void) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); platform_driver_unregister(&sr501); class_destroy(sr501_class); unregister_chrdev(major, "mysr501"); } module_init(sr501_init); module_exit(sr501_exit); MODULE_LICENSE("GPL");
总结
本篇文章就讲解到这里,只要掌握Linux驱动的核心框架写一个驱动程序并不是那么困难。