element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • About Us
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Raspberry Pi
  • Products
  • More
Raspberry Pi
Blog C++ gpio library for Raspberry Pi - Pt 1: Design and How To Use
  • Blog
  • Forum
  • Documents
  • Quiz
  • Events
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join Raspberry Pi to participate - click to join for free!
Featured Articles
Announcing Pi
Technical Specifications
Raspberry Pi FAQs
Win a Pi
GPIO Pinout
Raspberry Pi Wishlist
Comparison Chart
Quiz
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: Jan Cumps
  • Date Created: 5 Jan 2024 12:13 PM Date Created
  • Views 5289 views
  • Likes 10 likes
  • Comments 7 comments
Related
Recommended
  • raspberry-pi-projects
  • c++

C++ gpio library for Raspberry Pi - Pt 1: Design and How To Use

Jan Cumps
Jan Cumps
5 Jan 2024
C++ gpio library for Raspberry Pi - Pt 1: Design and How To Use

An exercise to build a C++ library for the GPIO pins. The programmer can decide what underlying mechanism is used to talk to the pins. In this initial release, one mechanism is supported: the sysfs interface.

typedef dgpio::pin<dgpio::sysfsdevice> pin;

pin p21 = pin(21);
p21.init(pin::dir::out, pin::status::off);
p21.set(pin::status::on);

Example program

Easiest way to show the library is with an example.

#include "pin.h"
#include "sysfsdevice.h"

typedef dgpio::pin<dgpio::sysfsdevice> pin;

int main() {

	pin p21 = pin(21);

//	p21.init(pin::dir::out, pin::status::off);
//	p21.set(pin::status::off);
	p21.init(pin::dir::in);
	
	pin::status status = pin::status::on;
	while (status != pin::status::off) {
		status = p21.get();
	}

	p21.deinit();

}

gpio 21 is set to input, and the program waits until the pin is pulled to ground (a "button press"). The typedef defines what class is used to talk to the Pi pins. sysfsdevice is the class that knows how to talk to the Linux gpio sysfs.

Prerequisite: your user has to be part of the gpio Linux group. to check:

groups $USER

To add your account to the group:

sudo usermod -a -G gpio $USER

The OO API: class pin

The whole interface is a single class, named pin. It's a template class. As template type, it will accept a class that knows how to talk to the Pi.

#ifndef PIN_H_
#define PIN_H_

#include "pin.h"

namespace dgpio {

template <class D>
class pin {
public:
    enum class status : unsigned {
        off  = 0u,
        on
    };

    enum class dir {
        in, // default for Pi
        out,
    };

    pin(unsigned gpio) : gpio(gpio) {}
	void init(dir dir = dir::in, status status = status::off) {
			D::init(gpio, dir, status);
		}
	void deinit() {
		D::deinit(gpio);
	}

	status get() {
		return D::get(gpio);
	}
	void set(status status) {
		D::set(gpio, status);
	}

private:
	unsigned gpio;
};

} // namespace dgpio

#endif /* PIN_H_ */

This is everything. There is no .cpp file. All work is forwarded to the templated class.

API:

  • constructor: unsigned gpio
  • init: optional dir (in), optional state (off)
  • deinit
  • get: return state
  • set: state

To create a pin that uses the sysfsdevice worker, you can use a typedef:

typedef dgpio::pin<dgpio::sysfsdevice> pin;
pin p21 = pin(21);

See the example above on how you can use the pin in your code.

The worker: class sysfsdevice

This class knows how to talk to Linux and control the GPIO pins. A developer does not have to know this class or its API to work with the library.

Header:

#ifndef SYSFSDEVICE_H_
#define SYSFSDEVICE_H_

#include <string>
#include "pin.h"

namespace dgpio {

class sysfsdevice {
public:
	static void init(unsigned gpio, pin<sysfsdevice>::dir dir, pin<sysfsdevice>::status status);
	static void deinit(unsigned gpio);
	static pin<sysfsdevice>::status get(unsigned gpio);
	static void set(unsigned gpio, pin<sysfsdevice>::status status);


private:
	static bool exists (const unsigned gpio);
	static void exp(const unsigned gpio);
	static void unexp(const unsigned gpio);

	static const std::string device_path;
	inline static std::string directoryname(unsigned gpio) {return device_path + "/gpio" + std::to_string(gpio);}
	inline static std::string exportname() { return device_path + "/export";}
	inline static std::string unexportname() { return device_path + "/unexport";}
	inline static std::string directionname(unsigned gpio) { return directoryname(gpio) + "/direction";}
	inline static std::string valuename(unsigned gpio) { return directoryname(gpio) + "/value";}
};

} // namespace dgpio

#endif /* SYSFSDEVICE_H_ */

CPP:

#include <sysfsdevice.h>
#include <filesystem>
#include <fstream>
#include <string>
#include <cassert>

#include <iostream>

