GPIO with sysfs on Raspberry Pi (Part 2)

In my last post on using sysfs for GPIO on Raspberry Pi, I showed you how you can use the sysfs file system to manipulate the state of the GPIO pins.

This time, we’re going to explore how to do the same thing programmatically. I’m going to walk you through developing a C library to control the GPIO – but exactly the same principles could be used in any other language.

Why C? Well, mostly because I like C. But proficiency in C is useful if you’re going to be working with embedded systems, and microcontrollers; so it’s a good language to use. I’m also picking C because there are a lot of tutorials & libraries using Python – and few (if any) working with C. None of which is to say that Python isn’t a great language (and if you don’t know it – then I highly recommend learning it too).


Picking up from where we left off last time, we’ll use exactly the same circuit as last time: so the process is essentially exactly the same as last time.

  • Export the pin we want to use
    • Write the pin number to /sys/class/gpio/export
  • Set the direction
    • Write "in" or "out" to /sys/class/gpio/gpio??/direction
  • Set the value
    • Write "1" or "0" to /sys/class/gpio/gpio??/value

At it’s absolute simplest, this can be done in just a few lines.

For example, to set GPIO pin 4 high we could write:

 

FILE *sysfs_handle = NULL;

if ((sysfs_handle = fopen("/sys/class/gpio/export", "w")) != NULL)
{
    fwrite("4", sizeof(char), 2, sysfs_handle);
    fclose(sysfs_handle);
}

