Writing code could be divided into three parts.

1.Interface structure

At this stage programmer should describe all the devices used in his project. Of course, only those of them which Arduino programmaticaly interacts with (sensors, chips and not intermediate resistors, for example). Each device at this stage is represented by a class - description-class. Description-class has special member functions - pin-functions. Those functions are pure virtual and each is intended to return 8-bit unsigned integer value from the set of Arduino pin numbers. Pin-functions play a role of wires between Arduino and peripherals. Description-class should also contain functions which are Arduino instructions and intended for this concrete device.

Arduino also has a description-class but now it is used as a namespace with constants. No object of this class is needed so far.

Below is an example of using the interface.

  • Controller: Arduino Uno R3
  • Peripheral: Ultrasonic sensor HC-SR04

1.1 Arduino Uno R3 class-description

Arduino Uno R3 description-class consists only of static constants that are pin numbers.

1.1.1 Uno.h

#ifndef UNO_H
#define UNO_H

#include <inttypes.h>

class Uno
    public :
        //Digital pins
        static const uint8_t D0 = 0;
        static const uint8_t D1 = 1;
        static const uint8_t D2 = 2;
        static const uint8_t D3 = 3;
        static const uint8_t D4 = 4;
        static const uint8_t D5 = 5;
        static const uint8_t D6 = 6;
        static const uint8_t D7 = 7;
        static const uint8_t D8 = 8;
        static const uint8_t D9 = 9;
        static const uint8_t D10 = 10;
        static const uint8_t D11 = 11;
        static const uint8_t D12 = 12;
        static const uint8_t D13 = 13;
        //Analog pins
        static const uint8_t A0 = 0;
        static const uint8_t A1 = 0;
        static const uint8_t A2 = 0;
        static const uint8_t A3 = 0;
        static const uint8_t A4 = 0;
        static const uint8_t A5 = 0;
        static const uint8_t SS = D10;
        static const uint8_t MOSI = D11;
        static const uint8_t MISO = D12;
        static const uint8_t SCK = D13;
        static const uint8_t SDA = A4;
        static const uint8_t SCL = A5;
        static const uint8_t TX = D0;
        static const uint8_t RX = D1;

1.2.Ultrasonic sensor class-description (abstract device)

For this example I chose to use ultrasonic sensor HC-SR04. It has four pins: VCC, GND, TRIG and ECHO. Though in reality (whatever it is) all four pins are connected to Arduino only TRIG and ECHO are used in program. I don't know what to do with those pins so far. Maybe they should never appear in code.

1.2.1 HC_SR04.h

#ifndef HC_SR04_H
#define HC_SR04_H

#include <inttypes.h>

#include <Arduino.h>

class HC_SR04
    public :
        virtual uint8_t VCC() = 0;//???
        virtual uint8_t TRIG() = 0;
        virtual uint8_t ECHO() = 0;
        virtual uint8_t GND() = 0;//???
        //device specific functions
        void setup();

        long get_distance();

1.2.2 HC_SR04.cpp

#include "HC_SR04.h"

void HC_SR04::setup()
    pinMode( TRIG(), OUTPUT );
    pinMode( ECHO(), INPUT );

long HC_SR04::get_distance()
    digitalWrite( TRIG(), LOW );
    delayMicroseconds( 2 );
    digitalWrite( TRIG(), HIGH );
    delayMicroseconds( 10 );
    digitalWrite( TRIG(), HIGH );

    return 0.017 * pulseIn( ECHO(), HIGH );

2.Commutation description

At this stage programmer should "plug in" all the real devices on the table by overloading pin-funcions in a subclass derived from corresponding class-description.

2.1 Ultrasonic sensor (real device)

2.1.1 US.cpp

#include "Uno.h"
#include "HC_SR04.h"

class US : public HC_SR04
    public :
        uint8_t VCC() { return 0; }//What to do with this pin
        uint8_t GND() { return 0; }//What to do with this pin
        uint8_t TRIG() { return Uno::D10; }//wire between TRIG-pin on US and 10th digital pin on Uno
        uint8_t ECHO() { return Uno::D11; }//...

3. Main file (.ino file)

#include "US.cpp"//bad line?

US us;//Create ultrasonic device plugged in as descripted in US.cpp file

void setup()
    Serial.begin( 9600 );


void loop()
    Serial.println( us.get_distance() );
    delay( 1000 );

4. Project directory structure

enter image description here

5. Discussion

Here is my sight on the approach above. Of course, could be wrong.


  • Additional level of abstraction = (perhaps) bad for low-memory controllers
  • Whole program is divided into parts. Each part is responsible for the communication with the concrete device. (perhaps) Bad for a project with dependent devices


  • Whole program is divided into parts. Each part is responsible for the communication with the concrete device. Good for a project with many independent peripherals
  • Self-descriptive code
  • Reusable code for similar devices or other projects with the same devices

