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