Detect key input with Linux kernel interrupt

Detect key input with Linux kernel interrupt

The basic key input capture process is introduced in the article Key input driver development under Linux. Here we will further introduce how to use the interrupt method to drive the key. At the same time, the key debounce function is realized through the timer. The application program reads the key value and passes Terminal prints out

The following code is written according to the kernel interrupt usage template introduced in the article Linux Kernel Interrupt Frameworkinsert image description here

1. Modify the device tree file

Based on the key node of the device tree driven by the Linux button , add interrupt-related attributes

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";
};

After the device tree is written, use "make dtbs"command to recompile the device tree and use the new device tree file to start the linux system

2. Write the driver

After the device tree is ready, you can write the driver program, create a new imx6uirq.c file, and write the program

  • Define the key device structure and the description structure of the interrupt 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; 							  //gpio
	int irqnum; 						  //中断号
	unsigned char value; 				  //按键对应的键值
	char name[10]; 						  //名字
	irqreturn_t (*handler)(int, void *);  //中断服务函数
};

/* imx6uirq 设备结构体 */
struct imx6uirq_dev{
    
    
	dev_t devid; 						  //设备号
	struct cdev cdev; 					  //cdev
	struct 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 设备 */
  • Write interrupt processing function and timer processing function to realize key debounce
/* 中断服务函数,开启定时器,延时 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); 	//标记松开按键
	}
}
  • Initialize the IO used, get the interrupt number, and request an interrupt
/* 按键 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 0
		imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif
		printk("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;
}
  • Write device operation functions
/* 打开设备 */
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,
};
  • In the driver entry function, create a key device
/* 驱动入口函数 */
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;
}
  • In the driver exit function, delete the character device and release the interrupt
/* 驱动出口函数 */
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. Write the test program

The test program obtains the key value by continuously reading the /dev/imx6uirq file, and outputs the obtained key value on the terminal when the key is pressed. Create a new file named imx6uirqApp.c and write the test code

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. Compile and test

  • Compile the driver: create a Makefile in the current directory and compile it with 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.o

build: kernel_modules

kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
  • Compile the test program: no need for the kernel to participate, just compile it directly
arm-linux-gnueabihf-gcc imx6uirqApp.c -o imx6uirqApp
  • Copy the driver file and APP executable file to "rootfs/lib/modules/4.1.15", load the driver
depmod                     #第一次加载驱动的时候需要运行此命令
modprobe imx6uirq.ko       #加载驱动
  • After the driver is loaded successfully, you can check whether the corresponding interrupt is registered successfully by viewing the /proc/interrupts file
cat /proc/interrupts
  • Run the test program, press the KEY0 button, imx6uirqApp will get and output the button information
./imx6uirqApp /dev/imx6uirq  

insert image description here

Guess you like

Origin blog.csdn.net/Chuangke_Andy/article/details/125894249