GPIO的讀(itop-4412)

 

 

一:撥碼開關的原理圖

從圖中可以看到當撥碼開關在左邊(即1234所在方向),處於高電平(1.8v),當撥碼開關在右邊(5678所在方向),處於低電平。

本次要用到的是AP_SLEEP和XEINT6這兩個控制網絡的GPIO,首先看AP_SLEEP控制網絡對應的管腳,爲AJ26,用的GPC0_3  I/O引腳,同理XEINT6對應GPX0_6  I/O引腳。

 二:設置寄存器

在這裏先介紹一下普通GPIO寄存器:

普通的GPIO是分組管理的,每一組都有6個寄存器對GPIO進行控制,每組實現的功能也不同。前四個是正常模式下,而後兩個是斷電狀態(並非cpu斷電,而是處於某種低功耗狀態),我們只需要討論正常情況下:

    1. GPC0CON              // 對功能(輸入輸出等)的配置(每4位表示一個gpio腳)
    2. GPC0DAT               // 對gpio的高低電平進行配置(input時是pin管腳的狀態,output時控制pin管腳的輸出)
    3. GPC0PUD              // 對gpio的上下拉進行配置
    4. GPC0DRV              // 對gpio的上拉進行加強(控制電流驅動能力,控制倍數輸出)
    5. GPC0CONPDN           // 對功耗進行配置(控制低功耗輸入輸出狀態)
    6. GPC0PUDPDN           // 對功耗進行配置(控制上拉下拉等)

接下來我們開始對GPIO寄存器進行逐個分析,打開datasheet,可以找到GPC0_3對應的控制寄存器。

1.GPC0CON

我們需要將其設置爲輸入模式(0x0)

2.GPC0DAT

這個寄存器主要是用來設置GPIO的高低電平,在本次實驗中用不到。

3.GPC0PUD

這個寄存器是對GPIO進行上下拉設置,我們需要將其設置爲禁止上下拉(0x0)。

4.GPC0DRV

這個寄存器是對gpio的上下拉進行加強,同樣用不到,默認就好。

三:需要用到的函數

1.gpio_request

gpio_request函數主要是告訴內核這地址被佔用了。當其它地方調用同一地址的gpio_request就會報告錯誤,該地址已被申請。這種用法的保護作用前提是大家都遵守先申請再訪問,有一個地方沒遵守這個規則,這功能就失效了。好比進程互斥,必需大家在訪問臨界資源的時候都得先獲取鎖一樣,其中一個沒遵守約定,代碼就廢了。

gpio_request(unsigned gpio, const char *label),函數具體如何使用百度,在此不羅列了。

2.gpio_get_value

讀寄存器的值 gpio_get_value。

3.s3c_gpio_cfgpin

設置GPIO爲輸入模式s3c_gpio_cfgpin 。

4.s3c_gpio_setpull

設置上拉下拉。

5.gpio_free

釋放GPIO口,和gpio_request對應。

四:實際操作

接下來進行實際操作,需要做的工作主要有:設備註冊、驅動註冊、生成設備節點、編寫簡單應用調用程序

(1)設備註冊

我們通過仿照led的註冊方式來註冊我們的設備,打開平臺文件“vim arch/arm/mach-exynos/mach-itop4412.c”,然後搜索“leds”,找到s3c_device_leds_ctl結構體,仿照者寫一個新的設備

#ifdef CONFIG_LEDS_CTL  //條件編譯,下文會配置
struct platform_device s3c_device_leds_ctl = {
        .name   = "leds",   //設備名
        .id             = -1,  //只有一個設備時爲-1
};
#endif

我們的新設備爲:

#ifdef CONFIG_BMKG_CTL
struct platform_device s3c_device_bmkg_ctl = {
        .name   = "bmkg",
        .id             = -1,
};      
#endif  

接下來還要往這個平臺文件添加內容,繼續搜索“leds”,找到如下語句:

#ifdef CONFIG_LEDS_CTL
        &s3c_device_leds_ctl,
#endif

仿照着增加我們的設備信息:

#ifdef CONFIG_BMKG_CTL
        &s3c_device_bmkg_ctl,
#endif

如圖所示:

修改完畢,保存文件。接下來我們要添加“BMKG_CTL”宏定義,只有定義了這個宏,在生成內核的時候纔會將其編譯到內核。使用命令“vim drivers/char/Kconfig ”打開Kconfig文件。打開文件後搜索“LEDS”,找到config LEDS_CTL

config LEDS_CTL
        bool "Enable LEDS config"
        default y
        help
          Enable LEDS config

仿照着新增加我們的宏定義

config BMKG_CTL
        bool "Enable BMKG config"
        default y
        help 
          Enable BMKG config

