Linux-按键中断输入

原创
10/13 23:22
阅读数 94

一、工具

  1. 开发板:野火i.MX6ULL S1 Pro(EMMC版本)
  2. 虚拟机Linux系统:Ubuntu 16.04 LTS
  3. 辅助软件:Vscode

二、设备树配置

关于KEY的驱动编写相关链接:https://my.oschina.net/wenhao0717/blog/5139244

  1. 在按键驱动程序的基础上修改设备树文件imx6ull-fire-emmc.dts中的key节点信息,修改内容如下所示:
	key {
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "key";
		pinctrl-name = "default";
		pinctrl-0 = <&pinctrl_key>;
		key-gpio = <&gpio5 1 GPIO_ACTIVE_LOW>;
		
		/* 中断添加的 */
		interrupt-parent = <&gpio5>;
		interrupts = <1 IRQ_TYPE_EDGE_BOTH>;

		status = "okay";
	};
  1. 重新回到Linux内核文件的根目录,重新编译设备树文件,如下所示:
make dtbs
  1. 将重新编译好的设备树文件imx6ull-fire-emmc.dtb拷贝到tftp服务器文件夹中,复位开发板正常进入Linux中后在控制终端查看新添加的节点信息,如下所示:
cd proc/device-tree/
ls

cd key
ls

三、驱动编写

  1. 使用Vscode创建imx6uirq_driver字符设备驱动文件,并添加.json文件,文件的内容如下所示:
{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/home/wenhao/linux/i.mx6ull/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/include",
                "/home/wenhao/linux/i.mx6ull/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/arch/arm/include",
                "/home/wenhao/linux/i.mx6ull/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/arch/arm/include/generated"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "intelliSenseMode": "clang-x64"
        }
    ],
    "version": 4
}
  1. 添加Makefile文件,文件的内容如下所示:
KERNELDIR :=/home/wenhao/linux/i.mx6ull/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENT_PATH :=$(shell pwd)
obj-m := imx6uirq_driver.o

build: kernel_modules
kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
  1. 添加imx6uirq_driver.c文件,该文件为字符设备驱动文件,文件的内容如下所示:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.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 <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>



#define IMX6UIRQ_DEV_CNT		1				/* 设备个数 */
#define IMX6UIRQ_DEV_NAME		"imx6uirq"		/* 设备名字 */

#define KEY_VALUE				0x01			/* 按键值 */
#define INVAL_KEY				0xFF			/* 无效按键 */
#define KEY_NUM					1				/* 按键数量 */

/* 中断IO描述结构体 */
struct irq_keydesc {
	int gpio;
	int irqnum;									/* 中断号 */
	unsigned char value;						/* 按键值 */
	char name[10];								/* 名字 */
	irqreturn_t (*handler)(int, void *);		/* 中断服务函数 */
};

/* imx6uirq设备结构体 */
struct imx6uirq_dev {
	dev_t devid;
	struct cdev cdev;
	struct class *class;
	struct device *device;
	int major;
	int minor;
	struct device_node *nd;
	
	atomic_t key_value;							/* 有效的按键值 */
	atomic_t release_key;						/* 标记是否完成一次按键操作 */
	struct irq_keydesc irq_keydesc[KEY_NUM];	/* 按键描述数组 */
	unsigned char cur_key_num;					/* 当前的按键号 */

	struct timer_list timer;					/* 定义一个定时器 */
	
};

/* irq设备 */
struct imx6uirq_dev imx6u_irq;

/* @description : 中断服务函数,开启定时器,延时 10ms,
 *                定时器用于按键消抖。
 * @param - irq : 中断号
 * @param - dev_id : 设备结构。
 * @return : 中断执行结果
 */
static irqreturn_t key_handler(int irq, void *dev_id)
{
	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
	
	dev->cur_key_num = 0;
	dev->timer.data = (unsigned long)dev_id;
	printk("key has press!\r\n");
	/* 设置定时器的周期并开启定时器 */
	mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
	return IRQ_RETVAL(IRQ_HANDLED);
}

/* @description : 定时器服务函数,用于按键消抖,定时器到了以后
 * 				  再次读取按键值,如果按键还是处于按下状态就表示按键有效。
 * @param – arg : 设备结构变量
 * @return : 无
 */
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->cur_key_num;
	keydesc = &dev->irq_keydesc[num];
	
	/* 读取IO值 */
	value = gpio_get_value(keydesc->gpio);
	if(value == 0) {
		/* 按下按键 */
		atomic_set(&dev->key_value, keydesc->value);
	} else {
		/* 松开按键 */
		atomic_set(&dev->key_value, 0x80|keydesc->value);
		atomic_set(&dev->release_key, 1);
	}
}

