Linux驱动开发——(六)按键中断实验

目录

一、简介

二、修改设备树

[2.1 添加pinctrl节点](#2.1 添加pinctrl节点)

[2.2 添加KEY设备节点](#2.2 添加KEY设备节点)

[2.3 检查是否被其他外设使用](#2.3 检查是否被其他外设使用)

三、代码

[3.1 驱动代码](#3.1 驱动代码)

[3.2 测试代码](#3.2 测试代码)

[3.3 平台测试](#3.3 平台测试)


一、简介

以I.MX6U-MINI为例,实现KEY0按下后,设备识别到并将数据发送到平台。


二、修改设备树

2.1 添加pinctrl节点

首先查看KEY0使用的PIN,打开原理图,可以找到:

KEY0使用了UART1_CTS_B这个PIN,打开imx6ul-pinfunc.h文件,可以找到:

cpp 复制代码
#define MX6UL_PAD_UART1_CTS_B__GPIO1_IO18                         0x008C 0x0318 0x0000 0x5 0x0

打开设备树dts文件,在iomuxc节点的imx6ul-evk子节点下创建一个名为"pinctrl_key"的子节点:

cpp 复制代码
pinctrl_key: keygrp { 
    fsl,pins = < 
        MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 /* KEY0 */ 
    >; 
};

第3行,将UART1_CTS_B这个PIN复用为GPIO1_IO03,电气属性值为0XF0B0。

对于其电气属性配置,可以查看参考手册以参考配置:

2.2 添加KEY设备节点

在根节点"/"下创建KEY节点,节点名为"key":

cpp 复制代码
key { 
    #address-cells = <1>; 
    #size-cells = <1>; 
    compatible = "atkalpha-key"; 
    pinctrl-names = "default"; 
    pinctrl-0 = <&pinctrl_key>; 
    key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */ 
    interrupt-parent = <&gpio1>; 
    interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* FALLING RISING */
    status = "okay"; 
};

第6行,pinctrl-0属性设置KEY所使用的PIN对应的pinctrl节点。

第7行,key-gpio属性指定了KEY所使用的GPIO。

第8行,设置interrupt-parent属性值为"gpio1",因为KEY0所使用的GPIO为GPIO1_IO18,也就是设置KEY0的GPIO中断控制器为gpio1。

第9行,设置interrupts属性,也就是设置中断源,第一个cells的18表示GPIO1组的18号 IO。IRQ_TYPE_EDGE_BOTH定义在文件include/linux/irq.h中:

cpp 复制代码
IRQ_TYPE_NONE = 0x00000000, 
IRQ_TYPE_EDGE_RISING = 0x00000001, 
IRQ_TYPE_EDGE_FALLING = 0x00000002, 
IRQ_TYPE_EDGE_BOTH = (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING), 

IRQ_TYPE_EDGE_BOTH表示上升沿和下降沿同时有效,即KEY0按下和释放都会触发中断。

2.3 检查是否被其他外设使用

对于PIN:检查UART1_CTS_B是否有被其他的pinctrl节点使用,如果有的话就要屏蔽掉;

对于GPIO:检查GPIO1_IO18是否有被其他外设使用,如果有的话也要屏蔽掉。

以上完成后用"make dtbs"重新编译设备树,然后使用新编译出来的设备树dtb文件启动Linux系统。启动成功以后进入"/proc/device-tree"目录中,此时"key"节点应当存在!


三、代码

3.1 驱动代码

采用中断识别按钮按下,中断服务函数内开启10ms的定时器进行消抖,在定时器服务函数再次读取按键值,如果按键还是处于按下状态就表示按键有效。

首先编写好头文件、宏和数据结构:

cpp 复制代码
#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_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设备 */

然后完善驱动入口/出口函数------申请设备号注册设备创建类创建类里的设备初始化

cpp 复制代码
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;
}

module_init(imx6uirq_init);
MODULE_LICENSE("GPL");

初始化------获取设备节点GPIO编号初始化GPIO

初始化按键------获取中断号申请中断

cpp 复制代码
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;
}

中断初始化完了就编写中断服务函数

cpp 复制代码
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));	/* 10ms定时 */
	return IRQ_RETVAL(IRQ_HANDLED);
}

在该函数里开启了10ms的定时,再编写定时器服务函数

cpp 复制代码
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); 	/* 读取IO值 */
	if(value == 0){ 						/* 按下按键 */
		atomic_set(&dev->keyvalue, keydesc->value);
	}
	else{ 									/* 按键松开 */
		atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
		atomic_set(&dev->releasekey, 1);	/* 标记松开按键,即完成一次完整的按键过程 */
	}	
}

完善设备操作函数

cpp 复制代码
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,
};

3.2 测试代码

读取驱动代码发送过来的信息:

cpp 复制代码
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"

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) {
		ret = read(fd, &data, sizeof(data));
		if (ret < 0) {  /* 数据读取错误或者无效 */

			
		} else {		/* 数据读取正确 */
			if (data)	/* 读取到数据 */
				printf("key value = %#X\r\n", data);
		}
	}
	close(fd);
	return ret;
}

3.3 平台测试

insmod挂载模块:

使用测试代码打开设备后,每当KEY0按下,平台打印正确信息:

相关推荐
小安运维日记1 小时前
Linux云计算 |【第四阶段】NOSQL-DAY1
linux·运维·redis·sql·云计算·nosql
CoolTiger、4 小时前
【Vmware16安装教程】
linux·虚拟机·vmware16
学习3人组5 小时前
CentOS 中配置 OpenJDK以及多版本管理
linux·运维·centos
厨 神6 小时前
vmware中的ubuntu系统扩容分区
linux·运维·ubuntu
Karoku0666 小时前
【网站架构部署与优化】web服务与http协议
linux·运维·服务器·数据库·http·架构
geek_Chen016 小时前
虚拟机共享文件夹开启后mnt/hgfs/下无sharefiles? --已解决
linux·运维·服务器
(⊙o⊙)~哦6 小时前
linux 解压缩
linux·运维·服务器
牧小七7 小时前
Linux命令---查看端口是否被占用
linux
鸡鸭扣8 小时前
虚拟机:3、(待更)WSL2安装Ubuntu系统+实现GPU直通
linux·运维·ubuntu
友友马9 小时前
『 Linux 』HTTP(一)
linux·运维·服务器·网络·c++·tcp/ip·http