Linux通用GPIO驅(qū)動(dòng)寫法與應(yīng)用
掃描二維碼
隨時(shí)隨地手機(jī)看文章
1. 說(shuō)明
在Linux中,可以對(duì)GPIO進(jìn)行相關(guān)的控制,具體的做法就是利用字符設(shè)備驅(qū)動(dòng)程序?qū)ο嚓P(guān)的gpio進(jìn)行控制。由于操作系統(tǒng)的限制,在Linux上又無(wú)法直接在應(yīng)用程序的層面上對(duì)底層的硬件進(jìn)行操作。本文主要通過(guò)一個(gè)點(diǎn)亮紅外燈的實(shí)例,再次理解Linux下的應(yīng)用程序與驅(qū)動(dòng)程序的交互,同時(shí)加深驅(qū)動(dòng)程序編寫流程的理解。
2.方法一:采用通用sysfs文件系統(tǒng)的方式
這種方式是利用內(nèi)核配置sysfs文件系統(tǒng)

這種方式是將gpio映射到sysfs文件系統(tǒng)中,也就是操作/sys/class/gpio里的文件來(lái)對(duì)GPIO進(jìn)行相關(guān)的配置。應(yīng)用程序可以直接操作這個(gè)文件對(duì)GPIO進(jìn)行設(shè)置。
如果采用腳本的方式:
#bin/bash echo 87 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio87/direction
echo 1 > /sys/class/gpio/gpio87/value
以上的腳本中首先需要計(jì)算GPIO的編號(hào),比如需要采用PC(23),那么C組是第三組那么可以利用公式

其中num是GPIO的編號(hào),n是第幾組gpio,m是當(dāng)前的gpio的序號(hào)。經(jīng)過(guò)計(jì)算PC23的GPIO編號(hào)為87。
所以當(dāng)執(zhí)行
echo 87 > /sys/class/gpio/export
會(huì)在/sys/class/gpio/文件夾中生成gpio87這個(gè)目錄,里面有些文件可以設(shè)置GPIO的值。
執(zhí)行echo out > /sys/class/gpio/gpio87/direction表示設(shè)置該GPIO為輸出,最后向GPIO寫值即可。
echo 1 > /sys/class/gpio/gpio87/value
以上的方式實(shí)踐起來(lái)比較的容易,應(yīng)用程序完全不需要關(guān)注底層驅(qū)動(dòng)做了哪些事情,只是按照步驟進(jìn)行操作即可,程序的可預(yù)知性不強(qiáng)。但是操作簡(jiǎn)單。
如果要用在C程序中,也可以分為以下幾步:
第一步:在/sys/class/gpio/生成gpio相關(guān)的文件夾
第二步:設(shè)置gpio輸入輸出方向
第三步:寫gpio的值
具體操作代碼可以參考附錄1:采用sysfs文件系統(tǒng)的方式控制GPIO。
3. 方法二:自己編寫GPIO驅(qū)動(dòng)的方式
該方式主要利用字符設(shè)備驅(qū)動(dòng)程序,通過(guò)ioctl函數(shù)進(jìn)行控制。相比用sysfs文件系統(tǒng)的方式,這種方式的操作流程更加的清晰。但是需要完成的工作量較大,既要理解驅(qū)動(dòng)又要熟悉Linux應(yīng)用編程。下面來(lái)介紹這種方式。
3.1 什么是ioctl
ioctl是設(shè)備驅(qū)動(dòng)程序中對(duì)設(shè)備的I/O通道進(jìn)行管理的函數(shù)。所謂對(duì)I/O通道進(jìn)行管理,就是對(duì)設(shè)備的一些特性進(jìn)行控制。其函數(shù)原型如下:
#include int ioctl(int fd, int cmd, ...);

ioctl()執(zhí)行成功時(shí)返回0,失敗則返回-1并設(shè)置全局變量errorno值。
其中函數(shù)中的參數(shù)cmd交互協(xié)議可以劃分為四個(gè)位段:

