欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-06-09 16:32:53
...

由于手头上的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口接地时:

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

IO口接高电平时:

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

 由于我是直接将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 查看:

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

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

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

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