PID output always 0.

Hi, I am trying to write a program that will use the PID library to control the speed of two motors. I have quite a bit of code going on so I will explain what Is happening:
1.Reads the signals from a RC Receiver and then convert them into speed and direction signals for the motors
2.Use interrupts to read the encoders and determine the speed of the motors
3.Use a PID controller to keep the speed of the motors and the input the same.

My issue is that the PID loop does not output any value other than 0 when both the Input and Set point variables are changed.

Any help would be greatly appreciated!

#include <PID_v1.h>

//Define Variables for PID to connect to
double SetpointL, InputL, OutputL;
double SetpointR, InputR, OutputR;

//tuning parameters:
int Kp = 1;
int Ki = 1;
int Kd = 1;

//Specify the links
PID PIDL(&InputL, &OutputL, &SetpointL,Kp,Ki,Kd, DIRECT);
PID PIDR(&InputR, &OutputR, &SetpointR,Kp,Ki,Kd, DIRECT);

int LM;//Left motor vector
int LMD;//Left motor direction
int LMS;//Left motor speed
int RM;//Right motor vector
int RMD;//Right motor direction
int RMS;//Right motor speed

int SPD;//Robot speed
int DIR;//Robot direction

int LSP = 5; //Left speed pin
int LDP = 4; //Left direction pin
int RSP = 6; //Right speed pin
int RDP = 7; //Right direction pin

int CH1;//Throttle (Pin 10) 
int CH2;//Rudder (Pin 11)
int CH3;//Manual (Pin 12)

int CH1P = 10;//Channel 1 Pin
int CH2P = 11;//Channel 2 Pin
int CH3P = 12;//Channel 3 Pin

//Connect the encoder pins to variables.
const byte LPA = 2;
const byte LPB = 8;
const byte RPA = 3;
const byte RPB = 9;

byte LPALast;
byte RPALast;
int Lduration;//the number of the pulses
int Rduration;
boolean LDirection;//the rotation direction
boolean RDirection;  

void setup() {
  
  Serial.begin(9600);

  //Pin modes
  pinMode(LSP, OUTPUT);
  pinMode(RSP, OUTPUT);
  pinMode(LDP, OUTPUT);
  pinMode(RDP, OUTPUT);
  pinMode(CH1P, INPUT);
  pinMode(CH2P, INPUT);
  pinMode(CH3P, INPUT);

  EncoderInit();//Initialize the two modules

  //Initialize the variables we're linked to
  InputL = Lduration;
  InputR = Rduration;
  SetpointL = LM;
  SetpointR = RM;
  
  //turn the PID on
  PIDL.SetMode(AUTOMATIC);
  PIDR.SetMode(AUTOMATIC);
  PIDL.SetSampleTime(1);
  PIDR.SetSampleTime(1);
}

void loop() {

  //Input RC control
  CH1 = pulseIn(CH1P, HIGH, 25000);
  CH2 = pulseIn(CH2P, HIGH, 25000);
  CH3 = pulseIn(CH3P, HIGH, 25000);

  //Map RC input
  CH1 = map(CH1, 965, 1975, -128, 128);
  CH2 = map(CH2, 965, 1975, -128, 128);

  //Resolve input
  if(CH3  > 1470){
    SPD = CH1;
    DIR   = CH2;
  }else{
    SPD = 0;
    DIR = 0;
  }

  //Dead Zone
  if(abs(SPD) < 10){
    SPD = 0;
  }
  if(abs(DIR) < 10){
    DIR = 0;
  }

  //Resolve motor vectors
  LM = (SPD + DIR);
  RM = (SPD - DIR);

  //Resolve motor direction
  if(LM > 0){
    LMD = 1;  
  }else{
    LMD = 0;
  }
  if(RM > 0){
    RMD = 1;  
  }else{
    RMD = 0;
  }

  //Resolve motor magnitude
  abs(LM);
  abs(RM);
  if (LM > 255){
    LM = 255;
  }
  if (RM > 255){
    RM = 255;
  }

  Lduration = map(Lduration, -330, 330, -255, 255);
  Rduration = map(Rduration, -330, 330, -255, 255);
  InputL = Lduration;
  InputR = Rduration;
  SetpointL = LM;
  SetpointR = RM;
  PIDL.Compute();
  PIDR.Compute();
  OutputL = LMS;
  OutputR = RMS;

  //DE-BUG:
  Serial.print("LM SET: ");
  Serial.print(LM);
  Serial.print(" LM IN: ");
  Serial.print(InputL);
  Serial.print(" LM OUT: ");
  Serial.println(LMS);
  //END
  //Write to motors
  digitalWrite(LDP,LMD);
  digitalWrite(RDP,RMD);
  analogWrite(RSP,RMS);
  analogWrite(LSP,LMS);
    
  Lduration = 0;
  Rduration = 0;
  delay(100);
}

