element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • 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 & Tria Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • About Us
    About the element14 Community
  • 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
      •  Japan
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      •  Vietnam
      • 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 6393 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
  • DAB
    DAB over 2 years ago

    Nice post Jan.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps over 2 years ago

    If you'd like to use a different mechanism (e.g.: register access), you 'd need to write a class with minimum this interface:

    #include "pin.h"
    
    
    namespace dgpio {
    
    class registerdevice {
    public:
    	static void init(unsigned gpio, pin<registerdevice>::dir dir, pin<registerdevice>::status status);
    	static void deinit(unsigned gpio);
    	static pin<registerdevice>::status get(unsigned gpio);
    	static void set(unsigned gpio, pin<registerdevice>::status status);
    	
    };
    

    (I used the registerdevice classname from my original post).

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

    The implementation of the 4 members is responsible to access the underlying mechanism to set direction, set state, read state. 

    • 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 © 2026 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