DC servo .ino doesn't want to compile (solved, thanks!)

I’m attempting to put together a cnc plotter/milling machine, and being on a rather tight budget, am trying to use what I’ve got on hand.

I’ve modified a carriage from an OKI dot matrix printer, and am trying to use the servo motor with it’s encoder along with the original mechanicals for the carriage.

I have already gotten GRBL working on one of my Uno clones, and would like to put this on a mini clone to run the DC servo motor for the X axis.

I’ve found someone who has done this on his 3D printer, and for him it seems to work quite well.

He has his code and some explanations on GitHub GitHub - misan/dcservo: Position control of DC motors

In trying to compile the sketch, I get several error messages, right at the end of compiling. That is to say it appears to be compiling, then exits with several errors.

In looking at the build folder I.E. user>>>>\buildxxxx.tmp… I don’t see a .cpp.h file, so it definitely isn’t finishing the compile.

Here is the .ino file:

/* This one is not using any PinChangeInterrupt library */

/*
   This program uses an Arduino for a closed-loop control of a DC-motor. 
   Motor motion is detected by a quadrature encoder.
   Two inputs named STEP and DIR allow changing the target position.
   Serial port prints current position and target position every second.
   Serial input can be used to feed a new location for the servo (no CR LF).
   
   Pins used:
   Digital inputs 2 & 8 are connected to the two encoder signals (AB).
   Digital input 3 is the STEP input.
   Analog input 0 is the DIR input.
   Digital outputs 9 & 10 control the PWM outputs for the motor (I am using half L298 here).


   Please note PID gains kp, ki, kd need to be tuned to each different setup. 
*/
#include <EEPROM.h>
#include <PID_v1.h>
#define encoder0PinA  2 // PD2; 
#define encoder0PinB  8  // PC0;
#define M1            9
#define M2            10  // motor's PWM outputs

byte pos[1000]; int p=0;

double kp=3,ki=0,kd=0.0;
double input=0, output=0, setpoint=0;
PID myPID(&input, &output, &setpoint,kp,ki,kd, DIRECT);
volatile long encoder0Pos = 0;
boolean auto1=false, auto2=false,counting=false;
long previousMillis = 0;        // will store last time LED was updated

long target1=0;  // destination location at any moment

//for motor control ramps 1.4
bool newStep = false;
bool oldStep = false;
bool dir = false;
byte skip=0;

// Install Pin change interrupt for a pin, can be called multiple times
void pciSetup(byte pin) 
{
    *digitalPinToPCMSK(pin) |= bit (digitalPinToPCMSKbit(pin));  // enable pin
    PCIFR  |= bit (digitalPinToPCICRbit(pin)); // clear any outstanding interrupt
    PCICR  |= bit (digitalPinToPCICRbit(pin)); // enable interrupt for the group 
}

void setup() { 
  pinMode(encoder0PinA, INPUT); 
  pinMode(encoder0PinB, INPUT);  
  pciSetup(encoder0PinB);
  attachInterrupt(0, encoderInt, CHANGE);  // encoder pin on interrupt 0 - pin 2
  attachInterrupt(1, countStep      , RISING);  // step  input on interrupt 1 - pin 3
  TCCR1B = TCCR1B & 0b11111000 | 1; // set 31Kh PWM
  Serial.begin (115200);
  help();
  recoverPIDfromEEPROM();
  //Setup the pid 
  myPID.SetMode(AUTOMATIC);
  myPID.SetSampleTime(1);
  myPID.SetOutputLimits(-255,255);
} 

void loop(){
    input = encoder0Pos; 
    setpoint=target1;
    myPID.Compute();
    if(Serial.available()) process_line(); // it may induce a glitch to move motion, so use it sparingly 
    pwmOut(output); 
    if(auto1) if(millis() % 3000 == 0) target1=random(2000); // that was for self test with no input from main controller
    if(auto2) if(millis() % 1000 == 0) printPos();
    //if(counting && abs(input-target1)<15) counting=false; 
    if(counting &&  (skip++ % 5)==0 ) {pos[p]=encoder0Pos; if(p<999) p++; else counting=false;}
}

void pwmOut(int out) {
   if(out<0) { analogWrite(M1,0); analogWrite(M2,abs(out)); }
   else { analogWrite(M2,0); analogWrite(M1,abs(out)); }
  }

