Is there a chance for PID control of this DC motor?

Hello everyone!

I have a 2WD robot car, and I'd like to make a position control to it.
I use these motors and encoder disk with speed sensor:

Is there any chance to make a PID position control of these motors?

These speed sensors have only one channel and this is why am I concerned. I always find such motor position control code where encoders have 2 channel.

These speed sensors have only one channel

what do you mean with "channel" ?

J-M-L:
what do you mean with "channel" ?

Output pin.
I use an LM393 speed sensor modul.

You have a tachometer. You could use PID for speed control, but for position control you need a position encoder.

You can, however, count ticks and get the approximate distance that the robot has traveled in one direction. It won't be very accurate because the wheels slip, especially if the robot makes turns.

jremington:
You have a tachometer. You could use PID for speed control, but for position control you need a position encoder.

You can, however, count ticks and get the approximate distance that the robot has traveled in one direction. It won't be very accurate because the wheels slip, especially if the robot makes turns.

Thanks!
I suspected that this encoder won't be OK. But unfortunately now I can't buy another motors with encoder, because my country is closed. So I need use these.

My car should move only on straight way, and should not be too precise.
Where should I start the PID code? I've already made similar before, I am not a beginner.
What would be the PID's input? I count the ticks, so I have the position. From these I can get the distance in one direction.
Which would be the input? Distance or the counted ticks?

If the only sensor you have is the number of steps a wheel has taken then you don’t have much choice

The input to the PID could be the difference of the ticks on both sides which you want to keep at zero whilst moving

Assuming the wheels don’t slip and have the same diameter, if you want to move straight you have to turn by the same number of ticks on both sides to progress in a straight motion by a given distance.

You can count 20 ticks for a full revolution of the wheel and you know it’s 65mm in diameter so one revolution is 65.PI mm = 204mm and thus a tick is a bit more than 1cm

J-M-L:
If the only sensor you have is the number of steps a wheel has taken then you don’t have much choice

The input to the pie could be the difference of the ticks on both sides which you want to keep at zero whilst moving

Assuming the wheels don’t slip and have the same diameter, if you want to move straight you have to turn by the same number of ticks on both sides to progress in a straight motion by a given distance.

You can count 20 ticks for a full revolution of the wheel and you know it’s 65mm in diameter so one revolution is 65.PI mm = 204mm and thus a tick is a bit more than 1cm

Thanks!

There is something that is not really clear for me.

"The input to the pie could be the difference of the ticks on both sides which you want to keep at zero whilst moving"

Did you mean that while they are moving they are counting the ticks and for example the left motor already turned 36, however the right motor 31. Then the input of PID would be the difference, 5?

Or you meant that there would be one PID for each motor and for example I want to go forward 36 ticks then the desired value is 36 and this 36 is the input? The difference between the real ticks and counted ticks should be 0?

I meant PID not pie, damn auto-correct! :slight_smile:

A PID needs a feedback loop and tries to minimize the error ("distance") between a set target and the current mesure.

Trying to make the difference in number of ticks to 0 will theoretically help go straight. In your example, 36 versus 31 would be ±5 depending which one you select first, and so in order to bring back this ±5 to 0 one motor will have to run slower (or faster) than the other.

So both motors could have a base speed (PWM) and then the output of the PID is a small ∆pwm that you apply to one of the motor to adjust its speed so that the effective progression is the same.

J-M-L:
I meant PID not pie, damn auto-correct! :slight_smile:

A PID needs a feedback loop and tries to minimize the error ("distance") between a set target and the current mesure.

Trying to make the difference in number of ticks will theoretically help go straight. In your example, 36 versus 31 would be ±5 depending which one you select first, and so in order to bring back this ±5 to 0 one motor will have to run slower (or faster) than the other.

So both motors could have a base speed (PWM) and then the output of the PID is a small ∆pwm that you apply to one of the motor to adjust its speed so that the effective progression is the same.

Thanks again, it helped a lot!

As I noticed my motors can starts only if they get at least 40% dutycycle (120-130 in pwm) when they are running in free.

How can I achieve that my PID only gives higher value than 130? With precise tune, or I should convert the PID's output to certain value?

