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