namespace dgpio {

const std::string sysfsdevice::device_path = std::string("/sys/class/gpio");

bool sysfsdevice::exists(const unsigned gpio) {
	return std::filesystem::is_symlink(directoryname(gpio));
}

void sysfsdevice::exp(const unsigned gpio) {
	std::ofstream of(exportname());
	of << std::unitbuf; // unbuffered
	of << gpio;
	of.close();
}

void sysfsdevice::unexp(const unsigned gpio) {
	std::ofstream of(unexportname());
	of << std::unitbuf; // unbuffered
	of << gpio;
	of.close();
}

void sysfsdevice::init(unsigned gpio, pin<sysfsdevice>::dir dir, pin<sysfsdevice>::status status) {
	if (!exists(gpio)) {
		exp(gpio);
	}
	std::ofstream of;
	while(!of.is_open()) { // when you're not root, it takes a bit to get a valid open file
		of.open(directionname(gpio));
	}
	of << std::unitbuf; // unbuffered
	switch (dir) {
	case pin<sysfsdevice>::dir::in:
		of << "in";
		break;
	case pin<sysfsdevice>::dir::out:
		of << "out";
		set(gpio, status);
		break;
	}
	of.close();
}

void sysfsdevice::deinit(unsigned gpio) {
	if (exists(gpio)) {
		unexp(gpio);
	}
}

pin<sysfsdevice>::status sysfsdevice::get(unsigned gpio) {
	std::ifstream is(valuename(gpio));
	std::string value;
	pin<sysfsdevice>::status retval = pin<sysfsdevice>::status::off;

	is >> value;
	is.close();

	if (value == "0") {
		retval = pin<sysfsdevice>::status::off;
	} else if (value == "1") {
		retval = pin<sysfsdevice>::status::on;
	} else {
		assert(false); // invalid
	}

	return retval;
}


void sysfsdevice::set(unsigned gpio, pin<sysfsdevice>::status status) {
	std::ofstream of(valuename(gpio));
	of << std::unitbuf; // unbuffered
	switch (status) {
	case pin<sysfsdevice>::status::off:
		of << "0";
		break;
	case pin<sysfsdevice>::status::on:
		of << "1";
		break;
	}
	of.close();
}


} // namespace dgpio

If you would like to use a different mechanism to control the pins (maybe via the registers?), you could develop a similar class that knows how to talk to that hardware interface.

The developer could then switch by changing the typedef:

typedef dgpio::pin<dgpio::registerdevice> pin;
pin p21 = pin(21);

