Write your own drivers for Arduino

Lead Image © studiostocks, 123RF.com

Easy Driver

So, you have some new kit for your Arduino – maybe some sensors or ICs – but programming them is clumsy and painful. Don't despair: You can make your life easier by writing your own drivers!

Writing drivers (or libraries) for Arduino is not hard – quite the contrary. And, it can make your sketches clearer and reduce the line count considerably. Consider the driver for the MC14051B [1] in this article, for example.

I talked about this little critter in a previous article [2] (which you may want to read before tackling this one) and how it's easy for Arduino sketches to get out of hand because of the number of lines you need for even the simplest of set ups.

As mentioned previously, the MC14051B (Figure 1) gives you eight analog ports that you can connect to from one analog pin on your Arduino (Figure 2). You will also have to use four digital pins: three to tell the MC14051B which of its eight ports you're going to use (using binary), and one for the inhibitor pin that you use to reset the chip. Yet, eight analog ports in exchange for one analog and four digital pins is still a bargain.

Figure 1: Yep, we're talking about this little chap again. The Arduino ecosystem can benefit from a driver for the MC14051B.
Figure 2: The MC14051B ports and pins mapped.

The tradeoff was that sketches that used the multiplexer were long and clunky. Look at Listing 1, for example. This sketch reads in values from one sensor connected to the MC14051B's X3 port – I repeat, one (1) sensor – and it is already 37 lines long! Add new sensors or output devices, and the number of lines balloons.

Listing 1

MC14051B Sketch Without Library

01 #include "Wire.h"
02
03 int controlPin=7;
04 int A=8;
05 int B=9;
06 int C=10;
07 int X=A0;
08
09 void setup ()
10 {
11
12   pinMode(controlPin, OUTPUT);
13   pinMode(A, OUTPUT);
14   pinMode(B, OUTPUT);
15   pinMode(C, OUTPUT);
16   pinMode(X, INPUT);
17
18   Serial.begin(9600);
19 }
20
21 void loop ()
22 {
23   digitalWrite(A, 1);
24   digitalWrite(B, 1);
25   digitalWrite(C, 0);
26
27   Serial.println(analogRead(X));
28
29   reset();
30 }
31
32 void reset()
33 {
34   digitalWrite(controlPin, HIGH);
35   delay(10);
36   digitalWrite(controlPin, LOW);
37 }

A much cleaner and neater version using a library is what you can see in Listing 2. It is only 16 lines long, and you can understand better what's going on. It also makes writing scripts more intuitive, because it is easier to remember that

result=expander.mcAnalogRead(3);

reads from the MC14051B's X3 port than having to write

digitalWrite(8, 1);
digitalWrite(9, 1);
digitalWrite(10, 0);
result=analogRead(A0);

Right?

However, the MC14051B driver doesn't exist yet. That shouldn't be a problem. I'll explain how to write it.

Listing 2

MC14051B Sketch with Library

01 #include <MC14051B.h>
02 #include "Wire.h"
03
04 int ABCpins[] = {8, 9, 10};
05 MC14051B expander(7, ABCpins, A0);
06
07 void setup ()
08 {
09   Serial.begin(9600);
10 }
11
12 void loop ()
13 {
14   Serial.println(expander.mcAnalogRead(3));
15   expander.reset();
16 }

Two Files

For an Arduino driver you're always going to need at least two files: a header file (with a .h extension), which contains the definition of the class that implements the driver, and the code file (with a .cpp extension), which contains the code proper.

First, create a directory with the name of your library. For this example, you can use MC14051B. Next, cd into the directory you just made (or visit it with your file browser) and create two plain-text files inside, one called MC14051B.h and the other MC14051B.cpp.

Open MC14051B.h with your favorite text editor and type in what you see in Listing 3.

Listing 3

A Library Header Skeleton

01 #ifndef MC14051B_h
02 #define MC14051B_h
03
04 #include "Arduino.h"
05
06 class MC14051B
07 {
08   public:
09       MC14051B();
10       ~MC14051B();
11
12   private:
13 };
14
15 #endif

Arduino libraries are written in C++, and this is the most basic skeleton of a library header file. It defines the class that will give you the features you need.

Here, the #ifndef#endif combo checks to see whether MC14051B_h is defined; if not, it defines it by declaring the MC14051B class. Next, you must always include the Arduino.h library. This will drag in all the Arduino programming goodies that will make your life much easier.

As for the class itself, so far, you have the class constructor (MC14051B()) and the class destructor (~MC14051B()).

The class constructor would usually contain the code that initializes whatever you want the class to do. To initialize the MC14051B, your class will need to know into which pin on your Arduino the MC14051B's inhibitor port is plugged and the pins on your Arduino that are connected to the MC14051B's A, B, C, and X ports. To do this, change the declaration of the constructor to:

MC14051B(int controlPin, int ABCPin[3], int X);

The destructor will just need to deactivate the MC14051B. Because this is done through the chip's inhibitor port, and you already passed that onto the class via the constructor, it doesn't need any parameters.

Of course, you will also want a function to read from the chip and another to write to the chip. Add the following lines below the declaration of the destructor:

int mcAnalogRead(char pin);
void mcAnalogWrite(char pin, int value);

The mcAnalogRead() and mcAnalogWrite() functions need to know the port number the MC14051B has to read from. The mcAnalogWrite() function also needs to know what you want to write to the port. I'll explain how to convert the integer pin parameter into a set of 0s and 1s for the A, B, and C ports later.