void EncoderInit()
{
  LDirection = true;//default -> Forward  
  RDirection = true;
  
  pinMode(LPB,INPUT);
  pinMode(RPB,INPUT);   
  attachInterrupt(0, LwheelSpeed, CHANGE);//int.0
  attachInterrupt(1, RwheelSpeed, CHANGE);//int.1  
}


void LwheelSpeed()
{
  int LLstate = digitalRead(LPA);
  if((LPALast == LOW) && LLstate==HIGH)
  {
    int Lval = digitalRead(LPB);
    if(Lval == LOW && LDirection)
    {
      LDirection = false; //Reverse
    }
    else if(Lval == HIGH && !LDirection)
    {
      LDirection = true;  //Forward
    }
  }
  LPALast = LLstate;
   
  if(!LDirection){
    Lduration++;
  }else{
    Lduration--;
  }
}

void RwheelSpeed()
{
  int RLstate = digitalRead(RPA);
  if((RPALast == LOW) && RLstate==HIGH)
  {
    int Rval = digitalRead(RPB);
    if(Rval == HIGH && RDirection)
    {
      RDirection = false; //Reverse
    }
    else if(Rval == LOW && !RDirection)
    {
      RDirection = true;  //Forward
    }
  }
  RPALast = RLstate;
   
  if(!RDirection){
    Rduration++;
  }else{
    Rduration--;
  }
}

Hi,

After a quick scan of your code, try to set Kp to 6 and Ki to 5 and your setpoint to some high value and then see

Thank you for your fast response.

With Ki = 5, and Kp = 6 and SetpointL = 1024 my output remained 0.
I also manually moved the motors to introduce a encoder Input and the output remained 0.

Example of the Serial monitor output:

LM SET: 1024.00 LM IN: 73.00 LM OUT: 0

Thank you again for your help!

So, I'm guessing that the motor is not turning? :slight_smile:

Just so you know, the PID library has default low and hi limits set to 0 and 255, so you don't have to set them.

And default sampletime is 100 ms.

Check the .cpp file.

How about with Kp 32 and Ki 5?

Also, I think you should move your functions above the setup and loop functions.

zenseidk:
So, I'm guessing that the motor is not turning? :slight_smile:

Just so you know, the PID library has default low and hi limits set to 0 and 255, so you don't have to set them.

And default sampletime is 100 ms.

Check the .cpp file.

How about with Kp 32 and Ki 5?

Thank you for the information about the default limits, Id put them In while I was using the system without a PID loop but did also not know that the PID has limits in place.

I have also removed the sample rate adjustment from the program, however the output remains 0.

About this .cpp file, I checked my arduino libarys folder and cannot find the PID folder or .cpp file relating to the PID libary, however the IDE says that the PID libary is installed. Am I looking in the wrong place?

Thank you for your help!

The updated program is as so:

#include <PID_v1.h>

//Define Variables for PID to connect to
double SetpointL, InputL, OutputL;
double SetpointR, InputR, OutputR;

