NanoPi 中断(Nanopi-S2)

1. GPIO概述

Linux 内核中已经设计好了相关 GPIO 驱动模型,并设计了相关的操作函数接口。

int gpio_request(unsigned gpio, const char *tag)           //申请一个空闲的GPIO
int gpio_direction_input(unsigned gpio)                    //设置GPIO为输入
int gpio_direction_output(unsigned gpio, int value)        //设置GPIO为输出或输入
void gpio_set_value(unsigned gpio, int value)              //设置GPIO输出高电平或低电平
int gpio_get_value(unsigned gpio)                          //获取GPIO的当前状态,高电平或低电平
int gpio_to_irq(unsigned gpio)                             //返回的中断编号
request_irq()   //注册中断服务
free_irq()      //注销服务

三星官方把在源码中把 GPIO 分为了A、B、C、D、E组,每组32个引脚,通过上述函数申请GPIO 资源时,比如申请 GPB31 这个引脚为 GPIO 时,只需提交参数 PAD_GPIO_B + 31 就可以了。GPIO 的中断号在s5p4418_irq.h中定义:

这里写图片描述

既可以通过gpio_to_irq (PAD_GPIO_B +31)来获取 GPB31 引脚对应的中断号,也可以通过64 + PAD_GPIO_B + 31得到中断号。

2. 驱动代码

本例以 GPB31 作为 GPIO 的外部中断输入引脚,GPB30 作为 GPIO 输出得出中断的状态

2.1 头文件

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio.h>

#include <mach/platform.h>
#include <mach/gpio_desc.h>
#include <mach/gpio.h>

2.2 全局变量

#define LED_GPIO    (PAD_GPIO_B + 30)           //GPB30为LED输出控制引脚
#define KEY_GPIO     (PAD_GPIO_B + 31)           //GPB31为触发终端源
#define KEY_GPIO_IRQ gpio_to_irq(KEY_GPIO)       //根据GPIO编号获取对应的引脚的中断编号
#define DEVICE_NAME "key_irq"

static int major;
static int minor;
struct cdev *key_irq; /* cdev 数据结构 */
static dev_t devno; /* 设备编号 */
static struct class *key_irq_class;

static int flag = 0;      //屏蔽因驱动初始化中添加定时器导致的在没有按下按键,定时器计时到,出现没有中断而调用了LED点亮动作

static struct timer_list key_timer;        /*消抖定时器*/

char const irq_types[5] = {
      IRQ_TYPE_EDGE_RISING,
      IRQ_TYPE_EDGE_FALLING,
      IRQ_TYPE_EDGE_BOTH,
      IRQ_TYPE_LEVEL_HIGH,
      IRQ_TYPE_LEVEL_LOW
};

2.3 中断服务函数

由于按键本身存在的抖动现象,必须滤除掉按键按下后20ms内的不稳定抖动信号,这里采用内核定时器,按键信号触发中断,并进入中断服务程序后,修改定时器的定时值,如果20ms内没有其他信号触发中断,则执行定时器定时结束后的回调函数,否则从新开始计时20ms过程,直到按键信号稳定为止。中断服务函数如下:

static irqreturn_t key_irq_irq_handler(unsigned int irq, void *dev_id)
{
      /*20ms后启动定时器*/
      flag= 1; //只有flag为1时才能说明是按键中断导致的定时器函数调用
      mod_timer(&key_timer,jiffies+ HZ/50);      /*去抖时间设为20ms*/
      returnIRQ_HANDLED;
}

2.4 定时器回调函数

当中断信号确实稳定后,定时器20ms到时,才执行定时器回调函数。在本例程中,定时器回调函数主要执行打印信息,以及翻转LED的亮灭,说明驱动程序工作正常。

在试用过程中,发现一个奇怪现象,通过insmod加载驱动后,即使没有按下按键,LED自动点亮。后来发现,在驱动的初始化函数中,即使我们设置了LED的GPIO引脚为高电平,即LED灭,由于在初始化并添加内核定时器后,会自动执行一次内核定时器的回调函数,导致即使没有中断发生,也会出现加载驱动模块时的LED点亮的现象。

为了解决这一问题,我设置了一个全局的flag,只用flag为1时,才允许在定时器的回调函数中判断LED灯的翻转逻辑;并且只有在真正的中断发生时,在中断服务函数中将flag设置为1。定时器回调函数如下:

static void key_timer_function(unsignedlong data)
{
      static int count = 0;
      if(1== flag)
      {
             printk(KERN_INFO"KEY IRQ HAPPENED!\n");
             /*根据按键KEY按下的次数交替点亮/熄灭LED灯*/
             if(count% 2  == 0)
                    gpio_set_value(LED_GPIO,0);         /*点亮LED灯*/
             else
                    gpio_set_value(LED_GPIO,1);         /*熄灭LED灯*/
             count++;
      }
}

2.5 驱动初始化函数