對(duì)于cmd的宏的定義如下:
// include/uapi/asm-generic/ioctl.h /* used to create numbers */ #define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0) #define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size))) #define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size))) #define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
對(duì)于實(shí)際gpio驅(qū)動(dòng)的編寫,我們可以做如下的交互協(xié)議
#define IOCTL_MAGIC 'g' #define GPIO_OUT_LOW _IOW(IOCTL_MAGIC, 0x00, unsigned long) #define GPIO_OUT_HIG _IOW(IOCTL_MAGIC, 0x01, unsigned long) #define GPIO_INPUT _IOR(IOCTL_MAGIC, 0x02, unsigned long)
3.2 gpio驅(qū)動(dòng)程序的編寫
gpio屬于字符設(shè)備驅(qū)動(dòng),所以可以通過(guò)字符設(shè)備驅(qū)動(dòng)程序的框架來(lái)完善gpio控制驅(qū)動(dòng)。
先寫出模板
#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*DEV INIT*/ static int __init gpio_init(void) {
} /*DEV EXIT*/ static void __exit gpio_exit(void) {
}
module_init(gpio_init);
module_exit(gpio_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZFJ");
MODULE_DESCRIPTION("GPIO driver for test");
然后完善里面的功能。需要申請(qǐng)字符設(shè)備驅(qū)動(dòng),并且提供write,read和ioctl函數(shù)。
安裝字符設(shè)備驅(qū)動(dòng)函數(shù)的通用寫法
第一步:申請(qǐng)?jiān)O(shè)備號(hào)
可以采用register_chrdev_region進(jìn)行靜態(tài)申請(qǐng)或者采用alloc_chrdev_region動(dòng)態(tài)申請(qǐng)?jiān)O(shè)備號(hào)。
第二步:注冊(cè)字符設(shè)備
在這一步中,需要向內(nèi)核注冊(cè)設(shè)備,并且填充fops結(jié)構(gòu)體,完善read,write及ioctl函數(shù),由于這里只是控制gpio,所以只會(huì)用到ioctl函數(shù)。
第三步:向sysfs文件系統(tǒng)注冊(cè)設(shè)備
通過(guò)調(diào)用class_create函數(shù),可以向sysfs注冊(cè)設(shè)備。
第四步:生成設(shè)備節(jié)點(diǎn)
通過(guò)調(diào)用device_create生成設(shè)備節(jié)點(diǎn),應(yīng)用程序通過(guò)控制設(shè)備節(jié)點(diǎn)來(lái)對(duì)gpio進(jìn)行控制。
以上的具體代碼可以參考附錄2:GPIO驅(qū)動(dòng)程序。
3.3 編譯及驗(yàn)證
程序編寫完成后,編譯內(nèi)核驅(qū)動(dòng)程序需要編寫Makefile文件。具體的程序代碼可以參考附錄。
obj-m:=gpio.o KDIR:=/home/xxx/xxx/xxx/kernel #內(nèi)核的具體目錄 PWD:=$(shell pwd) all: make ARCH=mips CROSS_COMPILE=mips-linux-gnu- -C $(KDIR) M=$(PWD) modules clean: rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions *.order *symvers *Module.markers
在宿主機(jī)上交叉編譯后會(huì)生成.ko文件,將該文件傳到開發(fā)板即可。

在開發(fā)板上,輸入insmod gpio.ko看到掛載完成表示成功。

如果要測(cè)試該驅(qū)動(dòng)程序是否成功,可以寫一個(gè)測(cè)試程序來(lái)進(jìn)行測(cè)試。
測(cè)試程序可以讓其輸入兩個(gè)參數(shù),第一個(gè)是傳入的GPIO的編號(hào),第二個(gè)是GPIO的電平,用字符串on/off來(lái)表示。
核心操作就是
第一步:打開設(shè)備
gpiofd = open("/dev/gpiodrv0", O_RDWR)
第二步:通過(guò)ioctl進(jìn)行引腳設(shè)置
ioctl(gpiofd, gpio_state, gpio)
目前設(shè)置的引腳狀態(tài)如下

第三步:關(guān)閉設(shè)備
close(gpiofd);
經(jīng)過(guò)以上幾步,即可編寫一個(gè)完整的測(cè)試程序。
最后是進(jìn)行交叉編譯生成可執(zhí)行文件即可。下面是TFM_V2上點(diǎn)亮紅外燈的操作。

測(cè)試程序的代碼可以參考附錄3:測(cè)試程序。
4. 將GPIO驅(qū)動(dòng)集成到內(nèi)核中
由于前面已經(jīng)將問(wèn)題驅(qū)動(dòng)模塊單獨(dú)編譯,此時(shí)若想集成到內(nèi)核中,則需要做以下幾件事:
4.1 向內(nèi)核中添加文件
由于GPIO驅(qū)動(dòng)屬于字符設(shè)備驅(qū)動(dòng),所以應(yīng)該放在kernel/drivers/char目錄中。

