0

My C++ skills are rather amateurish, but I’m trying to write a library for a differential drive robotic platform. The library in question needs to utilise a PID library and a rotary encoder library.

I’d like to utilise “levels of abstraction” to do this, rather than just having a massive “sketch” as it makes things a lot easier to read, and to be honest, is the point of object orientated programming. I.e i can write a class to control the a dc motor and just use multiple instances for each side. (I apologise if I’ve got the terminology wrong).

Essentially I’m having trouble utilising the third party libraries within my library.

If i was to write a simple sketch that used the two third party libraries, it would look like this (paraphrasing) -

#include <Arduino.h>
#include <RotaryEncoder.h>
#include <PID_v1.h>
#include <L298N.h>

char A_IN1 = 8;
char A_IN2 = 9;
char A_EN = 17;
char PIN_IN1 = 4;
char PIN_IN2 = 5;

double Setpoint, Output;
double Input = 0;
double Kp=1.3, Ki=15, Kd=0.01;

RotaryEncoder encoder(PIN_IN1, PIN_IN2, RotaryEncoder::LatchMode::TWO03);
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);


void checkPosition(){
   //some code that increments the encoder value
}

void setup(){
  attachInterrupt(digitalPinToInterrupt(PIN_IN1), checkPosition, CHANGE);
  pinMode(PIN_IN1, INPUT);
  attachInterrupt(digitalPinToInterrupt(PIN_IN2), checkPosition, CHANGE);
  pinMode(PIN_IN2, INPUT);
  Setpoint = 0;
  myPID.SetSampleTime(20);
  myPID.SetMode(AUTOMATIC);
  myPID.SetOutputLimits(0,255);
}

void loop(){
  //loop code that uses PID to drive motors at appropriate speed etc etc
} 

How does one go about implementing the two libraries into a custom library?

E.g two files L298N.cpp and L298N.h? Below is what I’ve tried, but no luck.

L298N.cpp

#include "Arduino.h"
#include "L298N.h"
#include <RotaryEncoder.h>
#include <PID_v1.h>

L298N::L298N(char IN1, char IN2, char EN, char A_INT1, char A_INT2){
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  pinMode(EN, OUTPUT);

  attachInterrupt(digitalPinToInterrupt(A_INT1), checkPosition, CHANGE);
  attachInterrupt(digitalPinToInterrupt(A_INT2), checkPosition, CHANGE);
  pinMode(A_INT1, INPUT);
  pinMode(A_INT2, INPUT);
    
  RotaryEncoder encoder(A_INT1, A_INT2, RotaryEncoder::LatchMode::TWO03);
  PID pid(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);

  pid.SetSampleTime(20);
  pid.SetMode(AUTOMATIC);
  pid.SetOutputLimits(0,255);
    
  _IN1 = IN1;
  _IN2 = IN2;
  _EN = EN; 
}

void L298N::checkPosition(){
  encoder.tick(); // just call tick() to check the state.
}

double L298N::calculate_rpm(){
  long new_position = encoder.getPosition();
  long position_change;
  double RPM;

  if (new_position != old_position) {
    tick_time = (millis() - last_time);
    position_change = old_position - new_position;
    RPM = 1 / ((double(tick_time / position_change) * 125)/1000/60); //10041 18538 = ticks per rev, 1 rev = 42.73cm
    old_position = new_position;
    last_time = millis();   
  }
  else{
    RPM = 0.0;
  }
  delay(20); // required for dagu as encoders are shit and only pulse 125 times per rev
  return RPM;
}

void L298N::set_rpm(int rpm){
  Setpoint = rpm;//covert_vel_rpm(vel);
  Input = calculate_rpm();
  pid.Compute();
  
  if (Setpoint > 0.0){
    forwards(char(Output)); 
  }
  else{
    backwards(char(Output));
  } 
}