pestiofficial:
Thanks again, it helped a lot!

As I noticed my motors can starts only if they get at least 40% dutycycle (120-130 in pwm) when they are running in free.

How can I achieve that my PID only gives higher value than 130? With precise tune, or I should convert the PID's output to certain value?

Or I should add this small pwm value to the already existing pwm value?

exactly You could set the common base "speed" to 150 and constrain the ∆pwm you obtain from the PID to be above -20. This way 150-20 gets you to 130.

You could also decide to split the ∆pwm amongst both motors, one get basePWM + (∆[sub]pwm[/sub] / 2) and the other basePWM - (∆[sub]pwm[/sub] / 2). in that case you can constrain the ∆pwm to be between -40 and +155 (as max PWM will be 255 anyway)

if motors are not too different the ∆pwm would usually be small anyway and the PID won't wait to be 5 ticks off before trying to course correct. (if one wheel move 5cm further than the other, you have turned quite significantly depending on the distance between the two wheels)

I don't know why you would have two PIDs...

Use one PID function to control the speed to both motors, as described above:

split the ∆pwm amongst both motors, one get basePWM + (∆pwm / 2) and the other basePWM - (∆pwm / 2). in that case you can constrain the ∆pwm to be between -40 and +155 (as max PWM will be 255 anyway)

J-M-L:
exactly You could set the common base "speed" to 150 and constrain the ∆pwm you obtain from the PID to be above -20. This way 150-20 gets you to 130.

You could also decide to split the ∆pwm amongst both motors, one get basePWM + (∆[sub]pwm[/sub] / 2) and the other basePWM - (∆[sub]pwm[/sub] / 2). in that case you can constrain the ∆pwm to be between -40 and +155 (as max PWM will be 255 anyway)

if motors are not too different the ∆pwm would usually be small anyway and the PID won't wait to be 5 ticks off before trying to course correct. (if one wheel move 5cm further than the other, you have turned quite significantly depending on the distance between the two wheels)

"split the ∆pwm amongst both motors, one get basePWM + (∆pwm / 2) and the other basePWM - (∆pwm / 2). in that case you can constrain the ∆pwm to be between -40 and +155 (as max PWM will be 255 anyway)"

How can I decide that which motor gets the + (∆pwm / 2) and which gets the - (∆pwm / 2)?

I noticed that one of my motors turning a little bit slower than other.

Thanks in advance!

Made a small mistake in the constraining, as ∆pwm will actually be a signed number so you need to constrain the resulting value keeping the sign.

Assume you have N1 steps for Motor1 and N2 steps for Motor2

If the error is written as (N2 - N1) then when the error is positive it means N2 is greater than N1 which translates into Motor2 has done more steps than Motor1 in a given time and thus you need to slow Motor2 and accelerate Motor1.

The PID formulas will mess around with the error but the resulting ∆pwm will still represent a correction that will respect this idea of minimizing the error through accelerating one and slowing down the other one.

==> So the way you calculate the error gives you which one gets the + (∆pwm / 2) and which one gets the - (∆pwm / 2)

makes sense ?

J-M-L:
Made a small mistake in the constraining, as ∆pwm will actually be a signed number so you need to constrain the resulting value keeping the sign.

Assume you have N1 steps for Motor1 and N2 steps for Motor2

If the error is written as (N2 - N1) then when the error is positive it means N2 is greater than N1 which translates into Motor2 has done more steps than Motor1 in a given time and thus you need to slow Motor2 and accelerate Motor1.

The PID formulas will mess around with the error but the resulting ∆pwm will still represent a correction that will respect this idea of minimizing the error through accelerating one and slowing down the other one.

==> So the way you calculate the error gives you which one gets the + (∆pwm / 2) and which one gets the - (∆pwm / 2)

makes sense ?

Yes, I think I understood.

I've written a code, but it didn't show any result.

Here is the code:

#include <Arduino.h>
#include <PID_v1.h>
//#include <analogWrite.h>
//#include <EnableInterrupt.h>

const int pinA = 14;//23;//14
const int pinB = 4;//22;//4

int N1 = 0, sumN1 = 0;
int N2 = 0, sumN2 = 0;

//PID stuff
double error = 0, desired = 0, dPWM = 0;

