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按下,平台打印正确信息:

相关推荐
嵌入式-老费17 小时前
Linux camera驱动开发(串行和解串)
驱动开发
郝亚军17 小时前
ubuntu-18.04.6-desktop-amd64安装步骤
linux·运维·ubuntu
Konwledging18 小时前
kernel-devel_kernel-headers_libmodules
linux
Web极客码18 小时前
CentOS 7.x如何快速升级到CentOS 7.9
linux·运维·centos
一位赵18 小时前
小练2 选择题
linux·运维·windows
代码游侠19 小时前
学习笔记——Linux字符设备驱动开发
linux·arm开发·驱动开发·单片机·嵌入式硬件·学习·算法
Lw老王要学习19 小时前
CentOS 7.9达梦数据库安装全流程解析
linux·运维·数据库·centos·达梦
CRUD酱19 小时前
CentOS的yum仓库失效问题解决(换镜像源)
linux·运维·服务器·centos
zly350020 小时前
VMware vCenter Converter Standalone 转换Linux系统,出现两个磁盘的处理
linux·运维·服务器
Albert Edison20 小时前
【Python】函数
java·linux·python·pip