Introduction to accessing the Raspberry Pi’s GPIO in C++ (sysfs)

Introduction

This blog post will show how to access the GPIO of the Raspberry Pi using C++. There are two methods to achieve this. One way is to control the Raspberry Pi’s GPIO registers by hand, similar to programming a microcontroller without an OS or memory management unit (using mmap). This method’s benefit is that by completely bypassing the OS (Linux), the GPIO pins can toggle rapidly. By circumventing the operating system, there is a possibility of an unsafe resource conflict occurring if two processes attempt to access the same physical GPIO registers simultaneously. Here are some instances of using this method for GPIO coding.

The alternative method is known as the “Linux approach” (sysfs). There is already a pre-installed driver in Linux for securely utilizing the GPIOs. Each GPIO pin is essentially treated like a file in this system. This is the recommended way to access GPIO. This method has a downside as it leads to slower (yet safer) GPIO pin toggling. You can find an example code for this method here, as well as a wiki explaining how to access GPIO signals with this method. This method will be implemented in this tutorial. The accesses to the file will be enclosed within a C++ class to simplify and ensure GPIO access (to some extent).

Introduction to accessing the Raspberry Pi’s GPIO in C++ (sysfs) Schematic

Setting up the  test hardware

In order to test our GPIO access program, we must physically connect the GPIO pins to real hardware components. I made the choice to designate GPIO pin 4 as an output pin for controlling an LED, and GPIO pin 17 as an input pin for monitoring a pushbutton’s status. A diagram showing the connection is available in Figure 1.

An LED is connected to GPIO4 through a 330Ohm current limiting resistor. A push-button is connected to GPIO17 along with a 10KOhm pull-up resistor. If the pushbutton is not pressed, the voltage on GPIO17 will remain at 3.3V. Upon pressing the button, the voltage of GPIO17 will drop to 0V.

The connections between the Raspberry Pi and the other parts can be made via Male-to Female jumper wires or via one of Adafruit’s Pi cobbler kits (ver1  or ver2).

Accessing GPIO From the Terminal

  • When using the Linux (sysfs) way to access GPIO, we must ensure that we’re logged in as root:
pi@raspberrypi ~ $ sudo -i
  • we then must “export the pin” in question. For example to use pins GPIO4 & 17 we  must first export them by writing the GPIO pin numbers to the file “/sys/class/gpio/export” as follows:
root@raspberrypi:~# echo "4" > /sys/class/gpio/export
root@raspberrypi:~# echo "17" > /sys/class/gpio/export

The next step is to set pin 4 as output and pin 17 as input. This is achieved by writing “out” into the “/sys/class/gpio/gpio4/direction” file and writing “in” into the “/sys/class/gpio/gpio17/direction” file as follows:

root@raspberrypi:~# echo "out" > /sys/class/gpio/gpio4/direction
root@raspberrypi:~# echo "in" > /sys/class/gpio/gpio17/direction

To set the output GPIO4 pin high and therefore cause the LED to light-up, we need to write a “1” to the “/sys/class/gpio/gpio4/value” file:

root@raspberrypi:~# echo "1" > /sys/class/gpio/gpio4/value

To clear the output GPIO4 pin to low and therefore cause the LED to turn off we need to write a “0” to the “/sys/class/gpio/gpio4/value” file:

root@raspberrypi:~# echo "0" > /sys/class/gpio/gpio4/value

To read the state of the input pin GPIO17, we need to read the “/sys/class/gpio/gpio17/value” file.  This will return either a “0” if the pin is connected to 0V or a “1” if the pin is connected to 3.3V:

root@raspberrypi:~#cat /sys/class/gpio/gpio17/value

And finally, when we’re done with the GPIO pins we must unexport them by writing the GPIO pin numbers to the “/sys/class/gpio/unexport” file!

root@raspberrypi:~#echo "4" > /sys/class/gpio/unexport
root@raspberrypi:~#echo "17" > /sys/class/gpio/unexport

Notice how the sysfs approach reduces the problem of GPIO access to reading and writing files! This is by the way the norm for Linux. All I/O access (serial ports, console, printers e.t.c.) involves reading to and writing from files. The next step will be to perform the same functions in C++!

Writing a GPIO C++ Class

The goal of the GPIOClass class is to enable the control of a single GPIO  pin. The class was designed so that the GPIO pin number must be passed to the GPIOClass object when its created via an overloaded constructor. The “GPIOClass.h” header file is provided below

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#ifndef GPIO_CLASS_H
#define GPIO_CLASS_H
#include <string>
using namespace std;
/* GPIO Class
 * Purpose: Each object instantiated from this class will control a GPIO pin
 * The GPIO pin number must be passed to the overloaded class constructor
 */