Please, tell me what do you think. As always any ideas, corrections, critic and etc. would be appreciated.

P.S. To upload program to Arduino I use Makefile for Arduino.

  • 1
    \$\begingroup\$ You got the wrong site. With how it's currently phrased, the wrong network even. Discussion isn't something we do except in questions on Stack Exchange. If you got questions about your current software architecture and it's pitfalls, you might want to check Software Engineering @ SE. There are some things to keep in mind before doing so though 1 2 3. \$\endgroup\$
    – Mast
    Sep 10 '18 at 5:28
  • \$\begingroup\$ @Mast. Thank you for your comment. Did not know about Software Engineering SE. Better fits really. \$\endgroup\$
    Sep 10 '18 at 5:47

You describe what you did but you did not sufficiently describe why you did it. What problem you are trying to solve by your design? I see some major problems in your design.


  • does not need inttypes.h. uint8_t is in stdint.h.
  • namespace instead of class would be sufficient. Define these constants as plain int constexpr.
  • Never ever define pins like A0 to A5 incomplete, false and/or overlapping. That compiles and gives nasty errors at runtime. If you have no idea about the pin numbers or your board does not support some you want to know that at compile time.

Main file (.ino file)

#include "US.cpp"//bad line? - The answer is "yes". But it is not your fault here as the arduino suite "asks" for that. For a regular design (you could use a plain cpp-suite) you would split headers and cpp files to gain compile speed. However arduino does not offer a make project but suggest you should work in a single file and include only. You could have the code in your header files for projects that size. see examples (constructor, setup) below.


  • virtual uint8_t VCC() = 0;//??? - even you do not know what it shall be. how should someone else? What problem are you trying to solve? Unless you want to automatically draw schematics you do not need power pin numbers and should remove that ???-lines
  • virtual uint8_t TRIG() = 0; If it was designed for inheritance only it sould be protected. But again here we miss reason for this inheritance-design. I'd prefer a simple private member set at construction time.


  • in `` the last digitalWrite( _trig_pin, HIGH ); is redundant

The inheritance-design as such ...

... is broken.

  • a second sensor on a board requires you to write a second class.
  • the same sensor on a different board requires you to write another class with identical code but a modified include line for the board.

You will end up with a full blown matrix of classes for all board-pin combinations. the same matrix again for a different sensor.

If you want to connect multiple sensors to a single board you definitely do not want to write multiple classes in a class hierarchy differing only by pin numbers. You want to instantiate a single class with different parameters multiple times.

So we change the sensor project

  • to be completely agnostic about board and/or pin
  • to couple modules at the very last level possible. This is at (test-)application level in our case

we change the class to

class HC_SR04
    public :
        HC_SR04(int trig_pin, echo_pin)
            : _trig_pin(trig_pin)
            , _echo_pin(echo_pin)
        //device specific functions
        void setup() {
            pinMode( _trig_pin, OUTPUT );
            pinMode( _echo_pin, INPUT );
        long get_distance() {
            digitalWrite( _trig_pin, LOW );
            delayMicroseconds( 2 );
            digitalWrite( _trig_pin, HIGH );
            delayMicroseconds( 10 );
            return 0.017 * pulseIn( _echo_pin, HIGH );
        int _trig_pin;
        int _echo_pin;

and use it like

#include "HC_SR04.h"

HC_SR04 us(Uno::D10, Uno::D11);
  • \$\begingroup\$ Thank you for your answer. About analog pins (A0, A1, ...). Of course, it is a typo. Corrected. About your change. I thought about it. But changed my mind when realized that there are peripherals with many pins so I should take care of the order of arguments in the constructor. \$\endgroup\$
    Sep 10 '18 at 6:23
  • \$\begingroup\$ If you name the params properly you will have perfect documentation what to pass. there is no way to avoid that. how do you provide a library to be used in future? what is easier to document? params to a constructor? or a class hierarchy where the library has to be extended? \$\endgroup\$
    – stefan
    Sep 10 '18 at 6:27
  • \$\begingroup\$ Analog pins start afaik at 13. but anyway, you should not change your code here as this is then hard to follow for other readers. \$\endgroup\$
    – stefan
    Sep 10 '18 at 6:40
  • \$\begingroup\$ Firstly, this is from analogRead function description: "pin: the number of the analog input pin to read from (0 to 5 on most boards, 0 to 7 on the Mini and Nano, 0 to 15 on the Mega)". Secondly, OK. I won't change my code. \$\endgroup\$
    Sep 10 '18 at 8:27

Not the answer you're looking for? Browse other questions tagged or ask your own question.