const int QEM [16] = {0,-1,1,2,1,0,2,-1,-1,2,0,1,2,1,-1,0};               // Quadrature Encoder Matrix
static unsigned char New, Old;
ISR (PCINT0_vect) { // handle pin change interrupt for D8
  Old = New;
  New = (PINB & 1 )+ ((PIND & 4) >> 1); //
  encoder0Pos+= QEM [Old * 4 + New];
}

void encoderInt() { // handle pin change interrupt for D2
  Old = New;
  New = (PINB & 1 )+ ((PIND & 4) >> 1); //
  encoder0Pos+= QEM [Old * 4 + New];
}


void countStep(){ if (PINC&B0000001) target1--;else target1++; } // pin A0 represents direction

void process_line() {
 char cmd = Serial.read();
 if(cmd>'Z') cmd-=32;
 switch(cmd) {
  case 'P': kp=Serial.parseFloat(); myPID.SetTunings(kp,ki,kd); break;
  case 'D': kd=Serial.parseFloat(); myPID.SetTunings(kp,ki,kd); break;
  case 'I': ki=Serial.parseFloat(); myPID.SetTunings(kp,ki,kd); break;
  case '?': printPos(); break;
  case 'X': target1=Serial.parseInt(); p=0; counting=true; for(int i=0; i<300; i++) pos[i]=0; break;
  case 'T': auto1 = !auto1; break;
  case 'A': auto2 = !auto2; break;
  case 'Q': Serial.print("P="); Serial.print(kp); Serial.print(" I="); Serial.print(ki); Serial.print(" D="); Serial.println(kd); break;
  case 'H': help(); break;
  case 'W': writetoEEPROM(); break;
  case 'K': eedump(); break;
  case 'R': recoverPIDfromEEPROM() ; break;
  case 'S': for(int i=0; i<p; i++) Serial.println(pos[i]); break;
 }
 while(Serial.read()!=10); // dump extra characters till LF is seen (you can use CRLF or just LF)
}

void printPos() {
  Serial.print(F("Position=")); Serial.print(encoder0Pos); Serial.print(F(" PID_output=")); Serial.print(output); Serial.print(F(" Target=")); Serial.println(setpoint);
}
void help() {
 Serial.println(F("\nPID DC motor controller and stepper interface emulator"));
 Serial.println(F("by misan"));
 Serial.println(F("Available serial commands: (lines end with CRLF or LF)"));
 Serial.println(F("P123.34 sets proportional term to 123.34"));
 Serial.println(F("I123.34 sets integral term to 123.34"));
 Serial.println(F("D123.34 sets derivative term to 123.34"));
 Serial.println(F("? prints out current encoder, output and setpoint values"));
 Serial.println(F("X123 sets the target destination for the motor to 123 encoder pulses"));
 Serial.println(F("T will start a sequence of random destinations (between 0 and 2000) every 3 seconds. T again will disable that"));
 Serial.println(F("Q will print out the current values of P, I and D parameters")); 
 Serial.println(F("W will store current values of P, I and D parameters into EEPROM")); 
 Serial.println(F("H will print this help message again")); 
 Serial.println(F("A will toggle on/off showing regulator status every second\n")); 
}

void writetoEEPROM() { // keep PID set values in EEPROM so they are kept when arduino goes off
  eeput(kp,0);
  eeput(ki,4);
  eeput(kd,8);
  double cks=0;
  for(int i=0; i<12; i++) cks+=EEPROM.read(i);
  eeput(cks,12);
  Serial.println("\nPID values stored to EEPROM");
  //Serial.println(cks);
}

void recoverPIDfromEEPROM() {
  double cks=0;
  double cksEE;
  for(int i=0; i<12; i++) cks+=EEPROM.read(i);
  cksEE=eeget(12);
  //Serial.println(cks);
  if(cks==cksEE) {
    Serial.println(F("*** Found PID values on EEPROM"));
    kp=eeget(0);
    ki=eeget(4);
    kd=eeget(8);
    myPID.SetTunings(kp,ki,kd); 
  }
  else Serial.println(F("*** Bad checksum"));
}

void eeput(double value, int dir) { // Snow Leopard keeps me grounded to 1.0.6 Arduino, so I have to do this :-(
  char * addr = (char * ) &value;
  for(int i=dir; i<dir+4; i++)  EEPROM.write(i,addr[i-dir]);
}

