Linux下如何使用中断的方式来驱动按键

嵌入式技术

1368人已加入

描述

   

 

Linux下的按键输入驱动开发模板一文中介绍了基本的按键输入捕获流程,这里将进一步介绍如何使用中断的方式来驱动按键,同时通过定时器实现按键消抖功能,应用程序读取按键值并通过终端打印出来 下面根据Linux内核中断框架一文中介绍的内核中断使用模板,来进行代码的编写

中断

 

 

1. 修改设备树文件

 

在Linux按键驱动的设备树key节点基础上,添加中断相关属性

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

设备树编写完成后使用make dtbs命令重新编译设备树,使用新的设备树文件启动 linux 系统

 

 

2. 编写驱动程序

  设备树准备好后就可以编写驱动程序了,新建imx6uirq.c文件,编写程序

 定义按键设备结构体,以及中断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)(intvoid *);  //中断服务函数
};
/* 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设备 */

 编写中断处理函数和定时器处理函数,实现按键消抖

/* 中断服务函数,开启定时器,延时 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);   //标记松开按键
 }
}

 初始化所使用的IO,获取中断号,并请求中断

/* 按键 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!
");
  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
", i);
  }
 }
 /* 初始化key所使用的IO,获取中断号 */
 for (i = 0; i < KEY_NUM; i++) {
  memset(imx6uirq.irqkeydesc[i].name, 0sizeof(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
",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!
",imx6uirq.irqkeydesc[i].irqnum);
   return -EFAULT;
  }
 }
 /* 创建定时器 */
 init_timer(&imx6uirq.timer);
 imx6uirq.timer.function = timer_function;
 return 0;
}

 编写设备操作函数

/* 打开设备 */
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,
};

 驱动入口函数中,创建按键设备

/* 驱动入口函数 */
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_MODULEIMX6UIRQ_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;
}

 驱动出口函数中,删除字符设备,释放中断

/* 驱动出口函数 */
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. 编写测试程序

 

测试程序通过不断的读取/dev/imx6uirq文件来获取按键值,当按键按下以后就会将获取到的按键值输出在终端上。新建imx6uirqApp.c文件,并编写代码

int main(int argc, char *argv[]){
 int fd;
 int ret = 0;
 char *filename;
 unsigned char data;
 if (argc != 2) {
  printf("Error Usage!
");
  return -1;
 }

 filename = argv[1];
 fd = open(filename, O_RDWR);
 if (fd < 0) {
  printf("Can't open file %s
", filename);
  return -1;
 }

 while (1) {
  read(fd, &data, sizeof(data));
  if (data)   //读取到数据
   printf("key value = %#X
", data);
 }
 
 ret= close(fd); 
 if(ret < 0){
  printf("file %s close failed!
", argv[1]);
  return -1;
 }
 return 0;
}

 

 

4. 编译测试

 

 编译驱动程序:当前目录下创建Makefile文件,并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

 编译测试程序:无需内核参与,直接编译即可

arm-linux-gnueabihf-gcc imx6uirqApp.c -o imx6uirqApp

 将驱动文件和测试文件拷贝至rootfs/lib/modules/4.1.15中,加载驱动

depmod                     #第一次加载驱动的时候需要运行此命令
modprobe imx6uirq.ko       #加载驱动

 加载成功后可查看/proc/interrupts文件来检查对应的中断是否注册成功

cat /proc/interrupts

 运行测试程序,按下KEY0按键,imx6uirqApp会获取并且输出按键信息

./imx6uirqApp /dev/key   

中断

 

  审核编辑:汤梓红
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分