驱动初始化函数完成 GPIO 资源申请,获取 GPIO 的中断号,将 GPIO 中断号与中断服务函数绑定。申请字符设备驱动主设备号,创建设备类,和设备实体,以及自动在/dev目录下自动生成设备节点。本例程,采用按键上升沿作为中断的触发条件,即按键松开后,触发中断。初始化函数如下:

static int __init key_irq_init(void)
{
      intret;

      /*注册定时器*/
      init_timer(&key_timer);
      key_timer.function= key_timer_function;
      add_timer(&key_timer);

      /*申请GPIO*/
      gpio_free(KEY_GPIO);              /*首先释放GPIO*/
      gpio_free(LED_GPIO);             /*首先释放GPIO*/
      ret= gpio_request_one(KEY_GPIO, GPIOF_IN, "KEY IRQ"); /* 申请IO为输入*/
      if(ret < 0) {
             printk(KERN_ERR"Failed to request GPIO for KEY\n");
      }

      ret= gpio_request_one(LED_GPIO, GPIOF_OUT_INIT_HIGH, "LED OUTPUT"); /* 申请IO为输出*/
      if(ret < 0) {
             printk(KERN_ERR"Failed to request GPIO for LED\n");
      }

      gpio_direction_input(KEY_GPIO);/* 设置 GPIO 为输入 */
      gpio_direction_output(LED_GPIO,0);/* 设置 GPIO 为输出,参数0代表输出*/
      gpio_set_value(LED_GPIO,1);/*初始化LED为熄灭状态*/

      /*申请GPB31对应的中断*/
      if(request_irq(KEY_GPIO_IRQ, key_irq_irq_handler, IRQF_TRIGGER_RISING, "key_irqirq", NULL) )
      {/* 申请中断 */
             printk(KERN_WARNINGDEVICE_NAME": Can't get IRQ: %d!\n", KEY_GPIO_IRQ);
      }
      irq_set_irq_type(KEY_GPIO_IRQ,irq_types[0]); /*按键上升沿出发中断*/
      disable_irq(KEY_GPIO_IRQ);
      enable_irq(KEY_GPIO_IRQ);

      ret= alloc_chrdev_region(&devno, minor, 1, DEVICE_NAME); /* 从系统获取主设备号 */
      major= MAJOR(devno);
      if(ret < 0) {
             printk(KERN_ERR"cannot get major %d \n", major);
             return-1;
      }

      key_irq= cdev_alloc(); /* 分配 key_irq 结构 */
      if(key_irq != NULL) {
             cdev_init(key_irq,&key_irq_fops); /* 初始化 key_irq 结构 */
             key_irq->owner= THIS_MODULE;
             if(cdev_add(key_irq, devno, 1) != 0) { /* 增加 key_irq 到系统中 */
                    printk(KERN_ERR"add cdev error!\n");
                    gotoerror;
             }
      }
      else{
             printk(KERN_ERR"cdev_alloc error!\n");
             return-1;
      }

      key_irq_class= class_create(THIS_MODULE, "key_irq_class");
      if(IS_ERR(key_irq_class)) {
             printk(KERN_INFO"create class error\n");
             return-1;
      }

      device_create(key_irq_class,NULL, devno, NULL, DEVICE_NAME);
      printk("Initcompleted!\n");
      return0;

      error:
             unregister_chrdev_region(devno,1); /* 释放已经获得的设备号 */
      returnret;
}

2.6 驱动退出函数

在驱动退出函数中,释放申请到的 GPIO 资源,释放 GPIO 对应的中断号,释放系统自动分配的字符设备驱动主设备号,删除设备类和设备实体,删除去抖定时器函数等。

static void __exit key_irq_exit(void)
{
      gpio_set_value(LED_GPIO,1);  /*退出时设置LED为熄灭状态*/

      gpio_free(KEY_GPIO);       /*释放为按键KEY申请的GPIO资源*/
      gpio_free(LED_GPIO);       /*释放为LED申请的GPIO资源*/

      disable_irq(KEY_GPIO_IRQ);
      free_irq(KEY_GPIO_IRQ,NULL);
      cdev_del(key_irq);/* 移除字符设备 */
      unregister_chrdev_region(devno,1); /* 释放设备号 */
      device_destroy(key_irq_class,devno);
      class_destroy(key_irq_class);

      del_timer(&key_timer); /*删除定时器*/

      printk(KERN_INFO"Exit completed!\n");
}

2.7 编写 Makefile

KERN_DIR = /home/fa/linux-3.4.y
all:
      make-C $(KERN_DIR) M=`pwd` modules
clean:
      make-C $(KERN_DIR) M=`pwd` modules clean
      rm-rf modules.order
obj-m    += keyinterrupt.o

KERN_DIR 要修改成源码路径keyinterrupt要修改成驱动.c文件名称

2.8 小结

本例程只是简单的实现了基于中断的按键驱动程序,还没有实现异步功能,在中断服务程序中向用户态的程序发送SIG,然后让用户态的驱动程序在信号处理函数中读取 GPIO 引脚状态。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值