The last public method you need is a reset(). This resets the MC14051B by stopping and flushing data from the chip and then switching the flow back on again. Add the line

void reset();

to the class's public section.

You also need to declare the variables you will use within the class. You need an array to store the Arduino pin numbers connected to the MC14051B's A, B, and C ports and integers for the X and control pins (i.e., the pin connected to the inhibitor). Many programmers signal that a variable is internal to a class by starting it with an underscore (_), so I'll do that for clarity's sake. Add the following lines to the private section in the class declaration:

int _ABC[3];
int _X;
int _control;

Finally, you need a module that will figure out which of the A, B, and C ports to set to HIGH and which to LOW given the value of the pin parameter passed to the read and write modules you saw above. Add the following to the end of the private section:

void setABCPin(char pin);

In the end, your final header file should look similar to Listing 4.

Listing 4

MC14051B.h

01 #ifndef MC14051B_h
02 #define MC14051B_h
03
04 #include "Arduino.h"
05
06 class MC14051B
07 {
08   public:
09       MC14051B(int controlPin, int ABCPin[3], int X);
10       ~MC14051B();
11       int mcAnalogRead(char pin);
12       void mcAnalogWrite(char pin, int value);
13       void reset();
14
15  private:
16       int _ABC[3];
17       int _X;
18       int _control;
19       void setABCPin(char pin);
20 };
21
22 #endif

Implementing the code for the class itself in MC14051B.cpp is pretty straightforward. Take a look at Listing 5.

Listing 5

MC14051B.cpp

01 #include "Arduino.h"
02 #include "MC14051B.h"
03
04 MC14051B::MC14051B(int controlPin, int ABCPin[3], int X)
05 {
06     int i;
07
08     pinMode(controlPin, OUTPUT);
09
10     for (i=0; i<3 ; i++)
11     {
12         pinMode(ABCPin[i], OUTPUT);
13         _ABC[i]=ABCPin[i];
14     }
15     _control=controlPin;
16     _X=X;
17 }
18
19 int MC14051B::mcAnalogRead(char pin)
20 {
21     pinMode(_X, INPUT);
22
23     setABCPin(pin);
24     return(analogRead(_X));
25 }
26
27 void MC14051B::mcAnalogWrite(char pin, int value)
28 {
29     pinMode(_X, OUTPUT);
30
31     setABCPin(pin);
32     analogWrite(_X, value);
33 }
34
35 void MC14051B::setABCPin(char pin)
36 {
37     int i;
38
39     for (i=0; i<3; i++)
40     {
41         digitalWrite(_ABC[i], bitRead(pin, i));
42     }
43 }
44
45 void MC14051B::reset()
46 {
47     digitalWrite(_control, HIGH);
48     delay(10);
49     digitalWrite(_control, LOW);
50 }
51
52 MC14051B::~MC14051B()
53 {
54     digitalWrite(_control, HIGH);
55 }

The MC14051B() constructor (lines 4--17) reads the parameters and assigns them to internal class variables and does a bit of housework, such as setting some pins to OUTPUT.

The mcAnalogRead() module (lines 19--25) does what it says on the package: it first sets the _X pin to INPUT so you can read in from it. Then, it sends off the pin parameter to setABCPin() to set the A, B, and C pins to HIGH and LOW. Once that is done, it reads in and returns the value available on the MC14051B's X port.

The mcAnalogWrite() module (lines 27--33) is equally simple. It sets _X to OUTPUT and push the contents of value through X to pin on the MC14051B.

The setABCPin() module (lines 35--43) picks out the 0s and 1s from the pin parameter using Arduino's bitReader() function. These are then pushed to the A, B, and C pins to set which port on the MC14051B you're going to read from or write to. Check out the different combinations in Table 1.

Table 1

A, B, and C Combinations

C

B

A

Port

0

0

0

X0

0

0

1

X1

0

1

0

X2

0

1

1

X3

1

0

0

X4

1

0

1

X5

1

1

0

X6

1

1

1

X7

For a detailed explanation on how this works, again, check out my previous article on the MC14051B [2].

The reset() function (lines 45--50) sets the inhibitor port on the MC14051B to HIGH, which stops the flow of data through the chip. It waits a few milliseconds, then sets the inhibitor to LOW again, which opens up the chip to more data.

The ~MC14051B() destructor simply closes the chip down by poking a HIGH to the inhibitor port.

Installing and Testing

To install your library, copy the whole directory containing MC14051B.h and MC14051B.cpp to your Arduino/libraries/ directory. Then, the next time you run the Arduino IDE, you will find your library under Sketch > Include library, at the bottom of the list, under Contributed libraries.

To check everything is okay and your library compiles, you can create a bare-bones sketch like the one shown in Listing 6 in your Arduino IDE (Figure 3).

Listing 6

Test Library

01 #include <MC14051B.h>
02 void setup (){}
03 void loop (){}
Figure 3: Use a bare-bones Arduino sketch to debug your library.

Hit the Verify button (the one with the tick in the toolbar), and your library will be compiled. Any errors in your code will show up in the bottom half of the IDE in red.

Of course, uploading this sketch to an actual Arduino is pointless, because it doesn't do anything. Also, this sketch will not reveal any problems with the logic in your code; it only shows syntactical mistakes. The proof of the logic pudding is in the eating – that is, running the code – so here goes!

Buy this article as PDF

Express-Checkout as PDF

Pages: 6

Price $2.95
(incl. VAT)

Buy Raspberry Pi Geek

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content