PID-Controller , Velocity control - DC motor. Fails to compute a PWM output

Hi!

I tried to implement a PID (PI) - controller to control the velocity of a DC motor. I used the basic example on this site - Arduino Playground - PIDLibaryBasicExample - to write the code, and i read the guide written by Brett Beauregard to understand how library works.
I calculated Kp and Ki myself according to how i want the actual system to behave. I'm fairly new to control theory and coding so bare with me.

The actual problem is that the PID fails to compute any output. I'm not exactly sure of why this happens, but i attached a picture where i manually rotated the wheel in order to check how the controller would react.

This is my code:

#include <PID_v1.h>
#include <Encoder.h>
Encoder FrontTicks(18, 19);

long NewFrontTicks, OldFrontTicks, myTime,myTimeOld;

double Velocity,FrontOutputFWD,SpeedRef, error;
double kp = 7.69;
double ki = 48.9;
double kd = 0;

int FrontPWM = 11;
int FrontEA = 12;

PID FrontControllerFWD(&Velocity, &FrontOutputFWD, &SpeedRef,kp,ki,kd, DIRECT);

void setup() {
  // put your setup code here, to run once:

FrontControllerFWD.SetMode(AUTOMATIC);
pinMode(FrontEA, OUTPUT); // Pin used to enable the H-Bridge
pinMode(FrontPWM, OUTPUT); // Pin used for the PWN Output
digitalWrite(FrontEA, HIGH); // Enables the H-Bridge used
Velocity = (((NewFrontTicks-OldFrontTicks)))/((myTime-myTimeOld)*0.001);
SpeedRef = 38000;
Serial.begin(9600);
Serial.println("Parameters");

}
void loop() {

myTime=millis();
NewFrontTicks = FrontTicks.read();
Velocity = (((NewFrontTicks-OldFrontTicks)))/((myTime-myTimeOld)*0.001);
error = SpeedRef-Velocity; // Used to check the error used by the PID

FrontControllerFWD.Compute();

analogWrite(FrontPWM, FrontOutputFWD);

Serial.print("Frontwheel Velocity = ");
Serial.print(Velocity);
Serial.print("    Frontwheel error = ");
Serial.print(error);
Serial.print("    Frontwheel PWM output = ");
Serial.print(FrontOutputFWD);
Serial.println();

 delay(50); // Allow some time for the new ticks to update
 
OldFrontTicks = NewFrontTicks ;
myTimeOld = myTime;

}

You are getting NaN's so some variable is overflowing - you need to find out which.

That PID library takes care of its own timing - so call it every time round loop(), don't use
delay() and make sure to call SetSampleTime() appropriately in setup().

I see from the library that it grabs copies of the variables at initialization time, not just when
calling Compute(), so make sure all you variables have sensible values before the initializer is
called - an uninitialized value may be a NaN

Hi Mark!

I changed the code as you suggested. I think the problem might be related to the velocity calculation. If i try implementing a counter instead of calculating the velocity based on the encoder feedback, the PID seems to change properly. Is it because that it for some reason is unable to link it as a double as the PID computes the variables? I listed the code below, and added a attached where i just increase the velocity for each loop run.

I think the problem might be related to how the code calculated the velocity, more specifically how the encoder library calculates new ticks?

#include <PID_v1.h>
#include <Encoder.h>
Encoder FrontTicks(18, 19);

double NewFrontTicks, OldFrontTicks, myTime,myTimeOld;

double FrontOutputFWD, error, SpeedRef, Velocity;
double kp = 2.1;
double ki = 1.9;
double kd = 0;
double c = 0;
int FrontPWM = 11;
int FrontEA = 12;

PID FrontControllerFWD(&Velocity, &FrontOutputFWD, &SpeedRef,kp,ki,kd, DIRECT);

void setup() {
  // put your setup code here, to run once:
pinMode(FrontEA, OUTPUT); // Pin used to enable the H-Bridge
pinMode(FrontPWM, OUTPUT); // Pin used for the PWN Output
digitalWrite(FrontEA, HIGH); // Enables the H-Bridge used
FrontControllerFWD.SetSampleTime(10); 
Velocity = 30000;
SpeedRef = 38000;
Serial.begin(9600);
Serial.println("Parameters");
FrontControllerFWD.SetMode(AUTOMATIC);
}
void loop() {

  
FrontControllerFWD.Compute();
myTime=millis();
NewFrontTicks = FrontTicks.read();
Velocity = Velocity+c;
//Velocity = (((NewFrontTicks-OldFrontTicks)))/((myTime-myTimeOld)*0.001);
error = SpeedRef-Velocity; // Used to check the error used by the PID

analogWrite(FrontPWM,FrontOutputFWD);

Serial.print("Frontwheel Velocity = ");
Serial.print(Velocity);
Serial.print("    Frontwheel error = ");
Serial.print(error);
Serial.print("    Frontwheel PWM output = ");
Serial.print(FrontOutputFWD);
Serial.println();
c  = c+500;
OldFrontTicks = NewFrontTicks ;
myTimeOld = myTime;

}

