前言
本篇文章我们来讲解按键的驱动程序,按键驱动程序的主要思路就是使用中断的方式,当按键按下时会发生中断这个时候就可以通过中断来获取按键的状态。
一、设备树编写
我的开发板上有两个按键,那么我们就在gpios里面添加两个gpio的信息。
mykey:mykey{ compatible = "my,gpio_key"; gpios = <&gpio5 1 GPIO_ACTIVE_LOW>, <&gpio4 14 GPIO_ACTIVE_LOW>; };
二、驱动程序编写
按键的驱动程序总的来说是非常常规的,这里我主要讲解一下难点。
这里首先需要提供一个环形缓冲区,这里环形缓冲区的作用如下:
防止按键过期:当按键处理时间较长时,正常的 IRQ(中断服务例程)处理可能无法及时响应新的按键事件,环形缓冲区可以将该事件保存起来,以便IRQ处理完之后再进行处理,防止按键丢失或失效。
前面我们都是使用gpiod_get来获取相关的硬件信息,但是在这个驱动程序中有两个gpio所以我们这里换一个函数来获取gpio信息。
函数原型如下:
struct gpio_desc *gpiod_get_index(struct device *dev, const char *propname, unsigned int index, unsigned long flags);
参数:
dev:表示GPIO设备所在的设备节点。
propname:表示设备树中属性名为 propname 的GPIO引脚。
index:表示GPIO设备中具有相同 propname 的GPIO引脚的索引号,如果所有引脚名均不相同,则将 index 值设置为 0。
flags:表示GPIO描述符的标志。
返回值:
struct gpio_desc *:成功则返回GPIO描述符,失败则返回错误指针。
全部代码:
#include <linux/module.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> static struct gpio_desc *key0; static struct gpio_desc *key1; static int key0_irq; static int key1_irq; /* 主设备号 */ static int major = 0; static struct class *gpio_key_class; /* 环形缓冲区 */ #define BUF_LEN 128 static int g_keys[BUF_LEN]; static int r, w; #define NEXT_POS(x) ((x+1) % BUF_LEN) static int is_key_buf_empty(void) { return (r == w); } static int is_key_buf_full(void) { return (r == NEXT_POS(w)); } static void put_key(int key) { if (!is_key_buf_full()) { g_keys[w] = key; w = NEXT_POS(w); } } static int get_key(void) { int key = 0; if (!is_key_buf_empty()) { key = g_keys[r]; r = NEXT_POS(r); } return key; } static DECLARE_WAIT_QUEUE_HEAD(gpio_key_wait); /* 实现对应的open/read/write等函数,填入file_operations结构体 */ static ssize_t gpio_key_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset) { //printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); int err; int key; wait_event_interruptible(gpio_key_wait, !is_key_buf_empty()); key = get_key(); err = copy_to_user(buf, &key, 4); return 4; } /* 定义自己的file_operations结构体 */ static struct file_operations gpio_key_drv = { .owner = THIS_MODULE, .read = gpio_key_drv_read, }; static irqreturn_t gpio_key_isr(int irq, void *dev_id) { int val = 0; int key = 0; if(irq == key0_irq) { val = gpiod_get_value(key0); key = val | (0 << 8); printk("this key0 status is %d\n",val); } else if(irq == key1_irq) { val = gpiod_get_value(key1); key = val | (1 << 8); printk("this key1 status is %d\n",val); } put_key(key); wake_up_interruptible(&gpio_key_wait); return IRQ_HANDLED; } /* 1. 从platform_device获得GPIO * 2. gpio=>irq * 3. request_irq */ static int gpio_key_probe(struct platform_device *pdev) { int err; printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); /* 获取硬件信息 */ key0 = gpiod_get_index(&pdev->dev, NULL, 0, 0); if (IS_ERR(key0)) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); return -1; } key1 = gpiod_get_index(&pdev->dev, NULL, 1, 0); if (IS_ERR(key0)) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); return -1; } /*将gpio设置为输入引脚*/ gpiod_direction_input(key0); gpiod_direction_input(key1); /*获得gpio中断号*/ key0_irq = gpiod_to_irq(key0); if(key0_irq < 0) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); return key0_irq; } key1_irq = gpiod_to_irq(key1); if(key1_irq < 0) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); return key1_irq; } /*根据irq申请中断*/ err = request_irq(key0_irq, gpio_key_isr, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "key", NULL); err = request_irq(key1_irq, gpio_key_isr, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "key", NULL); /* 注册file_operations */ major = register_chrdev(0, "100ask_gpio_key", &gpio_key_drv); /* /dev/gpio_key */ gpio_key_class = class_create(THIS_MODULE, "100ask_gpio_key_class"); if (IS_ERR(gpio_key_class)) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); unregister_chrdev(major, "100ask_gpio_key"); return PTR_ERR(gpio_key_class); } device_create(gpio_key_class, NULL, MKDEV(major, 0), NULL, "100ask_gpio_key"); /* /dev/100ask_gpio_key */ return 0; } static int gpio_key_remove(struct platform_device *pdev) { device_destroy(gpio_key_class, MKDEV(major, 0)); class_destroy(gpio_key_class); unregister_chrdev(major, "100ask_gpio_key"); /*释放中断*/ free_irq(key1_irq,&key1_irq); free_irq(key0_irq,&key0_irq); /*释放引脚*/ gpiod_put(key1); gpiod_put(key0); printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); return 0; } static const struct of_device_id ask100_keys[] = { { .compatible = "my,gpio_key" }, { }, }; /* 1. 定义platform_driver */ static struct platform_driver gpio_keys_driver = { .probe = gpio_key_probe, .remove = gpio_key_remove, .driver = { .name = "100ask_gpio_key", .of_match_table = ask100_keys, }, }; /* 2. 在入口函数注册platform_driver */ static int __init gpio_key_init(void) { int err; printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); err = platform_driver_register(&gpio_keys_driver); return err; } /* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数 * 卸载platform_driver */ static void __exit gpio_key_exit(void) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); platform_driver_unregister(&gpio_keys_driver); } /* 7. 其他完善:提供设备信息,自动创建设备节点 */ module_init(gpio_key_init); module_exit(gpio_key_exit); MODULE_LICENSE("GPL");
三、应用程序编写
在应用程序中使用read函数可以读取到按键的状态。
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <string.h> /* * ./button_test /dev/100ask_button0 * */ int main(int argc, char **argv) { int fd; int val; int status; int num; /* 1. 判断参数 */ if (argc != 2) { printf("Usage: %s <dev>\n", argv[0]); return -1; } /* 2. 打开文件 */ fd = open(argv[1], O_RDWR); if (fd == -1) { printf("can not open file %s\n", argv[1]); return -1; } while (1) { /* 3. 读文件 */ read(fd, &val, 4); status = val&0xff; num = val>>8; printf("get button : 0x%x\n", val); printf("key%d status is %d\n",num,status); } close(fd); return 0; }
总结
按键驱动的编写是比较简单的,主要就是需要注意设备树的编写和gpio硬件信息的获取函数。