当前位置:首页 > > 嵌入式IoT

1. 说明

在Linux中,可以对GPIO进行相关的控制,具体的做法就是利用字符设备驱动程序对相关的gpio进行控制。由于操作系统的限制,在Linux上又无法直接在应用程序的层面上对底层的硬件进行操作。本文主要通过一个点亮红外灯的实例,再次理解Linux下的应用程序与驱动程序的交互,同时加深驱动程序编写流程的理解。

2.方法一:采用通用sysfs文件系统的方式

这种方式是利用内核配置sysfs文件系统

这种方式是将gpio映射到sysfs文件系统中,也就是操作/sys/class/gpio里的文件来对GPIO进行相关的配置。应用程序可以直接操作这个文件对GPIO进行设置。

如果采用脚本的方式:

#bin/bash echo 87 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio87/direction
echo 1 > /sys/class/gpio/gpio87/value

以上的脚本中首先需要计算GPIO的编号,比如需要采用PC(23),那么C组是第三组那么可以利用公式

其中num是GPIO的编号,n是第几组gpio,m是当前的gpio的序号。经过计算PC23的GPIO编号为87。

所以当执行

echo 87 > /sys/class/gpio/export 

会在/sys/class/gpio/文件夹中生成gpio87这个目录,里面有些文件可以设置GPIO的值。

执行echo out > /sys/class/gpio/gpio87/direction表示设置该GPIO为输出,最后向GPIO写值即可。

echo 1 > /sys/class/gpio/gpio87/value 

以上的方式实践起来比较的容易,应用程序完全不需要关注底层驱动做了哪些事情,只是按照步骤进行操作即可,程序的可预知性不强。但是操作简单。

如果要用在C程序中,也可以分为以下几步:

第一步:在/sys/class/gpio/生成gpio相关的文件夹

第二步:设置gpio输入输出方向

第三步:写gpio的值

具体操作代码可以参考附录1:采用sysfs文件系统的方式控制GPIO

3. 方法二:自己编写GPIO驱动的方式

该方式主要利用字符设备驱动程序,通过ioctl函数进行控制。相比用sysfs文件系统的方式,这种方式的操作流程更加的清晰。但是需要完成的工作量较大,既要理解驱动又要熟悉Linux应用编程。下面来介绍这种方式。

3.1 什么是ioctl

ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制。其函数原型如下:

#include  int ioctl(int fd, int cmd, ...);

ioctl()执行成功时返回0,失败则返回-1并设置全局变量errorno值。

其中函数中的参数cmd交互协议可以划分为四个位段:

对于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))) 

对于实际gpio驱动的编写,我们可以做如下的交互协议

#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驱动程序的编写

gpio属于字符设备驱动,所以可以通过字符设备驱动程序的框架来完善gpio控制驱动。

先写出模板

#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");

然后完善里面的功能。需要申请字符设备驱动,并且提供write,read和ioctl函数。

安装字符设备驱动函数的通用写法

第一步:申请设备号

可以采用register_chrdev_region进行静态申请或者采用alloc_chrdev_region动态申请设备号。

第二步:注册字符设备

在这一步中,需要向内核注册设备,并且填充fops结构体,完善read,write及ioctl函数,由于这里只是控制gpio,所以只会用到ioctl函数。

第三步:向sysfs文件系统注册设备

通过调用class_create函数,可以向sysfs注册设备。

第四步:生成设备节点

通过调用device_create生成设备节点,应用程序通过控制设备节点来对gpio进行控制。

以上的具体代码可以参考附录2:GPIO驱动程序

3.3 编译及验证

程序编写完成后,编译内核驱动程序需要编写Makefile文件。具体的程序代码可以参考附录。

obj-m:=gpio.o KDIR:=/home/xxx/xxx/xxx/kernel #内核的具体目录 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

在宿主机上交叉编译后会生成.ko文件,将该文件传到开发板即可。

在开发板上,输入insmod gpio.ko看到挂载完成表示成功。

如果要测试该驱动程序是否成功,可以写一个测试程序来进行测试。

测试程序可以让其输入两个参数,第一个是传入的GPIO的编号,第二个是GPIO的电平,用字符串on/off来表示。

核心操作就是

第一步:打开设备

gpiofd = open("/dev/gpiodrv0", O_RDWR)

第二步:通过ioctl进行引脚设置

ioctl(gpiofd, gpio_state, gpio)

目前设置的引脚状态如下

第三步:关闭设备

close(gpiofd);

经过以上几步,即可编写一个完整的测试程序。

最后是进行交叉编译生成可执行文件即可。下面是TFM_V2上点亮红外灯的操作。

测试程序的代码可以参考附录3:测试程序

4. 将GPIO驱动集成到内核中

由于前面已经将问题驱动模块单独编译,此时若想集成到内核中,则需要做以下几件事:

4.1 向内核中添加文件

由于GPIO驱动属于字符设备驱动,所以应该放在kernel/drivers/char目录中。

4.2 修改Kconfig

如果要通过配置manuconfig配置是否选择gpio,则需要配置Kconfig。这样可以通过宏来控制是否加载驱动模块。

这里选择在头部添加这一条。此时查看图形配置界面

4.3 让驱动编译到内核中

通过Kconfig只是选择了编译的宏,如果让驱动正真编译到内核中,还需要修改Makefile。也就是修改kernel/drivers/char/Makefile

这个宏表示当配置了TFM_V2_GPIO宏时,tfmv2_gpio.c将会编译成驱动,内核启动时,该驱动自动加载。

下图是Linux启动后自动加载的tfm_v2的gpio驱动。

同时启动后再dev目录中可以看到生成的设备

5. 总结

由于应用层不能直接操作gpio,但是应用程序可以调用驱动程序的接口来操作gpio。这也是为什么控制gpio这么麻烦的原因。

文章中叙述了两种操作gpio的办法,第一种是利用sysfs文件系统的方式,这种方式操作起来简单,方便应用程序的调用,第二种是写一个驱动函数的方式,通过ioctl进行控制,这种办法虽然操作起来比较麻烦,但是app调用起来也比较容易。并且可以知道调用过程,思路清晰。

通过这次的总结,对Linux的驱动的内核层与应用层要区分清楚,同时也加深对驱动程序编写流程的理解。

附录1:采用sysfs文件系统的方式控制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驱动程序

/**
 * 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; //申请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.申请主次设备号 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.加入字符设备结构体 ret = gpio_setup(&cdev, devno); if (ret < 0) {
 printk(KERN_ALERT"failed in setup dev.\n"); return ret;
 } //3.在class目录中创建文件 gpio_class = class_create(THIS_MODULE, DEVICE_NAME); if (IS_ERR(gpio_class)) {
 printk(KERN_ALERT"failed in creating class.\n"); return -1;
 } //4.生成设备节点 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");

附录三:测试程序

/**
 *  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;
}


本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
关闭