在单片机上异常简单的LED 闪灯程序在Linux OS 下居然会异常麻烦,网络上介绍linux 控制GPIO的文章,各种说法都有。而且大都是shell 命令控制,而不是C语言如何控制。 在树莓PI 中,有一个wiringpi 库,而且资料非常清楚。但是其它厂商的东西就凌乱了。我同时使用瑞芯微的RK3399 PC 板和一块全志低成本的H6 板。方式各不相同。终于搞得明白了点,将内容分享给大家。
Linux 下的设备驱动模型主要包含:类(class)、总线(bus)、设备(device)、驱动(driver)。SysFs文件系统用来表示设备的结构.将设备的层次结构形象的反应到用户空间中。应用程序通过访问SysFs中的文件来控制硬件设备。例如GPIO,SPI和I2C 等等。
GPIO 端口操作
gpio 的文件存放在/sys/class/gpio 中。不过不同的SOC 有些不同,在RK3399 的ubuntu OS 中,gpio 下的目录为:
gpiochip0
gpiochip32
gpiochip64
gpiochip96
gpiochip128
/sys/class/gpio/gpiochipX目录保存系统中GPIO寄存器的信息,包括每个寄存器控制引脚的起始编号 base,寄存器名称,引脚总数
计算引脚编号
在SysFs 中是采用gpio+GPIO 引脚编号来控制GPIO 端口的,首先需要将一个物理gpio的名称,转换为一个GPIO 的编号。在RK3399 中,gpio的物理编号为 GPIO0_B2_d 或者GPIO0_A3_d。其中0 是gpiochip 的编号。计算此引脚编号的方法如下:
引脚编号 = 控制引脚的寄存器基数 + 控制引脚寄存器位数
例如:GPIO0_A3_d 的编号为 3
在全志H6 SOC 中,GPIO 的物理名称为PC13,PH3 这样的方式。GPIO 引脚的编号方法为:
以PC13 为例
P=Pin 可以或略
C=端口字母,该字母在字母表中的位置(从0开始)称为端口号
13=硬件号
GPIO 编号=端口号*32+引脚号
PC13=2*32+13=77
例如PH7=17*32+7=231
PL02=11*32+2=354
产生gpio3 文件
获得了引脚编号之后,要向/sys/class/gpio/export写入此编号,比如12号引脚,在Linux shell中可以通过以下命令实现,
echo 3 > /sys/class/gpio/export
命令成功后生成/sys/class/gpio/gpio3目录,如果没有出现相应的目录,说明此引脚不可导出:
设置GPIO 的方向
direction文件,定义输入输入方向,可以通过下面命令定义为输出
echo out > /sys/class/gpio/gpio12/direction
direction接受的参数:in, out, high, low。high/low同时设置方向为输出。
设置GPIO 的输出值
echo 1 >/sys/class/gpio/gpio3/value
echo 0 >/sys/class/gpio/gpio3/value
C 语言程序
上面介绍的方法是采用shell 命令来实现的,下面介绍C 语言的程序实现。在RK3399 PC 的GPIO 扩展槽2脚是GPIO0_A3_d ,其引脚编号位 3。在上面连接了一个LED 串联一个150欧姆的电阻到地(扩展槽3脚)
下面的程序是在RK3399 PC 板上调试通过的,请安心引用。
gpiolib.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/stat.h>
#include "gpiolib.h"
int gpio_direction(int gpio, int dir)
{
int ret = 0;
char buf[50];
sprintf(buf, "/sys/class/gpio/gpio%d/direction", gpio);
int gpiofd = open(buf, O_WRONLY);
if(gpiofd < 0) {
perror("Couldn't open IRQ file");
ret = -1;
}
if(dir == 2 && gpiofd){
if (3 != write(gpiofd, "high", 3)) {
perror("Couldn't set GPIO direction to out");
ret = -2;
}
}
if(dir == 1 && gpiofd){
if (3 != write(gpiofd, "out", 3)) {
perror("Couldn't set GPIO direction to out");
ret = -3;
}
}
else if(gpiofd) {
if(2 != write(gpiofd, "in", 2)) {
perror("Couldn't set GPIO directio to in");
ret = -4;
}
}
close(gpiofd);
return ret;
}
int gpio_setedge(int gpio, int rising, int falling)
{
int ret = 0;
char buf[50];
sprintf(buf, "/sys/class/gpio/gpio%d/edge", gpio);
int gpiofd = open(buf, O_WRONLY);
if(gpiofd < 0) {
perror("Couldn't open IRQ file");
ret = -1;
}
if(gpiofd && rising && falling) {
if(4 != write(gpiofd, "both", 4)) {
perror("Failed to set IRQ to both falling & rising");
ret = -2;
}
} else {
if(rising && gpiofd) {
if(6 != write(gpiofd, "rising", 6)) {
perror("Failed to set IRQ to rising");
ret = -2;
}
} else if(falling && gpiofd) {
if(7 != write(gpiofd, "falling", 7)) {
perror("Failed to set IRQ to falling");
ret = -3;
}
}
}
close(gpiofd);
return ret;
}
int gpio_export(int gpio)
{
int efd;
char buf[50];
int gpiofd, ret;
/* Quick test if it has already been exported */
sprintf(buf, "/sys/class/gpio/gpio%d/value", gpio);
efd = open(buf, O_WRONLY);
if(efd != -1) {
close(efd);
return 0;
}
efd = open("/sys/class/gpio/export", O_WRONLY);
if(efd != -1) {
sprintf(buf, "%d", gpio);
ret = write(efd, buf, strlen(buf));
if(ret < 0) {
perror("Export failed");
return -2;
}
close(efd);
} else {
// If we can't open the export file, we probably
// dont have any gpio permissions
return -1;
}
return 0;
}
void gpio_unexport(int gpio)
{
int gpiofd, ret;
char buf[50];
gpiofd = open("/sys/class/gpio/unexport", O_WRONLY);
sprintf(buf, "%d", gpio);
ret = write(gpiofd, buf, strlen(buf));
close(gpiofd);
}
int gpio_getfd(int gpio)
{
char in[3] = {0, 0, 0};
char buf[50];
int gpiofd;
sprintf(buf, "/sys/class/gpio/gpio%d/value", gpio);
gpiofd = open(buf, O_RDWR);
if(gpiofd < 0) {
fprintf(stderr, "Failed to open gpio %d value\n", gpio);
perror("gpio failed");
}
return gpiofd;
}
int gpio_read(int gpio)
{
char in[3] = {0, 0, 0};
char buf[50];
int nread, gpiofd;
sprintf(buf, "/sys/class/gpio/gpio%d/value", gpio);
gpiofd = open(buf, O_RDWR);
if(gpiofd < 0) {
fprintf(stderr, "Failed to open gpio %d value\n", gpio);
perror("gpio failed");
}
do {
nread = read(gpiofd, in, 1);
} while (nread == 0);
if(nread == -1){
perror("GPIO Read failed");
return -1;
}
close(gpiofd);
return atoi(in);
}
int gpio_write(int gpio, int val)
{
char buf[50];
int nread, ret, gpiofd;
sprintf(buf, "/sys/class/gpio/gpio%d/value", gpio);
gpiofd = open(buf, O_RDWR);
if(gpiofd > 0) {
snprintf(buf, 2, "%d", val);
ret = write(gpiofd, buf, 2);
if(ret < 0) {
perror("failed to set gpio");
return 1;
}
close(gpiofd);
if(ret == 2) return 0;
}
return 1;
}
int gpio_select(int gpio)
{
char gpio_irq[64];
int ret = 0, buf, irqfd;
fd_set fds;
FD_ZERO(&fds);
snprintf(gpio_irq, sizeof(gpio_irq), "/sys/class/gpio/gpio%d/value", gpio);
irqfd = open(gpio_irq, O_RDONLY, S_IREAD);
if(irqfd < 1) {
perror("Couldn't open the value file");
return -13;
}
// Read first since there is always an initial status
ret = read(irqfd, &buf, sizeof(buf));
while(1) {
FD_SET(irqfd, &fds);
ret = select(irqfd + 1, NULL, NULL, &fds, NULL);
if(FD_ISSET(irqfd, &fds))
{
FD_CLR(irqfd, &fds); //Remove the filedes from set
// Clear the junk data in the IRQ file
ret = read(irqfd, &buf, sizeof(buf));
return 1;
}
}
}
gpio.h
#ifndef _GPIOLIB_H_
/* returns -1 or the file descriptor of the gpio value file */
int gpio_export(int gpio);
/* Set direction to 2 = high output, 1 low output, 0 input */
int gpio_direction(int gpio, int dir);
/* Release the GPIO to be claimed by other processes or a kernel driver */
void gpio_unexport(int gpio);
/* Single GPIO read */
int gpio_read(int gpio);
/* Set GPIO to val (1 = high) */
int gpio_write(int gpio, int val);
/* Set which edge(s) causes the value select to return */
int gpio_setedge(int gpio, int rising, int falling);
/* Blocks on select until GPIO toggles on edge */
int gpio_select(int gpio);
/* Return the GPIO file descriptor */
int gpio_getfd(int gpio);
#endif //_GPIOLIB_H_
main.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include "gpiolib.h"
int main(int argc, char **argv) {
int gpio_pin = 3;
gpio_export(gpio_pin);
gpio_direction(gpio_pin, 1);
for(int i = 0; i < 5; i++) {
printf(">> GPIO %d ON\n", gpio_pin);
gpio_write(gpio_pin, 1);
sleep(1);
printf(">> GPIO %d OFF\n", gpio_pin);
gpio_write(gpio_pin, 0);
sleep(1);
}
return 0;
}