//tuning parameters:
int Kp = 32;
int Ki = 5;
int Kd = 1;

//Specify the links
PID PIDL(&InputL, &OutputL, &SetpointL,Kp,Ki,Kd, DIRECT);
PID PIDR(&InputR, &OutputR, &SetpointR,Kp,Ki,Kd, DIRECT);

int LM;//Left motor vector
int LMD;//Left motor direction
int LMS;//Left motor speed
int RM;//Right motor vector
int RMD;//Right motor direction
int RMS;//Right motor speed

int SPD;//Robot speed
int DIR;//Robot direction

int LSP = 5; //Left speed pin
int LDP = 4; //Left direction pin
int RSP = 6; //Right speed pin
int RDP = 7; //Right direction pin

int CH1;//Throttle (Pin 10) 
int CH2;//Rudder (Pin 11)
int CH3;//Manual (Pin 12)

int CH1P = 10;//Channel 1 Pin
int CH2P = 11;//Channel 2 Pin
int CH3P = 12;//Channel 3 Pin

//Connect the encoder pins to variables.
const byte LPA = 2;
const byte LPB = 8;
const byte RPA = 3;
const byte RPB = 9;

byte LPALast;
byte RPALast;
int Lduration;//the number of the pulses
int Rduration;
boolean LDirection;//the rotation direction
boolean RDirection;  

void setup() {
  
  Serial.begin(9600);

  //Pin modes
  pinMode(LSP, OUTPUT);
  pinMode(RSP, OUTPUT);
  pinMode(LDP, OUTPUT);
  pinMode(RDP, OUTPUT);
  pinMode(CH1P, INPUT);
  pinMode(CH2P, INPUT);
  pinMode(CH3P, INPUT);

  EncoderInit();//Initialize the two modules

  //Initialize the variables we're linked to
  InputL = Lduration;
  InputR = Rduration;
  SetpointL = LM;
  SetpointR = RM;
  
  //turn the PID on
  PIDL.SetMode(AUTOMATIC);
  PIDR.SetMode(AUTOMATIC);
}

void loop() {

  //Input RC control
  CH1 = pulseIn(CH1P, HIGH, 25000);
  CH2 = pulseIn(CH2P, HIGH, 25000);
  CH3 = pulseIn(CH3P, HIGH, 25000);

  //Map RC input
  CH1 = map(CH1, 965, 1975, -128, 128);
  CH2 = map(CH2, 965, 1975, -128, 128);

  //Resolve input
  if(CH3  > 1470){
    SPD = CH1;
    DIR   = CH2;
  }else{
    SPD = 0;
    DIR = 0;
  }

  //Dead Zone
  if(abs(SPD) < 10){
    SPD = 0;
  }
  if(abs(DIR) < 10){
    DIR = 0;
  }

  //Resolve motor vectors
  LM = (SPD + DIR);
  RM = (SPD - DIR);

  //Resolve motor direction
  if(LM > 0){
    LMD = 1;  
  }else{
    LMD = 0;
  }
  if(RM > 0){
    RMD = 1;  
  }else{
    RMD = 0;
  }

  //all positive
  abs(LM);
  abs(RM);


  Lduration = map(Lduration, -330, 330, -255, 255);
  Rduration = map(Rduration, -330, 330, -255, 255);
  InputL = Lduration;
  InputR = Rduration;
  SetpointL = 1024;
  SetpointR = RM;
  PIDL.Compute();
  PIDR.Compute();
  OutputL = LMS;
  OutputR = RMS;

  //DE-BUG:
  Serial.print("LM SET: ");
  Serial.print(SetpointL);
  Serial.print(" LM IN: ");
  Serial.print(InputL);
  Serial.print(" LM OUT: ");
  Serial.println(LMS);
  //END
  //Write to motors
  digitalWrite(LDP,LMD);
  digitalWrite(RDP,RMD);
  analogWrite(RSP,RMS);
  analogWrite(LSP,LMS);
    
  Lduration = 0;
  Rduration = 0;
  delay(100);
}

