4412开发板学习之Linux驱动开发(八):GPIO读操作与按键轮询实现

GPIO读操作

前面我们使用GPIO来控制IO口,点亮了LED灯,当然,IO口是可以有多种配置的,输入输出是最基本的两种,今天我们就来尝试一下GPIO的输入操作,我们使用4412开发板上的3、4号拨码开关来实现

硬件

查找对应IO口

在这里插入图片描述
可以看出3、4号分别为AP_SLEEP、XEINT6
经过查阅原理图、手册我们可以找到以下的对应关系

  • AP_SLEEP->GPC0_3->EXYNOS4_GPC0(3)
  • XEINT6->GPX0_6->EXYNOS4_GPX0(6)

按键上就有下拉电阻,所以向内为低电平,向外为高电平

寄存器配置

1、设置为输入状态
2、读DAT寄存器
3、既不上拉也不下拉
以下为配置、数据、上下拉寄存器
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

软件

需要的函数

  • gpio_request申请GPIO
  • s3c_gpio_cfgpin初始化GPIO S3C_GPIO_INPUT
  • gpio_get_value读值
  • s3c_gpio_setpull设置上下拉S3C_GPIO_PULL_NONE
  • gpio_free释放GPIO

注册设备

注册设备这个是很重要的一个点,但是它也是很简单的,我们需要修改iTop4412的平台文件、字符驱动的Kconfig

vim arch/arm/mach-exynos/mach-itop4412.c

添加已下两处

#ifdef CONFIG_GPIO_READ_CTL
struct platform_device s3c_device_gpio_read_ctl = {
        .name   = "gpio_read_ctl",
        .id             = -1,
};
#endif
#ifdef CONFIG_GPIO_READ_CTL
        &s3c_device_gpio_read_ctl,
#endif
vim drivers/char/Kconfig

添加

config GPIO_READ_CTL
        bool "Enable GPIO_READ config"
        default y
        help
          Enable GPIO_READ config

然后make menuconfig
接着make zImage
最后将生成的zImage烧录到开发板

代码及分析

驱动代码

#include <linux/init.h>
#include <linux/module.h>
/*driver register*/
#include <linux/platform_device.h>

/*注册杂项设备头文件*/
#include <linux/miscdevice.h>
/*注册设备节点的文件结构体*/
#include <linux/fs.h>

/*Linux中申请GPIO的头文件*/
#include <linux/gpio.h>
/*三星平台的GPIO配置函数头文件*/
/*GPIO配置参数宏定义头文件*/
#include <plat/gpio-cfg.h>
#include <mach/gpio.h>
/*三星平台4412平台,GPIO宏定义头文件*/
#include <mach/gpio-exynos4.h>

#define DRIVER_NAME "gpio_read_ctl"
#define DEVICE_NAME "gpio_read_ctl_dev"//设备名
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("GYY");


static int gpio_read_open(struct inode * pinode , struct file * pfile )
{
	printk(KERN_EMERG "gpio_read OPEN !!\n");
	return 0;
}

static int gpio_read_release(struct inode * pinode, struct file * pfile)
{
	printk(KERN_EMERG "gpio_read RELEASE !!\n");
	return 0;
}
/*应用中通过ioctl来获取管脚电平*/
static long gpio_read_ioctl(struct file * pfile, unsigned int cmd, unsigned long arg)
{
	int ret;
	printk("cmd is %d ,arg is %d\n",cmd,arg);
	//参数cmd可以是0或1
	if(cmd > 1)
	{
		printk(KERN_EMERG "cmd is 0 or 1 \n");
		return 0;
	}
	if(arg > 1)
	{
		printk(KERN_EMERG "arg is only 1 \n");
		return 0;
	}
	
	/*cmd为0返回AP_SLEEP->GPC0_3->EXYNOS4_GPC0(3),SWITCH3*/
	if(cmd==0)
	{
		ret = gpio_get_value(EXYNOS4_GPC0(3));
	}
	/*cmd为0返回XEINT6->GPX0_6->EXYNOS4_GPX0(6),SWITCH4*/
	else if(cmd==1)
	{
		ret = gpio_get_value(EXYNOS4_GPX0(6));
	}
	return ret;
}