double eeget(int dir) { // Snow Leopard keeps me grounded to 1.0.6 Arduino, so I have to do this :-(
  double value;
  char * addr = (char * ) &value;
  for(int i=dir; i<dir+4; i++) addr[i-dir]=EEPROM.read(i);
  return value;
}

void eedump() {
 for(int i=0; i<16; i++) { Serial.print(EEPROM.read(i),HEX); Serial.print(" "); }Serial.println(); 
}

I’ll post the error codes in a reply, as this doesn’t want to post with them in.

These are the error messages that show up:

C:\Users\bob\AppData\Local\Temp\build7771012305295474050.tmp\dcservo.cpp.o: In function `recoverPIDfromEEPROM()':
C:\Program Files (x86)\Arduino/dcservo.ino:164: undefined reference to `PID::SetTunings(double, double, double)'
C:\Users\bob\AppData\Local\Temp\build7771012305295474050.tmp\dcservo.cpp.o: In function `setup':
C:\Program Files (x86)\Arduino/dcservo.ino:63: undefined reference to `PID::SetMode(int)'
C:\Program Files (x86)\Arduino/dcservo.ino:64: undefined reference to `PID::SetSampleTime(int)'
C:\Program Files (x86)\Arduino/dcservo.ino:65: undefined reference to `PID::SetOutputLimits(double, double)'
C:\Users\bob\AppData\Local\Temp\build7771012305295474050.tmp\dcservo.cpp.o: In function `__static_initialization_and_destruction_0':
C:\Program Files (x86)\Arduino/dcservo.ino:31: undefined reference to `PID::PID(double*, double*, double*, double, double, double, int)'
C:\Users\bob\AppData\Local\Temp\build7771012305295474050.tmp\dcservo.cpp.o: In function `process_line()':
C:\Program Files (x86)\Arduino/dcservo.ino:107: undefined reference to `PID::SetTunings(double, double, double)'
C:\Users\bob\AppData\Local\Temp\build7771012305295474050.tmp\dcservo.cpp.o: In function `loop':
C:\Program Files (x86)\Arduino/dcservo.ino:71: undefined reference to `PID::Compute()'
collect2.exe: error: ld returned 1 exit status
Error compiling.

Hopefully someone can at least point me in the right direction to solving this.

Thanks!

You have (the right) PID_v1.h installed?

I believe I do, it’s “PID_v1.h” from the github. Installed in C:\program files(x86)\arduino\hardware\arduino\avr\libraries\PID

That was the only location the IDE would recognize it. There are multiple copies in several other normal libraries when I was trying to do this on my own.

Here’s the code that I have:

#ifndef PID_v1_h
#define PID_v1_h
#define LIBRARY_VERSION 1.1.1

class PID
{


  public:

  //Constants used in some of the functions below
  #define AUTOMATIC 1
  #define MANUAL 0
  #define DIRECT  0
  #define REVERSE  1

  //commonly used functions **************************************************************************
    PID(double*, double*, double*,        // * constructor.  links the PID to the Input, Output, and 
        double, double, double, int);     //   Setpoint.  Initial tuning parameters are also set here
 
    void SetMode(int Mode);               // * sets PID to either Manual (0) or Auto (non-0)

    bool Compute();                       // * performs the PID calculation.  it should be
                                          //   called every time loop() cycles. ON/OFF and
                                          //   calculation frequency can be set using SetMode
                                          //   SetSampleTime respectively

    void SetOutputLimits(double, double); //clamps the output to a specific range. 0-255 by default, but
  //it's likely the user will want to change this depending on
  //the application
 


  //available but not commonly used functions ********************************************************
    void SetTunings(double, double,       // * While most users will set the tunings once in the 
                    double);          //   constructor, this function gives the user the option
                                          //   of changing tunings during runtime for Adaptive control
 void SetControllerDirection(int);  // * Sets the Direction, or "Action" of the controller. DIRECT
  //   means the output will increase when error is positive. REVERSE
  //   means the opposite.  it's very unlikely that this will be needed
  //   once it is set in the constructor.
    void SetSampleTime(int);              // * sets the frequency, in Milliseconds, with which 
                                          //   the PID calculation is performed.  default is 100
  
  
  
  //Display functions ****************************************************************
 double GetKp();  // These functions query the pid for interal values.
 double GetKi();  //  they were created mainly for the pid front-end,
 double GetKd();  // where it's important to know what is actually 
 int GetMode();  //  inside the PID.
 int GetDirection();  //

  private:
 void Initialize();
 