void EncoderInit()
{
  LDirection = true;//default -> Forward  
  RDirection = true;
  
  pinMode(LPB,INPUT);
  pinMode(RPB,INPUT);   
  attachInterrupt(0, LwheelSpeed, CHANGE);//int.0
  attachInterrupt(1, RwheelSpeed, CHANGE);//int.1  
}


void LwheelSpeed()
{
  int LLstate = digitalRead(LPA);
  if((LPALast == LOW) && LLstate==HIGH)
  {
    int Lval = digitalRead(LPB);
    if(Lval == LOW && LDirection)
    {
      LDirection = false; //Reverse
    }
    else if(Lval == HIGH && !LDirection)
    {
      LDirection = true;  //Forward
    }
  }
  LPALast = LLstate;
   
  if(!LDirection){
    Lduration++;
  }else{
    Lduration--;
  }
}

void RwheelSpeed()
{
  int RLstate = digitalRead(RPA);
  if((RPALast == LOW) && RLstate==HIGH)
  {
    int Rval = digitalRead(RPB);
    if(Rval == HIGH && RDirection)
    {
      RDirection = false; //Reverse
    }
    else if(Rval == LOW && !RDirection)
    {
      RDirection = true;  //Forward
    }
  }
  RPALast = RLstate;
   
  if(!RDirection){
    Rduration++;
  }else{
    Rduration--;
  }
}

Set Kd to 0. It won't make the difference, but I don't think you will need it.

How about setting an initial motor speed in the setup function with analogWrite(pin, 100);

That will give you a starting speed with which you can check if the encoder feedback works, using print statements.

The encoders work, the RC control works, just the PID output is 0, I set the speed to 100 in setup, the PID still outputs 0. I am going to sleep over the problem and will return to it tomorrow morning, Ill probably attempt a new program focusing on just the PID aspect, removing all other variables.

Thank you for your help so far I will update you on my progress tomorrow.

I just working on this very thing using a tacometer to control a motor with someone else.
but I had to modify the PID_v1.cpp to get it to work.

PID_v1.cpp
In my opinion PID_v1.cpp has this major problem, it has a fixed time window so if you don't execute your PIDL.Compute(); PIDR.Compute(); functions exactly every 100 milliseconds but not before 100 milliseconds you will not get accurate PID calculations!

This code below and the modified PID_v1 files works well with encoders mine currently uses a single encoder 400 (clock) steps per revolution and is running between 1100 to 2100 rpm which is the min and max of my motor at 5 volts and it maintains accurate RPM setpoints.

The only real attribute change is SetSampleTime(int NewSampleTime) is now SetMinimumSampleTime and it can be set quite low I defaulted it at 5 but the overridden program is set to 1

also on line 113 you can remove the ending */ to remark out the debug code I've added.

Here is my working motor control code and the modified PID_v1.cpp and PID_v1.h files are attached required to get this to work.
Feel free to modify and add it to you program. I'll keep an eye out for questions and answer them as soon as I can.

// This version of Tacometer auto adjusts for difference in pulses
#include <PID_v2.h> // uses my new version of PID_v1

//Define Variables we'll be connecting to
double Setpoint, Input, Output;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, .05, .08, .001, DIRECT);

#define PWMpin  3

