Go Down

Topic: Problems using PID to control DC Motor? (Read 2758 times) previous topic - next topic

vinicius0197

I've tried to implement a PID controller using the PID library on the Arduino but without success. I'm using a simple 6 V DC motor to move a wheel, upon which I have attached a simple sha'ft encoder to measure RPM. I'm using the L293D H-bridge to control the DC motor.
My objective is to control the RPM to a desired value using PID. The code I'm using is the one below:
Code: [Select]

#include <PID_v1.h>

#define SpeedSetPoint 200

double Input, Output, Setpoint; //PID Variables

PID myPid(&Input, &Output, &Setpoint, 1, 0.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(9600);
  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() {
  if(millis() - start > fin)
  {
    start = millis();
    rpm = pulse * konstant;
    rpm = abs(rpm);
    //Serial.println(rpm);
    pulse = 0;
  }//End RPM reading

  Input = rpm;
  myPid.Compute();
  //Serial.println(Output);
  outputValue = Output;
  outputValue = map(outputValue, 0, 400, 180, 255);
  analogWrite(pwm, outputValue);
 
  Serial.print("Output = ");
  Serial.print(outputValue);
  Serial.print("\t");
  Serial.print("RPM = ");
  Serial.println(rpm);
}

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
}


And the Serial Monitor shows the following:
Code: [Select]

Output = 180 RPM = 206
Output = 180 RPM = 206
Output = 180 RPM = 206
Output = 180 RPM = 206
Output = 180 RPM = 206
Output = 180 RPM = 206
Output = 180 RPM = 206
Output = 180 RPM = 206
Output = 180 RPM = 206
Output = 180 RPM = 206
Output = 180 RPM = 206
Output = 180 RPM = 206
Output = 180 RPM = 206
Output = 180 RPM = 206
Output = 180 RPM = 206
Output = 180 RPM = 172
Output = 185 RPM = 172
Output = 185 RPM = 172
Output = 185 RPM = 172
Output = 185 RPM = 172
Output = 185 RPM = 172
Output = 185 RPM = 172
Output = 185 RPM = 172
Output = 185 RPM = 172
Output = 185 RPM = 172
Output = 185 RPM = 172
Output = 185 RPM = 172
Output = 185 RPM = 172
Output = 185 RPM = 172
Output = 185 RPM = 172
Output = 185 RPM = 172
Output = 185 RPM = 172
Output = 185 RPM = 172
Output = 185 RPM = 172
Output = 185 RPM = 172
Output = 185 RPM = 172
Output = 185 RPM = 235
Output = 185 RPM = 235
Output = 185 RPM = 235
Output = 185 RPM = 235
Output = 180 RPM = 235
Output = 180 RPM = 235
Output = 180 RPM = 235
Output = 180 RPM = 235
Output = 180 RPM = 235
Output = 180 RPM = 235
Output = 180 RPM = 235
Output = 180 RPM = 235
Output = 180 RPM = 235
Output = 180 RPM = 235
Output = 180 RPM = 235
Output = 180 RPM = 235
Output = 180 RPM = 235
Output = 180 RPM = 235
Output = 180 RPM = 235
Output = 180 RPM = 235
Output = 180 RPM = 235
Output = 180 RPM = 211
Output = 180 RPM = 211
Output = 180 RPM = 211
Output = 180 RPM = 211
Output = 180 RPM = 211
Output = 180 RPM = 211
Output = 180 RPM = 211
Output = 180 RPM = 211
Output = 180 RPM = 211
Output = 180 RPM = 211
Output = 180 RPM = 211
Output = 180 RPM = 211
Output = 180 RPM = 211
Output = 180 RPM = 211
Output = 180 RPM = 211
Output = 180 RPM = 211
Output = 180 RPM = 211
Output = 180 RPM = 211
Output = 180 RPM = 211
Output = 180 RPM = 211
Output = 180 RPM = 211
Output = 180 RPM = 204
Output = 180 RPM = 204
Output = 180 RPM = 204
Output = 180 RPM = 204
Output = 180 RPM = 204
Output = 180 RPM = 204
Output = 180 RPM = 204
Output = 180 RPM = 204
Output = 180 RPM = 204
Output = 180 RPM = 204
Output = 180 RPM = 204
Output = 180 RPM = 204
Output = 180 RPM = 204
Output = 180 RPM = 204
Output = 180 RPM = 204
Output = 180 RPM = 204
Output = 180 RPM = 204
Output = 180 RPM = 204
Output = 180 RPM = 204
Output = 180 RPM = 204
Output = 180 RPM = 204
Output = 180 RPM = 208
Output = 180 RPM = 208
Output = 180 RPM = 208
Output = 180 RPM = 208
Output = 180 RPM = 208
Output = 180 RPM = 208
Output = 180 RPM = 208
Output = 180 RPM = 208
Output = 180 RPM = 208
Output = 180 RPM = 208
Output = 180 RPM = 208
Output = 180 RPM = 208
Output = 180 RPM = 208
Output = 180 RPM = 208


