Drive Inches With Class

In the last section we showed you how to calibrate your encoders so you could specify distances in inches instead of the arbitrary ticks that the encoders return.  We did computing the inches to ticks ratio and creating a function that does the conversion.  For this program, doing it that way is probably find.  However, the power of C++ gives us another way to approach the problem which we explore in this chapter.

We have, provided for us, a class RobotEncoder which we can use to read an arbitrary tick count which is proportional to the distance the robot moves.  However, what we would really like is an encoder class which returns the distance directly in inches.

As it turns out, C++ will let us create a new class which extends the functionality of the built in RobotEncoder class.  C++ (as well as many other high level languages) has a feature called inheritance whereby you can create a new class that is based on an existing class.  This new class will have all of the functionality of the existing base class, but will also allow us to add new functionality.

To begin, load the SimpleRobotDriveInches example program as our starting point:

#include <Servo.h>
#include  <MsTimer2.h>

#include <SimpleRobot.h>

class MyRobot : public Robot
{
public:
    RobotMotor    *  m_pRightMotor   = 0;
    RobotMotor    *  m_pLeftMotor    = 0;
    RobotEncoder  *  m_pLeftEncoder  = 0;
    RobotEncoder  *  m_pRightEncoder = 0;
    float            m_adjustFactor  = 0.0025;
    float            m_power         = 0.75;
    float            m_scale         = 0.008;
    
    void Setup()
    {
      Serial.println("Setup");
      
      m_pRightMotor   = new RobotYellowMotor(0);
      m_pLeftMotor    = new RobotYellowMotor(1);
      m_pRightEncoder = new RobotEncoder(0);
      m_pLeftEncoder  = new RobotEncoder(1);
      
      m_pLeftMotor->SetPower(m_power);
      m_pRightMotor->SetPower(m_power);
      
      m_pLeftEncoder->SetInvert(true);
      m_pRightEncoder->Reset();
      m_pLeftEncoder->Reset();
    }
    
    float GetDistanceInches(RobotEncoder * pEncoder)
    {
        return(pEncoder->GetPosition() * m_scale);
    }
    
    void Loop()
    {    
        long rightDistance = m_pRightEncoder->GetPosition();
        long leftDistance = m_pLeftEncoder->GetPosition();
        
        if (GetDistanceInches(m_pRightEncoder) >= 20.0)
        {
            m_pLeftMotor->SetPower(0);
            m_pRightMotor->SetPower(0);
        }
        else
        {
            int diff = (rightDistance - leftDistance);
            
            m_pRightMotor->SetPower(m_power - diff * m_adjustFactor);
            m_pLeftMotor->SetPower(m_power + diff * m_adjustFactor);
        }
    }
    
    void Terminate()
    {
    }
} MyRobot;

Robot    *    g_pRobot    = &MyRobot;

Once again, this is the program which we ended up with in the last chapter.

We now want to define our new class, which we will call MyEncoder which will be based on the built in RobotEncoder class.  The first step is to add the framework for the class declaration.  Enter the following code just before the declaration of your MyRobot class:

class MyEncoder : public RobotEncoder
{
};

This is how we create a new class which is based on an existing class.  Our new class is named MyEncoder and it is based on the built in class RobotEncoder.  This new class will have all of the functionality of the RobotEncoder class but will allow us to add additional features.  Note the public declaration right before RobotEncoder.  This is required, if we want users of our class to have access to the methods of the RobotEncoder class (which we do in this case).

We can use our new MyEncoder class by replacing the instances of RobotEncoder in our program.  In the variable declaration section we want to change the following lines:

    RobotEncoder  *  m_pLeftEncoder  = 0;
    RobotEncoder  *  m_pRightEncoder = 0;

to:

    MyEncoder     *  m_pLeftEncoder  = 0;
    MyEncoder     *  m_pRightEncoder = 0;

