PID controller for motor speed control not working

I have a car chassis with L298N motor controller . I am using an LM393 speed sensor module to sense the speed. Using the standard arduino pid library. The output from PID is directly sent as power (PWM) to the left and right motor wheels.

The value of the output reaches till 255 first , probably full power to get to the target speed , then there's an overshoot .

#include <PID_v1.h>

int sensor = 3;
int myPins[6] = {5, 4, 7, 8, 9, 6};

unsigned long start_time = 0;
unsigned long end_time = 0;
int steps=0;
float steps_old=0;
float temp=0;
float rps=0;
int sensorRpm=0;
int targetRpm=0;

const int buffersize = 10;
int buffer[buffersize] ;
int index;

//PID variables
double Setpoint, Input, Output;
double Kp=5;
double Ki=0;
double Kd=0;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);

void setup() 
{
  Serial.begin(9600);
  Serial.begin(9600);
  pinMode(sensor,INPUT_PULLUP);
  for (int i = 0; i < 6; i++) {
    pinMode(myPins[i], OUTPUT);
  }

  //turn the PID on
  myPID.SetSampleTime(2);
  myPID.SetOutputLimits(0, 255); // Standard PWM Range
  myPID.SetMode(AUTOMATIC);

}
 
void loop()
{
 int sensorspeed;
 start_time=millis();
 end_time=start_time+1000;
 while(millis()<end_time)
 {
   if(digitalRead(sensor))
   {
    steps=steps+1; 
    while(digitalRead(sensor));
   }
 }
    temp=steps-steps_old;
    steps_old=steps;
    sensorspeed = average();
    rps=(sensorspeed/20);  // no of slots in the encoder wheel

    sensorRpm=rps * 60;


    //update average buffer
    buffer[index]=temp;
    index++;
    if(index>=buffersize)
    {
      index=0;
    }
    //PID Control
    //target RPM
    targetRpm = 660; 
    Setpoint = targetRpm;
    Input = sensorRpm;
    myPID.Compute();
    Serial.print("Output=");
    Serial.println(Output);

    Serial.print("targetRpm:");
    Serial.print(targetRpm);
    Serial.print("\t");
    Serial.print("sensorRpm:");
    Serial.println(sensorRpm);
    
  if(Output>0)
  {
    moveRobot(Output,Output);
  }
}

void moveRobot(int leftSpeed, int rightSpeed)
{
  if (leftSpeed >= 0) {
    digitalWrite(myPins[1], 0);
    digitalWrite(myPins[2], 1);
  }
  else {
    digitalWrite(myPins[1], 1);
    digitalWrite(myPins[2], 0);

  }

  if (rightSpeed >= 0) {
    digitalWrite(myPins[3], 0);
    digitalWrite(myPins[4], 1);

  }
  else {
    digitalWrite(myPins[3], 1);
    digitalWrite(myPins[4], 0);

  }

  analogWrite(myPins[0], abs(leftSpeed));
  analogWrite(myPins[5], abs(rightSpeed));

}

//sensor reading averaging function
int average()
{
   long sum = 0;
   int i;
   int avg;
   
   for(i=0;i<buffersize;i++)
   {
     sum += buffer[i]  ;
   }
   
   avg = (sum/buffersize);
   
   return avg;
}

anything fundamentally wrong with my code ?

Initial overshoot is due to Kp being too high, with no compensation from Kd. What is your strategy for selecting these parameters?

If you don't have one, search for "PID tuning method" and choose from the several general approaches that turn up.

What type of power supply are you using? Also I would suggest you use a motor driver with MOSFET outputs, the L298 has bipolar outputs which will give you about 3 volts less then the power supply.

Yes, Kp is too high. Halve it and try again.

What does your output look like?

Does the process settle down? If so, what is the Output when the process is settled at Setpoint? That can give you a big clue about Kp.

I suggest to start with Kp = 0.2

Example: at dead stop, setpoint rpm = 660, measured rpm = 0, error = 660, Kp*error = output = 132, which is about half the maximum of 255.

But do look up "PID tuning".

BTW it is not clear how your code calculates speed when stopped. The while loop never exits if the sensor state does not change:

   if(digitalRead(sensor))
   {
    steps=steps+1; 
    while(digitalRead(sensor));   //<<<
   }

Normally a timeout is required.

1 Like

I will look into it. Thanks

Started with Kp=1 and results were the same. Overshoots , comes back and again does the same. output oscillates from 0-255 and then back again

Using a 11.1 V Li-Po battery

This is the o/p for kp=0.2. The output sort of oscillates. The behavior is the same for different Kp values , only the range of output changes , making me wonder if there's something wrong with how I am giving the output directly to PWM.


This is the general behavior of the system ,irrespective of kp value

I do get that when tuning we update kp value till the system oscillates . But here , the system oscillated even for the lowest value.

Keep going down. Kp is the constant that changes units of input error into units of the output value, so with Kp=1, and analogWrite(pin,Output), it means outputPWMcount = 1*Kp = 1PWMCount/RPMerror.

If you had a potentiometer attached to A0 and ran something like

loop(){
  int potVal = analogRead(A0);
  int motorPWM_0_255 = map(potVal,0,1023,0,255);
  analogWrite(MyPins[1],motorPWM);
  Serial.print("Pot:");
  Serial.print(potVal);
  Serial.print(" MotorPWM:");
  Serial.println(motorPWM_0_255);
  delay(250);
}

What sort of PWM value would you need to get 600RPM out of your motor?

Also, is the oscillation synchronized with this second-long loop?

If it oscillates in time with the measurement, it could be that could be that this measurement scheme is too slow for the response of the motor. With 10000us between pulses at 600RPM*20PPR/(60s/M), I'd consider using a speed measurement scheme based on noticing the falling edges of the encoder per something like https://www.arduino.cc/en/Tutorial/BuiltInExamples/StateChangeDetection or a FreqMeasure Library, for Measuring Frequencies in the 0.1 to 1000 Hz range, or RPM Tachometer Applications

Nonsense. When Kp=0, it won't respond at all. I agree with DaveX that your RPM measurement scheme will not work well and needs to be improved.

For stability, the PID loop timing needs to be very uniform, for example one calculation cycle every 1/10 second.

1 Like

of course .. I didn't mean kp=0.

let me look into that. Thanks

Yea, let me look into it. Thank you

My point is, you need to experiment with a reasonable range of values. When Kp=0.0001 the motor won't respond either.

Somewhere in between, there will be some sort of optimum. But this is irrelevant since you aren't measuring the speed correctly, either.

Work on those things and get back to us when you have improved the code.

Unless I'm reading that code wrong, that code will never work. You are tying up the CPU for one full second each time you take a speed reading. During that time, you are NOT updating the PID. Then you update the PID. The PID is updating once per second, NOT every 2 mSec as you set in the setSampleTime call. You CANNOT block the processor while the PID is running. myPID.Compute needs to be called, at an absolute minimum, every 2 mSec. ALWAYS.

And, even if you make the speed measurement non-blocking it still will not work properly unless the measurement update rate is at least nearly as fast as the PID update rate. Ideally, both will update at the same rate.

The sampling/adjustment time should depend on the "time constant" of the system. If the speed can't
physically change significantly in 2ms, making calculations or adjustments at that rate could amplify noise in the system. As a rule, setting the sampling/compute/adjustment time at 1/10 of the system time constant is a good start.

The PID_v1 code takes into account some level of slop, but for the time-based parameters, it acts as if the exact chosen sampling period has occurred, which would slow down the integral contribution, and amplify the derivative contribution.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.