GPIO Programming Part 5

How to Control GPIO Hardware from C or C++

By Jeff Tranter

My last post, part 4 in the GPIO Programming series, showed some ways to program GPIO from Python. Python offers a number of advantages as a programming language including a short learning curve, no need for compilation, and the availability of many add-on modules. However, true compiled languages like C or C++ can offer some advantages in embedded applications, such as higher performance. Because you are working closer to the level of the hardware you don't have to worry about what a JIT compiler or interpreter might be doing. If desired, you can look at the compiler's generated code or even use in line assembly language. There is also no need to ship full source code to end users.

That said, you still should not expect to do hard real-time programming under a standard Linux-based system. But you often can do soft real-time, such as reading buttons and slow sensors, driving LEDs, motors, etc. In this installment we'll look at a couple of ways to control GPIO hardware from the C programming language. The examples will also be valid C++ programs.

Sysfs Example

As covered earlier in part 3 of this series, you can access GPIO pins through the file system using the sysfs interface. This is straightforward to do from C or C++. Here is an example program that toggles a GPIO pin every 100 milliseconds:

/*

Example of programming GPIO from C or C++ using the sysfs interface on
a Raspberry Pi.

Will toggle GPIO24 (physical pin 18) at a 100 millisecond rate for 10
seconds and then exit.

Jeff Tranter <<a href="mailto:jtranter@ics.com">jtranter@ics.com</a>>

*/


#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
    // Export the desired pin by writing to /sys/class/gpio/export

    int fd = open("/sys/class/gpio/export", O_WRONLY);
    if (fd == -1) {
        perror("Unable to open /sys/class/gpio/export");
        exit(1);
    }

    if (write(fd, "24", 2) != 2) {
        perror("Error writing to /sys/class/gpio/export");
        exit(1);
    }

    close(fd);

    // Set the pin to be an output by writing "out" to /sys/class/gpio/gpio24/direction

    fd = open("/sys/class/gpio/gpio24/direction", O_WRONLY);
    if (fd == -1) {
        perror("Unable to open /sys/class/gpio/gpio24/direction");
        exit(1);
    }

    if (write(fd, "out", 3) != 3) {
        perror("Error writing to /sys/class/gpio/gpio24/direction");
        exit(1);
    }

    close(fd);

    fd = open("/sys/class/gpio/gpio24/value", O_WRONLY);
    if (fd == -1) {
        perror("Unable to open /sys/class/gpio/gpio24/value");
        exit(1);
    }

    // Toggle LED 50 ms on, 50ms off, 100 times (10 seconds)

    for (int i = 0; i < 100; i++) {
        if (write(fd, "1", 1) != 1) {
            perror("Error writing to /sys/class/gpio/gpio24/value");
            exit(1);
        }
        usleep(50000);

        if (write(fd, "0", 1) != 1) {
            perror("Error writing to /sys/class/gpio/gpio24/value");
            exit(1);
        }
        usleep(50000);
    }

    close(fd);

    // Unexport the pin by writing to /sys/class/gpio/unexport

    fd = open("/sys/class/gpio/unexport", O_WRONLY);
    if (fd == -1) {
        perror("Unable to open /sys/class/gpio/unexport");
        exit(1);
    }

    if (write(fd, "24", 2) != 2) {
        perror("Error writing to /sys/class/gpio/unexport");
        exit(1);
    }

    close(fd);

    // And exit
    return 0;
}

Note that if you want to try running this program, it is recommended to run it as root, otherwise you may run into file locking timing issues with udev that cause access errors.

I did some benchmarking of this example on a Raspberry Pi 3B. With delays removed, it would toggle the GPIO pin at a rate of about 350 kilohertz. This gives some idea of what can be done at this level of programming. Without using other programming tricks, there is no guarantee that the program won't be periodically preempted to run other tasks.

The size of the binary when optimized and stripped was about 6K. and it had few dependencies on other shared libraries. This is several orders of magnitude smaller than Python, for example.

The Raspberry Pi also has hardware support for PWM on some pins, and other features that can help optimize programs like this, and there are other performance optimization techniques like direct GPIO register access. We may look at this in a future post.

WiringPi

WiringPi [1] is a GPIO access library for the Raspberry Pi. Written in C, it is usable from C or C++ or any language than can call C APIs. Third party bindings for a number a number of languages including Ruby, Python, and Perl are also available.

Released under a GNU LGPLv3 license. it is available from Raspbian Linux or you can build it from source.

It supports a similar API to the Wiring IDE supported on Arduino microcontrollers, making it easier to port hardware control code between the Raspberry Pi and Arduino platforms.

As well as the Raspberry Pi GPIO pins, it also supports some add-on boards like the PiFace and Gertboard.

The gpio command we looked at in an earlier blog post is part of WiringPi.

The library provides basic functions to read and write pins, including pullup and pulldown resistors and hardware PWM. It also provides routines for software delays down to the microsecond level. There is also support for higher level protocols including serial, SPI, and I2C.

