博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【嵌入式Linux驱动开发】十五、实操Linux开发中的中断,编写第一个按键驱动程序
阅读量:2028 次
发布时间:2019-04-28

本文共 9301 字,大约阅读时间需要 31 分钟。

   慷慨歌燕市,从容作楚囚。

  引刀成一快,不负少年头。

文章目录


一、实验目标与原理图分析

本节实验目标

  • 中断方式实现按键操作
  • 采用Linux内核定时器消抖
  • 应用程序读取按键值并通过终端打印出来!

IMX6ULL-qemu开发板通过SNVS_TAMPER1 引脚复用为GPIO5_IO01来控制按键,原理图如下图所示。

在这里插入图片描述

二、编写程序

在 Linux 下编写按键驱动之前,我们先来理一下需要做的工作:

  • ①、在设备树中添加 SNVS_TAMPER1 引脚的 pinctrl 信息。
  • ②、在设备树中创建按键节点,在按键节点中加入 GPIO 信息。
  • ③、编写驱动程序和测试 APP

2.1 修改、编译、覆盖设备树文件

2.1.1 添加 pinctrl 节点

添加按键节点之前,需要先确定GPIO5_IO01的父节点,即确定其处于那个复用控制器下。

  • 方法一:通过参考手册获取
    打开IMX6ULL的参考手册,全局搜索GPIO5_IO01,可以看到寄存器IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1,注意SW前面的字母,即IOMUXC_SNVS它就是GPIO5_IO01的父节点

在这里插入图片描述

  • 方法二:通过IMX引脚配置工具获取
    关于IMX引脚配置工具使用方法参考。

在这里插入图片描述

  对比方法一和方法二,推荐使用第二种方法,方便快捷,同时生成节点具体信息可直接套用,之所以介绍第一种方法是在没有生产工具的权宜之计。

  另外需要说明的是,关于具体的电气属性,不管是在第九节还是这一节,我都不打算具体使用软件配置,而是使用好的配置值。

  • GPIO为输入时,配置为0xF080
  • GPIO为输出时,配置为0x10B0

  所以针对本次使用的GPIO5_IO01按键输入引脚,应该在设备树文件100ask_imx6ull_qemu.dts中,在iomuxc_snvs节点的imx6ul-evk子节点下创建名为pinctrl_key的子节点,节点内容如下所示:

pinctrl_mykey: mykeygrp {
fsl,pins = < MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 0xF080 /* KEY1 */ >;};

2.1.2 添加 mykey 设备节点

mykey{
compatible = "clay,keydrv"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_mykey>; key-gpios = <&gpio5 1 GPIO_ACTIVE_LOW>; interrupt-parent = <&gpio5>; interrupts = <1 IRQ_TYPE_EDGE_BOTH>; /* FALLING RISING */ status = "okay";};
  • IRQ_TYPE_EDGE_BOTH 表示上升沿和下降沿同时有效,相当于 KEY0 按下和释放都会触发中断。

2.1.3 检查 PIN 是否被其他外设使用

  在设备树文件中全局搜索MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01关键字,看看是否被其他pinctrl节点使用,如果有的话就要屏蔽掉。

2.1.4 修改、编译、覆盖设备树文件

  • 修改完毕后,打开终端进入Linux源码目录,输入make dtbs编译设备树文件。
  • 编译成功后,在./arch/arm/boot/dts/100ask_imx6ull_qemu.dtb得到设备树文件。
  • 然后替换qemu安装目录./imx6ull-system-image文件夹下的100ask_imx6ull_qemu.dtb即可达到更新设备树的目的。

2.2 驱动程序

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/***************************************************************Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.文件名 : imx6uirq.c作者 : 左忠凯版本 : V1.0描述 : Linux中断驱动实验其他 : 无论坛 : www.openedv.com日志 : 初版V1.0 2019/7/26 左忠凯创建***************************************************************/#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设备 *//* @description : 中断服务函数,开启定时器,延时10ms, * 定时器用于按键消抖。 * @param - irq : 中断号 * @param - dev_id : 设备结构。 * @return : 中断执行结果 */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);}/* @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->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); /* 标记松开按键,即完成一次完整的按键过程 */ } }/* * @description : 按键IO初始化 * @param : 无 * @return : 无 */static int keyio_init(void){ unsigned char i = 0; char name[10]; int ret = 0; imx6uirq.nd = of_find_node_by_path("/mykey"); 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-gpios", 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(name)); /* 缓冲区清零 */ sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i); /* 组合名字 */ gpio_request(imx6uirq.irqkeydesc[i].gpio, 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;}/* * @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 = &imx6uirq; /* 设置私有数据 */ 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 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,};/* * @description : 驱动入口函数 * @param : 无 * @return : 无 */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;}/* * @description : 驱动出口函数 * @param : 无 * @return : 无 */static void __exit imx6uirq_exit(void){ unsigned 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");MODULE_AUTHOR("zuozhongkai");

2.3 应用程序

#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"/***************************************************************Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.文件名		: imx6uirqApp.c作者	  	: 左忠凯版本	   	: V1.0描述	   	: 定时器测试应用程序其他	   	: 无使用方法	:./imx6uirqApp /dev/imx6uirq 打开测试App论坛 	   	: www.openedv.com日志	   	: 初版V1.0 2019/7/26 左忠凯创建***************************************************************//* * @description		: main主程序 * @param - argc 	: argv数组元素个数 * @param - argv 	: 具体参数 * @return 			: 0 成功;其他 失败 */int main(int argc, char *argv[]){
int fd; int ret = 0; int data = 0; char *filename; 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;}

三、运行程序

insmod imx6uirq.ko	//加载驱动模块./imx6uirqApp /dev/imx6uirq //运行应用程序

在这里插入图片描述

转载地址:http://pbnaf.baihongyu.com/

你可能感兴趣的文章
guava学习--monitor
查看>>
guava学习--FutureCallback
查看>>
golang的数据类型之布尔类型
查看>>
golang的数据类型之字符类型
查看>>
安装MySQL
查看>>
golang简介
查看>>
golang的数据类型之整型类型
查看>>
安装go版本
查看>>
golang的数据类型之基本数据类型的默认值和转换
查看>>
golang的数据类型之浮点类型
查看>>
golang的数据类型之字符串类型
查看>>
标识符
查看>>
scala函数
查看>>
Scala集合
查看>>
defer
查看>>
init函数和匿名函数
查看>>
函数参数的传递方式和变量作用域
查看>>
字符串函数
查看>>
时间和日期相关函数
查看>>
Go错误处理机制及自定义错误
查看>>