 double dispKp; // * we'll hold on to the tuning parameters in user-entered 
 double dispKi; //   format for display purposes
 double dispKd; //
    
 double kp;                  // * (P)roportional Tuning Parameter
    double ki;                  // * (I)ntegral Tuning Parameter
    double kd;                  // * (D)erivative Tuning Parameter

 int controllerDirection;

    double *myInput;              // * Pointers to the Input, Output, and Setpoint variables
    double *myOutput;             //   This creates a hard link between the variables and the 
    double *mySetpoint;           //   PID, freeing the user from having to constantly tell us
                                  //   what these values are.  with pointers we'll just know.
  
 unsigned long lastTime;
 double ITerm, lastInput;

 unsigned long SampleTime;
 double outMin, outMax;
 bool inAuto;
};
#endif

I am curious about one of the errors.

If some of you gurus here can check and correct my understanding, I might be able figure some of this out on my own.

The function is:

void recoverPIDfromEEPROM() {
  double cks=0;
  double cksEE;
  for(int i=0; i<12; i++) cks+=EEPROM.read(i);
  cksEE=eeget(12);
  //Serial.println(cks);
  if(cks==cksEE) {
    Serial.println(F("*** Found PID values on EEPROM"));
    kp=eeget(0);
    ki=eeget(4);
    kd=eeget(8);
    myPID.SetTunings(kp,ki,kd); 
  }
  else Serial.println(F("*** Bad checksum"));
}

If I have this right, the “void recoverPID…()” part is just naming the function, right? so the name itself isn’t the problem. What comes after probably is then. As I understand it, a “void” means the function is not expected to return anything.

The first line in the function tells the program to look for a double called cks. I assume this stands for checksum.

Next line, double cksEE is looking for this in the EEPROM. Is this for comparison? Or are both calls for the same thing?

Then the “for”. Is this just setting the parameters of what the function is supposed to be doing? int i=0 is
setting an integer named “i” to 0, then checking if it is <12 within the same statement. Seems to me that if you are setting i to 0, you should then close that bracket before checking if it is a certain value.

To continue that line, the program then increments i by 1, then cecks it against the cks, which is supposed to be equal to or greater than the cks in the EEPROM.read(i) This is the part I’m not quite understanding, and need a bit of clarification on.

cksEE=eeget(12) tells me i’m looking for a value in location 12 of the EEPROM.

if (cks==cksEE) should mean that I was correct about the pair of doubles at the start of the function, and this is the actual compare. should the values be the same, it should continue on to the next lines, and retrieve the values, then place them in the variable myPID.SetTunings

Next three lines are calling for values at the specified locations, so rather straightforward.

Completing the if-else is just letting me know that the checksum came back not equal.

With the void at the start of the function, I would expect that there would not be any returns to set anywhere.

Am I understanding the situation?

In trying to decipher what the problem actually is, I've come across Brian W. Evan's Arduino Notebook.

Currently reading through that, and have noted that I am a bit off on the for() bit. I see now that it is written correctly, so will have to look elsewhere for the problem.

In the program, recoverPIDfromEEPROM() is declared in the void setup, at least I think that's right.

I may have posted this thread in the wrong place. If I did, could I have a moderator move it to where it does belong?

Most likely, no one knows anything about that particular code.

The errors you posted in reply #1 all appear to result from not finding the required PID library files.

You might try putting the files in the Arduino/libraries (i.e. the "libraries" subfolder to where your sketches normally reside).

Alternatively, you can try putting them in the same folder as the sketch. They will show up under separate tabs in the IDE.

Put libraries you download into sketchbook/libararies/, not the system place, or you'll simply
lose them when you upgrade the IDE.

I do have a copy of the PID_v1.h in the same folder as the sketch. That's what I did first, expecting it to work.

I added a copy to the program files folder, because the first time I tried to compile the errors showed that the IDE was looking for it there.

I finally have it compiling. The solution was that I didn't have the PID_v1.cpp in the folder as well.

I have no idea why it has to be that way, but it works.

Now I need to download it to a mini and try it out.

I'll be using my USBasp to load the mini, I seem to have better results doing it that way.

Loaded onto the mini, no problems.

Testing it, I will need to make a few fine adjustments to that axis. Other than that, it works quite well. Nice smooth movements, and very quiet.

Now that I've got it working, I'm considering doing this for both the x and y axes.

Who would have known that I needed to have the .cpp file in there as well?.......