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: enter image description here

I noticed that the I-error does not converge to zero and continues to increase: enter image description here

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


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

//Read Sensor Data

//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
    cout << " MinPWM = " << minPWM << endl;

        float fillPwm=baseThrottle-minPWM; //Get deficiency and use this to fill all 4 motors
        cout << " Fill = " << fillPwm << endl;

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

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

        float trimPwm=maxPWM-maxThrottle; //Get excess and use this to trim all 4 motors
        cout << " Trim = " << trimPwm << endl;

//PWM Output
    gpioPWM(24,motorPwm1);  //1
    gpioPWM(17,motorPwm2);  //2
    gpioPWM(22,motorPwm3);  //3
    gpioPWM(18,motorPwm4);  //4
  • $\begingroup$ 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? $\endgroup$ Commented Feb 13, 2016 at 10:32
  • $\begingroup$ @BendingUnit22 unit the test was ran at 20 seconds $\endgroup$ Commented Feb 13, 2016 at 10:39
  • $\begingroup$ 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? $\endgroup$ Commented Feb 13, 2016 at 11:24
  • $\begingroup$ @BendingUnit22 I will try to test it for a longer time period, I have to set the quad up again for roll testing $\endgroup$ Commented Feb 13, 2016 at 12:15

2 Answers 2


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.

  • $\begingroup$ 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/… $\endgroup$ Commented Feb 13, 2016 at 13:38
  • 2
    $\begingroup$ 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. $\endgroup$
    – fibonatic
    Commented Feb 14, 2016 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.

  • $\begingroup$ 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/… $\endgroup$ Commented Feb 13, 2016 at 13:41
  • 1
    $\begingroup$ 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! $\endgroup$
    – SteveO
    Commented Feb 13, 2016 at 13:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.