i.MX283开发板按键驱动和GPIO中断

由于手头上的i.MX283开发板没有独立按键,所以只能用一个IO口手动拉高拉低来模拟按键,但是这样会造成一个小问题,这个后面会提到。按键驱动与LED驱动最大的区别就是前者是GPIO输入,后者是GPIO输出,我们只需要读取IO口电平即可,同样的这也是一个字符设备,按照字符设备驱动框架编写驱动即可。

  • 按键驱动编写:

1.首先引用头文件、定义驱动名称和按键IO口

/*
   BUTTON Driver driver for EasyARM-iMX283
*/
#include <linux/module.h>//模块加载卸载函数
#include <linux/kernel.h>//内核头文件
#include <linux/types.h>//数据类型定义
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/fs.h>//file_operations结构体
#include <linux/device.h>//class_create等函数
#include <linux/ioctl.h>
#include <linux/delay.h>
#include <linux/bcd.h>
#include <linux/capability.h>
#include <linux/rtc.h>
#include <linux/cdev.h>
#include <linux/gpio.h>//gpio_request  gpio_free函数 

#include <../arch/arm/mach-mx28/mx28_pins.h>
#define DEVICE_NAME	"imx283_key"//驱动名称
#define BUTTON	MXS_PIN_TO_GPIO(PINID_SSP0_DATA7)  //P2.7脚

2.编写open、write、read、relese函数

static int button_open(struct inode *inode ,struct file *flip)
{

   int ret = -1;
   gpio_free(BUTTON); //释放GPIO
   ret = gpio_request(BUTTON, "KEY1");//申请GPIO
   printk("gpio_request = %d\r\n",ret);
   return 0;
}


static int button_release(struct inode *inode ,struct file *flip)
{
  gpio_free(BUTTON);
  return 0;  
}


static int button_write(struct file *filp, const char __user *buf, size_t count,
                loff_t *f_pos)
{

   return 0;//按键不需要输出,直接返回0即可
}


static  ssize_t button_read(struct file *filp,  char __user *buf, size_t count,
                loff_t *f_pos)
{
    int ret = -1;
	unsigned char databuf[1];
	gpio_direction_input(BUTTON);//配置为输入
	databuf[0]=gpio_get_value(BUTTON);//获取GPIO电平
	databuf[0]=databuf[0]?1:0; 
	ret=copy_to_user(buf, databuf, 1);
	if(ret < 0)
	{
      printk("kernel read error \n");
	}
    return 0;
}

这里需要注意一个地方,gpio_get_value(类似的还有gpio_set_value )实际上是一个宏定义,它跟cpu类型(平台)有关,我是i.mx28系列平台,该宏定义路径为:linux-2.6.35.3\arch\arm\plat-mxs\include\mach\gpio.h,定义如下:

#define gpio_get_value          __gpio_get_value
//#define gpio_set_value          __gpio_set_value

int __gpio_get_value(unsigned gpio)
{
	struct gpio_chip	*chip;

	chip = gpio_to_chip(gpio);
	WARN_ON(extra_checks && chip->can_sleep);
	return chip->get ? chip->get(chip, gpio - chip->base) : 0;
}

此函数返回的结果只有0或非0值,若当前GPIO为高电平,其返回值是该引脚对应的那一位被置位的值,比如,我接的是P2.7脚,当该脚被拉高时,此函数返回值为(1<<7)128。因此,如果应用层只希望返回值是0或1,驱动read函数最好做下处理:

	databuf[0]=gpio_get_value(BUTTON);//获取GPIO电平
	databuf[0]=databuf[0]?1:0; 

接着,定义一个file_operations结构体并填充

static struct file_operations button_fops={
	.owner		= THIS_MODULE,
	.open 		= button_open,
	.write		= button_write,
	.read       = button_read,
	.release	= button_release,
};

3.编写设备注册与注销函数

同样的,我们还是采用前面提到的新字符设备驱动的注册和注销方法,同时,让设备自动创建设备节点。

static struct cdev button_cdev;//定义一个cdev结构体
static struct class *button_class;//创建一个button类
static struct device *button_device;//创建一个设备 
static int major;//主设备号
static dev_t  devid;//设备号 