/*
 * @description : 按键 IO 初始化
 * @param : 无
 * @return : 无
 */
int keygpio_init(void)
{
	unsigned char i = 0;
	int ret = 0;
	
	/* 获取按键节点 */
	imx6u_irq.nd = of_find_node_by_path("/key");
	if(imx6u_irq.nd == NULL) {
		printk("not find key node!\r\n");
		return -EINVAL;
	}
	/* 得到按键gpio */
	for(i = 0; i < KEY_NUM; i++)
	{
		imx6u_irq.irq_keydesc[i].gpio = of_get_named_gpio(imx6u_irq.nd, "key-gpio", i);
		
		if(imx6u_irq.irq_keydesc[i].gpio < 0) {
			printk("can't get key%d gpio!\r\n", i);
			return -EINVAL;
		}		
	}
	/* 初始化key所使用的的gpio,并且设置成中断模式 */
	for(i = 0; i < KEY_NUM; i++)
	{
		memset(imx6u_irq.irq_keydesc[i].name, 
			   0, sizeof(imx6u_irq.irq_keydesc[i].name));
		sprintf(imx6u_irq.irq_keydesc[i].name, "KEY%d", i);
		/* 申请gpio */
		gpio_request(imx6u_irq.irq_keydesc[i].gpio, 
					 imx6u_irq.irq_keydesc[i].name);
		/* 设置gpio为输入 */
		gpio_direction_input(imx6u_irq.irq_keydesc[i].gpio);
		/* 获取gpio的中断号 */
		imx6u_irq.irq_keydesc[i].irqnum = gpio_to_irq(imx6u_irq.irq_keydesc[i].gpio);

		printk("key%d:gpio=%d, irqnum=%d\r\n", i, 
		       imx6u_irq.irq_keydesc[i].gpio,
			   imx6u_irq.irq_keydesc[i].irqnum);

		imx6u_irq.irq_keydesc[i].handler = key_handler;
		imx6u_irq.irq_keydesc[i].value = KEY_VALUE;	
	}
	
	
	for(i = 0; i < KEY_NUM; i++)
	{
		/* 申请中断 */
		ret = request_irq(imx6u_irq.irq_keydesc[i].irqnum,
		                  imx6u_irq.irq_keydesc[i].handler,
						  IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
						  imx6u_irq.irq_keydesc[i].name, &imx6u_irq);
		if(ret < 0)
		{
			printk("irq %d request failed!\r\n", imx6u_irq.irq_keydesc[i].irqnum);
			return -EFAULT;
		}
	}
	
	/* 创建定时器 */
	init_timer(&imx6u_irq.timer);
	imx6u_irq.timer.function = timer_function;
	
	return 0;
}

/*
 * @description : 打开设备
 * @param – inode : 传递给驱动的 inode
 * @param - filp : 设备文件, file 结构体有个叫做 private_data 的成员变量
 *                 一般在 open 的时候将 private_data 指向设备结构体。
 * @return : 0 成功;其他 失败
 */
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &imx6u_irq;
	return 0;
}

