# PID Control: Integral error does not converge to zero

Good day,

I had been recently reading up more on PID controllers and stumbled upon something called integral wind up. I am currently working on an autonomous quadcopter concentrating at the moment on PID tuning. I noticed that even with the setpoint of zero degrees reached in this video, the quadcopter would still occasionally overshoot a bit: https://youtu.be/XD8WgVFfEsM

Here is the corresponding data testing the roll axis:

I noticed that the I-error does not converge to zero and continues to increase:

Is this the integral wind-up?

What is the most effective way to resolve this?

I have seen many implementations mainly focusing on limiting the output of the system by means of saturation. However I do not see this bringing the integral error eventually back to zero once the system is stable.

Here is my current code implementation with the setpoint of 0 degrees:

cout << "Starting Quadcopter" << endl;

float baseThrottle = 155; //1510ms
float maxThrottle = 180; //This is the current set max throttle for the PITCH YAW and ROLL PID to give allowance to the altitude PWM. 205 is the maximum which is equivalent to 2000ms time high PWM
float baseCompensation = 0; //For the Altitude PID to be implemented later

delay(3000);

float startTime=(float)getTickCount();
deltaTimeInit=(float)getTickCount(); //Starting value for first pass

while(1){

//Time Stamp
//The while loop is used to get a consistent dt for the proper integration to obtain the correct gyroscope angles. I found that with a variable dt, it is impossible to obtain correct angles from the gyroscope.

while( ( ((float)getTickCount()-deltaTimeInit) / ( ((float)getTickFrequency()) ) ) < 0.005){ //0.00209715|0.00419
deltaTime2=((float)getTickCount()-deltaTimeInit)/(((float)getTickFrequency())); //Get Time Elapsed
cout << " DT endx = " << deltaTime2 << endl;
}
//deltaTime2=((float)getTickCount()-deltaTimeInit)/(((float)getTickFrequency())); //Get Time Elapsed
deltaTimeInit=(float)getTickCount(); //Start counting time elapsed
cout << " DT end = " << deltaTime2 << endl;

//Complementary Filter
float pitchAngleCF=(alpha)*(pitchAngleCF+gyroAngleArray.Pitch*deltaTime2)+(1-alpha)*(accelmagAngleArray.Pitch);
float rollAngleCF=(alpha)*(rollAngleCF+gyroAngleArray.Roll*deltaTime2)+(1-alpha)*(accelmagAngleArray.Roll);
float yawAngleCF=(alpha)*(yawAngleCF+gyroAngleArray.Yaw*deltaTime2)+(1-alpha)*(accelmagAngleArray.Yaw);

//Calculate Orientation Error (current - target)
float pitchError = pitchAngleCF - pitchTarget;
pitchErrorSum += (pitchError*deltaTime2);
float pitchErrorDiff = pitchError - pitchPrevError;
pitchPrevError = pitchError;

float rollError = rollAngleCF - rollTarget;
rollErrorSum += (rollError*deltaTime2);
float rollErrorDiff = rollError - rollPrevError;
rollPrevError = rollError;

float yawError = yawAngleCF - yawTarget;
yawErrorSum += (yawError*deltaTime2);
float yawErrorDiff = yawError - yawPrevError;
yawPrevError = yawError;

//PID controller list
float pitchPID = pitchKp*pitchError + pitchKi*pitchErrorSum + pitchKd*pitchErrorDiff/deltaTime2;
float rollPID = rollKp*rollError + rollKi*rollErrorSum + rollKd*rollErrorDiff/deltaTime2;
float yawPID = yawKp*yawError + yawKi*yawErrorSum + yawKd*yawErrorDiff/deltaTime2;

//Motor Control - Mixing
//Motor Front Left (1)
float motorPwm1 =  -pitchPID + rollPID - yawPID + baseThrottle + baseCompensation;

//Motor Front Right (2)
float motorPwm2 =  -pitchPID - rollPID + yawPID + baseThrottle + baseCompensation;

//Motor Back Left (3)
float motorPwm3 = pitchPID + rollPID + yawPID + baseThrottle + baseCompensation;

//Motor Back Right (4)
float motorPwm4 = pitchPID - rollPID - yawPID + baseThrottle + baseCompensation;

//Check if PWM is Saturating - This method is used to fill then trim the outputs of the pwm that gets fed into the gpioPWM() function to avoid exceeding the earlier set maximum throttle while maintaining the ratios of the 4 motor throttles.
float motorPWM[4] = {motorPwm1, motorPwm2, motorPwm3, motorPwm4};
float minPWM = motorPWM[0];
int i;
for(i=0; i<4; i++){ // Get minimum PWM for filling
if(motorPWM[i]<minPWM){
minPWM=motorPWM[i];
}
}

cout << " MinPWM = " << minPWM << endl;

if(minPWM<baseThrottle){
float fillPwm=baseThrottle-minPWM; //Get deficiency and use this to fill all 4 motors
cout << " Fill = " << fillPwm << endl;
motorPwm1=motorPwm1+fillPwm;
motorPwm2=motorPwm2+fillPwm;
motorPwm3=motorPwm3+fillPwm;
motorPwm4=motorPwm4+fillPwm;
}

float motorPWM2[4] = {motorPwm1, motorPwm2, motorPwm3, motorPwm4};
float maxPWM = motorPWM2[0];
for(i=0; i<4; i++){ // Get max PWM for trimming
if(motorPWM2[i]>maxPWM){
maxPWM=motorPWM2[i];
}
}

cout << " MaxPWM = " << maxPWM << endl;

if(maxPWM>maxThrottle){
float trimPwm=maxPWM-maxThrottle; //Get excess and use this to trim all 4 motors
cout << " Trim = " << trimPwm << endl;
motorPwm1=motorPwm1-trimPwm;
motorPwm2=motorPwm2-trimPwm;
motorPwm3=motorPwm3-trimPwm;
motorPwm4=motorPwm4-trimPwm;
}

//PWM Output
gpioPWM(24,motorPwm1);  //1
gpioPWM(17,motorPwm2);  //2
gpioPWM(22,motorPwm3);  //3
gpioPWM(18,motorPwm4);  //4

• What's the unit ot the time axis? What happens when you run it for a much longer time? How close is the pwm output to the maximum value? Feb 13 '16 at 10:32
• @BendingUnit22 unit the test was ran at 20 seconds Feb 13 '16 at 10:39
• Does the I-error converge to some value if you keep it running for longer, say 50s or 2minutes even? Or does it keep growing? Feb 13 '16 at 11:24
• @BendingUnit22 I will try to test it for a longer time period, I have to set the quad up again for roll testing Feb 13 '16 at 12:15

I don't think this is related to integral windup at all.

I noticed that the I-error does not converge to zero

That's a good thing, because it means your integral term is not useless.

The integral term is there to compensate for steady-state errors. If you set the integral gain to 0, you should see that your system never reaches the setpoint.

The I-error that builds up will fix that.

and continues to increase

I'm not sure if that's really the case. From the image that you posted it might as well converge to some constant value. Hence my request to let the system run for a longer period to see how the I-error behaves in the long run.

Maybe there actually is something in your system that increases the steady-state error over time. I imagine that the increasing temperature of an electrical resistance (in the driver or the motor) might cause that.

It's somewhat hard to tell what the reason is because it's one PID for everything. If you had several (cascaded) controllers, you could locate the reason by looking at the individual controllers. In case of a rising electrical resistance due to rising temperature, that should be visible in the current controller.

tl:dr; There's a steady-state error in your system and the integral part of your controller compensates it. If that keeps growing, it means that the steady-state error is growing over time, which I think suggests that there's some kind of drift.

• Thank you, I have just finished altering the code for a cascaded PID controller setup as seems to be more stable as shown here: robotics.stackexchange.com/questions/2800/… Feb 13 '16 at 13:38
• One way you can reduce/remove the steady state error of the integral is by applying feedforward, however does require you to formulate an accurate model of the system. However there will probably always be some error, so you probably will always have some steady state error. Feb 14 '16 at 0:47

When I look at your graphs of position error and integral error, I don't see any unexpected behaviors for a system which is not quite tuned well enough. You are not showing integral windup nor saturation. Make sure to use the integral loop of your controller only when holding at a steady-state position and you will avoid windup.

It looks like your value for $k_p$ needs to be increased, and possibly also $k_i$. Disturbances are causing you to leave the steady-state position, and your system is not able to remain close enough to its steady-state position (or else you wouldn't be trying to improve the performance). The $k_i$ and $k_p$ terms are your options for locking that position in tighter around the steady-state value. How low do you want the steady state error band to go before you consider it acceptable behavior?

The integral response will be more sluggish than the position-loop response, so I would begin by increasing $k_p$ a bit, and contine to do so until you see the fist signs of unstable behavior, then back it off by 25 - 50%. Then do the same for $k_i$, and see if those two gains squash your steady state error sufficiently.

• I have been trying to tune the PID parameters using a single loop pid controller with angle setpoints but I haven't been able to get it to be more stable than +-5 degrees for almost 2 months now. I have decided to try out cascaded PID's using both angle and angular velocity setpoints as I have read that many people have had success with this even with basic tuning. Here is a testimony: robotics.stackexchange.com/questions/2800/… Feb 13 '16 at 13:41
• Wouldn't that help in the dynamic case of moving between setpoints, but have little effect on holding a steady state position? It seems your current controller is too soft. Good luck with whatever you try! Feb 13 '16 at 13:44