static int __init button_init(void)
{ 
    /*1.申请设备号 此处由内核动态分配设备号*/
    alloc_chrdev_region(&devid, 0, 1, DEVICE_NAME);
	major = MAJOR(devid);
	/*2.初始化 button_cdev结构体*/
   	cdev_init(&button_cdev, &button_fops);
	/*3.向button_cdev添加设备*/
   	cdev_add(&button_cdev,devid, 1);
   //4.创建一个button_class类
   button_class = class_create(THIS_MODULE,"button_class");
   //5.在button类下面创建一个button设备 然后mdev通过这个自动创建/dev/"DEVICE_NAME"
   button_device = device_create(button_class,NULL,devid,NULL,DEVICE_NAME);
   printk("module init ok\n");
   return 0; 
}


static void __exit button_exit(void)
{
  /*1.删除button_cdev结构体*/
  cdev_del(&button_cdev); 
  /*2.注销设备*/
   unregister_chrdev_region(devid,1);
  /*3.删除button设备*/
  device_destroy(button_class, devid);
  /*4.删除button类*/
  class_destroy(button_class);
  printk("module exit ok\n");
}

module_init(button_init);
module_exit(button_exit);

4.添加作者和LICENSE信息

MODULE_LICENSE("GPL");
MODULE_AUTHOR("xzx2020");

5.测试程序

每隔500ms读取一次GPIO电平并打印,会阻塞控制台。

注意:这里仅仅是作为测试驱动,实际上这样读取IO口是不可取的。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <limits.h>
#include <asm/ioctls.h>
#include <time.h>
#include <pthread.h>

int main(void)
{
	int fd;
	char buf[1] = {0};

	fd = open("/dev/imx283_key", O_RDWR);
	if (fd < 0)
	{
		printf("open /dev/imx283_key error \n");
	}

    while(1)
    {
     read(fd, buf, 1);
     printf("key value = %d \r\n",buf[0]);
	 usleep(500000);
	}
}

在开发板上运行测试:

IO口接地时:

IO口接高电平时:

 由于我是直接将IO用杜邦线拉低或者拉高,但是这个IO口实际上是浮空的,这会造成一个问题:当IO从接地状态或者从接高电平状态切换到浮空时,IO的电平会保持上一次的状态,也就是说,当IO一开始是接高电平,接着让它保持浮空,读取IO口的值仍然是高电平。

所以,实际按键电路设计时需要外接上拉或者下拉电阻,不要让IO口浮空,IO浮空时的电平值是不确定的。

 


GPIO中断

将某一个具有 GPIO 功能的引脚,配置成 GPIO 功能模式并设置为输入工作状态时,该引脚就可以检测外部输入的中断信号。

这里演示一个简单的GPIO中断。

1.引用头文件、定义引脚和相关变量

/*
GPIO IRQ driver for EasyARM-iMX283
*/
#include<linux/init.h>
#include<linux/module.h>
#include<mach/gpio.h>
#include<asm/io.h>
#include"mach/../../mx28_pins.h"
#include <mach/pinctrl.h>
#include "mach/mx28.h"
#include <linux/fs.h>
#include <linux/io.h>
#include <asm/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/io.h>


#define INT_PIN  MXS_PIN_TO_GPIO(PINID_SSP0_DATA4)//P2_4
static int irq = 0;//中断号

2.中断服务函数 

中断服务函数的入口参数和返回值都是有规定的,后面会介绍。

这里为了测试,就让它打印一句"irq test。

//中断服务程序
static irqreturn_t gpio_irq(int irq, void *dev_id)
{
	printk("irq test \n"); 
	return IRQ_RETVAL(IRQ_HANDLED);
}

3.注册函数和注销函数

GPIO中断注册与注销主要需要以下几个函数,第一个是gpio_to_irq,这是一个宏定义,作用是申请中断号,只有申请了中断号,才可以进行后面的操作。

#define gpio_to_irq		__gpio_to_irq

int __gpio_to_irq(unsigned gpio)
{
	struct gpio_chip	*chip;

	chip = gpio_to_chip(gpio);
	return chip->to_irq ? chip->to_irq(chip, gpio - chip->base) : -ENXIO;
}

第二个是set_irq_type,设置中断触发方式:

int set_irq_type(unsigned int irq, unsigned int type)
{
	struct irq_desc *desc = irq_to_desc(irq);
	unsigned long flags;
	int ret = -ENXIO;

	if (!desc) {
		printk(KERN_ERR "Trying to set irq type for IRQ%d\n", irq);
		return -ENODEV;
	}

	type &= IRQ_TYPE_SENSE_MASK;
	if (type == IRQ_TYPE_NONE)
		return 0;

	raw_spin_lock_irqsave(&desc->lock, flags);
	ret = __irq_set_trigger(desc, irq, type);
	raw_spin_unlock_irqrestore(&desc->lock, flags);
	return ret;
}
/*
irq:中断号
type:中断类型
*/

可配置的中断类型有以下几种:

#define IRQ_TYPE_NONE		0x00000000	/* Default, unspecified type */
#define IRQ_TYPE_EDGE_RISING	0x00000001	/* Edge rising type */
#define IRQ_TYPE_EDGE_FALLING	0x00000002	/* Edge falling type */
#define IRQ_TYPE_EDGE_BOTH (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)
#define IRQ_TYPE_LEVEL_HIGH	0x00000004	/* Level high type */
#define IRQ_TYPE_LEVEL_LOW	0x00000008	/* Level low type */

第三个函数就是request_irq,申请中断以及向内核注册中断服务程序

static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
	    const char *name, void *dev)
{
	return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

/*
irq:中断号
handler:中断服务程序
flags:中断类型
name:中断名称
dev: 如果将 flags 设置为 IRQF_SHARED 的话,dev 用来区分不同的中断,一般情况下将
dev 设置为设备结构体,dev 会传递给中断处理函数 irq_handler_t 的第二个参数。
*/

irq_handler_t是一个函数指针,其返回值是irqreturn_t型,入口参数有两个,一个是int类型,一个是void*(可以强制转化为任何其他类型)。

typedef irqreturn_t (*irq_handler_t)(int, void *);

enum irqreturn {
	IRQ_NONE,
	IRQ_HANDLED,
	IRQ_WAKE_THREAD,
};

typedef enum irqreturn irqreturn_t;

第四个是gpio_free,用于释放申请的中断号。

void free_irq(unsigned int irq, void *dev_id)
{
	struct irq_desc *desc = irq_to_desc(irq);

	if (!desc)
		return;

	chip_bus_lock(irq, desc);
	kfree(__free_irq(irq, dev_id));
	chip_bus_sync_unlock(irq, desc);
}
/*
irq:中断号
dev_id:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断
只有在释放最后中断处理函数的时候才会被禁止掉。
其他中断,此字段为NULL
*/
static int __init gpio_drv_init(void)
{
	int ret;
	/*1.先申请 GPIO 口,同时把该 GPIO 口设置为输入模式*/
	gpio_free(INT_PIN);
	ret = gpio_request(INT_PIN, "irq_pin");  
	if (ret < 0) 
	{  
		printk("request gpio failed \n");
		return ret;
	}
	gpio_direction_input(INT_PIN); 
	/*2.根据该 GPIO 申请其对应 IRQ 中断号*/
	irq = gpio_to_irq(INT_PIN);
	/*3.设置中断触发方式为下降沿触发*/
	set_irq_type(irq, IRQF_TRIGGER_RISING); 
	/*4.申请中断并向内核注册中断处理函数*/
	ret = request_irq(irq, gpio_irq, IRQF_DISABLED, "gpio_int", NULL);
	if (ret != 0)
	{
		printk("request irq failed!! ret: %d irq:%d gpio:%d \n", ret, irq, INT_PIN);
		return -EBUSY;
	}

	printk("module init ok\n");
	return 0;
}

static void __exit gpio_drv_exit(void)
{
    /*1.释放申请的中断号*/
	free_irq(irq, NULL);
	/*2.释放申请的GPIO*/
	gpio_free(INT_PIN);
	printk("module exit ok\n");
}

module_init(gpio_drv_init);
module_exit(gpio_drv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xzx2020");

此驱动测试不需要测试程序,在板子上加载驱动程序即可。用insmod指令加载完驱动,再用cat /proc/interrupt 查看:

gpio中断已经生成,中断号196,现在用杜邦线连接P2.4口产生一个下降沿。

可以看到中断服务程序执行了。

猜你喜欢

转载自blog.csdn.net/qq_24835087/article/details/104123521