2 pid controllers on same arduino

In order to try to drive my 2wd robot on a straight line I buy some motors with encoders.

I’m using the arduino pid controller lib.

When I try the sample with one encoder/motor the pid setpoint is reached quickly and motor stay on setpoint, or very close without a lot of errors.

The problem is with 2 pids

When I double the pid controller instances, duplicate all the required variables, and control code to have the arduino running each motor with its own pid controller, the results are bad, the speed up and down, and neither pid produce a good job.

I try to optimize it changing the p, I and d factors and the sampling time, and I manage to get better results, but they are far from the single pid results.

If you want I can post the code.

So the questions are:
do you have the same problem?
Any reason to have this issue?
And my main question is anyone have a sample code of arduino running 2 pid controllers on setpoint ?

Thanks for your reply’s

What are your loop error inputs and outputs? How about posting the code?

The pid input is the Encoder pulses, the output are speed motor changes, the setpoint are 60 pulses per sampling time, the error are +- 8 pulses, more than 10%.

Do you know any sample of 2 pid engine controller?

I can post the code for sure. But it also contains i2c code, and external commands parsing code, because it’s used as a program for a nano168 that should only control the motors as a i2c slave. It is a nice sample for working i2c control messages, but a example of a bad combination of pid controllers.

As soon as I got in my desk I will post the full code. It’s not big because I need to fit small nano168 memory.

Relevant 2 PIDs code parts, its just a duplication of one PID code. The 1 PID works smoth. The 2 PID bellow dont run smoth.

// BOF: ENCODERS
int encoderB_pin = 3;               // encoder pin        
volatile byte encoderB_pulses = 0;  // pulses reads
double encoderPulsesRight = 0;          // will keep the abs encoderB pulses value for pid;
long encoderTotalPulsesRight = 0;   // total pulses since last reset
int encoderA_pin = 2;               // encoder pin        
volatile byte encoderA_pulses = 0;  // pulses reads
long encoderTotalPulsesLeft = 0;    // total pulses since last reset
double encoderPulsesLeft = 0;           // will keep the abs encoderA pulses value for pid;
// EOF: ENCODERS 

// BOF: MOTORS PID
#include <PID_v1.h>

double pidRightInput; // will keep the input from process;
double pidLeftInput; // will keep the input from process;

double pidRightOutput;//Power supplied to the motor PWM value.
double pidLeftOutput;//Power supplied to the motor PWM value.

double pidRightSetPoint;
double pidLeftSetPoint;

double KpRight=0.1, KiRight=0.15, KdRight=0.0001;
double KpLeft=0.1, KiLeft=0.15, KdLeft=0.0001;

PID encoderPIDRight(&pidRightInput, &pidRightOutput, &pidRightSetPoint, KpRight, KiRight, KdRight, DIRECT); 
PID encoderPIDLeft(&pidLeftInput, &pidLeftOutput, &pidLeftSetPoint, KpLeft, KiLeft, KdLeft, DIRECT); 

double tmpEncoderLeftPulses;
double tmpEncoderRightPulses;

// EOF: MOTORS PID

void setup() {

// BOF: ENCODERS 
pinMode(encoderA_pin, INPUT);
attachInterrupt(0, encoderA_counter, FALLING);
//encoderA_pulses = 0;
// encoder B
pinMode(encoderB_pin, INPUT);
attachInterrupt(1, encoderB_counter, FALLING);
//encoderB_pulses = 0;
// EOF: ENCODERS HC020K

// BOF: MOTORS PID
encoderPIDRight.SetMode(AUTOMATIC);//PID is set to automatic mode
encoderPIDRight.SetSampleTime(100);//Set PID sampling frequency is 100ms
encoderPIDRight.SetOutputLimits(75, 255);
encoderPIDLeft.SetMode(AUTOMATIC);//PID is set to automatic mode
encoderPIDLeft.SetSampleTime(100);//Set PID sampling frequency is 100ms
encoderPIDLeft.SetOutputLimits(75, 255);
// EOF: MOTORS PID

}

