DC motor PID control with Arduino Motor Shield and encoder

Hi Everyone,

I have been trying to implement this for the last few months, and haven't really got anywhere. I am trying to use an 2 channel hall effect encoder to control the speed of a DC motor through the latest Arduino Motor shield (purchased off this website).

The Arduino board I am using is the Mega 2560.

I think the best way to implement this for me is to use P or PD control, with the error being the difference between the rpm, and the required rpm, which are found using interrupts with the encoder.

In all honesty, I am a total novice at programming, and haven't really got anywhere with this, after a lot of hours. It is frustrating, as I had hoped this would be the simple part of my project. However, I am determined not to just throw in the towel!

Is there anybody out there who is willing to share a relevant code example, or hold my hand through this..? I have looked at the PID and interrupt library ect, but I am still nowhere near being able to implement this.

Thank you so much for your time, I realise it is probably frustrating to have to deal with total noobs, but I can't tell you how grateful I would be for any help!

but I am still nowhere near being able to implement this.

Why not? Break the problem down into small enough pieces. Are you able to read the speed of the motor? Are you able to control the speed of the motor? Do you have any code that attempts to implement PID?

Hi Pauls,

Thank you for your reply.

The code I have so far is:

Driving the motor

 //Motor
const int MOTOR_DIR = 13;      // Non PWM pin for direction control
const int MOTOR_PWM = 11;      // PWM controlled pin 
const int MOTOR_BRAKE = 8;     // Motor brake
const int MOTOR_CURRENT = A1;  // Motor Current

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

void loop() 
{
  
  //Engage Motor
  digitalWrite(MOTOR_DIR, HIGH);    //Establishes forward direction of Channel A
  digitalWrite(MOTOR_BRAKE, LOW);   //Disengage the Brake for Channel A
  analogWrite(MOTOR_PWM, 123);    //Spins the motor on Channel A at half speed

  /*delay(500);                 // hold the motor at full speed for 5 seconds
  Serial.print("current consumption at full speed: ");
  Serial.println(analogRead(MOTOR_CURRENT));*/
}

Reading the encoder (using the high performance library)

#include <Encoder.h>

Encoder myEnc(2, 18); //(digital 2, analog 4)


void setup() {
  Serial.begin(9600);
  Serial.println("Basic Encoder Test:");
}

long oldPosition  = -999;

void loop() {
  long newPosition = myEnc.read();
  if (newPosition != oldPosition) {
    oldPosition = newPosition;
    Serial.println(newPosition);
  }
}

I was hoping to use the PID library as well. My biggest difficulties at the moment are;