In practice, WiringPi is very easy to use. Functions are defined in the header file <wiringPi.h>. You call a setup function to initialize the library, set the GPIO pin modes, and then call methods to read or write. The trickiest issue is probably getting the pin numbers correct - WiringPi uses a pin numbering convention based on the Broadcom SOM channel names, not physical pins. The documentation covers this (and the command gpio readall will also show it in tabular form). There is also a function you can call to convert physical pin numbers to WiringPi numbers.

Here is a simple example that toggles an LED connected to pin 18. It should be straightforward to understand from the source code:

/*

Example of programming GPIO from C or C++ using the WiringPi library
on a Raspberry Pi.

Will continuously toggle GPIO24 (physical pin 18) at a 500 millisecond
rate.

Jeff Tranter <<a href="mailto:jtranter@ics.com">jtranter@ics.com</a>>

*/


#include <wiringPi.h>

int main(void)
{
    // Red LED: Physical pin 18, BCM GPIO24, and WiringPi pin 5.
    const int led = 5;

    wiringPiSetup();

    pinMode(led, OUTPUT);

    while (1) {
        digitalWrite(led, HIGH);
        delay(500);
        digitalWrite(led, LOW);
        delay(500);
    }

    return 0;
}

Here is an example of reading an input and showing its level:

/*

Example of programming GPIO from C or C++ using the WiringPi library
on a Raspberry Pi.

Will read a pushbutton switch on GPIO6 (physical pin 31) every 500
milliseconds and report the status. seconds and then exit.

Jeff Tranter <<a href="mailto:jtranter@ics.com">jtranter@ics.com</a>>

*/


#include <stdio.h>
#include <wiringPi.h>

int main(void)
{
    // Switch: Physical pin 31, BCM GPIO6, and WiringPi pin 22.
    const int button = 22;

    wiringPiSetup();

    pinMode(button, INPUT);

    while (1) {
        if (digitalRead(button) == LOW) {
            fprintf(stderr, "Switch is pressed\n");
        } else {
            fprintf(stderr, "Switch is released\n");
        }
        delay(500);
    }

    return 0;
}

This final example will cycle through three LEDs in a binary pattern. If the pushbutton is pressed, it will turn off all LEDs and exit.

/*

Example of programming GPIO from C or C++ using the WiringPi library
on a Raspberry Pi.

Will cycle through three LEDs in a binary pattern. If the pushbutton is
pressed, it will turn off all LEDs and exit.

Jeff Tranter <<a href="mailto:jtranter@ics.com">jtranter@ics.com</a>>

*/


#include <stdio.h>
#include <stdlib.h>
#include <wiringPi.h>

int main(void)
{
    // Red LED: Physical pin 18, BCM GPIO24, and WiringPi pin 5.
    const int led1 = 5;

    // Green LED: Physical pin 22, BCM GPIO25, and WiringPi pin 6.
    const int led2 = 6;

    // Yellow LED: Physical pin 29, BCM GPIO5, and WiringPi pin 21.
    const int led3 = 21;

    // Switch: Physical pin 31, BCM GPIO6, and WiringPi pin 22.
    const int button = 22;

    wiringPiSetup();

    pinMode(led1, OUTPUT);
    pinMode(led2, OUTPUT);
    pinMode(led3, OUTPUT);
    pinMode(button, INPUT);

    fprintf(stderr, "Running on Raspberry Pi revision %d\n", piBoardRev());

    while (1) {
        for (int i = 0; i < 8; i++) {
            if (i & 1) {
                digitalWrite(led1, HIGH);
            } else {
                digitalWrite(led1, LOW);
            }
            if (i & 2) {
                digitalWrite(led2, HIGH);
            } else {
                digitalWrite(led2, LOW);
            }
            if (i & 4) {
                digitalWrite(led3, HIGH);
            } else {
                digitalWrite(led3, LOW);
            }

            delay(100);
        }

        if (digitalRead(button) == LOW) {
            digitalWrite(led1, LOW);
            digitalWrite(led2, LOW);
            digitalWrite(led3, LOW);
            exit(1);
        }
    }
}

I did a few performance measurements on the code in demo2.c. Running the loop with no delays produced a GPIO pin cycling at a rate of almost 12 MHz on a Raspberry Pi 3. The main reason for the high performance is that it uses direct register access to avoid the kernel/user space context switching and other overhead needed with sysfs.

Using delays of delayMicroseconds(10) produced a very consistent and accurate 50 kHz square wave.

Conclusions

These examples, implemented in C or C++, are more representative of how commercial and industrial embedded applications are usually written. The resource requirements are significantly lower and performance is higher than when using Python or other interpreted languages. The cost is the additional complexity in programming and building.

I highly recommend using WiringPi as it is high performance and provides a nice API for programming. It's main downside is being tied to the Raspberry Pi platform.

Shortly before I wrote this blog post, the new Raspberry Pi 4 was released. This model is approximately three times faster and has a number of features and performance improvements. Initial benchmarks indicate it is significantly faster at GPIO handling than the Pi 3. The new model needs new version of the WiringPi, which was available on release day of the Raspberry Pi 4, but not yet included in Raspbian Linux as a package.

References

  1. WiringPi library web site: http://wiringpi.com
  2. Link to code examples: https://github.com/tranter/blogs/tree/master/gpio/part5

Have a question or want to add to the conversation?

You must be logged in to continue.

Log in Register