void L298N::forwards(char pwm){
 digitalWrite(_IN1, HIGH); 
  digitalWrite(_IN2, LOW); 
  analogWrite(_EN, pwm);
  return; 
}

void L298N::backwards(char pwm){
  digitalWrite(_IN1, LOW); 
  digitalWrite(_IN2, HIGH); 
  analogWrite(_EN, pwm);
  return;
}

L298N.h

#ifndef L298N_h
#define L298N_h

#include "Arduino.h"
#include <RotaryEncoder.h>
#include <PID_v1.h>

class L298N
{
    public:
        L298N(char IN1, char IN2, char EN, char A_INT1, char A_INT2);
        void set_rpm(int rpm);
    private:
        char _IN1, _IN2, _EN, _INT1, _INT2;
        double last_time = millis();
        double tick_time = 0;
        long old_position = 0;
        double Setpoint, Output;
        double Input = 0;
        double Kp=1.3, Ki=15, Kd=0.01;
        RotaryEncoder encoder;//(char A_INT1, char A_INT2, RotaryEncoder::LatchMode::TWO03);
        PID pid;//(double &Input, double &Output, double &Setpoint, double Kp, double Ki, double Kd, char DIRECT);  
        void checkPosition();
        double calculate_rpm();
        void forwards(char pwm);
        void backwards(char pwm);
};

#endif

test.ino

#include <L298N.h>
#include <RotaryEncoder.h>
#include <PID_v1.h>

char A_IN1 = 8;
char A_IN2 = 9;
char A_EN = 17;
char A_INT1 = 3;
char A_INT2 = 4;

L298N left(A_IN1, A_IN2, A_EN, A_INT1, A_INT2);

void setup(){
}

void loop(){
    left.set_rpm(20);
}

errors:

Arduino: 1.8.15 (Linux), Board: "Arduino Uno"