Note: I've adjusted the map function to map from 180 to 255 because, for PWM values below 180, the DC motor just does a high pitched noise and doesn't move at all.

Thing is: my output just stays like that (with just some small variations), so I'm not really controlling the RPM. I've just started using PID, so I'm really newbie with this.

Is my code correct? How should I do the tuning for the PID? I've just left Kp = 1 and all others equal to zero.

jremington

#1
Aug 13, 2016, 04:54 pm Last Edit: Aug 13, 2016, 04:56 pm by jremington
It is a mistake to use the map() function until you are sure that the PID algorithm is running correctly, which includes the proper direction of change in the output value. If the RPM is too low, the output value should be high, etc.

After it is running correctly, then it is appropriate to limit the output values (NOT using a map function) to be physically reasonable.

Google "pid tuning" for many tutorials on how to tune the loop.

MarkT

#2
Aug 13, 2016, 07:46 pm Last Edit: Aug 13, 2016, 07:50 pm by MarkT
You make a common mistake.  The output of a PID is signed, but you try to force
its non-negative.  You have a throttle and no brake, game over(*).

If you have a non-zero integral term that will make up for the defficiency, but
with only a proportional term its not really control...

(*) typical behaviour is hunting - make sure your control system can drive the motor
in reverse (ie active braking), then it can a good chance of stabilizing under a fluctuating load.
[ I will NOT respond to personal messages, I WILL delete them, use the forum please ]

vinicius0197

Okay, I've left the map function out and now looks like at least the algorithm is doing something. The DC motor accelerates, stops, accelerates again, and keeps oscillating. I've tried using some pid tuning methods I've found on web, but looks like I'm not getting anywhere. First, I let Kp = 1, and Kd and ki = 0. With Kp = 1.5 and Ki = 2, looks like the system oscillates less.

MarkT

#4
Aug 13, 2016, 09:50 pm Last Edit: Aug 13, 2016, 09:51 pm by MarkT
Try turning down the gain, gradually increase as far as you can without oscillation (hint
add some pots to analog pins and use those to set the P, I, D values, then you can experiment
live.)

The I term you set to zero to start with, use it wisely or it will bite you hard!
[ I will NOT respond to personal messages, I WILL delete them, use the forum please ]

vinicius0197

I'm trying to follow your tip (about using pots to set the P, I, D values), but I'm stuck on how to update those values to the PID. I've done the following:
Code: [Select]

int pot = 2;
float potValue = 0;

PID myPid(&Input, &Output, &Setpoint, potValue, 0, 0, DIRECT);
[/code

And then, in the loop:
[code]
potValue = analogRead(pot);


But of course the potValue in the myPid won't update with the new values when I use the pot (it's probably going to be 0 always, since that's how a initialized potValue.
Any tips?

jremington


vinicius0197

Okay, sorry, I was messing things up. Now I'm able to tune the PID values using the pots. That's okay.
Problem is: I'm trying to use the Ziegler-Nichols method to tune the PID loop. So I try to get a steady oscillation by varying Kp. I had a hard time doing that just by looking at the Serial monitor, so I tried using Python to plot the data in real time. With Ki and Kd both equal to zero, I get something like this:

That doesn't look like a steady oscillation to me. As Kp approaches zero, the system oscillates less, but I can't see a significant difference by rising it's value. I tried varying Kd and Ki also, without sucess. I just can't see any real difference.

That's the complete code I'm using:

Code: [Select]
#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)/10.0;
  Ki = analogRead(potKi)/10.0;
  Kd = analogRead(potKd)/10.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.println(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? I really don't know what to do.

Southpark

#8
Aug 15, 2016, 10:15 pm Last Edit: Aug 19, 2016, 09:57 pm by Southpark
Increasing Kp from small value slowly to a larger value should give some oscillatory behaviour like that. Then Kd should be increased for the damping effect. If the system isn't doing damping when you increase Kd adequately, then will need to check to make sure that you're really getting some decent Kd*derror(t)/dt values for the system to work with.

MarkT

You'll get steady oscillation for a linear machine - your 6v small motor is nothing like a mathematically
ideal linear dynamic system, the oscillation you get is perfectly reasonable.
[ I will NOT respond to personal messages, I WILL delete them, use the forum please ]

Southpark

#10
Aug 19, 2016, 10:03 pm Last Edit: Aug 20, 2016, 02:19 am by Southpark
Any tips? I really don't know what to do.
I need to check the rules of coding....... but.... the variable you used "start" doesn't seem to have an initial value.... unless the coding rules automatically initialise it to something.

Probably good practice to manually initialise variables.

Go Up