Code covered by the BSD License  

Highlights from
PID Basics

image thumbnail

PID Basics

by

 

04 Sep 2012 (Updated )

PID Basics is a nice way to obtain a quick intuition and understanding of PID control.

PIDBasics(varargin)
function varargout = PIDBasics(varargin)
% PIDBASICS MATLAB code for PIDBasics.fig
%      PIDBASICS, by itself, creates a new PIDBASICS or raises the existing
%      singleton*.
%
%      H = PIDBASICS returns the handle to a new PIDBASICS or the handle to
%      the existing singleton*.
%
%      PIDBASICS('CALLBACK',hObject,eventData,handles,...) calls the local
%      function named CALLBACK in PIDBASICS.M with the given input arguments.
%
%      PIDBASICS('Property','Value',...) creates a new PIDBASICS or raises the
%      existing singleton*.  Starting from the left, property value pairs are
%      applied to the GUI before PIDBasics_OpeningFcn gets called.  An
%      unrecognized property name or invalid value makes property application
%      stop.  All inputs are passed to PIDBasics_OpeningFcn via varargin.
%
%      *See GUI Options on GUIDE's Tools menu.  Choose "GUI allows only one
%      instance to run (singleton)".
%
% Copyright 2012 The MathWorks, Inc
% See also: GUIDE, GUIDATA, GUIHANDLES

% Edit the above text to modify the response to help PIDBasics

% Last Modified by GUIDE v2.5 03-Aug-2012 11:49:03

% Checking that Control System Toolbox(TM) is available
if isempty(ver('control')) 
    errordlg('You need Control System Toolbox to run this GUI', ...
        'Toolbox missing')
    return;
end
% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name',       mfilename, ...
                   'gui_Singleton',  gui_Singleton, ...
                   'gui_OpeningFcn', @PIDBasics_OpeningFcn, ...
                   'gui_OutputFcn',  @PIDBasics_OutputFcn, ...
                   'gui_LayoutFcn',  [] , ...
                   'gui_Callback',   []);
if nargin && ischar(varargin{1})
    gui_State.gui_Callback = str2func(varargin{1});
end