void loop() {

double testEncoderLeftPulses;
double testEncoderRightPulses;


testEncoderLeftPulses = encoderSyncMotorLeft();
testEncoderRightPulses = encoderSyncMotorRight();

moveForward(pidRightOutput, pidLeftOutput);

}

// BOF: ENCODERS
void encoderA_counter(){
encoderA_pulses++;
}

void encoderB_counter(){
encoderB_pulses++;
}

// EOF: ENCODERS

// BOF: PID + ENCODERS
double encoderSyncMotorLeft() {
double tmpRet = 0;
boolean encoderResult;

encoderPulsesLeft = abs(encoderA_pulses);
pidLeftInput = encoderPulsesLeft;
encoderResult = encoderPIDLeft.Compute();//PID conversion is complete and returns 1
if(encoderResult) {
encoderTotalPulsesLeft += encoderA_pulses;
tmpRet = encoderA_pulses;
encoderA_pulses = 0; //Count clear, wait for the next count
}
return tmpRet;
}

double encoderSyncMotorRight() {
double tmpRet = 0;
boolean encoderResult;

encoderPulsesRight = abs(encoderB_pulses);
pidRightInput = encoderPulsesRight;
encoderResult = encoderPIDRight.Compute();//PID conversion is complete and returns 1
if(encoderResult) {
encoderTotalPulsesRight += encoderB_pulses;
tmpRet = encoderB_pulses;
encoderB_pulses = 0; //Count clear, wait for the next count
}
return tmpRet;
}
// EOF: PID + ENCODERS

@inaciose, nobody is going to study all that code the way you have posted it in Replies #4, 5 and 6. You have not used the code button </> and it is too easy to introduce errors when trying to join pieces together.

The best thing to do is to delete those Replies and add your .ino file as an attachment in a new Reply. See How to use the Forum

...R

Ok. I will delete them right after this post, and then I will send the full code as attachment as you suggest.

And extract the pid relevant code for easy reading.

Why is so dificult to found a working sample code of a 2 pid engine controller for a 2wd robot.

inaciose:
The pid input is the Encoder pulses, the output are speed motor changes, the setpoint are 60 pulses per sampling time, the error are +- 8 pulses, more than 10%.

Do you know any sample of 2 pid engine controller?

I think you don't have a stable setup there - 60 pulses per sampling time implies a long sampling time,
meaning the PID loop isn't running at a suitably high rate for stability.

The pid loop time needs to be pretty small for tight control, you need to calculate your speed error from
the instanteous rate of encoder ticks, you cannot wait and count them, input to a PID loop should be
about the present state, not some time in the past.

maybe you are right, but then i suppose that the 1 pid versions should have same problems to, and it dont.

Maybe im not understant the pid implementation.

put it simply:

using a encoder with: 20 holes (so 1 rotation are 20 pulses)
setting up a pid with: encoderPIDLeft.SetSampleTime(100);//Set PID sampling frequency is 100ms
setting up a setpoint to: 60

reads as follows?

the pid controller should try to get 60 pulses (3 rotations) on input on every 100 ms (the SampleTime set above?)

on PID.compute(), if read lower, should raise the output, if higher should lower the output.

My assumptions is: if raise the the sampleTime, there are more time to generate pulses and the PID.compute() take more time to change the output.

Im right?

what do you mean with this?
"you need to calculate your speed error from the instanteous rate of encoder ticks, you cannot wait and count them, input to a PID loop should be about the present state, not some time in the past."

Its the current count, that are reset on every PID.compute().

what is a setpoint in a dc motor? i belive that are pulses per time (speed). so you need to count pulses on each time cycle. and the pid job is set the output to reach and mantain smothly the number of pulses per cycle on the setpoint we desire.

Can you supply a working sample of 2 pid controllers for 2 dc engines?

thanks

If this was my problem I would be measuring the time between pulses (using micros() ) and using that as the input for PID.

I control the speed of a small DC motor that way - calling the PID function every time a pulse is detected. It works fine for a wide range of speeds from about 30,000 µsecs down to 4,000 µsecs per revolution (1 pulse per rev).

...R