Often I want Arduino board to control several peripherals. Writing program for all of them in one document using #define SOME_PIN 4
constructions seems pretty inconvenient to me. Especially in such a case when you have to modify old code which was written a long time ago.
I chose to use the following approach to organize programming part of a project. So writing a 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 = 1;
static const uint8_t A2 = 2;
static const uint8_t A3 = 3;
static const uint8_t A4 = 4;
static const uint8_t A5 = 5;
//SPI
static const uint8_t SS = D10;
static const uint8_t MOSI = D11;
static const uint8_t MISO = D12;
static const uint8_t SCK = D13;
//I2C
static const uint8_t SDA = A4;
static const uint8_t SCL = A5;
//Serial
static const uint8_t TX = D0;
static const uint8_t RX = D1;
};
#endif
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 :
//pin-functions
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();
};
#endif
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 );
us.setup();
}
void loop()
{
Serial.println( us.get_distance() );
delay( 1000 );
}
4. Project directory structure
5. Discussion
Here is my sight on the approach above. Of course, could be wrong.
Disadvantages:
- 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
Advantages:
- 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.