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 2: Plug In drivers for direct register GPIO, or file system based GPIO
  • 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 1:48 PM Date Created
  • Views 1308 views
  • Likes 5 likes
  • Comments 5 comments
Related
Recommended
  • raspberrypi
  • raspberry-pi-projects
  • c++

C++ gpio library for Raspberry Pi - Pt 2: Plug In drivers for direct register GPIO, or file system based GPIO

Jan Cumps
Jan Cumps
5 Jan 2024
C++ gpio library for Raspberry Pi - Pt 2: Plug In drivers for direct register GPIO, or file system based GPIO

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 second post, 3 mechanism are supported:

  • register access via memmap
  • the sysfs interface with C++ streams
  • the sysfs interface with traditional file access and file handlers

// #include <sysfsdevice_filehandler.h>
// typedef dgpio::pin<dgpio::sysfsdevice_filehandler> pin;

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

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

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

Example program

In  C++ gpio library for Raspberry Pi - Pt 1: Design and How To Use , I developed an OO class for Raspberry Pi GPIO handling. In that design, I created a single simple class that can set or get pin status. The handling was flexible: you could plug in a "driver" class that handles the actual pin management. 

I also provided one such driver: the sysfsdevice class. It used the traditional Raspberry /sys/class/gpio filesystem to manage the pins. You could select that driver at compile time, using the C++ template mechanism.

#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();

}

That sysfsdevice used C++ streams to talk to the /sys/class/gpio files.
In this post I add 2 additional drivers:

  • talk to the ARM CPU's registers, and
  • use the traditional C functions to talk to the /sys/class/gpio files.

That leaves us with 3 implementations that a developer can choose from:

  • dgpio::sysfsdevice_filehandler
  • dgpio::sysfsdevice_stream (the original one from post 1. I renamed it to distinguish it from the traditional filehandler implementation)
  • dgpio::memmapdevice

image
image: UML diagram

In the example, I provided a way to switch between mechanisms, with a define. You can select your desired template by uncommenting the relevant define line:

// uncomment the gpio handler that you want to use (enable one of the defines)
// #define DPGIO_SYSFS_FILEHANDLER
// #define DPGIO_SYSFS_STREAM
#define DPGIO_MEMMAP

#include "pin.h"

#if defined DPGIO_SYSFS_FILEHANDLER
#include <sysfsdevice_filehandler.h>
typedef dgpio::pin<dgpio::sysfsdevice_filehandler> pin;

#elif defined DPGIO_SYSFS_STREAM
#include "sysfsdevice_stream.h"
typedef dgpio::pin<dgpio::sysfsdevice_stream> pin;

#elif defined DPGIO_MEMMAP
#include "memmapdevice.h"
typedef dgpio::pin<dgpio::memmapdevice> pin;
#endif

Your code itself will not change, whatever template you plug in:

int main() {

	pin p21 = pin(21); // out test pin
	pin p23 = pin(23); // in test pin

    // out test
	p21.init(pin::dir::out);

	p21.set(pin::status::off);
	pin::status s = p21.get();

	p21.set(pin::status::on);
	s = p21.get();

	p21.set(pin::status::off);

    // in test
	p23.init(pin::dir::in);
	s = pin::status::on;
	while (s != pin::status::off) {
		s = p23.get();
	}

	p21.deinit();
	p23.deinit();

}

The Library Code

The Pin class code hasn't changed since post 1. The new templates just plug in. I will not repeat that classe's code here

Register plugin class memmapdriver:

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, pin<memmapdevice>::status status = pin<memmapdevice>::status::off);

	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_ */

C++

#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, pin<memmapdevice>::status status) {
	// 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);
	}

	set(gpio, status);

	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

Traditional filehandler plugin class sysfsdriver_filehandler:

header

 

#ifndef SYSFSDEVICE_FILEHANDLER_H_
#define SYSFSDEVICE_FILEHANDLER_H_

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