if nargout
    [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
    gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT


% --- Executes just before PIDBasics is made visible.
function PIDBasics_OpeningFcn(hObject, eventdata, handles, varargin)
% This function has no output args, see OutputFcn.
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
% varargin   command line arguments to PIDBasics (see VARARGIN)

% Choose default command line output for PIDBasics
handles.output = hObject;

% Default process creation
G = zpk([],[-1 -2 -4 -8],64);
set(G,'DisplayFormat','Time');
handles.G = G;
tfprocessText = displayTfProcess(G);
set(handles.text_tf,'String',tfprocessText)

% Setting DC gain for default process
Kp = computeDcGain(G);
set(handles.text_Kp, 'String', sprintf('Kp: %0.2f', Kp))
set(handles.slider_Kp, 'Min', log10(Kp/4), 'Max', log10(Kp*4), ...
    'Value', log10(Kp))

% Setting additional parameters
handles.params.Tfinal = 50;    % Final time
handles.params.freq = 11;      % Number of t points per second
handles.params.setpoint = 1;   % Setpoint magnitude
handles.params.distMag = 0.9;  % Load disturbance magnitude
handles.params.distTime = 17;  % Load disturbance time
handles.params.noiseVar = 0.6; % Noise variance
handles.params.noiseTime = 33; % Noise time
handles.params.sqwdx = -0.025; % X-axis plot margin
handles.params.sqwdy = -0.1;   % Y-axis plot margin

% Default controller creation: a P-Controller
enableDisableSlider({'on','off','off','off'}, handles)
handles = setDefaultControllerValues(handles);
K = power(10, get(handles.slider_K, 'Value'));
handles.C = pidstd(K);

handles.warn = [];

% Initial process and controller output plots. 
handles = processAndControllerPlots(handles, true, true);
% Robustness and Performance metrics
handles = robustnessMetrics(handles);
handles = performanceMetrics(handles);

% Handle creation for datatips
color = [1,0,0];
handles.cursor(1) = plot(handles.processOutput, NaN, NaN, ...
    'Color', color, 'Marker', 's', 'MarkerFaceColor', color, ...
    'MarkerSize', 4); 
handles.cursor(2) = plot(handles.controllerOutput, NaN, NaN, ...
    'Color', color, 'Marker', 's', 'MarkerFaceColor', color, ...
    'MarkerSize', 4); 
handles.hText(1) = text(NaN, NaN, '', 'Parent', handles.processOutput, ...
    'BackgroundColor', 'none', 'Color', [0,0,0], 'FontSize', 8);
handles.hText(2) = text(NaN, NaN, '', ...
    'Parent', handles.controllerOutput, ...
    'BackgroundColor', 'none', 'Color', [0,0,0], 'FontSize', 8);

% Plot titles and labels
title(handles.processOutput, 'Process Output', ...
    'HorizontalAlignment', 'left', 'Units', 'normalized', ...
    'Position', [0.025 1.025]);
title(handles.controllerOutput, 'Controller Output', ...
    'HorizontalAlignment', 'left', 'Units', 'normalized', ...
    'Position', [0.025 1.025]);
xlabel(handles.processOutput, 'Time', ...
    'HorizontalAlignment', 'right', 'Units', 'normalized', ...
    'Position', [0.975 -0.05]);
xlabel(handles.controllerOutput, 'Time', ...
    'HorizontalAlignment', 'right', 'Units', 'normalized', ...
    'Position', [0.975 -0.05]);
ylabel(handles.processOutput, 'y', ...
    'HorizontalAlignment', 'right', 'Units', 'normalized', ...
    'Position', [-0.03 0.9], 'Rotation', 0);
ylabel(handles.controllerOutput, 'u', ...
    'HorizontalAlignment', 'right', 'Units', 'normalized', ...
    'Position', [-0.03 0.9], 'Rotation', 0);

% Setting callback for selection change in the Controller buttongroup 
set(handles.controller, 'SelectionChangeFcn', ...
    {@controller_Change, handles})

% Setting sliders listeners
addlistener(handles.slider_Kp, 'ContinuousValueChange', ...
    @(src,evnt)slider_Kp_Scroll(src,evnt,handles));
addlistener(handles.slider_K, 'ContinuousValueChange',...
    @(src,evnt)slider_Gains_Scroll(src,evnt,handles.text_K));
addlistener(handles.slider_Ti, 'ContinuousValueChange',...
    @(src,evnt)slider_Gains_Scroll(src,evnt,handles.text_Ti));
addlistener(handles.slider_Td, 'ContinuousValueChange',...
    @(src,evnt)slider_Gains_Scroll(src,evnt,handles.text_Td));
addlistener(handles.slider_N, 'ContinuousValueChange',...
    @(src,evnt)slider_Gains_Scroll(src,evnt,handles.text_N));

% Update handles structure
guidata(hObject, handles);

% UIWAIT makes PIDBasics wait for user response (see UIRESUME)
% uiwait(handles.PIDBasics);


% --- Outputs from this function are returned to the command line.
function varargout = PIDBasics_OutputFcn(hObject, eventdata, handles) 
% varargout  cell array for returning output args (see VARARGOUT);
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Get default command line output from handles structure
varargout{1} = handles.output;


% --- Updates handles with default values for the controller sliders.
function handles = setDefaultControllerValues(handles)

hSlider = {'slider_K', 'slider_Ti', 'slider_Td', 'slider_N'};
hText = {'text_K', 'text_Ti', 'text_Td', 'text_N'};
value = [1,1,1,10]; % Logarithmic values are computed so that the slider 
                    % remains in the middle

controllers = get(handles.controller,'Children');
controllerP = controllers(strcmp(get(get(handles.controller,'Children'),...
    'Tag'),'controller_P'));
set(handles.controller,'SelectedObject',controllerP)

for i = 1:length(hSlider)
    set(handles.(hSlider{i}), 'Min', log10(value(i)/4), ...
        'Max', log10(value(i)*4), 'Value', log10(value(i)));
    str = textStr(handles.(hText{i}));
    set(handles.(hText{i}), 'String', sprintf([str, '%0.2f'], value(i)))
end


% --- Auxiliary function to set the string for some static text parameters.
function str = textStr(hText)
str = get(hText,'String');
str = str(1:regexp(str,':')+1);


% --- Updates handles with the new selected controller.
function handles = controllerUpdate(handles)

% Uibuttongroup selection
selectedRB = get(get(handles.controller,'SelectedObject'),'String');

switch selectedRB
    case{'P'}
        K = power(10,get(handles.slider_K, 'Value'));
        handles.C = pidstd(K);
    case{'I'}
        Ti = power(10,get(handles.slider_Ti, 'Value'));
        s = tf('s');
        handles.C = 1/(Ti*s);
    case{'PI'}
        K  = power(10,get(handles.slider_K,  'Value'));
        Ti = power(10,get(handles.slider_Ti, 'Value'));
        handles.C = pidstd(K,Ti);
	case{'PD'}
        K  = power(10,get(handles.slider_K,  'Value'));
        Td = power(10,get(handles.slider_Td, 'Value'));
        N  = power(10,get(handles.slider_N,  'Value'));
        handles.C = pidstd(K,Inf,Td,N);
	case{'PID'}
        K  = power(10,get(handles.slider_K,  'Value'));
        Td = power(10,get(handles.slider_Td, 'Value'));
        Ti = power(10,get(handles.slider_Ti, 'Value'));
        N  = power(10,get(handles.slider_N,  'Value'));
        handles.C = pidstd(K,Ti,Td,N);
end


% --- Process and controller plots based on current plant and controller.
function handles = processAndControllerPlots(handles, newPlot, moveAxes)
t = linspace(0, handles.params.Tfinal, ...
    handles.params.freq*handles.params.Tfinal);

% Process and controller outputs
% y = (C*G)/(1+C*G) r +    G/(1+C*G) d - (C*G)/(1+C*G) n
% u =     C/(1+C*G) r - (C*G)/(1+C*G)d -     C/(1+C*G) n

T = feedback(handles.C*handles.G,1); % T -> (C*G)/(1+C*G)
TC = T/handles.C;
TG = T/handles.G;

% Step response: r
r = handles.params.setpoint * ones(length(t),1);
[y1,ty] = lsim(T , r, t); % (C*G)/(1+C*G)
[u1,tu] = lsim(TG, r, t); %     C/(1+C*G)

% Disturbance: d
d = zeros(length(t),1); 
[~,iTimeMag] = min(abs(t - handles.params.distTime));
d(iTimeMag:end) = d(iTimeMag:end) + handles.params.distMag;
y2 = lsim(TC, d, t); %       G/(1+C*G)
u2 = lsim(-T, d, t); % -((C*G)/(1+C*G))

% Noise: n
n = zeros(length(t),1); 
[~,iTimeNoise] = min(abs(t - handles.params.noiseTime));
n(iTimeNoise:end) = n(iTimeNoise:end) + ...
    handles.params.noiseVar * 0.05* randn(length(n)-iTimeNoise+1,1);
y3 = lsim(-T , n, t);  % -(G*C/(1+C*G))
u3 = lsim(-TG, n, t);  %   -(C/(1+C*G))

y = y1 + y2 + y3 + n;
u = u1 + u2 + u3;

% Performance metrics. Computed here for later use.
handles.params.performance.sigmau = std(u3);
[~,iOvershootTime] = max(y1);
handles.params.performance.overshootTime = t(iOvershootTime);

% Minimum and maximum values for X and Y axes
minT1 = min(ty); minY = min(y);
maxT1 = max(ty); maxY = max([y;handles.params.setpoint;...
    handles.params.distMag;handles.params.noiseVar]);
minT2 = min(tu); minU = min(0,min(u));
maxT2 = max(tu); maxU = max([u;handles.params.setpoint;...
    handles.params.distMag;handles.params.noiseVar]);

% Limit creation function handle
myLim = @(minV,maxV,border) [minV + border*(maxV - minV), ...
    maxV - border*(maxV - minV)];

% 'XLim' and 'YLim' values for process and controller output plots
xLim1 =  myLim(minT1, maxT1, handles.params.sqwdx);
yLim1 =  myLim(minY , maxY , handles.params.sqwdy);
xLim2 =  myLim(minT2, maxT2, handles.params.sqwdx);
yLim2 =  myLim(minU , maxU , handles.params.sqwdy);

if moveAxes % axes limits need to be updated
    if newPlot && ~fieldExists(handles,'line') % Executes when opening the 
                                               % GUI or when running it 
                                               % again if already opened
                                               
        % X-axes line for process and controller output plots
        handles.lines.xLine(1) = plot(handles.processOutput, xLim1, ...
            [0,0],'k');
        handles.lines.xLine(2) = plot(handles.controllerOutput, xLim2, ...
            [0,0],'k');
        % Y-axes line for process and controller output plots
        handles.lines.yLine(2) = plot(handles.controllerOutput, ...
            [0,0], yLim2,'k');
        handles.lines.yLine(1) = plot(handles.processOutput, [0,0], ...
            yLim1,'k');

        % Setpoint point and lines for process output plot
        handles.lines.setpoint.xLine(1) = plot(handles.processOutput, ...
            xLim1, [handles.params.setpoint, handles.params.setpoint],...
            'g:');
        handles.lines.setpoint.yLine(1) = plot(handles.processOutput, ...
            [0,0], [0, handles.params.setpoint],'g-');
        handles.lines.setpoint.point(1) = plot(handles.processOutput, ...
            0, handles.params.setpoint, 'marker', 'o', 'color', 'g',  ...
            'MarkerFaceColor','w', 'Tag', 'setpoint1',...
            'ButtonDownFcn', {@startDragPoint,handles});

        % Setpoint point and lines for controller output plot        
        handles.lines.setpoint.yLine(2) = plot(handles.controllerOutput,...
            [0,0], [0, handles.params.setpoint],'g-');
        handles.lines.setpoint.point(2) = plot(handles.controllerOutput,...
            0, handles.params.setpoint, 'marker', 'o', 'color', 'g', ...
            'MarkerFaceColor', 'w', 'Tag', 'setpoint2', ...
            'ButtonDownFcn', {@startDragPoint,handles});

        % Load disturbance magnitude, time and line for process output plot
        handles.lines.disturbance.yLine(1) = plot(handles.processOutput,...
            [handles.params.distTime, handles.params.distTime], ...
            [0, handles.params.distMag], 'g-');
        handles.lines.disturbance.mag(1) = plot(handles.processOutput, ...
            handles.params.distTime, handles.params.distMag, ...
            'marker', 'o', 'color', 'g', 'MarkerFaceColor','w', ...
            'Tag', 'distMag1', 'ButtonDownFcn', {@startDragPoint,handles});      
        handles.lines.disturbance.time(1) = plot(handles.processOutput, ...
            handles.params.distTime, 0, 'marker', 'o', 'color', 'g', ...
            'MarkerFaceColor','w', 'Tag', 'distTime1', ...
            'ButtonDownFcn', {@startDragPoint,handles});

        % Load disturbance mag., time and line for controller output plot        
        handles.lines.disturbance.yLine(2) = ...
            plot(handles.controllerOutput, ...
            [handles.params.distTime, handles.params.distTime], ...
            [0, handles.params.distMag],'g-');
        handles.lines.disturbance.mag(2) = plot(...
            handles.controllerOutput, ...
            handles.params.distTime, handles.params.distMag, ...
            'marker', 'o', 'color', 'g', 'MarkerFaceColor','w', ...
            'Tag', 'distMag2', ...
            'ButtonDownFcn', {@startDragPoint,handles});
        handles.lines.disturbance.time(2) = plot(...
            handles.controllerOutput, ...
            handles.params.distTime, 0, 'marker', 'o', 'color', 'g', ...
            'MarkerFaceColor','w', 'Tag', 'distTime2', ...
            'ButtonDownFcn', {@startDragPoint,handles});        

        % Noise variance, time and line for process output plot       
        handles.lines.noise.yLine(1) = plot(handles.processOutput, ...
            [handles.params.noiseTime, handles.params.noiseTime], ...
            [0, handles.params.noiseVar],'k-');       
        handles.lines.noise.var(1) = plot(handles.processOutput, ...
            handles.params.noiseTime, handles.params.noiseVar, ...
            'marker', 'o', 'color', 'k', 'MarkerFaceColor','w', ...
            'Tag', 'noiseVar1', ...
            'ButtonDownFcn', {@startDragPoint,handles});       
        handles.lines.noise.time(1) = plot(handles.processOutput, ...
            handles.params.noiseTime, 0, 'marker', 'o', 'color', 'k', ...
            'MarkerFaceColor','w', 'Tag', 'noiseTime1', ...
            'ButtonDownFcn', {@startDragPoint,handles});

        % Noise variance, time and line for controller output plot       
        handles.lines.noise.yLine(2) = plot(handles.controllerOutput, ...
            [handles.params.noiseTime, handles.params.noiseTime], ...
            [0, handles.params.noiseVar],'k-');       
        handles.lines.noise.var(2) = plot(handles.controllerOutput, ...
            handles.params.noiseTime, handles.params.noiseVar, ...
            'marker', 'o', 'color', 'k', 'MarkerFaceColor','w', ...
            'Tag', 'noiseVar2', ...
            'ButtonDownFcn', {@startDragPoint,handles});       
        handles.lines.noise.time(2) = plot(handles.controllerOutput, ...
            handles.params.noiseTime, 0, 'marker', 'o', 'color', 'k', ...
            'MarkerFaceColor','w', 'Tag', 'noiseTime2', ...
            'ButtonDownFcn', {@startDragPoint,handles});
        
        % Process and controller output plot lines
        handles.lines.line(1) = plot(handles.processOutput, ty, y,...
            'tag', 'process','ButtonDownFcn', {@startCursor,handles});            
        handles.lines.line(2) = plot(handles.controllerOutput, tu, u,...
            'tag', 'controller','ButtonDownFcn', {@startCursor,handles});    

    else % Process and controller output plot and X-Y axes lines update
        set(handles.lines.yLine(1), 'YData', yLim1)
        set(handles.lines.yLine(2), 'YData', yLim2)
        set(handles.lines.line(1) , 'XData', ty, 'YData',y)
        set(handles.lines.line(2) , 'XData', tu, 'YData',u)
    end

    % Axes limits update
    set(handles.processOutput,    'Xlim', xLim1, 'Ylim', yLim1)
    set(handles.controllerOutput, 'Xlim', xLim2, 'Ylim', yLim2)
    
else % axes limits don't need to be updated
    
    % Process and controller output plots update
    set(handles.lines.line(1), 'XData', ty, 'YData', y)
    set(handles.lines.line(2), 'XData', tu, 'YData', u)

    yCurLim1 = get(handles.processOutput, 'YLim');  
    maxY = max(maxY,yCurLim1(2));
    yLim1 =  myLim(minY, maxY, handles.params.sqwdy);

    set(handles.lines.yLine(1), 'YData', yLim1)
    set(handles.lines.yLine(2), 'YData', ...
        get(handles.controllerOutput,'YLim'))    
end


% --- Auxiliary function to check if a fieldName exists in a structure.
function isFieldResult = fieldExists(thisStruct, fieldName)

isFieldResult = 0;
f = fieldnames(thisStruct(1));
for i=1:length(f)
    if(strcmp(f{i},strtrim(fieldName)))
        isFieldResult = 1;
        return;
	elseif isstruct(thisStruct(1).(f{i}))
        isFieldResult = fieldExists(thisStruct(1).(f{i}), fieldName);
        if isFieldResult
            return;
        end
    end
end


% --- Function for performance metrics calculation.
function handles = performanceMetrics(handles)

T = (handles.C*handles.G)/(1 + handles.C*handles.G);
perf = stepinfo(T);
if ~isnan(perf.Overshoot)
    perf.OvershootTime = handles.params.performance.overshootTime;
    perf.Sigmau = handles.params.performance.sigmau;    
else
    perf.OvershootTime = NaN;
    perf.Sigmau = NaN;
end

hText = {'text_overshoot_num', 'text_overshoottime_num',...
    'text_risetime_num', 'text_emax_num', 'text_tmax_num', ...
    'text_sigmau_num'};

fields = {'Overshoot','OvershootTime','RiseTime','Peak','PeakTime',...
    'Sigmau'};

for i = 1:length(hText)
    set(handles.(hText{i}), 'String', sprintf('%0.2f', ...
        perf.(fields{i})))
end

set(handles.text_sigmau_num, 'String', sprintf('%0.3f', perf.Sigmau))


% --- Function for robustness metrics calculation.
function handles = robustnessMetrics(handles)

S = 1/(1 + handles.C*handles.G);
T = (handles.C*handles.G)/(1 + handles.C*handles.G);
warning('off','Control:analysis:MarginUnstable')

lastwarn('','');
[Gm,Pm] = margin(handles.C*handles.G);
Ms = norm(S,Inf);
Mt = norm(T,Inf);

hText = {'text_Gm_num','text_Pm_num','text_Ms_num','text_Mt_num'};
data = [Gm, Pm, Ms, Mt];
for i = 1:length(hText)
    set(handles.(hText{i}), 'String', sprintf('%0.2f', data(i)))
end
[msg,id] = lastwarn;

% Warning dialog for unstable closed-loop system
if strcmp(id,'Control:analysis:MarginUnstable')
    if ~isempty(handles.warn) && ishandle(handles.warn)
        set(handles.warn,'Visible','on')
    else
        handles.warn = warndlg(msg,'Warning','replace');
    end

else
    if ~isempty(handles.warn) && ishandle(handles.warn)
        close(handles.warn);
        handles.warn = [];
    end    
end
warning('on','Control:analysis:MarginUnstable')

% --- Executes when clicking on process or controller output plots.
function startCursor(obj,evnt,handles)
set(handles.PIDBasics, 'WindowButtonMotionFcn', {@cursorMotion,obj})
set(handles.PIDBasics, 'WindowButtonUpFcn'    , @stopCursor)
set(handles.PIDBasics, 'Pointer', 'hand')

cursorMotion(handles.PIDBasics,evnt,obj)


% --- Executes when moving over process/controller output plots after click
function cursorMotion(src,evnt,obj)

handles = guidata(src);

plotTag = get(obj, 'tag');
cText = @(x,y) sprintf(' t = %0.2f, y = %0.2f ', x, y);

% Find handle for the plot that has been activated
hPlot = (1 - strcmp(plotTag, 'process')) * handles.controllerOutput + ...
    (1 - strcmp(plotTag,'controller')) * handles.processOutput;

point = get(hPlot, 'CurrentPoint');

x = point(1,1); 

% Find x and y CurrentPoint locations for process and controller output
% plots
xData1 = get(handles.lines.line(1), 'XData');
yData1 = get(handles.lines.line(1), 'YData');

xData2 = get(handles.lines.line(2), 'XData');
yData2 = get(handles.lines.line(2), 'YData');

y1 = interp1(xData1, yData1, x);
y2 = interp1(xData2, yData2, x);

% Setting cursor (marker) position and text box
set(handles.cursor(1), 'XData', x, 'YData', y1)
set(handles.cursor(2), 'XData', x, 'YData', y2)

set(handles.hText(1), 'Position', [x, y1], 'String', cText(x, y1));
set(handles.hText(2), 'Position', [x, y2], 'String', cText(x, y2));


% --- Executes when releasing click on process or controller output plots.
function stopCursor(src,evnt)

handles = guidata(src);

set(handles.PIDBasics, 'Pointer', 'arrow')

set(handles.hText, 'Position', [NaN,NaN], 'String', '');
set(handles.cursor, 'XData', NaN, 'YData', NaN)

set(handles.PIDBasics, 'WindowButtonMotionFcn', '')
set(handles.PIDBasics, 'WindowButtonUpFcn'    , '')


% --- Executes when clicking on one of the created draggable points.
function startDragPoint(obj,evnt,handles)
set(handles.PIDBasics, 'WindowButtonMotionFcn', {@draggingPoint,obj})
set(handles.PIDBasics, 'WindowButtonUpFcn'    , {@stopDragPoint,obj})
set(handles.PIDBasics, 'Pointer', 'hand')

set(obj,'MarkerFaceColor',get(obj,'color'))


% --- Executes when moving one of the created draggable points.
function draggingPoint(src,evnt,obj)

handles = guidata(src);
pointTag = get(obj, 'tag');

switch pointTag % Find which point is has been clicked and update plots 
                % accordingly
    case {'setpoint1','setpoint2'}        
        point = selectedPoint(handles, pointTag, ...
            {'setpoint1','setpoint2'});
        
        if point(1,2) >= 0
            set(handles.lines.setpoint.xLine(1), 'YData', point(1,2)*[1,1])
            set(handles.lines.setpoint.yLine   , 'YData', point(1,2)*[0,1])
            set(handles.lines.setpoint.point   , 'YData', point(1,2))

            handles.params.setpoint = point(1,2);

            handles = processAndControllerPlots(handles, false, false);
        end
    case {'distMag1','distMag2'}
        point = selectedPoint(handles, pointTag, {'distMag1','distMag2'});
        
        if point(1,2) >= 0
            set(handles.lines.disturbance.yLine, 'YData', point(1,2)*[0,1])
            set(handles.lines.disturbance.mag  , 'YData', point(1,2))

            handles.params.distMag = point(1,2);

            handles = processAndControllerPlots(handles, false, false);
        end
    case {'distTime1','distTime2'}
        point = selectedPoint(handles, pointTag, ...
            {'distTime1','distTime2'});
        
        if point(1,1) >= 0 && point(1,1) <= handles.params.Tfinal
            set(handles.lines.disturbance.yLine, 'XData',...
                point(1,1)*[1,1])

            set(handles.lines.disturbance.time, 'XData', point(1,1))
            set(handles.lines.disturbance.mag , 'XData', point(1,1))

            handles.params.distTime = point(1,1);

            handles = processAndControllerPlots(handles, false, false);
        end
    case {'noiseVar1','noiseVar2'}
        point = selectedPoint(handles, pointTag, ...
            {'noiseVar1','noiseVar2'});

        if point(1,2) >= 0   
            set(handles.lines.noise.yLine, 'YData', point(1,2)*[0,1])
            set(handles.lines.noise.var  , 'YData', point(1,2))

            handles.params.noiseVar = point(1,2);

            handles = processAndControllerPlots(handles, false, false);
        end
    case {'noiseTime1','noiseTime2'}
        point = selectedPoint(handles, pointTag, ...
            {'noiseTime1','noiseTime2'});
        
        if point(1,1) >= 0 && point(1,1) <= handles.params.Tfinal
            set(handles.lines.noise.yLine, 'XData', point(1,1)*[1,1])
            set(handles.lines.noise.time , 'XData', point(1,1))
            set(handles.lines.noise.var  , 'XData', point(1,1))

            handles.params.noiseTime = point(1,1);

            handles = processAndControllerPlots(handles, false, false);
        end
end

guidata(src,handles)


% --- Auxiliary function for draggingPoint
function point = selectedPoint(handles, pointTag, tags)


i = find(strcmp(pointTag, tags));
if i == 1
    point = get(handles.processOutput, 'CurrentPoint');
else
    point = get(handles.controllerOutput, 'CurrentPoint');
end


% --- Executes when releasing click on draggable point.
function stopDragPoint(src,evnt,obj)

handles = guidata(src);
set(obj,'MarkerFaceColor','w')

set(handles.PIDBasics, 'Pointer', 'arrow')
set(handles.PIDBasics, 'WindowButtonMotionFcn', '')
set(handles.PIDBasics, 'WindowButtonUpFcn'    , '')

pause(1); % 1-sec pause to notice what has been updated before axes 
          % relocation

y1 = get(handles.lines.line(1), 'YData');
y2 = get(handles.lines.line(2), 'YData');

minY1 = min(y1); minY2 = min([y2,0]);
maxY1 = max([y1,handles.params.setpoint,handles.params.distMag,...
    handles.params.noiseVar]);
maxY2 = max([y2,handles.params.setpoint,handles.params.distMag,...
    handles.params.noiseVar]);

myLim = @(minV,maxV,border) [minV + border*(maxV - minV), ...
    maxV - border*(maxV - minV)];

yLim1 = myLim(minY1, maxY1, handles.params.sqwdy);
yLim2 = myLim(minY2, maxY2, handles.params.sqwdy);

% YLim gets updated to the new calculated values
set(handles.processOutput   , 'YLim', yLim1)
set(handles.controllerOutput, 'YLim', yLim2)
set(handles.lines.yLine(1), 'YData', yLim1)
set(handles.lines.yLine(2), 'YData', yLim2)


% Function that displays the current process formula in the GUI.
function tftext = displayTfProcess(G) 

w = whos;
tftext = evalc(w.name);
removePos = regexp(tftext,'[=CS]');
tftext = tftext(removePos(1)+4:removePos(2)-4);

assignTo = 'G(s) = ';
cReturn = double(tftext) == 10;
if nnz(cReturn) == 2 % There is a fraction
    cReturns = find(cReturn);
    tftext = [char(32*ones(1,length(assignTo))), ...
        tftext(3:cReturns(1)), assignTo, ...
        tftext(cReturns(1)+3:cReturns(2)), ...
        char(32*ones(1,length(assignTo))),tftext(cReturns(2)+3:end)];
    
else
    tftext = [char(10), assignTo, tftext]; 
end


% --- Computes DC gain by factoring out z and p absolute values.
function Kp = computeDcGain(G)
p = G.p{1};
z = G.z{1};
Kp = G.k*abs(prod(z(z~=0))/prod(p(p~=0)));


% --- Reverse function of computeDcGain.
function Gk = computeDcGainRev(Kp,G)
p = G.p{1};
z = G.z{1};
Gk = Kp*abs(prod(p(p~=0))/prod(z(z~=0)));


% --- Executes on button press in discoveryButton.
function discoveryButton_Callback(hObject, eventdata, handles)
% hObject    handle to discoveryButton (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Shows Discovery PID Control website on default browser
web('http://www.mathworks.com/discovery/pid-control.html' ,'-browser')


% --- Executes on button press in customTfProcess.
function customTfProcess_Callback(hObject, eventdata, handles)
% hObject    handle to customTfProcess (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

handles.G = tfProcess;

if ~isempty(handles.G)
    tfprocessText = displayTfProcess(handles.G);
    set(handles.text_tf,'String',tfprocessText)

    Kp = computeDcGain(handles.G);
    set(handles.text_Kp, 'String', sprintf('Kp: %0.2f', Kp))
    set(handles.slider_Kp, 'Min', log10(Kp/4), 'Max', log10(Kp*4), ...
        'Value', log10(Kp))
    
    handles = processAndControllerPlots(handles, false, true);
    handles = robustnessMetrics(handles);
    handles = performanceMetrics(handles);
    
    guidata(hObject, handles)
end


% --- Executes on slider_Kp continuous movement.
function slider_Kp_Scroll(hObject, eventdata, handles)

handles=guidata(hObject);

Kp = power(10,get(hObject,'Value'));
set(handles.text_Kp, 'String', sprintf('Kp: %0.2f', Kp))
handles.G.k = computeDcGainRev(Kp,handles.G);
tfprocessText = displayTfProcess(handles.G);
set(handles.text_tf,'String',tfprocessText)

handles = processAndControllerPlots(handles, false, true);
handles = robustnessMetrics(handles);
handles = performanceMetrics(handles);

guidata(hObject, handles);


% --- Executes on slider_Kp slider movement.
function slider_Kp_Callback(hObject, eventdata, handles)
% hObject    handle to slider_Kp (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

Kp = power(10,get(hObject,'Value'));
set(hObject, 'Min', log10(Kp/4), 'Max', log10(Kp*4))


% --- Executes during object creation, after setting all properties.
function slider_Kp_CreateFcn(hObject, eventdata, handles)
% hObject    handle to slider_Kp (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: slider controls usually have a light gray background.
if isequal(get(hObject,'BackgroundColor'), ...
        get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end


% --- Executes on slider_(K,Ti,Td,N,b) continuous movement.
function slider_Gains_Scroll(hObject, eventdata, hText)

handles = guidata(hObject);

Gain = power(10,get(hObject,'Value'));
str = textStr(hText);
set(hText, 'String', sprintf([str, '%0.2f'], Gain))

handles = controllerUpdate(handles);
handles = processAndControllerPlots(handles, false, true);
handles = robustnessMetrics(handles);
handles = performanceMetrics(handles);

guidata(hObject, handles);


% --- Executes when choosing a radio button in the Button group
function controller_Change(hObject, eventdata, handles)

handles = guidata(hObject);
selectedRB = get(get(hObject,'SelectedObject'),'String');

switch selectedRB
    case{'P'}
        enableDisableSlider({'on','off','off','off'}, handles)
    case{'I'}
        enableDisableSlider({'off','on','off','off'}, handles)
    case{'PI'}
        enableDisableSlider({'on','on','off','off'}, handles)
	case{'PD'}
        enableDisableSlider({'on','off','on','on'}, handles)    
	case{'PID'}
        enableDisableSlider({'on','on','on','on'}, handles)
end

handles = controllerUpdate(handles);
handles = processAndControllerPlots(handles, false, true);
handles = robustnessMetrics(handles);
handles = performanceMetrics(handles);

guidata(hObject, handles)


% --- Executes during call to controller_Change, to enable/disable sliders
function enableDisableSlider(enableDisable, handles)

set(handles.slider_K,  'Enable', enableDisable{1})
set(handles.slider_Ti, 'Enable', enableDisable{2})
set(handles.slider_Td, 'Enable', enableDisable{3})
set(handles.slider_N,  'Enable', enableDisable{4})

set(handles.text_K,    'Enable', enableDisable{1})
set(handles.text_Ti,   'Enable', enableDisable{2})
set(handles.text_Td,   'Enable', enableDisable{3})
set(handles.text_N,    'Enable', enableDisable{4})


% --- Executes on slider_K movement.
function slider_K_Callback(hObject, eventdata, handles)
% hObject    handle to slider_K (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

K = power(10,get(hObject,'Value'));
set(hObject, 'Min', log10(K/4), 'Max', log10(K*4))


% --- Executes during object creation, after setting all properties.
function slider_K_CreateFcn(hObject, eventdata, handles)
% hObject    handle to slider_K (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: slider controls usually have a light gray background.
if isequal(get(hObject,'BackgroundColor'), ...
        get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end


% --- Executes on slider_Ti movement.
function slider_Ti_Callback(hObject, eventdata, handles)
% hObject    handle to slider_Ti (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

Ti = power(10,get(hObject,'Value'));
set(hObject, 'Min', log10(Ti/4), 'Max', log10(Ti*4))


% --- Executes during object creation, after setting all properties.
function slider_Ti_CreateFcn(hObject, eventdata, handles)
% hObject    handle to slider_Ti (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: slider controls usually have a light gray background.
if isequal(get(hObject,'BackgroundColor'), ...
        get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end


% --- Executes on slider_Td movement.
function slider_Td_Callback(hObject, eventdata, handles)
% hObject    handle to slider_Td (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

Td = power(10,get(hObject,'Value'));
set(hObject, 'Min', log10(Td/4), 'Max', log10(Td*4))


% --- Executes during object creation, after setting all properties.
function slider_Td_CreateFcn(hObject, eventdata, handles)
% hObject    handle to slider_Td (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: slider controls usually have a light gray background.
if isequal(get(hObject,'BackgroundColor'), ...
        get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end


% --- Executes on slider_N movement.
function slider_N_Callback(hObject, eventdata, handles)
% hObject    handle to slider_N (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'Value') returns position of slider
%        get(hObject,'Min') and get(hObject,'Max') to determine range of slider

N = power(10,get(hObject,'Value'));
set(hObject, 'Min', log10(N/4), 'Max', log10(N*4))


% --- Executes during object creation, after setting all properties.
function slider_N_CreateFcn(hObject, eventdata, handles)
% hObject    handle to slider_N (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: slider controls usually have a light gray background.
if isequal(get(hObject,'BackgroundColor'), ...
        get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end

Contact us