PID controller problems and their solutions.

This article is about unwanted behaviour of PID controllers.


During the study and development of the PID controller library it became clear that the "simple" implementation of the integration ("I") and differentiation ("D") mechanisms in PID controllers gave some serious drawbacks.

The Integration ("I") mechanism.

The problem

The behaviour of tne uncorrected integration mechanism is shown in figure A. It shows a system with a PID controller of which the Proportional and the Integration parts are used (both multipliers <> 0).

An "error" is introduced in the system at t1, and the controller takes of course corrective actions to make the error go away. It finally succeeds to do that at time t2, see "Error" in figure A.

But: The "Integration" part in the controller has been adding all errors so far, see curve "Integrated Error" in figure A. At the moment the actual error becomes zero, the "Integrated Error" is at its maximum (it can not go down unless the sign of the error itself changes).

This is a problem, because this "Integration residue" makes the controller believe there is still an error while this is not true: The result: the error now increases in the other direction, see the red "Error" line in figure A. This "overshoot" behaviour is of course totally unwanted.

The solution

The solution to this problem is to set the "Integrated Error" to zero as soon the "sign" of the error becomes different than the "sign" of the "Integrated Error". The result is shown in Figure B.

Some actual waveforms:

Proportional only:

Proportional + Integrational, uncorrected:

As one can see here the error itself is zero much more sooner than with proporional correction alone. Unfortunately the above described mechanism causes an overshoot and a damped oscillation.

Proportional + Integrational, corrected:
As one can see here the overshoot is almost gone, and the damped oscillation is absent.

The Differentiation ("D") mechanism.

The problem

Again we assume a PID controlled system in which we introduce an error. The error is corrected by the "Proportional" part in the PID contoller as shown in the black curves in figures A and B below.

Now some "differentiation" correction is added, resulting in the red error behaviour as shown in figure A.
The good part of the behaviour: the amplitude of the error has become smaller, this behaviour is desired.
The bad part of the behaviour: it takes longer to finally get back to the correct value (error = 0). This behaviour is of course undesired.

Ths reason for this is that the differential behaviour is symmetrical: it tries to prevent "changes" in the error, also if the error is getting less.

The solution

This is rather simple: switch off the differential correction as soon the absolute error is going down (so, make the behaviour assymmetrical). The result of this can be seen in figure B below.

Some actual waveforms:

Proportional only:

Proportional + Differential, uncorrected:

As can be seen here the amplitude of the error is much lower, but the time to go to zero error has almost doubled.

Proportional + Differential, corrected:

As can be seen here the zero error is achieved without extra delay.

An implementation example

type TPIDController =
       PID_Kp, PID_Ki, PID_Kd: real;
       PID_Integrated : real;
       PID_Prev_Input: real;
       PID_MinOutput, PID_MaxOutput: real;
       PID_First_Time, PID_Int_Improvement, PID_Diff_Improvement: boolean;
       PID_Prev_AbsError: real;
procedure Reset_PID(var Controller: TPIDController);
  with Controller do
    PID_Integrated    := 0.0;
    PID_Prev_Input    := 0.0;
    PID_Prev_AbsError := 0.0;
    PID_First_Time    := true;

procedure Set_PID_Int_Improvement(var Controller: TPIDController; On_: boolean);
  Controller.PID_Int_Improvement := On_;

procedure Set_PID_Diff_Improvement(var Controller: TPIDController; On_: boolean);
  Controller.PID_Diff_Improvement := On_;

procedure Init_PID(var Controller: TPIDController; Kp, Ki, Kd, MinOutput, MaxOutput: real);
  With Controller do
    PID_Kp               := Kp;
    PID_Ki               := Ki;
    PID_Kd               := Kd;
    PID_MinOutput        := MinOutput;
    PID_MaxOutput        := MaxOutput;
    PID_Integrated       := 0.0;
    PID_Prev_Input       := 0.0;
    PID_Prev_AbsError    := 0.0;
    PID_Int_Improvement  := false;
    PID_Diff_Improvement := false;
    PID_First_Time       := true;

function Sign(Value: real): byte;
  Result := 0;                     // negative
  if Value >= 0 then Result := 1;  // positive

function AbsReal(val1: real): real;
var Tmp: real;
  Result := Val1;
  if Result < 0 then Result := -Result;

procedure Limit(var Value: real; MinOutput, MaxOutput: real);
  if Value < MinOutput
  then Value := MinOutput
  else if Value > MaxOutput then Value := MaxOutput;

function PID_Calculate(var Controller: TPIDController; Setpoint, InputValue: real): real;
var Err, ErrValue, DiffValue, ErrAbs: real;
  With Controller do

    Err := SetPoint - InputValue;
    if PID_Diff_Improvement then ErrAbs := AbsReal(Err);
    // --- calculate proportional value ---
    ErrValue  := Err * PID_Kp;
    // --- PID Int Improvement ---
    if PID_Int_Improvement then if Sign(Err) <> Sign(PID_Integrated) then PID_Integrated := 0;

    // --- Calculate integrated value ---
    PID_Integrated := PID_Integrated + (Err * PID_Ki);
    // limit it to output minimum and maximum
    Limit(PID_Integrated, PID_MinOutput, PID_MaxOutput);
    // --- calculate derivative value ---
    if PID_First_Time then
    begin                           // to avoid a huge DiffValue the first time (PID_Prev_Input = 0)
      PID_First_Time    := false;
      PID_Prev_Input    := InputValue;
      PID_Prev_AbsError := 0;
    DiffValue := (InputValue - PID_Prev_Input) * PID_Kd;
    PID_Prev_Input := InputValue;

    // --- PID Diff Improvement ---
    if PID_Diff_Improvement then
      if ErrAbs < PID_Prev_AbsError then DiffValue := 0; // error becomes smaller, stop differentiation action
      PID_Prev_AbsError := ErrAbs;
    // --- calculate total ---
    Result := ErrValue + PID_Integrated - DiffValue; // mind the minus sign!!!
    // limit it to output minimum and maximum
    Limit(Result, PID_MinOutput, PID_MaxOutput);