I am not sure how to find the rpm from the interrupts (I imagine I can either find the time between each interrupt, or find the number of interrupts over a given time. I am not sure what is the best route to go down, (or how to achieve this)!. I believe this involves using the millis function, but I don't really understand how to implement this.

Implementing different codes in the same script (do I just integrate the motor and encoder loops).

I really appreciate your help with this!
Alex

links to the two libraries..

http://playground.arduino.cc/Code/PIDLibrary#.Uxyog-clm18

http://www.pjrc.com/teensy/td_libs_Encoder.html

I am not sure how to find the rpm from the interrupts (I imagine I can either find the time between each interrupt, or find the number of interrupts over a given time. I am not sure what is the best route to go down, (or how to achieve this)!.

Yes. Which way to go depends on whether you are looking for an instantaneous RPM reading (you want to know as soon as RMP changes) or for an average reading (for display, like a tachometer).

I believe this involves using the millis function, but I don't really understand how to implement this.

It does, but first you need to decide what RPM reading you are looking for.

Implementing different codes in the same script (do I just integrate the motor and encoder loops).

Yes.

As for PID control, you have some INPUT (RPM), some SETPOINT (the RPM you want to maintain), and some OUTPUT (this is a time-based value that you need to convert to a useable value to influence the analogWrite() value). Since it's a feedback-based system, exact interpretation of the OUTPUT value isn't critical. The OUTPUT value will go up (in magnitude) as the difference between the INPUT value and the SETPOINT value increases and will go down as the difference decreases. The sign of the value will be positive if the difference is negative and will be negative if the difference is positive.

I would prefer the instantaneous RPM reading, as this will increase the response time of the PID (I think!). However presumably the signal would be noisier? For reference the encoder I am using is this one:

http://www.active-robots.com/motors-wheels/dc-motors/12vdc-encoder-motors/3258e-0-12v-1-5kg-cm-365rpm-10-1-dc-gear-motor-w-encoder.html

This has 360 counts per revolution. The maximum speed of the motor is 365rpm.

Do you have any advice on how to implement finding the time between the interrupts?

Thank you so much for your advice, really appreciate you taking the time to help me with this.

I would prefer the instantaneous RPM reading

So, record the time each pulse arrives, and compute the RPM based on the time between this pulse and the previous one.

as this will increase the response time of the PID (I think!).

Increasing response time is not a good thing. Increasing responsiveness, by decreasing response time, is. Usually.

Ah I see the difference! Thank you very much. I will have a go at this today!

Would the millis() or stopwatch class be better in this situation? (I realise the stopwatch class is based on millis()). It seems like this could be simpler to implement. Sorry if this is idiotic!

Here is my current iteration of code;

#include <Encoder.h>


//Timer
unsigned long oldTime = 0;
unsigned long newTime;
unsigned long deltaT;

//Encoder
long oldPosition =-999;
long newPosition;
Encoder myEnc(2, 3);

//Motor
const int MOTOR_DIR = 13;      // Non PWM pin for direction control
const int MOTOR_PWM = 11;      // PWM controlled pin 
const int MOTOR_BRAKE = 8;     // Motor brake
const int MOTOR_CURRENT = A1;  // Motor Current

void setup() {
  Serial.begin(9600);
  Serial.println("Basic Encoder Test:");

}

void loop() {
  
  //Engage Motor
  digitalWrite(MOTOR_DIR, HIGH);    //Establishes forward direction of Channel A
  digitalWrite(MOTOR_BRAKE, LOW);   //Disengage the Brake for Channel A
  analogWrite(MOTOR_PWM, 123);    //Spins the motor on Channel A at half speed

  newPosition = myEnc.read();
  

  if (newPosition != oldPosition) {
    oldPosition = newPosition;
    newTime = micros();
    deltaT = (newTime - oldTime);    //time interval (in micro seconds)
    oldTime = newTime;

    Serial.println(deltaT);
  }
}

It outputs about 6240 +/- 12 no matter how fast the motor is going. I have clearly made a novice error, any advice on what I have done wrong?
Many thanks as usual!
Alex

It outputs about 6240 +/- 12 no matter how fast the motor is going.

Why does this surprise you? You are printing how long it took to recognize a change in encoder values. Regardless of the speed of the motor, that time is going to be relatively constant. You need to divide the time by the amount of change (that is the part that is going to change) to get RPM. Keep in mind that the time you are measuring is in microseconds, not minutes.

Here is the code currently

#include <Encoder.h>

//Timer
unsigned long oldTime = 0;
unsigned long newTime;

unsigned long deltaT;
unsigned long deltaX;
unsigned long deltaV;


//Encoder
long oldPosition = 0;
long newPosition;
int  encRes = 360;                        // Encoder resolution

Encoder myEnc(2, 3);
//   avoid using pins with LEDs attached

const int MOTOR_DIR = 13;                 // Non PWM pin for direction control
const int MOTOR_PWM = 11;                 // PWM controlled pin 
const int MOTOR_BRAKE = 8;                // Motor brake
const int MOTOR_CURRENT = A1;             // Motor Current

void setup() {
  Serial.begin(9600);
  Serial.println("Basic Encoder Test:");
}


void loop() {
  
  digitalWrite(MOTOR_DIR, HIGH);          // Establishes forward direction of Channel A
  digitalWrite(MOTOR_BRAKE, LOW);         // Disengage the Brake for Channel A
  analogWrite(MOTOR_PWM, 255);            // Spins the motor on Channel A at half speed
  
  newPosition = myEnc.read();
  if (newPosition != oldPosition) {
    
    newTime = millis();
    
    deltaT = (newTime - oldTime);
    deltaX = (newPosition - oldPosition);
    deltaV = ((deltaX*1000/deltaT)/encRes);    //RPS
    
    Serial.println("speed = ");
    Serial.println(deltaV);
    
    oldTime = newTime;
    oldPosition = newPosition;
    
    delay(250);
  }
}

I now have an output that correlates to speed, but it seems to be far too high. At the lowest speeds the motor appears to be going around 1 rps, whereas the serial read out it about 60/70. Any advice. Thank you in advance!

PS crate of beer for someone who can help me finish this!

At the lowest speeds the motor appears to be going around 1 rps, whereas the serial read out it about 60/70.

What are the values of oldPosition, newPosition, oldTime, and newTime?

Are there REALLY 360 pulses per revolution? Do you know that you are getting all of them?

You should be resetting oldTime and oldPosition BEFORE the Serial.print() statements.

I've moved the reset to before the Serial.print().

Here is a link to the motor:

http://www.active-robots.com/motors-wheels/dc-motors/12vdc-encoder-motors/3258e-0-12v-1-5kg-cm-365rpm-10-1-dc-gear-motor-w-encoder.html

The specification states 360CPR.

With this in the loop:

void loop() {

  digitalWrite(MOTOR_DIR, HIGH);          // Establishes forward direction of Channel A
  digitalWrite(MOTOR_BRAKE, LOW);         // Disengage the Brake for Channel A
  analogWrite(MOTOR_PWM, 255);            // Spins the motor on Channel A at half speed

  newPosition = myEnc.read();
  if (newPosition != oldPosition) {

    newTime = millis();

    Serial.println("before =");
    Serial.println(oldTime);
    Serial.println(oldPosition);

    deltaT = (newTime - oldTime);
    deltaX = (newPosition - oldPosition);
    deltaV = ((deltaX*1000/deltaT)/encRes);    //RPS

    oldTime = newTime;
    oldPosition = newPosition;
    
    Serial.println("after = ");
    Serial.println(newTime);
    Serial.println(newPosition);
    
    

    delay(250);
  }

I get this kind of serial output:

before =
8151
136314
after = 
8402
142399
before =
8402
142399
after = 
8654
148454
before =
8654
148454
after = 
8906
154540
before =
8906
154540
after = 
9158
160614
before =
9158
160614
after = 
9411
161886

At this point, the motor is turning at about 2rps,

The deltaX is around 70, and the deltaT is 250ms.

Thanks for your time as ever PaulS. Definitely owe you!

I get this kind of serial output:

So, the encoder reading increases by roughly 250 per iteration of loop, while the time increases roughly 6100 milliseconds.

The deltaX is around 70, and the deltaT is 250ms.

I don't see that.

250 pulses is roughly 2/3 of a revolution in roughly 6 seconds, for a back of the envelope calculation of 6.6 RPM.

Sorry,

My mistake. That should read:

DeltaT = 250ms (time increase)

DeltaX = 6200 (count increase)

DeltaV = 70 (speed outputted by serial monitor)

with the motor turning at around 2rps

    deltaV = ((deltaX*1000/deltaT)/encRes);    //RPS

So, deltaX is 6200. Multiplying that by 1000 gives 6,200,000. Dividing that by delta T gives 24,800. Dividing that by 360 gives 68.888. So, given the numbers you are seeing, the output of 70 is correct.

Therefore, there is something wrong with your formula.

A couple of points:

  1. the motor is internally geared 10:1, which means there are 3600 counts per output shaft revolution. This puts it in a similar range. Sorry for not realising this sooner.

  2. does the fact that there are A/B pins mean that there are double the number of interrupts if that makes sense? Could be way off base here!

  1. does the fact that there are A/B pins mean that there are double the number of interrupts

No. It means that you can tell which way the encoder is moving. In some cases it matters. In a geared motor setup, it is unlikely that you would not know which way it is rotating.

Ok, well I am in the right order of magnitude which is progress. Do you have any ideas what might be making it too high (350% too much...)?