修改後如圖所示:

 保存文件。至此,設備已經註冊完畢,使用“make menuconfig”可以看到設備註冊成功,然後編譯內核。

 

(2)驅動註冊和生成設備節點

新建一個“bmkg.c”文件,裏面加入如下代碼:

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

/*驅動註冊的頭文件,包含驅動的結構體和註冊和卸載的函數*/
#include <linux/platform_device.h>
/*註冊雜項設備頭文件*/
#include <linux/miscdevice.h>
/*註冊設備節點的文件結構體*/
#include <linux/fs.h>

/*Linux中申請GPIO的頭文件*/
#include <linux/gpio.h>
/*三星平臺的GPIO配置函數頭文件*/
/*三星平臺EXYNOS系列平臺,GPIO配置參數宏定義頭文件*/
#include <plat/gpio-cfg.h>
#include <mach/gpio.h>
/*三星平臺4412平臺,GPIO宏定義頭文件*/
#include <mach/gpio-exynos4.h>

#define DRIVER_NAME "bmkg"   //這裏的name應和剛註冊的設備名字保持一致,否則匹配不了
#define DEVICE_NAME "bmkgzx"   //這是註冊雜項設備的名字,可以和剛纔的設備名字不一樣


MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("TOPEET");

//應用裏面通過ioctl返回值來獲取管腳電平
static long read_gpio_ioctl( struct file *files, unsigned int cmd, unsigned long arg){
	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");
	}
	if(arg > 1){
		printk(KERN_EMERG "arg is only 1\n");
	}
	
	//如果cmd是0,則返回EXYNOS4_GPC0(3),撥碼開關3對應管腳的輸入電平
	//如果cmd是1,則返回EXYNOS4_GPX0(6),撥碼開關4對應管腳的輸入電平
	if(cmd == 0){
		return gpio_get_value(EXYNOS4_GPC0(3));
	}
	if(cmd == 1){
		return gpio_get_value(EXYNOS4_GPX0(6));
	}
	return 0;
}

static int read_gpio_release(struct inode *inode, struct file *file){
	printk(KERN_EMERG "hello release\n");
	return 0;
}

static int read_gpio_open(struct inode *inode, struct file *file){
	printk(KERN_EMERG "hello open\n");
	return 0;
}

static struct file_operations read_gpio_ops = {
	.owner = THIS_MODULE,
	.open = read_gpio_open,
	.release = read_gpio_release,
	.unlocked_ioctl = read_gpio_ioctl,
};

static  struct miscdevice read_gpio_dev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEVICE_NAME,
	.fops = &read_gpio_ops,
};


static int read_gpio_probe(struct platform_device *pdv){
	int ret;
	
	printk(KERN_EMERG "\tinitialized\n");
	
	ret = gpio_request(EXYNOS4_GPC0(3),"SWITCH3");
	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);	//禁止上下拉	
	}
	
	ret = gpio_request(EXYNOS4_GPX0(6),"SWITCH4");
	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(&read_gpio_dev);
	
	return 0;
}

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

static void read_gpio_shutdown(struct platform_device *pdv){
	
	;
}

static int read_gpio_suspend(struct platform_device *pdv,pm_message_t pmt){
	
	return 0;
}

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

struct platform_driver read_gpio_driver = {
	.probe = read_gpio_probe,  //當device的name和driver的name相同時,會執行probe函數
	.remove = read_gpio_remove,
	.shutdown = read_gpio_shutdown,
	.suspend = read_gpio_suspend,
	.resume = read_gpio_resume,
	.driver = {
		.name = DRIVER_NAME, //這裏的name應和剛註冊的設備名字保持一致,否則匹配不了
		.owner = THIS_MODULE,
	}
};


static int read_gpio_init(void)
{
	int DriverState;
	gpio_free(EXYNOS4_GPC0(3));   //EXYNOS4_GPC0(3)被佔用,應先釋放此IO
	gpio_free(EXYNOS4_GPX0(6));   //EXYNOS4_GPX0(6)被佔用,應先釋放此IO
	
	printk(KERN_EMERG "HELLO WORLD enter!\n");
	DriverState = platform_driver_register(&read_gpio_driver);
	
	printk(KERN_EMERG "\tDriverState is %d\n",DriverState);
	return 0;
}


static void read_gpio_exit(void)
{
	printk(KERN_EMERG "HELLO WORLD exit!\n");
	
	platform_driver_unregister(&read_gpio_driver);	
}

module_init(read_gpio_init);
module_exit(read_gpio_exit);

這是最基本的驅動框架,module_init(read_gpio_init)是程序的入口,進入read_gpio_init函數,釋放兩個端口io(在視頻中是在平臺文件中進行的,本文放在了驅動文件),然後進行驅動註冊:platform_driver_register(&read_gpio_driver); 

struct platform_driver read_gpio_driver是定義了一個platform_driver 類型的read_gpio_driver,platform_driver 是一個重要的結構體,當設備的name和驅動的name相同時,會執行probe函數。本文中的設備名字爲“bmkg”,設備名的命名就在platform_driver 裏的driver的name,當設備和驅動匹配時,運行read_gpio_probe函數。在read_gpio_probe函數中通過gpio_request函數申請EXYNOS4_GPC0(3)和EXYNOS4_GPX0(6) I/O口,然後s3c_gpio_cfgpin(EXYNOS4_GPC0(3),S3C_GPIO_INPUT)的作用是把EXYNOS4_GPC0(3)設置爲輸入模式,緊接着通過s3c_gpio_setpull(EXYNOS4_GPC0(3),S3C_GPIO_PULL_NONE)禁止上下拉。

misc_register函數是註冊雜項設備,雜項設備的主要目的是創建一個設備節點,讓上層應用操作使用。和struct platform_driver read_gpio_driver定義了platform_driver 類型的結構體相似,雜項設備也需要來使用一個結構體,struct miscdevice read_gpio_dev就是雜項類型的結構體,結構體 miscdevice 內的.minor = MISC_DYNAMIC_MINOR,MISC_DYNAMIC_MINOR指的是自動尋找未使用的次設備號,並不是代表的10,手冊上錯了。.name指的就是生成的設備節點的名字,指的是“/dev”裏的設備節點,和設備名,驅動名的名稱並沒有關係。.fops = &read_gpio_ops,裏面的函數和 Linux 系統給應用程序提供系統接口一一對應,就是提供文件操作的結構體,特別重要。 struct file_operations read_gpio_ops就是註冊了這個結構體,和前面兩個結構體類似,都是通過定義一個結構體來進行操作的,結構體內.unlocked_ioctl = read_gpio_ioctl,read_gpio_ioctl函數的作用就是讓應用裏面通過ioctl返回值來獲取管腳電平的。

(3)編寫應用程序

新建一個app_read_gpio.c文件,裏面輸入如下:

#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=2;
	char *read_gpio = "/dev/bmkgzx";  //這就是上一節生成的設備節點
	char *cmd0 = "0";
	char *cmd1 = "1";
	printf("argv[0] is %s;argv[1] is %s;",argv[0],argv[1]); //argv[0]指參數個數,argv[1]輸入參數
	
	if(strcmp(argv[1],cmd0)==0){
		cmd = 0;
	}
	if(strcmp(argv[1],cmd1)==0){
		cmd = 1;
	}
	
/*O_RDWR只讀打開,O_NDELAY非阻塞方式*/	
	if((fd = open(read_gpio,O_RDWR|O_NDELAY))<0){  //打開設備節點
		printf("APP open %s failed",read_gpio);
	}
	else{
		printf("APP open %s success!\n",read_gpio);
		printf("%d io value is %d\n",cmd,ioctl(fd,cmd,0));  //通過ioctl傳給剛寫的驅動程序
	}
	close(fd);
}

首先分析int main(int argc,char **argv),argc指的是參數的個數,argv爲一個數組,argv[0]指的是程序的名字,argv[1]以及以後指的都是參數,比如運行“./mnt/udisk/app_read_gpio 0”,argc就爲1、argv[0]爲“./mnt/udisk/app_read_gpio”、argv[1]爲0。char *read_gpio = "/dev/bmkgzx";  這就是上一節生成的設備節點,一定要分清設備節點、設備、驅動的關係。fd = open(read_gpio,O_RDWR|O_NDELAY)指的就是以只讀非阻塞打開設備節點bmkgzx,ioctl(fd,cmd,0)就將參數傳到驅動裏,對應驅動的read_gpio_ioctl,然後就進入到read_gpio_ioctl內執行,並返回電平值。

(4)運行測試

將設備註冊那一節編譯好的內核燒錄到開發板,將設備文件和應用程序分別編譯。然後加載驅動“insmod /mnt/udisk/read_gpio.ko”

 運行測試程序“./mnt/udisk/app_read_gpio 1”

*注意:運行結果中的“argc[0]”應爲argv[0]

五:總結

這是最簡單的驅動框架,結構體在驅動中顯得尤爲重要,多次使用了結構體,寫一個驅動的流程爲“硬件原理圖--設備註冊--編寫驅動--編寫設備節點--編寫測試程序”,本文用的是比較老的設備註冊方法,現在都是用的設備樹方式,這也是一個學習的過程,慢慢積累。我也是剛開始學習驅動,文中可能會有一些表達不當或者錯誤的地方,請諒解,文中的部分內容來自迅爲的驅動手冊,還有部分來自網絡。

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