int kp = 7;
int ki = 0;
int kd = 0;

PID speed_PID(&error, &dPWM, &desired, kp, ki, kd, DIRECT);

//Motor A
int enA = 16;//32;
int in1 = 13;//33;
int in2 = 15;//25;

//Motor B
int enB = 12;//13;
int in3 = 5;//26;
int in4 = 0;//27;

int speed = 400;

//Reading the rising edges
void IRAM_ATTR cntA() { 
  N1++;
  //Serial.print("N1:");
  //Serial.println(N1);
  }
void IRAM_ATTR cntB() { 
  N2++;
  //Serial.print("N2:");
  //Serial.println(N2);
  }

//Stop function
void Stop(){
  analogWrite(enA, 0);
  analogWrite(enB, 0);
  digitalWrite(in1, LOW);
  digitalWrite(in2, LOW);
  digitalWrite(in3, LOW);
  digitalWrite(in4, LOW);
  sumN1 += N1; //store the taken ticks
  sumN2 += N2; 
  N1 = 0;  //  reset counter A to zero
  N2 = 0;  //  reset counter B to zero 
}

//PID
void myPID(){
  error = sumN2 - sumN1;
  speed_PID.Compute();
}

void Print(){
  Serial.println("dPWM: ");
  Serial.println(dPWM);
  Serial.println("error: ");
  Serial.println(error);
  Serial.println("sumN1: ");
  Serial.println(sumN1);
  Serial.println("sumN2: ");
  Serial.println(sumN2);
  Serial.println();
  Serial.println();
}
//Forvard function
void Forward(int steps,int mspeed){
  N1 = 0;  //  reset counter A to zero
  N2 = 0;  //  reset counter B to zero
     // Set Motor A forward
   digitalWrite(in1, HIGH);
   digitalWrite(in2, LOW);
   // Set Motor B forward
   digitalWrite(in3, HIGH);
   digitalWrite(in4, LOW);
   
   // Go forward until step value is reached
  while (steps > N1 && steps > N2) {
     myPID();
     //slow M2, accelerate M1 speed
    if (error >= 0){
      if (steps > N1) {
     analogWrite(enA, mspeed+(dPWM/2));
     } else {
     analogWrite(enA, 0);
     }
      if (steps > N2) {
     analogWrite(enB, mspeed-(dPWM/2));
     } else {
     analogWrite(enB, 0);
     }
    }
    //slow M1, accelerate M2 speed
    if (error < 0){
      if (steps > N1) {
     analogWrite(enA, mspeed-(dPWM/2));
     } else {
     analogWrite(enA, 0);
     }
      if (steps > N2) {
     analogWrite(enB, mspeed+(dPWM/2));
     } else {
     analogWrite(enB, 0);
     }
    }
    delay(0);
   }
   // Stop when done
   Stop();
}

//Backward function
void Backward (int steps, int mspeed) {
  N1 = 0;  //  reset counter A to zero
  N2 = 0;  //  reset counter B to zero
     // Set Motor A forward
   digitalWrite(in1, LOW);
   digitalWrite(in2, HIGH);
   // Set Motor B forward
   digitalWrite(in3, LOW);
   digitalWrite(in4, HIGH);

   while (steps > N1 && steps > N2){
     myPID();
     //slow M2, accelerate M1 speed
    if (error >= 0){
      if (steps > N1) {
     analogWrite(enA, mspeed+(dPWM/2));
     } else {
     analogWrite(enA, 0);
     }
      if (steps > N2) {
     analogWrite(enB, mspeed-(dPWM/2));
     } else {
     analogWrite(enB, 0);
     }
    }
    //slow M1, accelerate M2 speed
    if (error < 0){
      if (steps > N1) {
     analogWrite(enA, mspeed-(dPWM/2));
     } else {
     analogWrite(enA, 0);
     }
      if (steps > N2) {
     analogWrite(enB, mspeed+(dPWM/2));
     } else {
     analogWrite(enB, 0);
     }
    }
    delay(0);
   }
   // Stop when done
   Stop();
}

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

  pinMode(in1,OUTPUT);
  pinMode(in2,OUTPUT);
  pinMode(enA,OUTPUT);

  pinMode(in3,OUTPUT);
  pinMode(in4,OUTPUT);
  pinMode(enB,OUTPUT);

  //Interrupts
  attachInterrupt(digitalPinToInterrupt (pinA), cntA, RISING);
  attachInterrupt(digitalPinToInterrupt (pinB), cntB, RISING); 

  //PID
  speed_PID.SetOutputLimits(-50, 1023);
  speed_PID.SetMode(AUTOMATIC);
 } 
