PID Primer
This page is dedicated to helping you understand what PID controllers are, what they do, how to use them, and how to tune them. To start with, let's quickly define what PID actually stands for and how a controller using this system should work in very general terms:
So, essentially, a PID controller is a type of rudimentary artificial intelligence based on feedback loops that can control simple processes. Now that we know in general what a PID controller is, let's dive in and see how it actually works, in particular with reference to Microsoft Flight Simulator.
PID Overview
There are many PIDs in Microsoft Flight Simulator, mostly within the aircraft autopilot systems but also in engine parameters, turbine parameters, landing gear calculations, etc... However, in order to use a PID controller for any of these systems you should have decent understanding of what they do and how to use them, which is what this primer aims to achieve.
A PID is a "controller" that controls something based on input values to get a specific result as an output value. To think of a simple example, let's take a car with cruise control - A PID controller can be used to control the gas pedal position on the car to keep the car driving at the speed defined by the cruise control.
A PID controller works iteratively by receiving one input value - along with a time delta for each timestep - and returning a value for the control that will change the input value for the next cycle. In this way, the PID controller will try to reduce the input value to ZERO. This means that one needs to give it the "error", or the difference with the wanted target, as the input value. Going back to our car with cruise control example - To control the gas pedal position to keep the cruise control speed, the PID controller will take as input the difference between the wanted cruise control speed and the current speed of the car. If you input the PID controller with the current speed, instead of the difference (error), it will try to zero the current speed, and in doing so it will try to stop the car!
NOTE: The PID will iteratively work at reducing the input value to ZERO. Therefor, when dealing with PID values, ask yourself the following:
- Will the input value be zero once my system is stabilized?
- Does the PID output have enough control over the system to actually bring the input value to zero?
The easiest way to understand this is to start with what the PID controller will look like once the system is stabilized and the input value is being maintained steady at zero. For our example with the car now driving at the target cruise control speed, the difference (error) between the current speed and target speed is - and stays - at zero for each iteration. The PID controllers output - the position of the gas pedal - is staying constant at a non zero position. So the PID is returning a constant value, with a zero input and this happens each iteration.
So, how is this achieved? Internally, the PID controller achieves this primarily with the \(Integral\) term. Both the \(Proportional\) and \(Derivative\) term are only proportional to the input value. So with a constant zero input, they both always return zero. The \(Integral\) part however has an internal accumulator that accumulates the past errors. When the input value reaches a stable zero, the accumulator stays constant and is equal to the PID controllers output and so maintains the system stable. We can say with confidence that the most important part of the PID controller is the \(Integral\) part because of this. It is the part that will be maintaining the system stable and keep the error input value at - or as close as possible to - zero. This part will become a constant and needs to become a constant for the PID to work optimally. This means that the PID controller's output needs to be a constant non-zero (except for some rare cases) for the PID to work as perfectly as possible.
If the PID controller's output always needs to be zero for the system to remain with a stable zero input, then the PID controller's output is applied on the derivative instead of the actual control. Basically, it may be added to the control instead of becoming the control. In this case, the PID will become highly ineffective, with the \(Proportional\) term playing the role of the \(Integral\) term, the controller value will be playing the role of the internal accumulator; the \(Integral\) term and \(Derivative\) terms will become ineffective and the PID will have no actual \(Proportional\) and \(Derivative\) control over the system. This way of implementing a PID is found in some Microsoft Flight Simulator parameters due to the legacy calculations from FSX, but it's not perfect and future updates will address and correct these.
NOTE: The easiest way to tune a standard and correctly implemented PID is to set the \(Proportional\) and \(Derivative\) factor to zero and then tune the \(Integral\) factor until the system is able to stabilize at a constant zero input state at the speed desired, but without any overshooting. The \(Proportional\) and \(Derivative\) factors are useful only to accelerate the convergence process without overshooting. So, you should be asking yourself:
- Will the PID output be constant once the system is stabilized?
NOTE: A few of the PID controllers in Microsoft Flight Simulator are not 100% perfectly implemented, and as such they will always produce a stable zero output of the PID when the input stabilizes at zero. In these cases, you will need to tune the \(Proportional\) part as if it was the \(Integral\) part and set the \(Integral\) and \(Derivative\) part to zero as they will work against the system, creating overshooting and unpredictable behaviors. For more information see PID Parameters.
Once the Integral part does its job and the PID controller stabilizes the input value to zero - without overshooting during the process - you may have a system that converges too slowly. This is where the \(Proportional\) parameter comes in and helps accelerate the process. The \(Proportional\) parameter cannot be used to stabilize a situation, because it will always contribute zero to the control once the input value reaches zero. If we go back once more to our car example, when the car speed reaches the target cruise speed, the input will reach zero and the \(Proportional\) output will be zero as well. This in turn will cause the car to slow down and lag behind. So, a correctly implemented PID controller can never reach a stable result with a \(Proportional\) part only. If it can, then that means it is poorly implemented and the PID is working on a rare specific situation where a zero output control value generates a stable zero input value.
NOTE: Use the \(Proportional\) term to accelerate convergence and the \(Derivative\) term to limit overshooting.
The PID Function
To start with, let's look at the mathematics behind how PID controllers work. This section of the primer is of particular interest to programmers, and explains how the PID is implemented "behind the scenes" in Microsoft Flight Simulator. Let's look at the general PID function first:
$$control = PID(error, dTime)$$
This can be further expanded:
$$control = (P \times error) + (D \times derivative(error)) + (I \times integral(error))$$
Where:
\(integral(error) = \textrm{integration (timed sum) of all errors over time}\) \(derivative(error) = \textrm{derivative (timed difference) between the last 2 errors}\)
Note that when stabilized to a zero input value, the output control value should be a non-zero constant.
- Correct:
$$control = PID(0, dTime) = (P \times 0) + (D \times 0) + (I \times integral(error)) = I \times integral(error) = \textrm {non-zero constant}$$
- Incorrect:
$$control \mathrel{+}= PID(0, dTime) = (P \times 0) + (D \times 0) + (I \times 0) = 0 = \textrm {zero}$$
PID Tuning
The following information is for both programmers and designers, and gives some simple rules for tuning the PID controller values in general circumstances:
- Tuning usually starts with setting \(P=0\) and \(D=0\) and then defining the \(I\) (Integral) to get a fast convergence without overshooting.
- The next step would be to tune \(P\) to accelerate convergence further if the initial tuning is not fast enough with just \(I\). You would then tweak the \(D\) value to limit overshooting.
That's the general process involved in setting these values, but it's worth keeping in mind the following when working with the different input values:
- If the PID controller always stabilizes to a zero output control value, then it is badly implemented and the \(P\) parameter will be playing the role of the \(I\) parameter; \(D\) and \(I\) can be ignored as they will work against the system.
- If the PID controller never stabilizes to a constant output control value, then it is badly implemented and the \(I\) parameter will be playing the role of the \(P\) parameter; \(D\) and \(P\) can be ignored as they will work against the system.
Boundary Values
PID controllers all have additional input values for the \(dBoundary\) and the \(iBoundary\). The goal of the these values is to limit undesired behavior that can appear from the Derivative and the Integral terms of the controller.
-
\(dBoundary\)
One problem that can occur with the derivative term, is a big "jump" when the error instantly changes. In the case of a "reset", or when the target input value instantly changes, the error derivative will make a jump inversely proportional to the timestep. This is totally unbound and with a microscopic timestep, the derivative can jump to infinity. The current \(dBoundary\) implementation is an ineffective way to limit this jump, as it does not limit the output but the input, and the output can still jump to infinity. Because of this issue, it is still possible to use the \(dBoundary\) but it needs to be smaller than necessary. When the error is larger than the \(dBoundary\), the derivative term is zeroed.
-
\(iBoundary\)
Another frequent problem is unreachable targets. When a target is unreachable, the integral term will accumulate to infinity. When a new, reachable target is then set, this infinite integral term will prevent the PID controller from functioning properly. The \(iBoundary\) should simply limit this integral sum, but in the current flight model implementation it resets the integral in the case of an error larger than the \(iBoundary\). This is an incomplete solution that can work in some cases but still needs improvement.
PID Parameters
In this section we'll list the different PID parameters and give a little bit of extra information on each of them explaining how the PID controller has been implemented for them:
-
pitchPID
Function: \(elevator = PID(pitchSpeedError, dTime)\)
Description: This PID controller is used by the copilot and/or autopilot for setting the elevator angle. The stabilized result will attempt to hold the desired pitch.
Recommendation: Tune the PID controller starting with \(I\), then \(P\), then \(D\).
-
rudderGroundPID
Function: \(rudder = PID(headingError, dTime)\)
Description: This PID controller is used by the copilot and/or autopilot for setting the rudder angle. The stabilized result will counter crosswind and not be always zero.
Recommendation: Tune the PID controller starting with \(I\), then \(P\), then \(D\).
-
throttlePID
Function: \(throttle = PID(speedError, dTime)\)
Description: This PID controller is used by the copilot and/or autopilot for setting the throttle position.
Recommendation: Tune the PID controller starting with \(I\), then \(P\), then \(D\).
-
rollPID
Function: \(aileron = PID(rollError, dTime)\)
Description: This PID controller is used by the copilot and/or autopilot for setting the aileron angle.
Recommendation: Tune the PID controller starting with \(I\), then \(P\), then \(D\).
-
throttleGroundPID
Function: \(throttle \mathrel{+}= PID(speedError, dTime)\)
Description: This PID controller is used by the copilot and/or autopilot to add to the throttle position, however the stabilized result is always zero due to the parameter being imperfectly implemented. Future updates will add a new PID parameter that overrides this one with a better implementation of the PID.
Recommendation: Tune the PID controller to use \(P\) as an \(Integrator\).
-
nav_yawPID
Function: \(rudder = PID(headingError, dTime)\)
Description: This PID controller is used by the copilot and/or autopilot for setting the rudder angle. The stabilized result will counter crosswind and not be always zero.
Recommendation: Tune the PID controller starting with \(I\), then \(P\), then \(D\).
-
headingPID
Function: \(bank = PID(turnrateError, dTime)\)
Description: This PID controller is used by the copilot and/or autopilot for setting the bank angle. The stabilized result will be a constant bank angle matching the desired turn rate.
Recommendation: Tune the PID controller starting with \(I\), then \(P\), then \(D\).
-
nav_ex1PID
Function: \(heading \mathrel{+}= PID(deviationError, dTime)\)
Description: This PID controller is used by the copilot and/or autopilot to set the desired heading change rate. The stabilized result will be a zero heading change rate matching a zero deviationError. Note that this parameter is imperfectly implemented. Future updates will add a new PID parameter that overrides this one with a better implementation of the PID that will act directly on the desired heading position.
Recommendation: Tune the PID controller to use \(P\) as an \(Integrator\).
-
flightLevelPID
Function: \(pitch = PID(airspeedError, dTime)\)
Description: This PID controller is used by the copilot and/or autopilot for setting the pitch angle. The stabilized result will be a constant pitch angle matching the desired airspeed.
Recommendation: Tune the PID controller starting with \(I\), then \(P\), then \(D\).
-
glideSlopePID
Function: \(pitch = PID(glideSlopeError, dTime)\)
Description: This PID controller is used by the copilot and/or autopilot for setting the pitch angle. The stabilized result will be a constant pitch angle matching the desired glideslope.
Recommendation: Tune the PID controller starting with \(I\), then \(P\), then \(D\).
-
verticalSpeedPID
Function: \(pitch = PID(verticalSpeedError, dTime)\)
Description: This PID controller is used by the copilot and/or autopilot for setting the pitch angle. The stabilized result will be a constant pitch angle matching the desired vertical speed.
Recommendation: Tune the PID controller starting with \(I\), then \(P\), then \(D\).
-
fuel_flow_controller_p
fuel_flow_controller_i
fuel_flow_controller_d
fuel_flow_controller_iboundary
fuel_flow_controller_dboundary
Function: \(correctedFF \mathrel{+}= PID(NeError, dTime)\)
Description: This PID controller is used to add to the corrected fuel-flow position, however the stabilized result is always zero due to the parameter being imperfectly implemented. Future updates will add a new PID parameter that overrides this one with a better implementation of the PID that acts directly on the corrected fuel-flow position.
Recommendation: Tune the PID controller to use \(P\) as an \(Integrator\).
-
brakeDifferentialPID
Function: \(brakediff \mathrel{+}= PID(rotspeederror, dTime)\)
Description: This PID controller is used to add to the braking difference, however the stabilized result is always zero due to the parameter being imperfectly implemented. Future updates will add a new PID parameter that overrides this one with a better implementation of the PID that acts directly on the braking difference.
Recommendation: Tune the PID controller to use \(P\) as an \(Integrator\).