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

server/2025/1/16 1:42:19/

目录

一、简介

二、修改设备树

2.1 添加pinctrl节点

2.2 添加KEY设备节点 

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

三、代码

3.1 驱动代码

3.2 测试代码

3.3 平台测试


一、简介

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


二、修改设备树

2.1 添加pinctrl节点

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

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

#define MX6UL_PAD_UART1_CTS_B__GPIO1_IO18                         0x008C 0x0318 0x0000 0x5 0x0

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

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”:

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中:

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的定时器进行消抖,在定时器服务函数再次读取按键值,如果按键还是处于按下状态就表示按键有效。

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

#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设备 */

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

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

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

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 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的定时,再编写定时器服务函数

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);	/* 标记松开按键,即完成一次完整的按键过程 */}	
}

完善设备操作函数

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 测试代码

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

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


http://www.ppmy.cn/server/28269.html

相关文章

Oracle故障处理:正在使用的索引失效了

项目场景 昨日&#xff0c;五一放假前一天&#xff0c;项目现场有同事反应业务系统出了问题&#xff1a; 在数据库中用测试数据发现insert、update 等操作不可用。 在应用系统的输出日志中发现数据库抛出了如下异常&#xff1a; ORA-01502:索引 ‘XX.PK_IXXX’ 或这类索引的…

基于Hyperf的CMS,企业官网通用php-swoole后台管理系统

2023年9月11日10:47:00 仓库地址&#xff1a; https://gitee.com/open-php/zx-hyperf-cms CMS&#xff0c;企业官网通用PHP后台管理系统 框架介绍 hyperf SCUI 后端开发组件 php 8.1 hyperf 3.1 数据库 sql(使用最新日期文件) hyperf\doc\sql_bak mysql 8. 系统默认账号…

大模型引领未来:探索其在多个领域的深度应用与无限可能【第七章、大模型在科技、网络安全、农业等方面的应用探索】

大模型引领未来&#xff1a;探索其在多个领域的深度应用与无限可能【第七章、大模型在科技、电商、网络安全、农业等方面的应用】 第七章、大模型在科技、电商、网络安全、农业等方面的应用探索1、大模型在科技上的应用1.1、大模型在科技上可能诞生的应用 2、大模型在网络安全上…

平面模型上提取凸凹多边形------pcl

平面模型上提取凸凹多边形 pcl::PointCloud<pcl::PointXYZ>::Ptr PclTool::ExtractConvexConcavePolygons(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud) {pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);p…

Pyspark+关联规则 Kaggle购物篮分析案例

数据集地址&#xff1a;Market Basket Analysis | Kaggle 我的NoteBook地址&#xff1a;pyspark Market Basket Analysis | Kaggle 零售商期望能够利用过去的零售数据在自己的行业中进行探索&#xff0c;并为客户提供有关商品集的建议&#xff0c;这样就能提高客户参与度、改…

Kubernetes学习笔记03

第八章、Kubernetes控制器Controller详解 Statefulset Statefulset主要是用来部署有状态应用 对于StatefulSet中的Pod&#xff0c;每个Pod挂载自己独立的存储&#xff0c;如果一个Pod出现故障&#xff0c;从其他节点启动一个同样名字的Pod&#xff0c;要挂载上原来Pod的存储…

Hive判空函数 COALESCE 和 NVL 使用示例

Hive判空函数 COALESCE 和 NVL 使用示例 1. 在Hive中&#xff0c; COALESCE 和 NVL 函数都是用于处理NULL值的函数&#xff0c;以下是它们的用途总结&#xff1a; COALESCE&#xff1a; COALESCE 函数用于返回参数列表中第一个非NULL的数值或表达式。语法&#xff1a; COALESC…

vulnhub靶场之FunBox-1

一.环境搭建 1.靶场描述 Boot2Root ! This is a reallife szenario, but easy going. You have to enumerate and understand the szenario to get the root-flag in round about 20min. This VM is created/tested with Virtualbox. Maybe it works with vmware. If you n…