void loop()
{
  Forward(120,speed);
  Print();
  delay(2000);

  Backward(120,speed);
  Print();
  delay(2000);
}

I'd like to know that if the code is correct, and the only problem is the PID tuning.

hum the motor can spin forward or backward. When you get a tick you should increment or decrement based on current motor direction (never use print in an ISR)

void IRAM_ATTR cntA() { 
  N1++; // that's only going fwd...
  }
void IRAM_ATTR cntB() { 
  N2++;// that's only going fwd...
  }

that's your loop()

int speed = 400;
...

void loop()
{
  Forward(120,speed);
  Print();
  delay(2000);

  Backward(120,speed);
  Print();
  delay(2000);
}

so tell me where the PID is taken into account and thus why would you expect any specific behavior?

J-M-L:
hum the motor can spin forward or backward. When you get a tick you should increment or decrement based on current motor direction (never use print in an ISR)

void IRAM_ATTR cntA() { 

N1++; // that's only going fwd...
  }
void IRAM_ATTR cntB() {
  N2++;// that's only going fwd...
  }




that's your loop()

int speed = 400;
...

void loop()
{
  Forward(120,speed);
  Print();
  delay(2000);

Backward(120,speed);
  Print();
  delay(2000);
}


so tell me where the PID is taken into account and thus why would you expect any specific behavior?

Well, PID is taken into account in the "Forward" and "Backward" function because I only want to calculate PID when it is moving.

Here:

//Forvard function
void Forward(int steps,int mspeed){
  N1 = 0;  //  reset counter A to zero
  N2 = 0;  //  reset counter B to zero
     // Set Motor A forward
   digitalWrite(in1, HIGH);
   digitalWrite(in2, LOW);
   // Set Motor B forward
   digitalWrite(in3, HIGH);
   digitalWrite(in4, LOW);
   
   // Go forward until step value is reached
  while (steps > N1 && steps > N2) {
     myPID();
     //slow M2, accelerate M1 speed
    if (error >= 0){
      if (steps > N1) {
     analogWrite(enA, mspeed+(dPWM/2));
     } else {
     analogWrite(enA, 0);
     }
      if (steps > N2) {
     analogWrite(enB, mspeed-(dPWM/2));
     } else {
     analogWrite(enB, 0);
     }
    }
    //slow M1, accelerate M2 speed
    if (error < 0){
      if (steps > N1) {
     analogWrite(enA, mspeed-(dPWM/2));
     } else {
     analogWrite(enA, 0);
     }
      if (steps > N2) {
     analogWrite(enB, mspeed+(dPWM/2));
     } else {
     analogWrite(enB, 0);
     }
    }
    delay(0);
   }
   // Stop when done
   Stop();
}

And this is the myPID() :

//PID
void myPID(){
  error = sumN2 - sumN1;
  speed_PID.Compute();
}

I'd like to change direction baceuse my car will be a wireless car which could be controlled by a gyroscope. I know it's a little bit stupid idea :smiley: . And from the gyroscope value I would like to calculate that how many steps/ticks go forward or backward.

"When you get a tick you should increment or decrement based on current motor direction"
I know I should, but this lm393 speed sensor has 1output. Is that important to decrease the step value when the wheel is turnin backward?

Oops I should have had my glasses
It seems you control your motors one after the other?
You should work with copies of N1 and N2 as they can change during the compute

J-M-L:
It seems you control your motors one after the other?

Yes it seems, but in practice it works. But I wonder if there is another method for control the motor for position without WHILE, because I dont like to use WHILE function.

I've tried the PID code, it seemed to work, but not perfect. After 5-6 turns they were not turning in synchron. It could be a PID tuning problem, or it will never be perfect?