4.2 修改Kconfig
如果要通過(guò)配置manuconfig配置是否選擇gpio,則需要配置Kconfig。這樣可以通過(guò)宏來(lái)控制是否加載驅(qū)動(dòng)模塊。

這里選擇在頭部添加這一條。此時(shí)查看圖形配置界面

4.3 讓驅(qū)動(dòng)編譯到內(nèi)核中
通過(guò)Kconfig只是選擇了編譯的宏,如果讓驅(qū)動(dòng)正真編譯到內(nèi)核中,還需要修改Makefile。也就是修改kernel/drivers/char/Makefile

這個(gè)宏表示當(dāng)配置了TFM_V2_GPIO宏時(shí),tfmv2_gpio.c將會(huì)編譯成驅(qū)動(dòng),內(nèi)核啟動(dòng)時(shí),該驅(qū)動(dòng)自動(dòng)加載。
下圖是Linux啟動(dòng)后自動(dòng)加載的tfm_v2的gpio驅(qū)動(dòng)。

同時(shí)啟動(dòng)后再dev目錄中可以看到生成的設(shè)備

5. 總結(jié)
由于應(yīng)用層不能直接操作gpio,但是應(yīng)用程序可以調(diào)用驅(qū)動(dòng)程序的接口來(lái)操作gpio。這也是為什么控制gpio這么麻煩的原因。
文章中敘述了兩種操作gpio的辦法,第一種是利用sysfs文件系統(tǒng)的方式,這種方式操作起來(lái)簡(jiǎn)單,方便應(yīng)用程序的調(diào)用,第二種是寫一個(gè)驅(qū)動(dòng)函數(shù)的方式,通過(guò)ioctl進(jìn)行控制,這種辦法雖然操作起來(lái)比較麻煩,但是app調(diào)用起來(lái)也比較容易。并且可以知道調(diào)用過(guò)程,思路清晰。
通過(guò)這次的總結(jié),對(duì)Linux的驅(qū)動(dòng)的內(nèi)核層與應(yīng)用層要區(qū)分清楚,同時(shí)也加深對(duì)驅(qū)動(dòng)程序編寫流程的理解。
附錄1:采用sysfs文件系統(tǒng)的方式控制GPIO
/* Copyright (c) 2011, RidgeRun
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the RidgeRun.
* 4. Neither the name of the RidgeRun nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY RIDGERUN ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL RIDGERUN BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ #include #include #include #include #include #include #include /****************************************************************
* Constants
****************************************************************/ #define SYSFS_GPIO_DIR "/sys/class/gpio" #define POLL_TIMEOUT (3 * 1000) /* 3 seconds */ #define MAX_BUF 64 /****************************************************************
* gpio_export
****************************************************************/ int gpio_export(unsigned int gpio) { int fd, len; char buf[MAX_BUF];
fd = open(SYSFS_GPIO_DIR "/export", O_WRONLY); if (fd < 0) {
perror("gpio/export"); return fd;
}
len = snprintf(buf, sizeof(buf), "%d", gpio);
write(fd, buf, len);
close(fd); return 0;
} /****************************************************************
* gpio_unexport
****************************************************************/ int gpio_unexport(unsigned int gpio) { int fd, len; char buf[MAX_BUF];
fd = open(SYSFS_GPIO_DIR "/unexport", O_WRONLY); if (fd < 0) {
perror("gpio/export"); return fd;
}
len = snprintf(buf, sizeof(buf), "%d", gpio);
write(fd, buf, len);
close(fd); return 0;
} /****************************************************************
* gpio_set_dir
****************************************************************/ int gpio_set_dir(unsigned int gpio, unsigned int out_flag) { int fd, len; char buf[MAX_BUF];
len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/direction", gpio);
fd = open(buf, O_WRONLY); if (fd < 0) {
perror("gpio/direction"); return fd;
} if (out_flag)
write(fd, "out", 4); else write(fd, "in", 3);
close(fd); return 0;
} /****************************************************************
* gpio_set_value
****************************************************************/ int gpio_set_value(unsigned int gpio, unsigned int value) { int fd, len; char buf[MAX_BUF];
len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/value", gpio);
fd = open(buf, O_WRONLY); if (fd < 0) {
perror("gpio/set-value"); return fd;
} if (value)
write(fd, "1", 2); else write(fd, "0", 2);
close(fd); return 0;
} /****************************************************************
* gpio_get_value
****************************************************************/ int gpio_get_value(unsigned int gpio, unsigned int *value) { int fd, len; char buf[MAX_BUF]; char ch;
len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/value", gpio);
fd = open(buf, O_RDONLY); if (fd < 0) {
perror("gpio/get-value"); return fd;
}
read(fd, &ch, 1); if (ch != '0') {
*value = 1;
} else {
*value = 0;
}
close(fd); return 0;
} /****************************************************************
* gpio_set_edge
****************************************************************/ int gpio_set_edge(unsigned int gpio, char *edge) { int fd, len; char buf[MAX_BUF];
len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/edge", gpio);
fd = open(buf, O_WRONLY); if (fd < 0) {
perror("gpio/set-edge"); return fd;
}
write(fd, edge, strlen(edge) + 1);
close(fd); return 0;
} /****************************************************************
* gpio_fd_open
****************************************************************/ int gpio_fd_open(unsigned int gpio) { int fd, len; char buf[MAX_BUF];
len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/value", gpio);
fd = open(buf, O_RDONLY | O_NONBLOCK ); if (fd < 0) {
perror("gpio/fd_open");
} return fd;
} /****************************************************************
* gpio_fd_close
****************************************************************/ int gpio_fd_close(int fd) { return close(fd);
} /****************************************************************
* Main
****************************************************************/ int main(int argc, char **argv, char **envp) { struct pollfd fdset[2]; int nfds = 2; int gpio_fd, timeout, rc; char *buf[MAX_BUF]; unsigned int gpio; int len; if (argc < 2) { printf("Usage: gpio-int\n\n"); printf("Waits for a change in the GPIO pin voltage level or input on stdin\n"); exit(-1);
}
gpio = atoi(argv[1]);
gpio_export(gpio);
gpio_set_dir(gpio, 0);
gpio_set_edge(gpio, "rising");
gpio_fd = gpio_fd_open(gpio);
timeout = POLL_TIMEOUT; while (1) { memset((void*)fdset, 0, sizeof(fdset));
fdset[0].fd = STDIN_FILENO;
fdset[0].events = POLLIN;
fdset[1].fd = gpio_fd;
fdset[1].events = POLLPRI;
rc = poll(fdset, nfds, timeout); if (rc < 0) { printf("\npoll() failed!\n"); return -1;
} if (rc == 0) { printf(".");
} if (fdset[1].revents & POLLPRI) {
len = read(fdset[1].fd, buf, MAX_BUF); printf("\npoll() GPIO %d interrupt occurred\n", gpio);
} if (fdset[0].revents & POLLIN) {
(void)read(fdset[0].fd, buf, 1); printf("\npoll() stdin read 0x%2.2X\n", (unsigned int) buf[0]);
}
fflush(stdout);
}
gpio_fd_close(gpio_fd); return 0;
}
附錄2:GPIO驅(qū)動(dòng)程序
/**
* drviers/char/tfmv2_gpio.c
*
* GPIO driver
*
*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEVICE_NAME "gpiodrv" #define GPIO_MAJOR 0 #define IOCTL_MAGIC 'g' #define GPIO_OUT_LOW _IOW(IOCTL_MAGIC, 0x00, unsigned long) #define GPIO_OUT_HIG _IOW(IOCTL_MAGIC, 0x01, unsigned long) #define GPIO_INPUT _IOR(IOCTL_MAGIC, 0x02, unsigned long) static struct cdev cdev; static struct class *gpio_class; static dev_t devno; /*OPEN*/ static int gpio_open(struct inode *inode, struct file *filp) { int ret = 0;
filp->private_data = &cdev; return ret;
} /*RELEASE*/ static int gpio_release(struct inode *inode, struct file *filp) { return 0;
} /*READ*/ static ssize_t gpio_read(struct file *filp, char __user *buff, size_t count, loff_t *offp) { return 0;
} /*IOCTL*/ static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { unsigned int ret = 0,err = 0; if (_IOC_TYPE(cmd) != IOCTL_MAGIC) return -EINVAL; if (arg > 128) return -EINVAL; //申請(qǐng)gpio引腳 err = gpio_request(arg,NULL); if(err)
{ //printk("gpio_ioctl request err!\n"); } switch(cmd) { case GPIO_OUT_LOW:
gpio_direction_output(arg,0); break; case GPIO_OUT_HIG:
gpio_direction_output(arg,1); break; case GPIO_INPUT:
gpio_direction_input(arg);
ret = gpio_get_value(arg); break; default:
ret = -EINVAL; break;
} return ret;
} static struct file_operations gpio_fops = { .owner = THIS_MODULE,
.open = gpio_open,
.release = gpio_release,
.read = gpio_read,
.unlocked_ioctl = gpio_ioctl,
}; /*DEV SETUP*/ static int gpio_setup(struct cdev *cdevp, dev_t dev) { int ret = 0;
cdev_init(cdevp, &gpio_fops);
cdevp->owner = THIS_MODULE;
cdevp->ops = &gpio_fops;
ret = cdev_add(cdevp, dev, 1); if (ret)
printk(KERN_ALERT"add gpio setup failed!\n"); return ret;
} /*DEV INIT*/ static int __init gpio_init(void) { struct device *dev; int ret; unsigned int gpio_major;
printk("init gpio driver module...\n"); //1.申請(qǐng)主次設(shè)備號(hào) devno = MKDEV(GPIO_MAJOR, 0);
gpio_major = MAJOR(devno); if (gpio_major)
ret = register_chrdev_region(devno, 1, DEVICE_NAME); else ret = alloc_chrdev_region(&devno, 0, 1, DEVICE_NAME); if (ret < 0) {
printk(KERN_ALERT"failed in registering dev.\n"); return ret;
} //2.加入字符設(shè)備結(jié)構(gòu)體 ret = gpio_setup(&cdev, devno); if (ret < 0) {
printk(KERN_ALERT"failed in setup dev.\n"); return ret;
} //3.在class目錄中創(chuàng)建文件 gpio_class = class_create(THIS_MODULE, DEVICE_NAME); if (IS_ERR(gpio_class)) {
printk(KERN_ALERT"failed in creating class.\n"); return -1;
} //4.生成設(shè)備節(jié)點(diǎn) dev = device_create(gpio_class, NULL, devno, NULL, DEVICE_NAME "%d", 0); if (IS_ERR(dev)) {
printk(KERN_ALERT"failed in creating class.\n"); return -1;
} return ret;
} /*DEV EXIT*/ static void __exit gpio_exit(void) {
cdev_del(&cdev);
unregister_chrdev_region(devno, 1);
device_destroy(gpio_class, devno);
class_destroy(gpio_class);
}
module_init(gpio_init);
module_exit(gpio_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZFJ");
MODULE_DESCRIPTION("GPIO driver for test");
附錄三:測(cè)試程序
/**
* test.c
*
* Copyright (C) 2014 W.J, All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/ #include #include #include #include #include #include #include #include #define GPIO(X) X #define GPIO_IOC_MAGIC 'g' /* general APIs - GPIO_IOC_MAGIC */ enum {
IOC_OUTPUT_CLR,
IOC_OUTPUT_SET,
IOC_SET_INPUT,
}; #define GPIO_IOC_OUTPUT_LOW _IOW(GPIO_IOC_MAGIC, IOC_OUTPUT_CLR, unsigned int) #define GPIO_IOC_OUTPUT_HIG _IOW(GPIO_IOC_MAGIC, IOC_OUTPUT_SET, unsigned int) #define GPIO_IOC_INPUT _IOR(GPIO_IOC_MAGIC, IOC_SET_INPUT, unsigned int) int main(int argc, char **argv) { int gpiofd = 0, gpio = 0; int gpio_state = 0; if (argc != 3) { printf("Usage: gpio-pin \n\n"); printf("gpio test\n"); exit(-1);
}
gpio = atoi(argv[1]); if ((gpiofd = open("/dev/gpiodrv0", O_RDWR)) < 0) {
perror("open"); return -1;
} if(strcmp(argv[2],"on")==0)
{
gpio_state = GPIO_IOC_OUTPUT_HIG;
} else if(strcmp(argv[2],"off")==0)
{
gpio_state = GPIO_IOC_OUTPUT_LOW;
} else {
gpio_state = GPIO_IOC_INPUT;
} if ((gpio_state = ioctl(gpiofd, gpio_state, gpio)) < 0) {
perror("ioctl err"); return -1;
} printf("GPIO state:%d\n", gpio_state);
close(gpiofd); return 0;
}