int SampleDuration = ; // in Milliseconds
volatile unsigned long timeX = 1;
int PulsesPerRevolution = 400;
int MaxRPM = 400;
volatile int Counts = 100;
double PulsesPerMinute;
volatile unsigned long LastTime;
volatile int PulseCtr;
unsigned long Counter;
int startRamp;
unsigned long Time;
/*
  //Self Test
  int RPM = 1;
  double TextRPM = 1;
*/
void setup() {
  // note that 1666666.67 = (60 seonds * 1000000 microseconds)microseconds in a minute / (36 / 9) pulses in 1 revolution
  PulsesPerMinute = (60 * 1000000) / (PulsesPerRevolution / Counts);
  Setpoint = 1800;
  pinMode(2, INPUT_PULLUP);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  digitalWrite(4, HIGH);
  Serial.begin(115200);
  Serial.println("aTest Tachometer");
  delay(1000);
  //Digital Pin 2 Set As An Interrupt for tacho.
  attachInterrupt(0, sensorInterrupt, FALLING);
  
  startRamp = 10;
  myPID.SetSampleTime(1);
  myPID.SetOutputLimits(30, 255);
  PulseCtr = 0;
  myPID.SetMode(AUTOMATIC);
  analogWrite(PWMpin, 130);
  myPID.Compute();
  delay(11);
  myPID.Compute();
}

void loop() {
  readRpm();
  static unsigned long SpamTimer;
  if ((unsigned long)(millis() - SpamTimer) >= 15000) {
    SpamTimer = millis();
    Setpoint = (Setpoint == 2100) ? 1100:2100;
  }
}

void sensorInterrupt()
{
  static int Ctr;
  unsigned long Time;
  Ctr++;
  if (Ctr >= Counts) { // so we are taking an average of "Counts" readings to use in our calculations
    Time = micros();
    timeX += (Time - LastTime); // this time is accumulative ovrer those "Counts" readings
    LastTime = Time;
    PulseCtr ++; // will usually be 1 unless something else delays the sample
    Ctr = 0;
  }
}


void readRpm()
{
  cli ();         // clear interrupts flag
  Time = timeX;   // Make a copy so if an interrupt occurs timeX can be altered and not affect the results.
  timeX = 0;
  sei ();         // set interrupts flag
  if (PulseCtr > 0) {
    Input =  (double) (PulsesPerMinute /  (double)(( (unsigned long)Time ) *  (unsigned long)PulseCtr)); // double has more percision
    //   PulseCtr = 0; // set pulse Ctr to zero
    debug();
    if(!myPID.Compute()) Serial.println();
    analogWrite(PWMpin, Output);
    //
    if(Time > ((SampleDuration + 1)*1000))Counts--;
    if(Time < ((SampleDuration - 1)*1000))Counts++;
    Counts = constrain(Counts, PulsesPerRevolution * .1,PulsesPerRevolution *4);
    Time = 0; // set time to zero to wait for the next rpm trigger.
    Counter += PulseCtr;
    PulseCtr = 0; // set pulse Ctr to zero
    PulsesPerMinute = (60.0 * 1000000.0) / (double)((double)PulsesPerRevolution / (double)Counts);
  }
}


void debug()
{
  char S[20];
    for (static long QTimer = millis(); (long)( millis() - QTimer ) >= 100; QTimer = millis() ) {  // one line Spam Delay at 100 miliseconds
    Serial.print("Counts: "); Serial.print(Counts );
    //    Serial.print(" Target RPM: ");Serial.print(RPM );
    //    Serial.print(" Counter: "); Serial.print(Counter );
    Serial.print(" timeX: "); Serial.print(Time );
    Serial.print(" DeltaT: "); Serial.print(Time *  PulseCtr);
    Serial.print(" PulseCtr: "); Serial.print(PulseCtr );
    Serial.print(" PulsesPerMinute: "); Serial.print(dtostrf(PulsesPerMinute, 6, 1, S));
    //   Serial.print(" Setpoint: "); Serial.print(dtostrf(Setpoint, 6, 1, S) );
    //   Serial.print(" Calculated RPM: "); Serial.print(dtostrf(Input, 6, 1, S));
    //   Serial.print(" Output: "); Serial.print(dtostrf(Output, 6, 2, S));
    //   Serial.println();

  }
}

PID_v1.cpp (9.3 KB)

MPU6050 - Copy.h (3.08 KB)