This declares our encoder pointers to be of our new class. We also will need to change the initialization of those pointers. In our Setup function we need to change the two lines:

      m_pRightEncoder = new RobotEncoder(0);
      m_pLeftEncoder  = new RobotEncoder(1);

to:

      m_pRightEncoder = new MyEncoder(0);
      m_pLeftEncoder  = new MyEncoder(1);

When we try and compile this program, however, we find the following error:

C:\Users\John\Documents\Arduino\Temp\Temp.ino: In member function 'virtual void MyRobot::Setup()':

Temp:27: error: no matching function for call to 'MyEncoder::MyEncoder(int)'

       m_pRightEncoder = new MyEncoder(0);

What is wrong?  The problem is that we need to pass the port number into the constructor, but our class has not defined any constructors that take any arguments.  So what we need to do is to create a constructor that will allow us to specify the port number for the encoder.

To do this, we add the following code to our MyEncoder class:

public:
    MyEncoder(int encoderNo)
    {
    }

Now as we recall, constructors look very much like functions, except they are always named the same as the class and never have a return value specified.  Notice also that we added the public declaration before we defined our constructor.  We need to do this if we wish our constructor to be accessible to users of our class.

Now our constructor take a parameter which will be the port number for the encoder that we will pass in when we create an instance.  Note, however, that we currently not doing anything with this number.  This cannot be right!

If we remember, the constructor for the RobotEncoder class, upon which our class is based, requires us to provide one integer argument which specifies the port.  So what we need to do is to somehow make our constructor pass the encoderNo that it is receiving to the constructor for the base class RobotEncoder.  We do this by changing our constructor as follows:

public:
    MyEncoder(int encoderNo) : RobotEncoder(encoderNo)
    {
    }

This tells the compiler that, before executing our constructor, that it should first call the constructor for the RobotEncoder class and pass it the value encoderNo.

At this point we should be able to compile our program, and if we run it, we will find that it works just as it did before.  This is because this new class, MyEncoder, now has exactly the same functionality as the original base class, RobotEncoder.

Now we can start to modify our class to add additional functionality.  What we want to add is a function that will return the distance in inches.  We will declare this function as follows:

    float GetDistanceInches()
    {
        return(GetPosition() * 0.008);
    }

This code should be added right after the MyEncoder constructor that we just created.

The body of our function is very simple.  First we call the GetPosition function of the RobotEncoder base class, which returns the distance in ticks.  We then multiply this number by the scale factor we computed in the previous section (i.e. 0.008) and return the result.  Note that we do not need any qualifiers to access the GetPosition function of RobotEncoder. This is because our class inherits from that class and thus has access to all (non private) functions and variables.  Also note that when we declared our GetDistanceInches function we had it return a type float instead of integer, because we wish to be able to measure the distance in fractions of an inch rather than just whole numbers.

Now the only thing left is to use our new function to measure the distance.  We do this by changing the following line in our Loop function:

        if (GetDistanceInches(m_pRightEncoder) >= 20.0)

to:

        if (m_pRightEncoder->GetDistanceInches() < 20.0)

We can also delete the GetDistanceInches function that we created in our MyRobot class, since we will no longer be needing it.

If we now compile and run our program, we once again should find that the robot drives straight for 20 inches.

Before we move on to the next chapter we need to clean up a few things.  If we look at our code, we once again see that we have an arbitrary number (i.e. 0.008) in the middle of our code.  Like before, we want to move this to a variable and initialize it to our 0.008 value.  In addition, to make this class more versatile, we would like a way to change the scale value to accommodate robots with different wheel sizes.  To this effect we will add a second function SetScale which will allow us to do this.  Taking these changes into account, our MyEncoder class should now look like:

class MyEncoder : public RobotEncoder
{
    float    m_scale    = 0.008;
    
public:
    MyEncoder(int encoderNo) : RobotEncoder(encoderNo)
    {
    }
    
    float GetDistanceInches()
    {
        return(GetPosition() * m_scale);
    }
    
    void SetScale(float scale)
    {
        m_scale    = scale;
    }
};