/home/ubuntu/Arduino/libraries/L298N_driver/L298N.cpp: In constructor 'L298N::L298N(char, char, char, char, char)': /home/ubuntu/Arduino/libraries/L298N_driver/L298N.cpp:7:67: error: no matching function for call to 'RotaryEncoder::RotaryEncoder()' L298N::L298N(char IN1, char IN2, char EN, char A_INT1, char A_INT2) ^ In file included from /home/ubuntu/Arduino/libraries/L298N_driver/L298N.h:10:0, from /home/ubuntu/Arduino/libraries/L298N_driver/L298N.cpp:3: /home/ubuntu/Arduino/libraries/RotaryEncoder/src/RotaryEncoder.h:39:3: note: candidate: RotaryEncoder::RotaryEncoder(int, int, RotaryEncoder::LatchMode) RotaryEncoder(int pin1, int pin2, LatchMode mode = LatchMode::FOUR0); ^~~~~~~~~~~~~ /home/ubuntu/Arduino/libraries/RotaryEncoder/src/RotaryEncoder.h:39:3: note: candidate expects 3 arguments, 0 provided /home/ubuntu/Arduino/libraries/RotaryEncoder/src/RotaryEncoder.h:23:7: note: candidate: constexpr RotaryEncoder::RotaryEncoder(const RotaryEncoder&) class RotaryEncoder ^~~~~~~~~~~~~ /home/ubuntu/Arduino/libraries/RotaryEncoder/src/RotaryEncoder.h:23:7: note: candidate expects 1 argument, 0 provided /home/ubuntu/Arduino/libraries/RotaryEncoder/src/RotaryEncoder.h:23:7: note: candidate: constexpr RotaryEncoder::RotaryEncoder(RotaryEncoder&&) /home/ubuntu/Arduino/libraries/RotaryEncoder/src/RotaryEncoder.h:23:7: note: candidate expects 1 argument, 0 provided /home/ubuntu/Arduino/libraries/L298N_driver/L298N.cpp:7:67: error: no matching function for call to 'PID::PID()' L298N::L298N(char IN1, char IN2, char EN, char A_INT1, char A_INT2) ^ In file included from /home/ubuntu/Arduino/libraries/L298N_driver/L298N.h:11:0, from /home/ubuntu/Arduino/libraries/L298N_driver/L298N.cpp:3: /home/ubuntu/Arduino/libraries/PID/PID_v1.h:24:5: note: candidate: PID::PID(double*, double*, double*, double, double, double, int) PID(double*, double*, double*, // * constructor. links the PID to the Input, Output, and ^~~ /home/ubuntu/Arduino/libraries/PID/PID_v1.h:24:5: note: candidate expects 7 arguments, 0 provided /home/ubuntu/Arduino/libraries/PID/PID_v1.h:20:5: note: candidate: PID::PID(double*, double*, double*, double, double, double, int, int) PID(double*, double*, double*, // * constructor. links the PID to the Input, Output, and ^~~ /home/ubuntu/Arduino/libraries/PID/PID_v1.h:20:5: note: candidate expects 8 arguments, 0 provided /home/ubuntu/Arduino/libraries/PID/PID_v1.h:5:7: note: candidate: constexpr PID::PID(const PID&) class PID ^~~ /home/ubuntu/Arduino/libraries/PID/PID_v1.h:5:7: note: candidate expects 1 argument, 0 provided /home/ubuntu/Arduino/libraries/PID/PID_v1.h:5:7: note: candidate: constexpr PID::PID(PID&&) /home/ubuntu/Arduino/libraries/PID/PID_v1.h:5:7: note: candidate expects 1 argument, 0 provided /home/ubuntu/Arduino/libraries/L298N_driver/L298N.cpp:13:70: error: invalid use of non-static member function 'void L298N::checkPosition()'
attachInterrupt(digitalPinToInterrupt(A_INT1), checkPosition, CHANGE); ^ In file included from /home/ubuntu/Arduino/libraries/L298N_driver/L298N.cpp:3:0: /home/ubuntu/Arduino/libraries/L298N_driver/L298N.h:33:7: note: declared here void checkPosition(); ^~~~~~~~~~~~~ /home/ubuntu/Arduino/libraries/L298N_driver/L298N.cpp:14:70: error: invalid use of non-static member function 'void L298N::checkPosition()'
attachInterrupt(digitalPinToInterrupt(A_INT2), checkPosition, CHANGE); ^ In file included from /home/ubuntu/Arduino/libraries/L298N_driver/L298N.cpp:3:0: /home/ubuntu/Arduino/libraries/L298N_driver/L298N.h:33:7: note: declared here void checkPosition(); ^~~~~~~~~~~~~ exit status 1 Error compiling for board Arduino Uno.

This report would have more information with "Show verbose output during compilation" option enabled in File -> Preferences.

Any help of how to do this properly would be greatly appreciated. It’s just using a class within a class (terminology might be wrong) but this is quite trivial to do in Python.

Cheers!

10
  • 1
    Can you elaborate on "I’m having trouble"? What kind of trouble? Commented Jul 4, 2021 at 22:38
  • It won’t compile. What’s the correct way to use a class within a class? Commented Jul 4, 2021 at 22:43
  • What compilation error are you getting and pertaining to what line(s) of code? Commented Jul 4, 2021 at 22:46
  • Give me 10 mins, I’ll update the question with the actual code and compilation errors Commented Jul 4, 2021 at 22:52
  • I tried your code and got some errors: 1. Don't use #include <L298N.h>, use #include "L298N.h" 2. Could't locate <RotaryEncoder.h> and <PID_v1.h>. If you have those files in your project, change the brackets to quotes as with L298.h. Commented Jul 4, 2021 at 23:24

2 Answers 2

1

You have several problems.

  1. You said

PID and RotaryEncoder libraries are standard arduino libraries

That's wrong, those libraries are not standard, you have to install them using the Library Manager (otherwise it would have compiled). Those are:

- RotaryEncoder, by Mathias Hertel
http://www.mathertel.de/Arduino/RotaryEncoderLibrary.aspx

- Arduino PID Library, by Brett Beauregard
https://github.com/br3ttb/Arduino-PID-Library

  1. Local includes should use quotes, system/external includes must use brackets:

     #include "L298N.h"
     #include <RotaryEncoder.h>
    

  1. RotaryEncoder does not have a default constructor, so you either initialize it using the initializer list or just remove the member variable.

  1. PID doesn't have a default constructor either.

  1. You cannot use a class member function as an interrupt handler. This:

    attachInterrupt(digitalPinToInterrupt(A_INT1), checkPosition, CHANGE);

will never work. You may have to use a singleton or similar:

L298N left(A_IN1, A_IN2, A_EN, A_INT1, A_INT2);

// code

void int_checkPosition() {
   left.checkPosition()
}
// more code

// this may go in setup()
attachInterrupt(digitalPinToInterrupt(A_INT2), checkPosition, CHANGE);

I changed the code in the constructor to make it look like this and compiles.

// encoder and pid initialized properly
L298N::L298N(byte IN1, byte IN2, byte mEN, byte A_INT1, byte A_INT2)
 : encoder((int)A_INT1, (int)A_INT2, RotaryEncoder::LatchMode::TWO03)
 , pid(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT)
{
  piL298N::L298N(byte IN1, byte IN2, byte EN, byte A_INT1, byte A_INT2)
 : encoder((int)A_INT1, (int)A_INT2, RotaryEncoder::LatchMode::TWO03)
 , pid(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT)
{
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  pinMode(EN, OUTPUT);

  // this doesn't work (use the recommendation above)
  //attachInterrupt(digitalPinToInterrupt(A_INT1), checkPosition, CHANGE);
  //attachInterrupt(digitalPinToInterrupt(A_INT2), checkPosition, CHANGE);
  pinMode(A_INT1, INPUT);
  pinMode(A_INT2, INPUT);
    
  // you don't need this. in fact, it wasn't going to work
  // because you needed it to be a class member.
  //RotaryEncoder encoder((int)A_INT1, (int)A_INT2, RotaryEncoder::LatchMode::TWO03);
  //PID pid(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);

  pid.SetSampleTime(20);
  pid.SetMode(AUTOMATIC);
  pid.SetOutputLimits(0,255);
    
  _IN1 = IN1;
  _IN2 = IN2;
  _EN = EN; 
}

It is up to you to fix the issue with the interrupt handler.

Sign up to request clarification or add additional context in comments.

Comments

0

i think I understand what are you asking. Arduino (and many other IDE) has two different types o library

1) Local libraries 2) External Libraries

Local Libraries are those that you include by the syntaxis:

#include "Your_library.h"

External Libraries are those with the syntaxis:

#include <Your_library.h>

The difference between them is that the Local Libraries are supposed to be in the same folder of the proyect, it means that if you have a folder

Arduino/My_proyect/My_Proyect.ino

the library should be in

Arduino/My_proyect/Your_library.h

Instead, external Libraries are in the Arduino "libraries" folder

Arduino/libraries/Your_library/Your_library.h

Maybe the problem with your code is that you are using the bad type of syntaxis for the library, try to put all the libraries you want to use (.h and .cpp) in the same file and create a folder in the arduino "libraries" folder, then, include your librarys using the external syntaxis

#include <Folder_name.h>

This should work!

3 Comments

The issue isn't how the libraries are added to the code (tried your way and the original way, the errors are the same), the issue is the correct syntx to utilise an existing OO library within my own OO library. Class within a class, etc
So when you say class within a class you mean nested classes. One of your errors is that the RotaryEncoder() function do not exist or cannot be found because it is a function from another class. Look at this example of code for nested classes. tutorialspoint.com/nested-classes-in-cplusplus
how files are included (which is something that has already being commented above) has very little to do with the main issue.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.