class GPIOClass
{
public:
    GPIOClass();  // create a GPIO object that controls GPIO4 (default
    GPIOClass(string x); // create a GPIO object that controls GPIOx, where x is passed to this constructor
    int export_gpio(); // exports GPIO
    int unexport_gpio(); // unexport GPIO
    int setdir_gpio(string dir); // Set GPIO Direction
    int setval_gpio(string val); // Set GPIO Value (putput pins)
    int getval_gpio(string& val); // Get GPIO Value (input/ output pins)
    string get_gpionum(); // return the GPIO number associated with the instance of an object
private:
    string gpionum; // GPIO number associated with the instance of an object
};
#endif

Each GPIOClass object has member functions that enable us to export/unexport GPIO pins, set the direction of the GPIO pins as well as set and get the value of the GPIO pins. The GPIOClass object has one private variable which is the GPIO pin number. The implementation of these member functions  are provided below “GPIOClass.cpp”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#include <fstream>
#include <string>
#include <iostream>
#include <sstream>
#include "GPIOClass.h"
using namespace std;
GPIOClass::GPIOClass()
{
    this->gpionum = "4"; //GPIO4 is default
}
GPIOClass::GPIOClass(string gnum)
{
    this->gpionum = gnum;  //Instatiate GPIOClass object for GPIO pin number "gnum"
}
int GPIOClass::export_gpio()
{
    string export_str = "/sys/class/gpio/export";
    ofstream exportgpio(export_str.c_str()); // Open "export" file. Convert C++ string to C string. Required for all Linux pathnames
    if (exportgpio < 0){
        cout << " OPERATION FAILED: Unable to export GPIO"<< this->gpionum <<" ."<< endl;
        return -1;
    }
    exportgpio << this->gpionum ; //write GPIO number to export
    exportgpio.close(); //close export file
    return 0;
}
int GPIOClass::unexport_gpio()
{
    string unexport_str = "/sys/class/gpio/unexport";
    ofstream unexportgpio(unexport_str.c_str()); //Open unexport file
    if (unexportgpio < 0){
        cout << " OPERATION FAILED: Unable to unexport GPIO"<< this->gpionum <<" ."<< endl;
        return -1;
    }
    unexportgpio << this->gpionum ; //write GPIO number to unexport
    unexportgpio.close(); //close unexport file
    return 0;
}
int GPIOClass::setdir_gpio(string dir)
{
    string setdir_str ="/sys/class/gpio/gpio" + this->gpionum + "/direction";
    ofstream setdirgpio(setdir_str.c_str()); // open direction file for gpio
        if (setdirgpio < 0){
            cout << " OPERATION FAILED: Unable to set direction of GPIO"<< this->gpionum <<" ."<< endl;
            return -1;
        }
        setdirgpio << dir; //write direction to direction file
        setdirgpio.close(); // close direction file
        return 0;
}
int GPIOClass::setval_gpio(string val)
{
    string setval_str = "/sys/class/gpio/gpio" + this->gpionum + "/value";
    ofstream setvalgpio(setval_str.c_str()); // open value file for gpio
        if (setvalgpio < 0){
            cout << " OPERATION FAILED: Unable to set the value of GPIO"<< this->gpionum <<" ."<< endl;
            return -1;
        }
        setvalgpio << val ;//write value to value file
        setvalgpio.close();// close value file
        return 0;
}
int GPIOClass::getval_gpio(string& val){
    string getval_str = "/sys/class/gpio/gpio" + this->gpionum + "/value";
    ifstream getvalgpio(getval_str.c_str());// open value file for gpio
    if (getvalgpio < 0){
        cout << " OPERATION FAILED: Unable to get value of GPIO"<< this->gpionum <<" ."<< endl;
        return -1;
            }
    getvalgpio >> val ;  //read gpio value
    if(val != "0")
        val = "1";
    else
        val = "0";
    getvalgpio.close(); //close the value file
    return 0;
}
string GPIOClass::get_gpionum(){
return this->gpionum;
}

To open and close files in C++, the ifstream/ofstream classes were utilized. Despite using the C++ string class, the C++ strings still needed to be transformed into C strings because only C strings are compatible with pathnames in Linux. The GPIOClass is quite easy to understand.


About The Author

Ibrar Ayyub

I am an experienced technical writer holding a Master's degree in computer science from BZU Multan, Pakistan University. With a background spanning various industries, particularly in home automation and engineering, I have honed my skills in crafting clear and concise content. Proficient in leveraging infographics and diagrams, I strive to simplify complex concepts for readers. My strength lies in thorough research and presenting information in a structured and logical format.

Follow Us:
LinkedinTwitter

Leave a Comment

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

Scroll to Top