Regards

You need to only calculate your velocity at the same rate as the PID runs, not every time round the
loop. You might do better to measure the time per revolution using an ISR and micros(), so that
the velocity information is fresher - averaging over several rotations effectively introduces a measurement
delay and all delays in sensor data make PID loops less stable (you have to turn down the gain to
compensate). However you could make the PID loop run more frequently anyway

Hi again!

I changed the velocity calculation to:

double Velocity = (((NewFrontTicks-OldFrontTicks)))/((myTime-myTimeOld)*0.000001);

I also used micros instead of millis and im already using interupt pins. The result was as you said a much much more consistent velocity calculation. Also the PID now turns on but it can't turn off when the error becomes too large. Any idea why this happens?

I didn't enforce, that the velocity should be calculated when the PID computes as i cannot see how this should result in the PID not decreasing the output.

I set the reference velocity to 38000 ticks/second and as you can see in the attached, the encoder velocity is well above. The current code is :

#include <PID_v1.h>
#include <Encoder.h>
Encoder FrontTicks(18, 19);

double NewFrontTicks, OldFrontTicks, myTime,myTimeOld;

double FrontOutputFWD, error, SpeedRef, Velocity;
double kp = 2.1;
double ki =1.9;
double kd = 0;
double c = 0;
int FrontPWM = 11;
int FrontEA = 12;

PID FrontControllerFWD(&Velocity, &FrontOutputFWD, &SpeedRef,kp,ki,kd, DIRECT);

void setup() {
  // put your setup code here, to run once:
pinMode(FrontEA, OUTPUT); // Pin used to enable the H-Bridge
pinMode(FrontPWM, OUTPUT); // Pin used for the PWN Output
digitalWrite(FrontEA, HIGH); // Enables the H-Bridge used
FrontControllerFWD.SetSampleTime(10); 
FrontControllerFWD.SetOutputLimits(0, 80); 
Velocity = 0;
SpeedRef = 38000;
Serial.begin(115200);
Serial.println("Parameters");
FrontControllerFWD.SetMode(AUTOMATIC);
}
void loop() {

  
FrontControllerFWD.Compute();
//myTime=myTime+c;
myTime=micros();
NewFrontTicks = FrontTicks.read();
//NewFrontTicks = NewFrontTicks+c;
//Velocity = Velocity+c;
double Velocity = (((NewFrontTicks-OldFrontTicks)))/((myTime-myTimeOld)*0.000001);
error = SpeedRef-Velocity; // Used to check the error used by the PID

analogWrite(FrontPWM,FrontOutputFWD);

Serial.print("Frontwheel Velocity = ");
Serial.print(Velocity);
Serial.print("    Frontwheel error = ");
Serial.print(error);
Serial.print("    Frontwheel PWM output = ");
Serial.print(FrontOutputFWD);
Serial.println();
//c  = c+500;
OldFrontTicks = NewFrontTicks ;
myTimeOld = myTime;

}

Please feel free to tell me if theres anything else i can post on this forum, that could clarify where the problem might be.

Regards

Well you've hit the maximum endstop despite the error being negative - have you checked the sense
is the right way round?

Yea, i verified it by creating a counter that "represented" the velocity from the encoder. The controller behaves as expected, if i use "feedback" from the counter, instead of the encoder. The controller behaves as i would expect, as you can see in the attachment, but only if i calculate the velocity as i demonstrated in the code below. However if i use the encoder feedback as i shown earlier, it turns the PID on, but fails to decrease the value.

myTime=micros();
NewFrontTicks = FrontTicks.read();

Velocity = Velocity+c;
if (Velocity > 60000){
Velocity = 15000;
}
if (c>70000){
  c=0;
}
c  = c+500;
//double Velocity = (((NewFrontTicks-OldFrontTicks)))/((myTime-myTimeOld)*0.000001);
error = SpeedRef-Velocity; // Used to check the error used by the PID

I hope this brought some clarification to my issue

Kind Regards
Squader