(this is a theoretical example - the class registerdevice isn't developed yet) edit: I developed it in this follow-up post: C++ gpio library for Raspberry Pi - Pt 2: Plug In drivers for direct register GPIO, or file system based GPIO 

The actual reason for building this library is to train my C++ skills. In particular developing generic code using templates. It's not intended as a resource friendly fast pin driver at this moment.

thumbnail source: nightcafe AI generated

github repo: https://github.com/jancumps/cppgpio

  • Sign in to reply
  • Jan Cumps
    Jan Cumps over 1 year ago in reply to shabaz

    My library doesn't do that Slight smile

    However, it is possible to plug in a driver for other platforms. Even microcontrollers.

    image

    You could then use the same API on a BB, Pi, Arduino, Pico, NXP, Renesas, ST, .... Just the driver template is different. And because this is compile time resolved, it doesn't add compute time, memory or firmware space. There's no run-time hit.

    As long as you can write a class that can init a pin (and if needed cleanup), set direction, read, write, it 'll be pluggable.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps over 1 year ago

    This follow-up post has 3 ways to talk to the GPIOs. You select the method of choice by plugging in the desired driver:  C++ gpio library for Raspberry Pi - Pt 2: Plug In drivers for direct register GPIO, or file system based GPIO 

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • shabaz
    shabaz over 1 year ago

    This is great! It's a disappointment that this sort of thing is so overlooked for C/C++ by SBC manufacturers. It would speed up adoption of SBCs if there was a known easy way to work with the GPIO such as this.

    I was thinking the other day, there should be an RFC or software-world equivalent! on the topic of improving GPIO.

    One idea (still only partially baked) was what if there was a config file with every program, such as this:

    {
        "INTRO": "Blinky GPIO setup rev 1",
        "LED": {
            "CIRCUIT_DESC": "Connect a 220 ohm resistor and LED to this pin and GND",
            "BCM_PIN": 12,
            "DIR": "OUTPUT"
        },
        "BUTTON1": {
            "CIRCUIT_DESC": "Connect a push-button between this pin and GND",
            "BCM_PIN": 13,
            "DIR": "INPUT",
            "PULLUP": 1
        }
    }
    

    The idea being that the names "LED" and "BUTTON1" would somehow become part of the definitions seen by the compiler at compile-time, but the GPIO library would read the file during execution to see the pin numbering. That way, users could build code (e.g. blinky code) and then later edit the config file to suit how they wire things up, without recompiling, they would just rerun the code to re-read the file. It would be slower, but barely, since C/C++ is speedy anyway. 

    The "DIR" and "PULLUP" would not be edited by the user, but was just an idea to speed up configuration of pins by the software developer if they didn't need to do it in the code. Things could break easily if the config file was wrong, but then things break if source code is wrong too.

    Also, since a lot of code comes with badly drawn Fritzing, it may be easier to simply see how to wire pins by reading the CIRCUIT_DESC lines in the config file.

    For sure it's unnecessary since people still get by without it, this is just thinking out loud, flaws and all.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps over 1 year ago

    I have this working with "gpio via registers" too.

    You can choose between sysfs gpio (/sys/class/gpio) or memmapped register gpio by changing an include and a typedef in your code.

    For talking to GPIO via sysfs :

    #include "sysfsdevice.h"
    typedef dgpio::pin<dgpio::sysfsdevice> pin;

    ... and via memmapped register access:

    #include "memmapdevice.h"
    typedef dgpio::pin<dgpio::memmapdevice> pin;

    For the rest the code is the same. Example:

    int main() {
    pin p21 = pin(21);
    p21.init(pin::dir::out);
    p21.set(pin::status::off);

    I haven't published the memmap code yet, but here is the current version:

    header:

    #ifndef MEMMAPDEVICE_H_
    #define MEMMAPDEVICE_H_
    
    #include <fcntl.h>    // for open
    #include <sys/mman.h> // for mmap
    #include "pin.h"
    
    namespace dgpio {
    
    class memmapdevice {
    public:
    	static void init(unsigned gpio, pin<memmapdevice>::dir dir);
    	static void deinit(unsigned gpio) {
    		// https://linux.die.net/man/2/close
    		// It is probably unwise to close file descriptors while they may
    		// be in use by system calls in other threads in the same process
    	}
    	static pin<memmapdevice>::status get(unsigned gpio) {
    		return gpiomem[13] & (1 << gpio) ? pin<memmapdevice>::status::on : pin<memmapdevice>::status::off; // GPLEV0
    	}
    	static void set(unsigned gpio, pin<memmapdevice>::status status) {
    		// always set status first
    		switch (status) {
    		case pin<memmapdevice>::status::on:
    			gpiomem[7] |= (1 << gpio); // GPSET0
    			break;
    		case pin<memmapdevice>::status::off:
    			gpiomem[10] |= (1 << gpio); // GPCLR0
    			break;
    		}
    	}
    
    private:
    	static unsigned int *gpiomem;
    	static int fdgpio;
    };
    
    } // namespace dgpio
    
    #endif /* MEMMAPDEVICE_H_ */
    

    cpp:

    #include "memmapdevice.h"
    
    
    namespace dgpio {
    
    // static members to be initialised in the cpp file
    // memory mapped registers
    unsigned int *memmapdevice::gpiomem = nullptr;
    // gpio memory map file interface
    int memmapdevice::fdgpio = -1;
    
    void memmapdevice::init(unsigned gpio, pin<memmapdevice>::dir dir) {
    	// inspiration
    	// https://www.cs.uaf.edu/2016/fall/cs301/lecture/11_09_raspberry_pi.html
    	if (fdgpio > 0) { // memmap already initialised?
    		return;
    	}
    	// map gpio memory to gpiomem
    	fdgpio=open("/dev/gpiomem", O_RDWR);
    	if (fdgpio > 0) {
    		gpiomem = (unsigned int *)mmap(0, 4096, PROT_READ+PROT_WRITE, MAP_SHARED, fdgpio,0);
    	}
    	unsigned int index = gpio / 10; // GPFSELn register to set this pin direction
    	unsigned int offset = (gpio % 10) * 3; // lsb for this pin's 3 bit status in that register
    
    	unsigned int curval = gpiomem[index];
    	curval &= ~0b111 << (offset);
    
    	switch (dir) {
    	case pin<memmapdevice>::dir::in:
    		break;
    	case pin<memmapdevice>::dir::out:
    		curval |= 0b1 << (offset);
    		break;
    	}
    
    	gpiomem[index] = curval;
    }
    
    } // namespace dgpio
    

    I'll blog the details when I have time.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps over 1 year ago

    preparation for the register based driver:

    	int fdgpio=open("/dev/gpiomem",O_RDWR);
    	if (fdgpio<0) { printf("Error opening /dev/gpiomem"); return -1; }
    
    	unsigned int *gpio=(unsigned int *)mmap(0,4096,
    		PROT_READ+PROT_WRITE, MAP_SHARED,
    		fdgpio,0);
    	printf("mmap'd gpiomem at pointer %p\n",gpio);
    
    	// set pin 21 as output, by writing 001 to 5-3 of GPFSEL2
    	gpio[2] |= (1<<3); // pin 21 output
    
    	//	toggle pin 21
    	gpio[7] |= (1<<21); // pin 21 high
    	gpio[10] |= (1<<21); // pin 21 low
    
    
    	// Read pin 8, by accessing bit 8 of GPLEV0
    	unsigned int retval = gpio[13]&(1<<8);

    This code first set pin 21 as output, then sets it high, then low. Then it reads pin 8. All from the registers.

    See page 90 of the datasheet: https://www.raspberrypi.org/app/uploads/2012/02/BCM2835-ARM-Peripherals.pdf

    inspiration: https://www.cs.uaf.edu/2016/fall/cs301/lecture/11_09_raspberry_pi.html

    I tested, and my LED blunk.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
>
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube