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過高
應使用中斷、異步通信、休眠等方式

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章