static struct file_operations gpio_read_ops = {
	.owner = THIS_MODULE,
	.open = gpio_read_open,
	.release = gpio_read_release,
	.unlocked_ioctl = gpio_read_ioctl,
	
};


static struct miscdevice gpio_read_dev = {
	.minor = MISC_DYNAMIC_MINOR,//自动分配设备号
	.name = DEVICE_NAME,//设备名
	.fops = &gpio_read_ops,
};

static int gpio_read_probe (struct platform_device *pdv){
	
	int ret;
	printk(KERN_EMERG "\tinitialized\n");
	/*申请GPIO*/
	ret = gpio_request(EXYNOS4_GPC0(3),"SWITCH 3");
	if(ret < 0)
	{
		printk(KERN_EMERG "gpio_request EXYNOS4_GPC0(3) failed\n");
		return ret;
	}
	else
	{
		/*设置为输入*/
		s3c_gpio_cfgpin(EXYNOS4_GPC0(3),S3C_GPIO_INPUT);
		/*不上拉不下拉*/
		s3c_gpio_setpull(EXYNOS4_GPC0(3),S3C_GPIO_PULL_NONE);
	}
	
	/*申请GPIO*/
	ret = gpio_request(EXYNOS4_GPX0(6),"SWITCH 4");
	if(ret < 0)
	{
		printk(KERN_EMERG "gpio_request EXYNOS4_GPX0(6) failed\n");
		return ret;
	}
	else
	{
		/*设置为输入*/
		s3c_gpio_cfgpin(EXYNOS4_GPX0(6),S3C_GPIO_INPUT);
		/*不上拉不下拉*/
		s3c_gpio_setpull(EXYNOS4_GPX0(6),S3C_GPIO_PULL_NONE);
	}	
	
	/*生成设备节点*/
	misc_register(&gpio_read_dev);
	return 0;
}

static int gpio_read_remove (struct platform_device *pdv){
	
	printk(KERN_EMERG "\tremove\n");
	misc_deregister(&gpio_read_dev);
	return 0;
}

static void gpio_read_shutdown (struct platform_device *pdv){
	
	
}

static int gpio_read_suspend (struct platform_device *pdv,pm_message_t state){
	
	return 0;
}

static int gpio_read_resume (struct platform_device *pdv){
	
	return 0;
}


struct platform_driver gpio_read_driver = {
	.probe = gpio_read_probe,
	.remove = gpio_read_remove,
	.shutdown = gpio_read_shutdown,
	.suspend = gpio_read_suspend,
	.resume = gpio_read_resume,
	.driver = {
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
	}
};


static int gpio_read_init(void)
{
	int DriverState;
	printk(KERN_EMERG "GPIO_READ enter!\n");
	DriverState=platform_driver_register(&gpio_read_driver);
	
	printk(KERN_EMERG "\t%d\n",DriverState);
	return 0;
}

static void gpio_read_exit(void)
{
	printk(KERN_EMERG "GPIO_READ exit!\n");
	platform_driver_unregister(&gpio_read_driver);
}

module_init(gpio_read_init);
module_exit(gpio_read_exit);

驱动代码分析

这个驱动程序和杂项驱动点灯的代码没有太大的变化,在probe函数中我们完成了对GPIO的申请与初始化,我们首先调用 gpio_request() 来申请GPIO,接下来通过调用 s3c_gpio_cfgpin() 来初始化IO为输入模式,最后调用 **s3c_gpio_setpull()**设置IO口既不上拉也不下拉
在ioctl函数中我们完成了对IO口的读操作,当应用程序调用ioctl时将会读出两个开关的电平

应用程序代码

#include <stdio.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>


int main(int argc,char **argv)
{
	int fd,cmd=0;
	char *read_node = "/dev/gpio_read_ctl_dev";
	
	char *cmd0 = "0";
	char *cmd1 = "1";
	printf("argv[1] is %s\n",argv[1]);
	if(strcmp(argv[1],cmd0)==0)
	{
		cmd=0;
	}
	else if(strcmp(argv[1],cmd1)==0)
	{
		cmd=1;
	}
	if((fd = open(read_node,O_RDWR|O_NDELAY))<0)
	{
		printf("APP open %s failed\n",read_node);
	}
	else
	{
		printf("APP open %s success\n",read_node);
		printf("%d io value is %d\n",cmd,ioctl(fd,cmd,0));
	}
	close(fd);
}

应用程序代码分析
这个应用程序所做的事情很简单就是打开设备节点文件,然后根据命令读出对应开关的电平并打印

实验效果

安装模块
在这里插入图片描述
查看设备
在这里插入图片描述
在这里插入图片描述
可以看到生成了gpio_read_ctl_dev设备节点
执行应用程序
在这里插入图片描述
可以看到我们改变拨码开关的状态读出的电平发生了改变,符合我们的要求

按键轮询实现

原理分析

在这里插入图片描述
原理图如上图所示
不按下为高电平,按下为低电平
通过GPIO的输入电平来检测按键的变化

硬件

通过查找原理图和datasheet找到IO口的如下对应关系

  • Home->UART_RING->GPX1_1->EXYNOS4_GPX1(1)
  • back->SIM_DET->GPX1_2->EXYNOS4_GPX1(2)
  • sleep->GYRO_INT->GPX3_3->EXYNOS4_GPX3(3)
  • Vol±>KP_ROW1->GPX2_1->EXYNOS4_GPX2(1)
  • Vol–>KP_ROW0->GPX2_0->EXYNOS4_GPX2(0)

软件

用到的函数

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *)

file_operations 结构体中的read函数,对应用户空间的read函数

unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)

将数据从内核空间拷贝到用户空间
参数1:用户空间目标地址
参数2:内核空间地址
参数3:要拷贝数据的数量

先前准备工作

当前内核这些IO口已被驱动占用,我们需要取消编译那个驱动
make menuconfig->device drivers->input device support->Keyboards ->去掉GPIO_button
接着在平台文件、Kconfig中注册设备添加pollkey,这个就和上面注册gpio_read_ctl是差不多的就不再赘述了
最后重新编译和烧录内核

代码及分析

驱动代码

#include <linux/init.h>
#include <linux/module.h>

#include <linux/kernel.h>
#include <linux/fs.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
//#include <mach/gpio-bank.h>
#include <mach/regs-gpio.h>
#include <asm/io.h>
#include <linux/regulator/consumer.h>
//#include "gps.h"
#include <linux/delay.h>
/*copy_to_user头文件*/
#include <asm/uaccess.h>

#define DPRINTK(x...) printk("POLLKEY_CTL DEBUG:" x)

/*驱动名*/
#define DRIVER_NAME "pollkey_ctl"


/*按键IO数组*/
static int key_gpios[] = {
	EXYNOS4_GPX1(1),//Home
	EXYNOS4_GPX1(2),//Back
	EXYNOS4_GPX3(3),//Sleep
	EXYNOS4_GPX2(1),//Vol+
	EXYNOS4_GPX2(0)//Vol-
};



int pollkey_open(struct inode *inode,struct file *filp)
{
	DPRINTK("Device Opened Success!\n");
	return nonseekable_open(inode,filp);
}

int pollkey_release(struct inode *inode,struct file *filp)
{
	DPRINTK("Device Closed Success!\n");
	return 0;
}
/*关键驱动:按键扫描(read)函数*/
static ssize_t pollkey_read (struct file *pfile, char __user *buff, size_t size, loff_t * ppos)
{
	unsigned char key_value[5];//键值数组存放读取到的电平
	int i;
	if(size != sizeof(key_value))
	{
		return -1;
	}
	/*循环读五个IO口*/
	for(i=0;i<5;i++)
	{
		key_value[i]=gpio_get_value(key_gpios[i]);
	}
	//将数据传递给用户空间
	copy_to_user(buff,key_value,sizeof(key_value));
	return 0;
}

int pollkey_pm(bool enable)
{
	int ret = 0;
	printk("debug: pollkey PM return %d\r\n" , ret);
	return ret;
};




static struct file_operations pollkey_ops = {
	.owner 	= THIS_MODULE,
	.open 	= pollkey_open,
	.release= pollkey_release,
	.read = pollkey_read,
};

static struct miscdevice pollkey_dev = {
	.minor	= MISC_DYNAMIC_MINOR,
	.fops	= &pollkey_ops,
	.name	= "pollkey_ctl_dev",
};


static int pollkey_probe(struct platform_device *pdev)
{
	int ret, i;
	char *banner = "pollkey Initialize\n";

	printk(banner);
	for(i=0;i<5;i++)
	{
		/*申请GPIO*/
		ret = gpio_request(key_gpios[i],"key_gpio");
		/*设置为输入*/
		s3c_gpio_cfgpin(key_gpios[i],S3C_GPIO_INPUT);
		/*不上拉不下拉*/
		s3c_gpio_setpull(key_gpios[i],S3C_GPIO_PULL_NONE);
	}
	ret = misc_register(&pollkey_dev);
	
	return 0;

}

static int pollkey_remove (struct platform_device *pdev)
{
	misc_deregister(&pollkey_dev);	

	return 0;
}

static int pollkey_suspend (struct platform_device *pdev, pm_message_t state)
{
	DPRINTK("pollkey suspend:power off!\n");
	return 0;
}

static int pollkey_resume (struct platform_device *pdev)
{
	DPRINTK("pollkey resume:power on!\n");
	return 0;
}

static struct platform_driver pollkey_driver = {
	.probe = pollkey_probe,
	.remove = pollkey_remove,
	.suspend = pollkey_suspend,
	.resume = pollkey_resume,
	.driver = {
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
	},
};

static void __exit pollkey_exit(void)
{
	platform_driver_unregister(&pollkey_driver);
}

static int __init pollkey_init(void)
{
	return platform_driver_register(&pollkey_driver);
}

module_init(pollkey_init);
module_exit(pollkey_exit);

MODULE_LICENSE("Dual BSD/GPL");

驱动代码分析
我们使用了一个数组来存放五个IO口,在probe函数中我们完成了对IO口的初始化,在pollkey_read函数中是最为关键的工作,我们用一个数组来存放五个IO口的状态,读取完成后并将该数据拷贝到用户空间

应用程序代码

#include <stdio.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>


int main()
{
	int fd;
	char *read_key = "/dev/pollkey_ctl";
	unsigned char buffer[5];
	if((fd = open(read_key,O_RDWR|O_NDELAY))<0)
	{
		printf("APP open %s failed\n",read_key);
		return -1;
	}
	printf("APP open %s success\n",read_key);
	while(1)
	{
		/*从文件中读取数据到buffer数组*/
		read(fd,buffer,sizeof(buffer));
		if(!buffer[0]||!buffer[1]||!buffer[2]||!buffer[3]||!buffer[4])
		{
			if(!buffer[0])
				printf("KEY:HOME\n");
			else if(!buffer[1])
				printf("KEY:BACK\n");
			else if(!buffer[2])
				printf("KEY:SLEEP\n");
			else if(!buffer[3])
				printf("KEY:VOL+\n");
			else if(!buffer[4])
				printf("KEY:VOL-\n");
		}
	}
	close(fd);
}

应用程序代码分析
在打开设备节点文件后,调用read函数读取数据到buffer数组,然后我们检测buffer数组中是否有0(按键按下为0,即有没有按键被按下),如果有按键按下,我们则进一步判断是哪个按键被按下并打印信息

实验效果

安装模块
在这里插入图片描述
在这里插入图片描述
生成了设备节点
执行应用程序
在这里插入图片描述
所以当按键按下时打印出了对应的按键名

总结

使用查询的方式处理按键效率很低,占用CPU过高
应使用中断、异步通信、休眠等方式

发布了123 篇原创文章 · 获赞 598 · 访问量 34万+

猜你喜欢

转载自blog.csdn.net/a568713197/article/details/89912984