if ((sysfs_handle = fopen("/sys/class/gpio/gpio4/direction, "w")) != NULL)
{
    fwrite("out", sizeof(char), 4, sysfs_handle);
    fclose(sysfs_handle);
}

if ((sysfs_handle = fopen("/sys/class/gpio/gpio4/value", "w")) != NULL)
{
    fwrite("1", sizeof(char), 2, sysfs_handle);
    fclose(sysfs_handle);
}

The only slightly tricky thing to note, is that because we’re using C strings (which are null-terminated – i.e. the last character of the string is ASCII 0) we need to 1 to all of the string lengths, when writing. You can check to see if you want – by checking the sizeof("1")/sizeof(char) – which will equal 2.

In essence that’s all our code needs to do; although good practice dictates that we should also unexport the pin when we’ve finished with it.

But of course, this isn’t all that convenient. If we want to write code to blink an LED a few times, we don’t want to have to do all this by hand every time.

So let’s look next at a couple of functions that can handle the files for us.

We’ll start with the export…

The code is basically the same as before – except that this time we have to work a little harder to convert the integer value we’re being given, into a string we can write to the file. We should also start to think about some error handling too.

#define MAXSTR 64

int setGPIO_Out(int pin)
{
    FILE *sysfs_handle = NULL;
    if ((sysfs_handle = fopen("/sys/class/gpio/export", "w")) == NULL)
    {
        printf("ERROR: Cannot open GPIO export...\n (Is this program running as root?)\n");
        return 1;
    }

    ...

Next we need to perform the integer conversion. The best way to do this – is by using sprintf to write a string. In fact, since we know exactly how long it’s going to be – we can be a little more defensive in our programming and use snprintf.

snprintf writes a fixed (maximum) number of bytes – so protects against any kind of overflow. In this particular context this isn’t perhaps all that likely to happen – but it’s good practice (by far the most common exploits in software are the results of unprotected overflows). Since we know that the highest GPIO pin on a Raspberry Pi is GPIO25 – we can safely assert that we will only ever need 2 chars to store the string value (plus one extra the terminating 0x00).

...
char str_pin[3];
snprintf(str_pin, (3*sizeof(char)), "%d", pin);
if (fwrite(&str_pin, sizeof(char), 3, sysfs_handle)!=3)
{
    printf("ERROR: Unable to export GPIO pin %d\n", pin);
    return 2;
}
fclose(sysfs_handle);

...

Note also that we’re testing the return value from fwrite. This should be equal to the number of items written (where the size of the item is defined in the second parameter – here sizeof(char)). If it’s not – then something went wrong with the write – so we need to return an error condition.

Once that value’s written – the export step is complete. To make our function more elegant, however, we’ll also set the direction. This makes the function nice and easy to use – and is broadly analogous to the way that we’d do things when using an Arduino, for example. To do that, we basically need to use the same techniques that we’ve already seen – the main difference is that this time building our string needs to be slightly more complex – since the filename of the sysfs file we’re going to need to open contains the pin name. Again we use snprintf to build str_direction_file. Note as well, that to avoid a magic number we’ve also defined a maximum length.

 

...

char str_direction_file[MAXSTR];
snprintf(str_direction_file, (MAXSTR*sizeof(char)),
"/sys/class/gpio/gpio%d/direction", pin);

if ((sysfs_handle = fopen(str_direction_file, "w")) == NULL)
{
    printf("ERROR: Cannot open direction file...\n");
    return 3;
}

if (fwrite("out", sizeof(char), 4, sysfs_handle) != 4)
{
    printf("ERROR: Unable to write direction for GPIO%d\n", pin);
    return 4;
}

fclose(sysfs_handle);

return 0;

We can then use the same techniques for a function to write the HIGH / LOW value, and to clean-up when we’ve finished.

If we were just writing a function for use in a single program – this would be pretty much suffice: but let’s take this on an extra step – and add a few things to make it even more robust, so we can use it as an external function library.

The code is already pretty defensive – but there’s one more thing we can do, to make it even more water-tight. The Raspberry Pi only exposes some of the Broadcom BCM2835’s GPIO pins on the P1 header: so we should check to see if the pin that’s been passed into the function is one of the valid pins.

The most elegant way to do that (to make the code as reusable as possible, and in case of any future changes to the Raspberry Pi hardware) is to define this list in the file – and that use that #define to populate an array.

 

#define VALID_PINS 0, 1, 4, 7, 8, 9, 10, 11, 14, 15, 17, 18, 21, 22, 23, 24, 25

...

int valid_pins[]={VALID_PINS};

...

Now we have the array, we need to check our pin is valid.

...

int c;
int valid = 0;

for (c=0;c<(sizeof(valid_pins) / sizeof(int));c++)
{
    if(pin == valid_pins[c])
    valid = 1;
}

if(!valid)
{
    printf("ERROR: Invalid pin!\nPin %d is not a GPIO pin...\n", pin);
    return -1;
}

Obviously we should do this at the top of the function – before we do anything else.

Another thing that we should probably do, is to move the text that’s generated in the event of an error, from the default stdout stream, to stderr… We can easily accomplish this by replacing our printf("..."); with fprintf(stderr,"...");. By pushing the error messages to stderr we ensure that they’re kept separate from any other output from the calling program, if the output from that program has been redirected to a file or other stream.

Lastly, we can add some more verbose output identifying where we are in the code, which will only be enabled if the code’s been built in “debug mode” (i.e. if debug is defined at compile time – gcc -Ddebug gpio.c ...).

The final version of the GPIO.C can be found here.

Finally, let’s take a look at some code to use these library functions.

At it’s simplest – all we’d need to write to recreate our first example, is:

...

setGPIO_Out(4);
GPIO_Write(4,1);
unsetGPIO(4);

...

Let’s take a look at a more interesting example. This code will blink our two LEDs on and off, in sequence, for one second at a time. We’ll use the sleep() function from unistd.h to add that one second delay.

 

[code lang="cpp"]
#include <unistd.h>

int main()
{
    int pins[2]={4,21};
    int i;
    int c;
    int j;

    for (i=0;i<(sizeof(pins) / sizeof(int));i++)
        if (setGPIO_Out(pins[i]))
            return -1;

    for (i=0;i<10;i++)
        for (c=0;c<(sizeof(pins) / sizeof(int));c++)
        {
            for (j=0;j<(sizeof(pins) / sizeof(int));j++)
            {
                if (c==j)
                {
                    if (GPIO_Write(pins[j],1))
                        return 1;
                }
                else
                    if (GPIO_Write(pins[j],0))
                        return 1;
            }
            sleep(1);
        }

    for (i=0;i<(sizeof(pins) / sizeof(int));i++)
        if (unsetGPIO(pins[i]))
            return 2;

    return 0;
}

Note how we’re checking the return value from each of the functions – a non-zero return value is the result of an error: so we’ll abort.

If I’m totally honest, this is a little over engineered for a demo – but it shows a nice way that you can very easily write code to work with an arbitrary number of GPIO pins, merely by changing the contents of the pins array: the code then iterates through the array.

The complete source code for this project – including the library functions, the demo code, and a makefile – can be found at https://github.com/Auctoris/GPIO-Lib

2 thoughts on “GPIO with sysfs on Raspberry Pi (Part 2)

  1. Thank you for taking the time to do this. I’ve been using wiringPi but feel it’s time to dig a bit deeper, plus I don’t like the fact that wiringPi can’t register interrupts to functions that pass parameters! There’s obviously a reason for it but I can’t appreciate the pros and cons until I’ve done that digging. This will put the shovel firmly in my hand!

Leave a Reply

Your email address will not be published. Required fields are marked *