Sunday, 5 April 2015

Yet More CI20: How to manipulate the GPIO

The GPIO in this case is pretty typical - it's a set of standard Linux interfaces under /sys/class/gpio/.

So the access is a simple three stage process:
  • Export the GPIO pin, to provide sysfs hooks
  • Turn the GPIO into an input or output using "direction"
  • Manipulate or read tthe value using "value"
The sample CI20 resource provide a blinking LED tutorial which goes over the basics of evaluating the PIO from the shell and using sysfs to set the value.

For this case I'll build a simple little C++ program to manipulate the system files using standard POSIX file calls, and which covers the first four GPIO pins on the expansion header (1,2,3 & 4).

This is actually fairly trivial: first I define a simple structure which relates a GPIO number to the sysfs number we use for export and access. I also throw in a PD reference for the CI20 here, but don't really need to use it


typedef struct{
    int gpio_number;
    int pd_ref;
    int sys_ref;
}portinfo;


static const portinfo ports[]={
    {1, 28, 124},
    {2, 26, 122},
    {3, 27, 123},
    {4, 29, 125},
};

Next up we create a simple "pin" class, which  hides the underlying sysfs access from the outside, and simply provides us with basic IO hooks

class GpioPin
{
public:
    GpioPin();
    int Open(int which);
    void Close();

    int SetOutput();
...
    int SetData(int val);
...
};

Following this then we'll fill in some of the simpler functions: there's a bit of vestigial stuff from the testing phases in the actual code (Toggle, etc) but the key functions we'll use here are those above:

  GpioPin::Open() Looks through the ports list, to try and find which pin we're accessing then opens the relevant sysfs interface.

 GpioPin::SetOutput() sets the pin as an output (which for the tests I'm doing is all I'm using for the moment)

 GpioPin::SetData() pushes a data value to the port pin.

 GpioPin::Close() closes any open sysfs file handles


So to use this class the simplest case to toggle a pin high then low is:

GpioPin pin;
  pin.Open(1);
  pin.SetOutput();
  pin.SetData(1);
...
  pin.SetData(0);
  pin.Close();
...

And a complete example to toggle the first four pins is attached below. Compile it with "mips-linux-gnu-g++ -Wall gpio_handler.cpp" and run it on the CI20.

Scoping the output on the header gets us:


There's some ringing on the data lines, but that's poor grounding hygiene on my part. Otherwise we're ticking along fairly well.

The full code is:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define MAX_STR (4096)

typedef struct{
    int gpio_number;
    int pd_ref;
    int sys_ref;
}portinfo;

static const portinfo ports[]={
    {1, 28, 124},
    {2, 26, 122},
    {3, 27, 123},
    {4, 29, 125},
};

class GpioPin
{
public:
    GpioPin();
    int Open(int which);
    void Close();
    int SetOutput();
    int SetData(int val);

private:
   const portinfo* _portinfo;
   int _fd_data;
   int _fd_direction;
   int _create_port_sysfs();
   int _do_open();
};

int GpioPin::_do_open()
{
char gpiodirection[MAX_STR];
char gpioval[MAX_STR];
int which;
 
    which = _portinfo->sys_ref;

    snprintf(gpiodirection, MAX_STR, "/sys/class/gpio/gpio%d/direction", which);
    snprintf(gpioval, MAX_STR, "/sys/class/gpio/gpio%d/value", which);

    _fd_data = open(gpioval, O_WRONLY);
    _fd_direction = open(gpiodirection, O_WRONLY);

    if ((_fd_data <0) || (_fd_direction <0))
    {
        printf("Can't open the port access files\n");
    }

return 0;
}

int GpioPin::_create_port_sysfs()
{
char gpiosel[MAX_STR];
char gpiodir[MAX_STR];
DIR * dirref;
int which;

    which = _portinfo->sys_ref;
    snprintf(gpiodir, MAX_STR, "/sys/class/gpio/gpio%d", which);
    dirref = opendir(gpiodir);
    if (dirref == NULL)
    {
    const char* exp_dir="/sys/class/gpio/export";
    int fd;
     
        fd = open(exp_dir, O_WRONLY);
        if (fd <0)
        {
            perror("Cannot access given GPIO");
        return -1;
        }

        snprintf(gpiosel, MAX_STR, "%d",which);
        write(fd, gpiosel, strlen(gpiosel));
        close(fd);

        dirref = opendir(gpiodir);
    }

    if (dirref == NULL)
    {
        printf("Can't get at Directory %s?\n", gpiodir);
    return -1;
    }
    closedir(dirref);
return 0;
}

GpioPin::GpioPin()
{
}

int GpioPin::Open(int which)
{
    _portinfo = NULL; //@todo: close if open

    for (unsigned int i=0; i< (sizeof(ports)/sizeof(portinfo)); i++)
    {
    const portinfo* inf;
        inf = &ports[i];
        if (inf->gpio_number == which)
        {
            printf("Found port pin %d on sysfs index %d\n", which,  inf->sys_ref);
         _portinfo=inf;
        break;
        }
    }

    if (_portinfo == NULL)
    {
        printf("Cannot find the specific port pin %d \n", which);
    return -1;
    }
    if (_create_port_sysfs() < 0)
    {
        printf("Cannot open the sysfs hooks for port pin %d \n", which);
    return -1;
    }
return _do_open();
}


int GpioPin::SetOutput()
{
    if (_fd_direction >0)
        return write(_fd_direction, "out", 3);

    printf("Cannot set the output");
return -1;
}

int GpioPin::SetData(int val)
{
const char* c;
    if (val>0)
    {
        c = "1";
    }
    else
    {
        c = "0";
    }
    if (_fd_data >0)
        return write(_fd_data, c, 1);

return -1;
}

void GpioPin::Close()
{
    if (_portinfo == NULL)
        return;
    if (_fd_data >0)
    {
        close(_fd_data);
    }
    if (_fd_direction >0)
    {
        close(_fd_direction);
    }
}

int main(int argc, char *argv[])
{
GpioPin pin[4];

    pin[0].Open(1);
    pin[1].Open(2);
    pin[2].Open(3);
    pin[3].Open(4);

    pin[0].SetOutput();
    pin[1].SetOutput();
    pin[2].SetOutput();
    pin[3].SetOutput();

    for (int i=0; i< 100; i++)
    {
        pin[0].SetData(1);
        pin[1].SetData(1);
        pin[2].SetData(1);
        pin[3].SetData(1);

        pin[0].SetData(0);
        pin[1].SetData(0);
        pin[2].SetData(0);
        pin[3].SetData(0);
    }

    pin[0].Close();
    pin[1].Close();
    pin[2].Close();
    pin[3].Close();

return 0;
}

Simple, no?