用 Linux 内核中断检测按键输入
用 Linux 内核中断检测按键输入
在Linux下的按键输入驱动开发一文中介绍了基本的按键输入捕获流程,这里将进一步介绍如何使用中断的方式来驱动按键,同时通过定时器实现按键消抖功能,应用程序读取按键值并通过终端打印出来
下面根据Linux 内核中断框架一文中介绍的内核中断使用模板,来进行代码的编写
1. 修改设备树文件
在Linux按键驱动的设备树 key 节点基础上,添加中断相关属性
key {#address-cells = <1>;#size-cells = <1>;compatible = "andyxi-key";pinctrl-names = "default";pinctrl-0 = <&pinctrl_key>;key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; interrupt-parent = <&gpio1>; //设置gpio1为中断控制器interrupts = <18 IRQ_TYPE_EDGE_BOTH>; //GPIO1组的18号IO,上升和下降沿触发status = "okay";
};
设备树编写完成后使用 “make dtbs”命令重新编译设备树,使用新的设备树文件启动 linux 系统
2. 编写驱动程序
设备树准备好后就可以编写驱动程序了,新建 imx6uirq.c 文件,编写程序
- 定义按键设备结构体,以及中断IO的描述结构体
#define IMX6UIRQ_CNT 1 //设备号个数
#define IMX6UIRQ_NAME "imx6uirq" //名字
#define KEY0VALUE 0X01 //KEY0 按键值
#define INVAKEY 0XFF //无效的按键值
#define KEY_NUM 1 //按键数量/* 中断 IO 描述结构体 */
struct irq_keydesc {int gpio; //gpioint irqnum; //中断号unsigned char value; //按键对应的键值char name[10]; //名字irqreturn_t (*handler)(int, void *); //中断服务函数
};/* imx6uirq 设备结构体 */
struct imx6uirq_dev{dev_t devid; //设备号struct cdev cdev; //cdevstruct class *class; //类struct device *device; //设备int major; //主设备号int minor; //次设备号struct device_node *nd; //设备节点atomic_t keyvalue; //有效的按键键值atomic_t releasekey; //标记是否完成一次完成的按键struct timer_list timer; //定义一个定时器struct irq_keydesc irqkeydesc[KEY_NUM]; //按键描述数组unsigned char curkeynum; //当前的按键号
};struct imx6uirq_dev imx6uirq; /* irq 设备 */
- 编写中断处理函数和定时器处理函数,实现按键消抖
/* 中断服务函数,开启定时器,延时 10ms */
static irqreturn_t key0_handler(int irq, void *dev_id){struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;dev->curkeynum = 0;dev->timer.data = (volatile long)dev_id;mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));return IRQ_RETVAL(IRQ_HANDLED);
}/* 定时器服务函数,用于按键消抖 */
void timer_function(unsigned long arg){unsigned char value;unsigned char num;struct irq_keydesc *keydesc;struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;num = dev->curkeynum;keydesc = &dev->irqkeydesc[num];value = gpio_get_value(keydesc->gpio); if(value == 0){ /* 按下按键 */atomic_set(&dev->keyvalue, keydesc->value);}else{ /* 按键松开 */atomic_set(&dev->keyvalue, 0x80 | keydesc->value);atomic_set(&dev->releasekey, 1); //标记松开按键}
}
- 初始化所使用的IO,获取中断号,并请求中断
/* 按键 IO 初始化 */
static int keyio_init(void){unsigned char i = 0;int ret = 0;imx6uirq.nd = of_find_node_by_path("/key");if (imx6uirq.nd== NULL){printk("key node not find!\r\n");return -EINVAL;}/* 提取 GPIO */for (i = 0; i < KEY_NUM; i++) {imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd, "key-gpio", i);if (imx6uirq.irqkeydesc[i].gpio < 0) {printk("can't get key%d\r\n", i);}}/* 初始化key所使用的IO,获取中断号 */for (i = 0; i < KEY_NUM; i++) {memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(imx6uirq.irqkeydesc[i].name));sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i);gpio_request(imx6uirq.irqkeydesc[i].gpio,imx6uirq.irqkeydesc[i].name);gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
#if 0imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endifprintk("key%d:gpio=%d, irqnum=%d\r\n",i,imx6uirq.irqkeydesc[i].gpio,imx6uirq.irqkeydesc[i].irqnum);}/* 申请中断 */imx6uirq.irqkeydesc[0].handler = key0_handler;imx6uirq.irqkeydesc[0].value = KEY0VALUE;for (i = 0; i < KEY_NUM; i++) {ret = request_irq(imx6uirq.irqkeydesc[i].irqnum,imx6uirq.irqkeydesc[i].handler,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,imx6uirq.irqkeydesc[i].name, &imx6uirq);if(ret < 0){printk("irq %d request failed!\r\n",imx6uirq.irqkeydesc[i].irqnum);return -EFAULT;}}/* 创建定时器 */init_timer(&imx6uirq.timer);imx6uirq.timer.function = timer_function;return 0;
}
- 编写设备操作函数
/* 打开设备 */
static int imx6uirq_open(struct inode *inode, struct file *filp){filp->private_data = &imx6uirq; /* 设置私有数据 */return 0;
}/* 从设备读取数据 */
static ssize_t imx6uirq_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt){int ret = 0;unsigned char keyvalue = 0;unsigned char releasekey = 0;struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;keyvalue = atomic_read(&dev->keyvalue);releasekey = atomic_read(&dev->releasekey);if (releasekey) { /* 有按键按下 */if (keyvalue & 0x80) {keyvalue &= ~0x80;ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));} else {goto data_error;}atomic_set(&dev->releasekey, 0); /* 按下标志清零 */} else {goto data_error;}return 0;data_error:return -EINVAL;
}/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {.owner = THIS_MODULE,.open = imx6uirq_open,.read = imx6uirq_read,
};
- 驱动入口函数中,创建按键设备
/* 驱动入口函数 */
static int __init imx6uirq_init(void){/* 1、构建设备号 */if (imx6uirq.major) {imx6uirq.devid = MKDEV(imx6uirq.major, 0);register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT,IMX6UIRQ_NAME);} else {alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT,IMX6UIRQ_NAME);imx6uirq.major = MAJOR(imx6uirq.devid);imx6uirq.minor = MINOR(imx6uirq.devid);}/* 2、注册字符设备 */cdev_init(&imx6uirq.cdev, &imx6uirq_fops);cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);/* 3、创建类 */imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);if (IS_ERR(imx6uirq.class)) {return PTR_ERR(imx6uirq.class);}/* 4、创建设备 */imx6uirq.device = device_create(imx6uirq.class,NULL,imx6uirq.devid, NULL,IMX6UIRQ_NAME);if (IS_ERR(imx6uirq.device)) {return PTR_ERR(imx6uirq.device);}/* 5、 初始化按键 */atomic_set(&imx6uirq.keyvalue, INVAKEY);atomic_set(&imx6uirq.releasekey, 0);keyio_init();return 0;
}
- 驱动出口函数中,删除字符设备,释放中断
/* 驱动出口函数 */
static void __exit imx6uirq_exit(void){unsigned int i = 0;/* 删除定时器 */del_timer_sync(&imx6uirq.timer);/* 释放中断 */for (i = 0; i < KEY_NUM; i++) {free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);}cdev_del(&imx6uirq.cdev);unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);device_destroy(imx6uirq.class, imx6uirq.devid);class_destroy(imx6uirq.class);
}module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
3. 编写测试程序
测试程序通过不断的读取/dev/imx6uirq 文件来获取按键值,当按键按下以后就会将获取到的按键值输出在终端上。新建名为 imx6uirqApp.c的文件,并编写测试代码
int main(int argc, char *argv[]){int fd;int ret = 0;char *filename;unsigned char data;if (argc != 2) {printf("Error Usage!\r\n");return -1;}filename = argv[1];fd = open(filename, O_RDWR);if (fd < 0) {printf("Can't open file %s\r\n", filename);return -1;}while (1) {read(fd, &data, sizeof(data));if (data) //读取到数据printf("key value = %#X\r\n", data);}ret= close(fd); if(ret < 0){printf("file %s close failed!\r\n", argv[1]);return -1;}return 0;
}
4. 编译测试
- 编译驱动程序:当前目录下创建Makefile文件,并make编译
KERNELDIR := /home/andyxi/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_andyxi
CURRENT_PATH := $(shell pwd)
obj-m := imx6uirq.obuild: kernel_moduleskernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
- 编译测试程序:无需内核参与,直接编译即可
arm-linux-gnueabihf-gcc imx6uirqApp.c -o imx6uirqApp
- 将驱动文件和APP可执行文件拷贝至“rootfs/lib/modules/4.1.15”中,加载驱动
depmod #第一次加载驱动的时候需要运行此命令
modprobe imx6uirq.ko #加载驱动
- 驱动加载成功后可以通过查看 /proc/interrupts 文件来检查对应的中断是否注册成功
cat /proc/interrupts
- 运行测试程序,按下KEY0按键,imx6uirqApp会获取并且输出按键信息
./imx6uirqApp /dev/imx6uirq

本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
