Search code examples
matlabsimulinkpid-controller

Differential part of PID and its low pass filter- time domain


I programmed PID in MATLAB:

classdef PID < handle
    properties
        Kp = 0
        Ki = 0
        Kd = 0
        SetPoint = 1
        Dt = 0.01
    end

    properties (Access = private)
        IState = 0
        PreErr = 0
    end

    methods
        function obj = PID(Kp, Ki, Kd, SetPoint, Dt)
            if nargin == 0
                return;
            end
            obj.Kp = Kp;
            obj.Ki = Ki;
            obj.Kd = Kd;
            obj.SetPoint = SetPoint;
            obj.Dt = Dt;
        end

        function output = update(obj, measuredValue, t)
            err = obj.SetPoint - measuredValue;
            P = obj.getP(err);
            I = obj.getI(err);
            val = lowPass(obj,t);
            D = obj.getD(err*val);
            output = P + I + D;
        end

        function val = getP(obj, err)
            val = obj.Kp*err;
        end

        function val = getI(obj, err)
            obj.IState = obj.IState + err * obj.Dt;
            val = obj.Ki * obj.IState;
        end

        function val = getD(obj, err)
            val = obj.Kd * (err - obj.PreErr) / obj.Dt;            
            obj.PreErr = err;
        end

        function val = lowPass(obj,t)
            N = 10;
            val = 1-exp(-N*t);
        end
    end
end

And tested it using a random low pass filter as the plant:

function r = getResponse(t)
r = 1 - exp(-5*t);
end

The test code:

sr = 1e2; % sampling rate 100Hz
st = 10; % sampling time 10s
ss = st*sr+1; % sample size
t = 0:1/sr:st; % time

input = ones(1,ss)*100;
output = zeros(1,ss);
measured = 0;

pid = PID(0,1,1,input(1),t(2)-t(1));
for i = 2:ss
    rPID(i) = pid.update(measured, t(i));
    output(i) = rPID(i)*getResponse(t(i));    
    measured = output(i);
end
figure
plot(t,output)
hold on;
plot(t,input)
plot(t,rPID)
legend('Output','Input','PID')

Note that the parameters are set to kp=0;ki=1;kd=1;. I'm only testing the differential part here. The result is very wrong:

enter image description here

Notice the Y-axis is scaled by 10^307. It gets too big that after ~1.6s the PID value exceeds the range of double precision and therefore, the curve stops.

I have ensured that both P and I parts work well enough (see this question I asked a while ago).

From the curve for the D component (see figure below), one can clearly see that it starts to oscillate heavy from the very beginning; its value reaches >50k after the 5th timestamp at 0.04s:

enter image description here

I'm almost certain I must have made a mistake in implementing the low pass filter, but I also noticed that even with the low pass filter removed, the differential values still behave similarly.


To have some sort of reference and comparison, I also made a Simulink simulation of the same system, using the exact same PID gains (i.e. kp=0;ki=1;kd=1;). Below is the block diagram (left), figure for input and output (top right figure) and figure for PID values (bottom right)

enter image description here

Note that there is no top/lower limit in the gain blocks and the initial inputs/outputs are set to zeros.

These PID gains are nowhere near optimised but they give completely different results in the simulation and coded PID.

Therefore the big question is am I doing something wrong here? Why is there a difference between the two results?


Solution

  • The implementation of the low pass filter is not correct. The difference equation of a low pass filter is as shown:

    eq1: LowPass

    The call of the getResponse function could look like this:

    pid = PID(0,1,1,input(1),t(2)-t(1)); 
    for i = 2:ss
        rPID(i) = pid.update(measured, t(i));   
        alpha = getResponse(0.25,0.01);
        output(i) = rPID(i)*alpha+(1-alpha)*output(i-1);   
        measured = output(i);
    end
    

    Thus getResponse is equivalent to alpha

    function r = getResponse(wc,Ts)
        r = 1 - exp(-wc*Ts);
    end
    

    Further you have to modify the lowPass function in the PID class.

        function output = update(obj, measuredValue)
            err = obj.SetPoint - measuredValue;
            P = obj.getP(err);
            I = obj.getI(err);
            val = lowPass(obj,err,0.1,0.01);
            D = obj.getD(val);
            output = P + I + D;
        end
        % ...
        function val = lowPass(obj,err,wc,Ts)
            alpha = getResponse(wc,Ts);
            output = err*alpha+(1-alpha)*obj.output_1;  
            obj.output_1 = output;
            val = output;
        end
    

    The resulting plot: