前面我们使用GPIO来控制IO口,点亮了LED灯,当然,IO口是可以有多种配置的,输入输出是最基本的两种,今天我们就来尝试一下GPIO的输入操作,我们使用4412开发板上的3、4号拨码开关来实现
可以看出3、4号分别为AP_SLEEP、XEINT6
经过查阅原理图、手册我们可以找到以下的对应关系
按键上就有下拉电阻,所以向内为低电平,向外为高电平
1、设置为输入状态
2、读DAT寄存器
3、既不上拉也不下拉
以下为配置、数据、上下拉寄存器
注册设备这个是很重要的一个点,但是它也是很简单的,我们需要修改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
#include
/*driver register*/
#include
/*注册杂项设备头文件*/
#include
/*注册设备节点的文件结构体*/
#include
/*Linux中申请GPIO的头文件*/
#include
/*三星平台的GPIO配置函数头文件*/
/*GPIO配置参数宏定义头文件*/
#include
#include
/*三星平台4412平台,GPIO宏定义头文件*/
#include
#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
#include
#include
#include
#include
#include
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口的如下对应关系
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
#include
#include
#include
#include
#include
#include
#include
//#include
#include
#include
#include
//#include "gps.h"
#include
/*copy_to_user头文件*/
#include
#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
#include
#include
#include
#include
#include
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过高
应使用中断、异步通信、休眠等方式