namespace dgpio {

class sysfsdevice_filehandler {
public:
	static void init(unsigned gpio, pin<sysfsdevice_filehandler>::dir dir, pin<sysfsdevice_filehandler>::status status);
	static void deinit(unsigned gpio);
	static pin<sysfsdevice_filehandler>::status get(unsigned gpio);
	static void set(unsigned gpio, pin<sysfsdevice_filehandler>::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_FILEHANDLER_H_ */

C++

#include <filesystem>
#include <string>
#include <cassert>
#include <fcntl.h>
#include <sysfsdevice_filehandler.h>
#include <unistd.h>


namespace dgpio {

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

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

void sysfsdevice_filehandler::exp(const unsigned gpio) {

	FILE *f = fopen(exportname().c_str(), "w");
    fprintf(f, std::to_string(gpio).c_str());
    fclose(f);
}

void sysfsdevice_filehandler::unexp(const unsigned gpio) {
	FILE *f = fopen(unexportname().c_str(), "w");
    fprintf(f, std::to_string(gpio).c_str());
    fclose(f);
}

void sysfsdevice_filehandler::init(const unsigned gpio,
		const pin<sysfsdevice_filehandler>::dir dir,
		const pin<sysfsdevice_filehandler>::status status) {
	if (!exists(gpio)) {
		exp(gpio);
	}
// 	while (!exists(gpio)) {}

	FILE *f = nullptr;

	while (f == nullptr) { // when you're not root, it takes a bit to get a valid pointer
		f = fopen(directionname(gpio).c_str(), "w");
	}

	switch (dir) {
	case pin<sysfsdevice_filehandler>::dir::in:
		fprintf(f, "in");
		break;
	case pin<sysfsdevice_filehandler>::dir::out:
		fprintf(f, "out");
		break;
	}
    fclose(f);
	set(gpio, status);
}

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

pin<sysfsdevice_filehandler>::status sysfsdevice_filehandler::get(const unsigned gpio) {
	pin<sysfsdevice_filehandler>::status retval = pin<sysfsdevice_filehandler>::status::off;
    char value_str[1];
	int fd = open(valuename(gpio).c_str(), O_RDONLY);
	read(fd, value_str, 1);
    if (value_str[0] == '0') {
    	retval = pin<sysfsdevice_filehandler>::status::off;
	} else if (value_str[0] == '1') {
    	retval = pin<sysfsdevice_filehandler>::status::on;
	} else {
		assert(false); // invalid
	}
    close(fd);

	return retval;
}


void sysfsdevice_filehandler::set(const unsigned gpio,
		const pin<sysfsdevice_filehandler>::status status) {
	FILE *f = fopen(valuename(gpio).c_str(), "w");

	switch (status) {
	case pin<sysfsdevice_filehandler>::status::off:
		fprintf(f, "0");
		break;
	case pin<sysfsdevice_filehandler>::status::on:
		fprintf(f, "1");
		break;
	}
    fclose(f);
}

} // namespace dgpio

And the original stream handler from post 1, renamed from sysfsdriver to sysfsdriver_stream:

header

 

#ifndef SYSFSDEVICE_STREAM_H_
#define SYSFSDEVICE_STREAM_H_

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

namespace dgpio {

class sysfsdevice_stream {
public:
	static void init(unsigned gpio, pin<sysfsdevice_stream>::dir dir, pin<sysfsdevice_stream>::status status);
	static void deinit(unsigned gpio);
	static pin<sysfsdevice_stream>::status get(unsigned gpio);
	static void set(unsigned gpio, pin<sysfsdevice_stream>::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_STREAM_H_ */

C++

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

#include <iostream>

namespace dgpio {

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

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

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

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

void sysfsdevice_stream::init(const unsigned gpio,
		const pin<sysfsdevice_stream>::dir dir,
		const pin<sysfsdevice_stream>::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_stream>::dir::in:
		of << "in";
		break;
	case pin<sysfsdevice_stream>::dir::out:
		of << "out";
		break;
	}
	of.close();
	set(gpio, status);
}

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

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

	is >> value;
	is.close();

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

	return retval;
}


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


} // namespace dgpio

But...

There is a 4th way to talk to the GPIOs. The sysfs method (/sys/class/gpio)is actually deprecated, and to be replaced with the userspace GPIO driver ( /dev/gpiochip#).  Once I figure out how that works, it should be straightforward to write a plugin driver class for that. Let me know if you have experience with that new driver ...

also: don't miss out on the blinky!  C++ gpio library for Raspberry Pi - Pt 3: Blinky! 

runtime hit?

There is virtually none. All constructs used here are compile time resolved. There's no dynamic lookups or virtual functions resolve. It's as if you call a C function. When you build a release version, with optimisation enabled and the non-used source files excluded from build, then only the code for the template that you selected will be in the firmware file. 

The example program that I made, has this firmware size for each of the mechanisms:

  1. sysfs_stream: 22.272
  2. memmap: 8.744
  3. file handler (classic C): 19.408

For baseline: the same applications, with all pin related code excluded: 8.220  bytes. Note that this baseline does not do anything with the pins. It's an indication for the individual solutions. It does not show how different the size is, confirmed to non-OO solution (because there's no standard GPIO API to compare to).

Eclipse project and source code:

Eclipse project (contains sources and pre-built executable): device_gpio_20240106.zip

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

  • Sign in to reply
  • DAB
    DAB over 1 year ago

    Great update Jan.

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

    This little test program lets you set or read a pin:

    // uncomment the gpio handler that you want to use
    //#define DPGIO_SYSFS_FILEHANDLER
    #define DPGIO_SYSFS_STREAM
    //#define DPGIO_MEMMAP
    
    #include "pin.h"
    
    #if defined DPGIO_SYSFS_FILEHANDLER
    #include <sysfsdevice_filehandler.h>
    typedef dgpio::pin<dgpio::sysfsdevice_filehandler> pin;
    #elif defined DPGIO_SYSFS_STREAM
    #include "sysfsdevice_stream.h"
    typedef dgpio::pin<dgpio::sysfsdevice_stream> pin;
    #elif defined DPGIO_MEMMAP
    #include "memmapdevice.h"
    typedef dgpio::pin<dgpio::memmapdevice> pin;
    #endif
    
    #include <stdlib.h>     /* atoi */
    int main(int argc, char *const *argv) {
        if (argc < 2) {
    		return -1;
        }
    
        int retval = 0;
    
        unsigned int gpio = atoi(argv[1]);
        pin::dir dir = pin::dir::in;
        pin::status status = pin::status::off;
    
        if (argc == 3) { // out
        	dir = pin::dir::out;
        	if (atoi(argv[2]) == 1) {
        		status = pin::status::on;
        	}
        }
    
    	pin p = pin(gpio);
    	p.init(dir);
    
    	if (dir == pin::dir::in) {
    		pin::status s = p.get();
    		retval = s == pin::status::on ? 1 : 0;
    	} else {
    		p.set(status);
    	}
    
    	p.deinit();
    	return retval;
    }
    
    

    to read a pin:

    ./device_gpio 21

    the return value holds the state, and can be retrieved:

    echo $?

    to set a pin high:

    ./device_gpio 21 1

    to set a pin low:

    ./device_gpio 21 0

    Test with a real GPIO pin please Grinning - the program will happily set any Broadcom BCMxxx CPU pin below 30, or change its function to GPIO.

    No root needed, but you have to be in the gpio group.

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

    fun fact: you can use one driver for pin A, and another for pin B. The only thing you have to do is make the two typedefs unique. Example:

    #include "pin.h"
    
    #include "sysfsdevice_stream.h"
    typedef dgpio::pin<dgpio::sysfsdevice_stream> fs_pin;
    
    #include "memmapdevice.h"
    typedef dgpio::pin<dgpio::memmapdevice> reg_pin;
    
    // ...
    
    	fs_pin p21 = fs_pin(21);
    	reg_pin p23 = reg_pin(23);
    
    // ...

    Is that useful? Who knows Slight smile

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

    I've been browsing the libgpiod code, for inspiration.

    It would be possible to create a template class for my design here that uses that lib. Maybe an exercise for the audience?

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

    Not sure if helpful, but I know Adafruit's Circuit Python library for the Raspberry Pi uses libgpiod. https://github.com/adafruit/Adafruit_Blinka

    • 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