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