4
\$\begingroup\$

I'm using a shaft encoder attached to a DC motor to measure RPM, a L293D IC to drive the motor and a Arduino board. My objective is to control the DC motor speed to roughly 200 RPM, and I'm trying to use the PID library of the arduino to achieve this. My problem, right now, is with the tuning. The system oscillates a lot, and I've tried several methods, but nothing seems to work. I've tried setting Kd and Ki to zero and adjusting Kp until I get a steady oscillation, without success. Also, adjusting Kd isn't doing damping. I've tried using a simple Python program to plot the RPM in function of time and this is what I'm getting: enter image description here

Below is the Arduino code I'm using:

#include <PID_v1.h>

#define SpeedSetPoint 200

double Input, Output, Setpoint; //PID Variables

int potKp = 2;
int potKd = 3;
int potKi = 4;
float Kp = 0, Kd = 0, Ki =0;


PID myPid(&Input, &Output, &Setpoint, 0, 0, 0, DIRECT); 

//RPM Counter
unsigned long start;
const byte encoderPinA = 2;//A pin -> interrupt pin 0
const byte encoderPinB = 4;//B pin -> digital pin 4
volatile long pulse;
volatile bool pinB, pinA, dir;
const byte ppr = 25, upDatesPerSec = 2;
const int fin = 1000 / upDatesPerSec;
const float konstant = 60.0 * upDatesPerSec / (ppr * 2);
int rpm=0;

int outputValue=0;

//DC Motor Pins
int pwm = 5;
int motorA1 = 10;
int motorA2 = 9;

void setup(){
  Serial.begin(19200);
  Input = 25;
  Setpoint = SpeedSetPoint;
  myPid.SetMode(AUTOMATIC);

  attachInterrupt(0, readEncoder, CHANGE);
  pinMode(encoderPinA,INPUT_PULLUP);
  pinMode(encoderPinB,INPUT_PULLUP);

  pinMode(pwm, OUTPUT);
  pinMode(motorA1, OUTPUT);
  pinMode(motorA2, OUTPUT);

  //Start DC Motor
  digitalWrite(pwm, HIGH);
  digitalWrite(motorA1, HIGH);
  digitalWrite(motorA2, LOW);
}


void loop() {

  Kp = analogRead(potKp)/100.0;
  Ki = analogRead(potKi)/1000.0;
  Kd = analogRead(potKd)/1000.0;

  myPid.SetTunings(Kp, Ki, Kd);



  if(millis() - start > fin)
  {
    start = millis();
    rpm = pulse * konstant;
    rpm = abs(rpm);
    Serial.println(rpm);
    pulse = 0;
  }//End RPM reading

  Input = rpm;
  myPid.Compute();
  outputValue = Output;
  //outputValue = map(outputValue, 0, 400, 180, 255);
  analogWrite(pwm, outputValue);

  Serial.print("RPM = ");
  Serial.print(rpm);
  Serial.print("\t");
  Serial.print("Kp = ");
  Serial.print(Kp);
  Serial.print("\t");
  Serial.print("Ki = ");
  Serial.print(Ki);
  Serial.print("\t");
  Serial.print("Kd = ");
  Serial.println(Kd);
}

void readEncoder()
{
  pinA = bitRead(PIND,encoderPinA);
  pinB = bitRead(PIND,encoderPinB);
  dir = pinA ^ pinB;          // if pinA & pinB are the same
  dir ? --pulse : ++pulse;    // dir is CW, else CCW
}

Any tips?

\$\endgroup\$
  • \$\begingroup\$ What is the time-scale of that plot? Also, you say "I've tried setting Kd and Ki to zero and adjusting Kp until I get a steady oscillation, without success." What form is your lack of success? It never stops oscillating? or it never starts oscillating? If the former, are you saying it oscillates when all gains are zero? \$\endgroup\$ – Mark Aug 17 '16 at 1:38
  • 1
    \$\begingroup\$ Your "readEncoder()" routine does not detect direction correctly. if the motor turns slowly in either direction, "dir" will oscillate back and forth at two cycles per encoder cycle. You need to be driven by transitions on A and B. \$\endgroup\$ – Mark Aug 17 '16 at 1:48
  • \$\begingroup\$ You should probably initialize your 'start' variable before using it in the if-statement. \$\endgroup\$ – Big6 Aug 17 '16 at 4:18
  • \$\begingroup\$ It never stops oscillating, unless when all gains are zero (it oscillates less). Adjusting kd has no effect on damping. I did not understand the problem with the readEncoder() routine. What do you mean by transitions? \$\endgroup\$ – Vinicius Aug 17 '16 at 11:32
  • \$\begingroup\$ The readings are something like two readings per second. I'm using the Serial.print(rpm) to send data to serial on python \$\endgroup\$ – Vinicius Aug 17 '16 at 11:34
1
\$\begingroup\$

The first thing that jumps out from your plot is the large and somewhat random variations in successive iterations. That means you're not running the PID loop fast enough. For something like a motor, every 5 to 10 ms (100-200 Hz) is probably good, but of course that depends on the motor. Typically you want 20 to 50 iterations within the first major time constant of the system.

Only after the loop is running fast enough does tuning make any sense.

Start with just the P term, keeping I and D at 0. Crank up P to just below where it causes ringing. Now slowly bring up the I term. This will cause oscillations when too high. Find a value that sortof works, and tweak the P term again, etc. Find a set of P,I values that work.

Often you stop there, since using the D term makes the system very susceptible to noise. If your feed back signal is clean then start with the good P and I values, and slowly increase D. On some systems D is actually negative to be useful. Look at the math of your implementation carefully to see which polarity to use.

Think of the D term logic as "We're already heading in the right direction, so back off the drive a bit". This can be useful to dampen things so that the other terms can be set more aggressively and you therefore get faster overall step response to within some error.

\$\endgroup\$
0
\$\begingroup\$

You've left out the important stuff, like your loop algorithm.

Start small. Output a constant PWM. Is your RPM constant? If not, fix it.

Play with your PWM until you get a stable RPM of about 200.

Now let your Kp increase. Increase it until you start oscillating and then back off. Do you have a way to dynamically load the motor? If so, do it and check what the error is. If a reasonable load produces an acceptable error level, stop there. You don't need a PID.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.