Introduction
With all the questions surrounding libraries in the Arduino IDE, I thought I'd start my next blog about creating a class used as a library for my Ping sensor. As a start, I'll use the example sketch from the Arduino examples. Below is the example code created by David Mellis and Tom Igoe with the majority of the comments removed:
Original (uncommented) Sketch
/*
created 3 Nov 2008
by David A. Mellis
modified 30 Aug 2011
by Tom Igoe
This example code is in the public domain.
*/
const int pingPin = 9;
void setup() {
Serial.begin(9600);
}
void loop()
{
long duration, inches, cm;
pinMode(pingPin, OUTPUT);
digitalWrite(pingPin, LOW);
delayMicroseconds(2);
digitalWrite(pingPin, HIGH);
delayMicroseconds(5);
digitalWrite(pingPin, LOW);
pinMode(pingPin, INPUT);
duration = pulseIn(pingPin, HIGH);
inches = microsecondsToInches(duration);
cm = microsecondsToCentimeters(duration);
Serial.print(inches);
Serial.print("in, ");
Serial.print(cm);
Serial.print("cm");
Serial.println();
delay(100);
}
long microsecondsToInches(long microseconds)
{
return microseconds / 74 / 2;
}
long microsecondsToCentimeters(long microseconds)
{
return microseconds / 29 / 2;
}
Verifying this sketch, the program comes in at 3432 bytes and produces some output in the serial window. I'm using pin 9 for my sensor, so that was the only minor change made to the sketch before compiling and running it on my Arduino.
Overview and Goals
The overall goal of this blog is to show how a simple sketch can be made into a C++ Class Library to hide some of the more complicated code and global variables from novice users. By using a class, we are able to encapsulate both the data and the logic into a user friendly interface and eliminate the need for global variables clogging up our main loop() method and source file.
Library Files
Next I create 2 files for my library in the project directory: Ping.h and Ping.cpp. Ping.h is the header file that defines the class and the available methods used by the public. It also contains any setup or other header files needed to operate correctly.
Ping.h
#include <Arduino.h>
#ifndef PING_H
#define PING_H
class Ping
{
private:
int sensorPin; // pin to use for the sensor
long duration; // microseconds of the ultrasonic pulse
long cm; // calculated centimeters of distance
long inches; // calculated inches of distance
Ping(); // don't allow default constructor
// conversions functions
long microsecondsToInches(long microseconds);
long microsecondsToCentimeters(long microseconds);
public:
Ping(int pin); // constructor
void pulse(); // send the command to send a ultrasonic pulse
long getInches() { return inches; } // get our reading in inches
long getCentimeters() { return cm; } // get our reading in cm
long getMicroseconds() { return duration; } // get our reading in microseconds
};
#endif
Some notes on the Ping.h file.
- We include the Arduino.h header file in this header file because we use pin functions in our class code (shown later). It is okay to include the Arduino.h header file here as it will have logic to only include the code one time in it's definition, just like we have in our Ping.h definition.
- The #ifndef PING_H and associated #define are there to ensure we only define our class one time in case we include our ping code in multiple places. Without these compiler directives, we might get errors telling us we've already defined our class.
- I've put all the stored distance values, the sensor pin number and the calculation functions as private. These are private to allow me to ensure that only my library can manipulate the values, perform calculations, etc. I don't want someone to set the duration of the ultrasonic pulse for instance and this allows me to control what the user has access to via getter/setter functions.
- The default constructor, or function used to create and initialize the object, is private. I only want the user to initialize the object by passing in the pin of the sensor. If you had a default pin, then it would be okay to move the Ping() constructor method from the private area to the public one and then set up the pin in the function definition.
- Finally, I'm hiding the conversion from microseconds to units from the user since I think they do not need to access those functions directly and it allows me to change the internal workings of the class without breaking any user code in the future.
Next up is the definition of the more complex methods of the class. For getter/setter methods, I usually just code those into the header file since the code is very small. If the code spreads over a couple of lines, then I believe it's best to just put them in the .cpp file.
Ping.cpp
#include "Ping.h"
Ping::Ping() {
// Do nothing
}
Ping::Ping(int pin) {
sensorPin = pin;
}
void Ping::pulse() {
// Tell the sensor to send a signal
pinMode(sensorPin, OUTPUT);
digitalWrite(sensorPin, LOW);
delayMicroseconds(2);
digitalWrite(sensorPin, HIGH);
delayMicroseconds(5);
digitalWrite(sensorPin, LOW);
// Read the duration of the ping
pinMode(sensorPin, INPUT);
duration = pulseIn(sensorPin, HIGH);
// Convert our units
inches = microsecondsToInches(duration);
cm = microsecondsToCentimeters(duration);
}
long Ping::microsecondsToInches(long microseconds) {
return microseconds / 74 / 2;
}
long Ping::microsecondsToCentimeters(long microseconds) {
return microseconds / 29 / 2;
}
Some notes on the Ping.cpp file:
- I usually put a comment in empty code blocks, like 'Do Nothing', to remind me that I intended to have an empty block. This is just a convension that I use.
- The majority of the sample code ended up in the pulse() method. This method triggers the Ping sensor, reads the signal duration, and calculates the distances in inches and centimeters. The user can then get the information out of the class using the getInches(), getCentimeters() and getDuration() methods instead of accessing the variables directly.
- Since all the calculations and variables are private to the class, I can change those names or functions without changing my main logic.
The main project code
#include "Ping.h"
Ping pingSensor(9);
void setup() {
Serial.begin(9600);
}
void loop()
{
pingSensor.pulse();
Serial.print(pingSensor.getInches());
Serial.print("in, ");
Serial.print(pingSensor.getCentimeters());
Serial.print("cm");
Serial.println();
delay(100);
}
The size is now 3518 bytes and produces the same output. As you can see, the class cleans up and hides all the implementation details but still allows us to perform the same tests and all for 86 more bytes than the original. We could control multiple Ping sensors with the same code by adding additional variables like 'pingSensor2(10)' for a sensor on pin 10, for example.
Conclusion
I'm hoping this simple example can help generate some conversation into classes and basic library construction. It will be the baseline of a series of blog articles about creating a library to run our robot using the Robot Builder's R-Duino board and hopefully help those out with similar projects.
Maus
Qustion via Shout Box: Couldn't you just divide the inches calculation by 37 instead of performing 2 calculations?
A: Of course we could, we can change those formulas and any code in the class without it bothering any other code in any program that uses this library. Our new function could look like this:
long Ping::microsecondsToInches(long microseconds) {
return microseconds / 37;
}
Question: What is a 'getter' function? What is a 'setter' function? How are they used or what good are they?
When you start looking at classes, you will start to see some methods of that class that start with the words 'get' and 'set'. These types of functions are sometimes called 'getters' and 'setters' and they provide a way for the user of the class to pass information into the class or retrieve data from the class.
In our sample code, we have a 'duration' in microseconds that we use for our calculations to inches and centimeters. I made the duration variable private because I did not want the user to access this variable directly in case I changed the name of the variable or how the variable is used within my class. I did want the user to be able to get the raw sensor data from the class. In this case I created a 'get' function to allow the user to access that piece of information within the Ping.h file:
long getMicroseconds() { return duration; }
This is a 'getter' type method because it does one thing, it gets the data for the user by returning a value from our class. Suppose, for this example, that we wanted to set the duration of the pulse in our class. In this case we could create a 'set' method to handle that action by the user. It would look something like this:
void setMicroseconds(long ms) { duration = ms; }
Again, in this case we hide the actual variables from the user but provide a method to handle the data coming into the class. A 'set' type method does one thing, it sets an internal variable in our class with some outside value.
Let us suppose that we were having some issues with the conversion code and wanted to make sure the duration being passed in was always greater than zero. We could now change our 'set' method to accomodate filtering the data and the program using our library would not have to change. For example:
void setMicroseconds(long ms) {
duration = ms;
if (duration < 0) {
duration = 0;
}
}
One advantage of using 'get' and 'set' methods is just what we showed in our example. The code in the get/set method can be changed to accomodate changing requirements without disrupting the calling program. If we had used our duration variable as a public variable, then the user could put whatever they wanted into our program and would have to know to only send positive integers. By hiding those variables from the user and creating methods to access and retrieve data, we can now ensure the data is okay to use and will not blow up unexpectedly.