在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的值。
执行echoout>/sys/class/gpio/gpio87/direction表示设置该GPIO为输出,最后向GPIO写值即可。
echo > /sys/class/gpio/gpio87/value
以上的方法实践上去比较的容易,应用程序完全不须要关注底层驱动做了什么事情,只是根据步骤进行操作即可,程序的可预知性不强。并且操作简单。
假如要用在C程序中,也可以分为以下几步:
第一步:在/sys/class/gpio/生成gpio相关的文件夹
第二步:设置gpio输入输出方向
第三步:写gpio的值
具体操作代码可以参考附表1:采用sysfs文件系统的方法控制GPIO。
3.技巧二:自己编撰GPIO驱动的形式
该方法主要借助字符设备驱动程序linux 应用linux删除命令,通过ioctl函数进行控制。相比用sysfs文件系统的方法,这些方法的操作流程愈发的清晰。而且须要完成的工作量较大,既要理解驱动又要熟悉Linux应用编程。下边来介绍这些方法。
3.1哪些是ioctl
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特点进行控制。其函数原型如下:
#include <sys/ioctl.h>
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.2gpio驱动程序的编撰
gpio属于字符设备驱动,所以可以通过字符设备驱动程序的框架来建立gpio控制驱动。
先写出模板
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/spinlock.h>
#include <linux/bitops.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/seq_file.h>
#include <linux/delay.h>
#include <linux/ioctl.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/proc_fs.h>
#include <linux/gpio.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/poll.h>
/*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动态申请设备号。
第二步:注册字符设备
在这一步中,须要向内核注册设备linux 应用,而且填充fops结构体,建立read,write及ioctl函数,因为这儿只是控制gpiodeepin linux,所以只会用到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文件,将该文件传到开发板即可。
在开发板上,输入insmodgpio.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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
/****************************************************************
* 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 < ) {
perror("gpio/export");
return fd;
}
len = snprintf(buf, sizeof(buf), "%d", gpio);
write(fd, buf, len);
close(fd);
return ;
}
/****************************************************************
* 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 < ) {
perror("gpio/export");
return fd;
}
len = snprintf(buf, sizeof(buf), "%d", gpio);
write(fd, buf, len);
close(fd);
return ;
}
/****************************************************************
* 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 < ) {
perror("gpio/direction");
return fd;
}
if (out_flag)
write(fd, "out", );
else
write(fd, "in", );
close(fd);
return ;
}
/****************************************************************
* 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 < ) {
perror("gpio/set-value");
return fd;
}
if (value)
write(fd, "1", );
else
write(fd, "0", );
close(fd);
return ;
}
/****************************************************************
* 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 < ) {
perror("gpio/get-value");
return fd;
}
read(fd, &ch, );
if (ch != '0') {
*value = ;
} else {
*value = ;
}
close(fd);
return ;
}
/****************************************************************
* 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 < ) {
perror("gpio/set-edge");
return fd;
}
write(fd, edge, strlen(edge) + );
close(fd);
return ;
}
/****************************************************************
* 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 < ) {
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 = ;
int gpio_fd, timeout, rc;
char *buf[MAX_BUF];
unsigned int gpio;
int len;
if (argc < ) {
printf("Usage: gpio-int nn");
printf("Waits for a change in the GPIO pin voltage level or input on stdinn");
exit(-1);
}
gpio = atoi(argv[]);
gpio_export(gpio);
gpio_set_dir(gpio, );
gpio_set_edge(gpio, "rising");
gpio_fd = gpio_fd_open(gpio);
timeout = POLL_TIMEOUT;
while () {
memset((void*)fdset, , sizeof(fdset));
fdset[].fd = STDIN_FILENO;
fdset[].events = POLLIN;
fdset[].fd = gpio_fd;
fdset[].events = POLLPRI;
rc = poll(fdset, nfds, timeout);
if (rc < ) {
printf("npoll() failed!n");
return -1;
}
if (rc == ) {
printf(".");
}
if (fdset[].revents & POLLPRI) {
len = read(fdset[].fd, buf, MAX_BUF);
printf("npoll() GPIO %d interrupt occurredn", gpio);
}
if (fdset[].revents & POLLIN) {
(void)read(fdset[].fd, buf, );
printf("npoll() stdin read 0x%2.2Xn", (unsigned int) buf[]);
}
fflush(stdout);
}
gpio_fd_close(gpio_fd);
return ;
}
附表2:GPIO驱动程序
/**
* drviers/char/tfmv2_gpio.c
*
* GPIO driver
*
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/spinlock.h>
#include <linux/bitops.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/seq_file.h>
#include <linux/delay.h>
#include <linux/ioctl.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/proc_fs.h>
#include <linux/gpio.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/poll.h>
#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 = ;
filp->private_data = &cdev;
return ret;
}
/*RELEASE*/
static int gpio_release(struct inode *inode, struct file *filp)
{
return ;
}
/*READ*/
static ssize_t gpio_read(struct file *filp, char __user *buff,
size_t count, loff_t *offp)
{
return ;
}
/*IOCTL*/
static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
unsigned int ret = ,err = ;
if (_IOC_TYPE(cmd) != IOCTL_MAGIC)
return -EINVAL;
if (arg > )
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,);
break;
case GPIO_OUT_HIG:
gpio_direction_output(arg,);
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 = ;
cdev_init(cdevp, &gpio_fops);
cdevp->owner = THIS_MODULE;
cdevp->ops = &gpio_fops;
ret = cdev_add(cdevp, dev, );
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, );
gpio_major = MAJOR(devno);
if (gpio_major)
ret = register_chrdev_region(devno, , DEVICE_NAME);
else
ret = alloc_chrdev_region(&devno, , , DEVICE_NAME);
if (ret < ) {
printk(KERN_ALERT"failed in registering dev.n");
return ret;
}
//2.加入字符设备结构体
ret = gpio_setup(&cdev, devno);
if (ret < ) {
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", );
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, );
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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#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 = , gpio = ;
int gpio_state = ;
if (argc != ) {
printf("Usage: gpio-pin nn");
printf("gpio testn");
exit(-1);
}
gpio = atoi(argv[]);
if ((gpiofd = open("/dev/gpiodrv0", O_RDWR)) < ) {
perror("open");
return -1;
}
if(strcmp(argv[],"on")==)
{
gpio_state = GPIO_IOC_OUTPUT_HIG;
}
else if(strcmp(argv[],"off")==)
{
gpio_state = GPIO_IOC_OUTPUT_LOW;
}
else
{
gpio_state = GPIO_IOC_INPUT;
}
if ((gpio_state = ioctl(gpiofd, gpio_state, gpio)) < ) {
perror("ioctl err");
return -1;
}
printf("GPIO state:%dn", gpio_state);
close(gpiofd);
return ;
}
int main(int argc, char **argv)
{
int gpiofd = , gpio = ;
int gpio_state = ;
if (argc != ) {
printf("Usage: gpio-pin nn");
printf("gpio testn");
exit(-1);
}
gpio = atoi(argv[]);
if ((gpiofd = open("/dev/gpiodrv0", O_RDWR)) < ) {
perror("open");
return -1;
}
if(strcmp(argv[],"on")==)
{
gpio_state = GPIO_IOC_OUTPUT_HIG;
}
else if(strcmp(argv[],"off")==)
{
gpio_state = GPIO_IOC_OUTPUT_LOW;
}
else
{
gpio_state = GPIO_IOC_INPUT;
}
if ((gpio_state = ioctl(gpiofd, gpio_state, gpio)) < ) {
perror("ioctl err");
return -1;
}
printf("GPIO state:%dn", gpio_state);
close(gpiofd);
return ;
}
转发联接:
文章评论