/*
 * @description : 从设备读取数据
 * @param – filp : 要打开的设备文件(文件描述符)
 * @param – buf : 返回给用户空间的数据缓冲区
 * @param - cnt : 要读取的数据长度
 * @param – offt : 相对于文件首地址的偏移
 * @return : 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t imx6uirq_read(struct file *filp, char __user *buf,
                             size_t cnt, loff_t *offt)
{
	int ret = 0;
	unsigned char key_value = 0;
	unsigned char release_key = 0;
	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
	
	/* 读按键值 */
	key_value = atomic_read(&dev->key_value);
	/* 读按键释放标记 */
	release_key = atomic_read(&dev->release_key);
	
	if(release_key) {
		if(key_value&0x80) {
			key_value &= ~0x80;
			/* 如果按键被释放,将按键值发送给用户 */
			ret = copy_to_user(buf, &key_value, sizeof(key_value));
		} else {
			goto data_error;
		}
		/* 设置按键释放标记为0 */
		atomic_set(&dev->release_key, 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,
};

/*
 * @description : 驱动入口函数
 * @param : 无
 * @return : 无
 */
static int __init imx6uirq_init(void)
{
	/* 构建设备号 */
	if(imx6u_irq.major) {
		imx6u_irq.devid = MKDEV(imx6u_irq.major, 0);
		register_chrdev_region(imx6u_irq.devid, 
		                       IMX6UIRQ_DEV_CNT, 
							   IMX6UIRQ_DEV_NAME);
	} else {
		alloc_chrdev_region(&imx6u_irq.devid, 0, 
		                    IMX6UIRQ_DEV_CNT,
							IMX6UIRQ_DEV_NAME);
		imx6u_irq.major = MAJOR(imx6u_irq.devid);
		imx6u_irq.minor = MINOR(imx6u_irq.devid);
	}
	
	/* 注册字符设备 */
	cdev_init(&imx6u_irq.cdev, &imx6uirq_fops);
	cdev_add(&imx6u_irq.cdev, imx6u_irq.devid, IMX6UIRQ_DEV_CNT);
	
	/* 创建类 */
	imx6u_irq.class = class_create(THIS_MODULE, IMX6UIRQ_DEV_NAME);
	if(IS_ERR(imx6u_irq.class)) {
		return PTR_ERR(imx6u_irq.class);
	}
	
	/* 创建设备 */
	imx6u_irq.device = device_create(imx6u_irq.class, NULL, 
	                                 imx6u_irq.devid, NULL, 
									 IMX6UIRQ_DEV_NAME);
	if(IS_ERR(imx6u_irq.device)) {
		return PTR_ERR(imx6u_irq.device);
	}
	
	/* 初始化按键 */
	atomic_set(&imx6u_irq.key_value, INVAL_KEY);
	atomic_set(&imx6u_irq.release_key, 0);
	keygpio_init();
	return 0;
}

/*
 * @description : 驱动出口函数
 * @param : 无
 * @return : 无
 */
static void __exit imx6uirq_exit(void)
{
	unsigned int i = 0;
	
	/* 删除定时器 */
	del_timer_sync(&imx6u_irq.timer);
	
	/* 释放中断 */
	for(i = 0; i < KEY_NUM; i++)
	{
		free_irq(imx6u_irq.irq_keydesc[i].irqnum, &imx6u_irq);
	}
	
	cdev_del(&imx6u_irq.cdev);
	unregister_chrdev_region(imx6u_irq.devid, IMX6UIRQ_DEV_CNT);
	device_destroy(imx6u_irq.class, imx6u_irq.devid);
	class_destroy(imx6u_irq.class);
}

module_init(imx6uirq_init);
module_exit(imx6uirq_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wenhao");
  1. 添加imx6uirq_app.c文件,该文件用于读取按键值,文件内容如下所示:
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

#define KEY_VALUE           0x01
#define KEY_INVAL           0xFF

/*
 * @description : main 主程序
 * @param - argc : argv 数组元素个数
 * @param - argv : 具体参数
 * @return : 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
    int fd, ret;
    char *filename;
    unsigned char key_value;

    if(argc != 2) {
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    /*打开KEY驱动*/
    fd = open(filename, O_RDWR);
    if(fd < 0) {
        printf("file %s open faild!\r\n", argv[1]);
        return -1;
    }
    
    /* 循环读取按键值数据 */
    while(1) {
        ret = read(fd, &key_value, sizeof(key_value));
        if(ret < 0) {
            
        } else {
            if(key_value)
                printf("KEY Press, value = %#X\r\n", key_value);
        }
    }

    /* 关闭文件 */
    ret = close(fd);
    if(ret < 0) {
        printf("file %s close failed!\r\n", argv[1]);
        return -1;
    }
    return 0;
}

四、验证结果

  1. 在imx6uirq_driver文件目录中使用“make”编译imx6uirq_driver.c文件为imx6uirq_driver.ko文件,使用“arm-linux-gnueabihf-gcc”编译imx6uirq_app.c文件为”imx6uirq_app“,如下所示:

  2. 将imx6uirq_driver.ko文件和imx6uirq_app拷贝到“/home/wenhao/linux/nfs/rootfs/lib/modules/4.1.15”文件目录中,如果目录不存在就重新创建,如下所示:

  3. 在开发板的Linu控制终端查看文件是否存在,如下所示:

  4. 依次使用如下命令挂载设备,挂载成功会显示提示信息,如下所示:

depmod
modprobe imx6uirq_driver.ko

  1. 查看中断有没有注册上,如下所示:
cat /proc/interrupts

  1. 执行imx6uirq_app程序,不停的按下按键,观察按键值是否被成功打印出来,如下所示:

  2. 执行如下命令卸载设备,如下所示:

rmmod imx6uirq_driver.ko
展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部