SARCNET Object Oriented Programming in C++
Published by Julie & Joe in Robotics · Saturday 24 Aug 2019
Getting into robotics with a group of enthusiastic youngsters all writing and sharing their own code can be a little daunting. We decided we would need to learn Object Oriented Programming in C++ so that we could create reusable Arduino libraries. You can also refer to our OOP workshop for more details.
The header file has the following important lines:
We have been working with different ways to control our robot platform's motors. We wired up our motors, battery and L298N motor controller. First we tried an ON/OFF control approach. It was pretty jerky. Then we tried using Pulse Width Modulation (PWM). It was much smoother, but we realized that motor control by itself isn't the entire solution. Eventually there will need to be some form of a closed-loop control system that actively controls the motors to achieve more precise movement and navigation. Before we embark on that ambitious journey, we decided to take some time off. We needed to learn about Object Oriented Programming in C++ which, unlike our "spaghetti" code, we should all be able to read and use more easily. And we need to learn how to create reusable Arduino libraries, which we should all be able to share with others.
Here is the original motor control demo sketch re-written in Object Oriented C++:
//Motor drive example using C++ programming//Declare the Drive class, which controls the four-wheel drive subsystemclass Drive {
public: //Declare the externally accessible variables and methods for this classDrive(); //Declare the class constructor. Executed when each class object is created.void velocity(int left, int right); //Set the left and right motor speedsvoid accelerate(int left, int right, int steptime); //Accelerate to the speeds in steptime incrementsprivate: //Declare the hidden variables and methods for the class//Declare the Arduino PWM pin numbers used to drive the motorsconst int PWM1 = 9; //Left forward drive pinconst int PWM2 = 6; //Left reverse drive pinconst int PWM3 = 5; //Right forward drive pinconst int PWM4 = 3; //Right reverse drive pin//Declare the left and right motor speed variablesint leftSpeed, rightSpeed;
};//Declare the Drive class methodsDrive::Drive() {
//Drive class constructor//Set the Arduino PWM pin mode for each pinpinMode(PWM1, OUTPUT);pinMode(PWM2, OUTPUT);pinMode(PWM3, OUTPUT);pinMode(PWM4, OUTPUT);//Make it safe - Stop the motor driveanalogWrite(PWM1, 0);analogWrite(PWM2, 0);analogWrite(PWM3, 0);analogWrite(PWM4, 0);//Set the initial speeds to zeroleftSpeed = 0;rightSpeed = 0;
}void Drive::velocity(int left, int right) {
//This velocity method sets the left and right motor speeds//Note: The left and right motor speeds parameters are in the range -255..255//Where: -255 means full reverse, 255 means full forward and 0 means stopped//These parameters must be converted to 0..255 on the appropriate PWM drive pin//Set the left motor speed in the range 0..255 on the appropriate PWM drive pinif (left > 0) {analogWrite(PWM1, left);analogWrite(PWM2, 0);} else {analogWrite(PWM1, 0);analogWrite(PWM2, -left);}//Set the right motor speed in the range 0..255 on the appropriate PWM drive pin.if (right > 0) {analogWrite(PWM3, right);analogWrite(PWM4, 0);} else {analogWrite(PWM3, 0);analogWrite(PWM4, -right);}//Save the left and right motor speeds for later use in the accelerate methodleftSpeed = left;rightSpeed = right;
}void Drive::accelerate(int left, int right, int steptime) {
//Accelerate (or decelerate) from the current speed to the specified speed//Get the left and right initial speeds (previously saved)int leftStart = leftSpeed;int rightStart = rightSpeed;//Get the left and right final speeds (as specified)int leftStop = left;int rightStop = right;//Linearly modify the speed from leftStart to leftStop and rightStart to rightStop//in 256 equal steps each lasting steptimefor (int i = 0; i <= 255; i = i + 1) {
//The map function atomatically computes the correct ratio of each stepvelocity(map(i, 0, 255, leftStart, leftStop), map(i, 0, 255, rightStart, rightStop));delay(steptime); //Wait for the step time between each step
}
}Drive drive;//Declare acceleration constantsconst int STEPTIME = 10;const int HOLDTIME = 1000;void setup() {
//Run once at startup
}void loop() {
//Repeat forever//Move around in an approximate square pattendrive.accelerate(255, 255, STEPTIME); //Accelerate to full forward.drive.accelerate(255, -255, STEPTIME); //Turn right for one seconddelay(TURNTIME);drive.accelerate(255, 255, STEPTIME); //Accelerate to full forward.drive.accelerate(255, -255, STEPTIME); //Turn right for one seconddelay(TURNTIME);
}
When we use C++ we first create classes, which contain their own methods and attributes, and then we create one or more objects based on those classes. Our classes have public and private parts. Anyone who uses our classes can use the public parts, but only our class methods can use the private parts. We also define a class constructor, which is a very special method that initialises all the attributes of our class.
Now we split up the same program into three separate files, which we can share with others as a motor drive library and our original demo program. The required files are a header file, a C++ file and an Arduino sketch. The header and C++ files are reusable as a library. The Arduino sketch is just our demo program.
Importantly the three files have different file extensions:
- The header file has the extension .h
- The C++ file has the extension .cpp
- The Arduino sketch file has the extension .ino
It is really easy to create the new files by first creating two new tabs on our Arduino IDE desktop. Just click on the dropdown to the right side of the tab bar and select New Tab. Then type in the full filename of each new file.
Our original demo sketch was called motor4.ino. We created two new tabs called drive.h and drive.cpp as shown below. Then we simply cut and pasted the Drive class definition to the drive.h tab and Drive class methods to the drive.cpp tab. OK there is slightly more to it than that, but it is pretty easy.
The three files will be placed in the Arduino sketch folder of the same name as the main Arduino sketch file.
When we create files to share, we always put our header at the top which shows the name and purpose of the file, then our copyright notice and finally the GNU public licence so that others can copy and modify the code for their own purposes.
So, here is the drive.h file. It is a C++ header file:
//drive.h - Library for the four-wheel motor drive subsystem.//Copyright (c) 2015, Julie Gonzales VK3FOWL and Joe Gonzales VK3YSP.//Released under the GNU General Public License.#ifndef DRIVE_H //Protect this library from being included multiple times#define DRIVE_H#include "Arduino.h"//Declare the Drive class, which controls the four-wheel motor drive subsystemclass Drive {
public: //Declare the externally accessible variables and methods for this class//Declare the defualt Arduino PWM pin numbers used to drive the motorsint pwm1; //Left forward drive pinint pwm2; //Left reverse drive pinint pwm3; //Right forward drive pinint pwm4; //Right reverse drive pinDrive(int pin1, int pin2, int pin3, int pin4); //Declare the class constructor. Executed when each class object is created.void velocity(int left, int right); //Set the left and right motor speedsvoid accelerate(int left, int right, int steptime); //Accelerate to the speeds in steptime incrementsprivate: //Declare the hidden variables and methods for this class//Declare the left and right motor speed variablesint leftSpeed, rightSpeed;
};#endif
The header file has the following important lines:
#ifndef DRIVE_H //Protect this library from being included multiple times#define DRIVE_H#include "Arduino.h"...#endif
These lines protect the library from being included multiple times. Just declare a variable, DRIVE_H, based on the filename, after first checking if the same variable has been previously defined. Note: You will probably need to include Arduino.h, manually, as its inclusion is automatic in the main sketch, but not elsewhere.
That's all for the header. So, here is the "guts" of the library files. It is the C++ file:
//drive.cpp - Library for the four-wheel motor drive subsystem.//Copyright (c) 2015, Julie Gonzales VK3FOWL and Joe Gonzales VK3YSP.//Released under the GNU General Public License.#include "Arduino.h"#include "drive.h"//Declare the Drive class methodsDrive::Drive(int pin1, int pin2, int pin3, int pin4) {
//Drive class constructorpwm1 = pin1;pwm2 = pin2;pwm3 = pin3;pwm4 = pin4;//Set the Arduino PWM pin mode for each pinpinMode(pwm1, OUTPUT);pinMode(pwm2, OUTPUT);pinMode(pwm3, OUTPUT);pinMode(pwm4, OUTPUT);//Make it safe - Stop the motor driveanalogWrite(pwm1, 0);analogWrite(pwm2, 0);analogWrite(pwm3, 0);analogWrite(pwm4, 0);//Set the initial speeds to zeroleftSpeed = 0;rightSpeed = 0;
}void Drive::velocity(int left, int right) {
//This velocity method sets the left and right motor speeds//Note: The left and right motor speeds parameters are in the range -255..255//Where: -255 means full reverse, 255 means full forward and 0 means stopped//These parameters must be converted to 0..255 on the appropriate PWM drive pin//Set the left motor speed in the range 0..255 on the appropriate PWM drive pinif (left > 0) {analogWrite(pwm1, left);analogWrite(pwm2, 0);} else {analogWrite(pwm1, 0);analogWrite(pwm2, -left);}//Set the right motor speed in the range 0..255 on the appropriate PWM drive pin.if (right > 0) {analogWrite(pwm3, right);analogWrite(pwm4, 0);} else {analogWrite(pwm3, 0);analogWrite(pwm4, -right);}//Save the left and right motor speeds for later use in the accelerate methodleftSpeed = left;rightSpeed = right;
}void Drive::accelerate(int left, int right, int steptime) {
//Accelerate (or decelerate) from the current speed to the specified speed//Get the left and right initial speeds (previously saved)int leftStart = leftSpeed;int rightStart = rightSpeed;//Get the left and right final speeds (as specified)int leftStop = left;int rightStop = right;//Linearly modify the speed from leftStart to leftStop and rightStart to rightStop//in 256 equal steps each lasting steptimefor (int i = 0; i <= 255; i = i + 1) {//The map function atomatically computes the correct ratio of each stepvelocity(map(i, 0, 255, leftStart, leftStop), map(i, 0, 255, rightStart, rightStop));delay(steptime); //Wait for the step time between each step}
}
Note that all class methods need to be prefixed with the class name and two colons (e.g. Drive::).
Now here is the original sketch (it is much simpler than before). It just includes the reference to the header file, an object based on the new class is created by calling the class constructor method with the required information. Also required are prefixes to all class methods and variable with the object name and a dot (e.g. drive.)
//motor4.ino - Motor drive example of the four-wheel motor drive subsystem using C++ programming.//Copyright (c) 2015, Julie Gonzales VK3FOWL and Joe Gonzales VK3YSP.//Released under the GNU General Public License.#include "drive.h" //Include the motor drive class//Declare constants//Motor drive pinsconst int LEFT_FWD = 9;const int LEFT_REV = 6;const int RIGHT_FWD = 5;const int RIGHT_REV = 3;//Create objectsDrive drive(LEFT_FWD, LEFT_REV, RIGHT_FWD, RIGHT_REV); //Create a motor drive object//Declare acceleration constants for the democonst int STEPTIME = 10;const int TURNTIME = 1000;void setup() {
//Run once at startup
}
void loop() {//Repeat forever//Move around in an approximate square pattendrive.accelerate(255, 255, STEPTIME); //Accelerate to full forward.drive.accelerate(255, -255, STEPTIME); //Turn right for one seconddelay(TURNTIME);drive.accelerate(255, 255, STEPTIME); //Accelerate to full forward.drive.accelerate(255, -255, STEPTIME); //Turn right for one seconddelay(TURNTIME);
}
So here is a summary of the structure of the files required:
header file: name.h
#ifndef NAME_H#define NAME_H#include "Arduino.h"class Name{public: //Visible methods and attributesName(); //Class constructor...private: //Hidden methods and attributes...};#endif
C++ file: name.cpp
#include "Arduino.h"#include "name.h"Name::Name() { //Class constructor has no type}void Name::method1(){ //Class method with type}
Demo file: demo.ino
#include "name.h"Name name();setup(){name.method1();}
Now that we can code in C++ and write Arduino libraries, the rest of our project will be much, much easier. We always say that it is never too early to start doing things properly. It turns out that little kids pick up the OOP patterns required far more quickly than adults. This is definitely the way to go. We hope that you agree.
In another Robotics Forum we will look at multitasking in C++ on the Arduino. Since the Arduino does not support threading, we will have to create our own DIY multitasking "operating system". Surprisingly, this is mainly about getting rid of the horrid delay() function and replacing it with our own, non-blocking timer class. It should be a lot of fun to see our little Arduino do several things at once!
0
reviews