function varargout = BodePlotGui(varargin) % BODEPLOTGUI Application M-file for BodePlotGui.fig % FIG = BODEPLOTGUI launch BodePlotGui GUI. % BODEPLOTGUI('callback_name', ...) invoke the named callback. % Last Modified by GUIDE v2.5 18-Oct-2011 14:11:08 %Written by Erik Cheever (Copyright 2002) %Contact: erik_cheever@swarthmore.edu % Erik Cheever % Dept. of Engineering % Swarthmore College % 500 College Avenue % Swarthmore, PA 19081 USA %This function acts as a switchyard for several callbacks. It also intializes %the variables used by the GUI. Note that all variables are initialized here to %default values. A brief description of each variable is included. if (nargin == 0) || (isa(varargin{1},'tf')) %If no arguments, or first fig = openfig(mfilename,'new'); handles = guihandles(fig); %Get handles structure. initBodePlotGui(handles); handles=guidata(handles.AsymBodePlot); %Reload handles after function call. if nargout > 0 %If output argument is used, set it to figure. varargout{1} = fig; end if ((nargin~=0) && (isa(varargin{1},'tf'))), %Transfer function chosen. handles.Sys=varargin{1}; %The variable Sys is the transfer function. doBodeGUI(handles); end elseif ischar(varargin{1}) % INVOKE NAMED SUBFUNCTION OR CALLBACK try [varargout{1:nargout}] = feval(varargin{:}); % FEVAL switchyard catch disp(lasterr); end end end % ------------------End of function BodePlotGui ---------------------- function initBodePlotGui(handles) handles.IncludeString=[]; %An array of strings representing terms to include in the plot. handles.ExcludeString=[]; %An array of strings representing terms to exclude from plot. handles.IncElem=[]; %An array of indices of terms corresponding to their % location in the IncludeString array. handles.ExcElem=[]; %An array of indices of terms corresponding to their % location in the ExcludeString array. handles.FirstPlot=1; %This term is 1 the first time a plot is made. This lets % Matlab do the original autoscaling. The scales are then % saved and reused. handles.MagLims=[]; %The limits on the magnitude plot determined by MatLab autoscaling. handles.PhaseLims=[]; %The limits on the phase plot determined by MatLab autoscaling. handles.LnWdth=2; set(handles.LineWidth,'String',num2str(handles.LnWdth)); %Set the color of lines used in gray scale. The plotting functions %cycle through these colors (and then cycle through the linestyles). handles.Gray=[0.75 0.75 0.75; 0.5 0.5 0.5; 0.25 0.25 0.25]; handles.GrayZero=[0.9 0.9 0.9]; %This is the color used for the zero reference. %Set the color of lines used in color plots. The plotting functions %cycle through these colors (and then cycle through the linestyles). handles.Color=[0 1 1; 0 0 1; 0 1 0; 1 0 0; 1 0 1;1 0.52 0.40]; handles.ColorZero=[1 1 0]; %Yellow, this is the color used for the zero reference. %Sets order of linestyles used. handles.linestyle={':','--','-.'}; %This sets the default scheme to color (GUI can set them to gray scale). handles.colors=handles.Color; handles.zrefColor=handles.ColorZero; handles.exactColor=[0 0 0]; %Black handles.Sys=[]; handles.SysInc=[]; handles.Terms=[]; %The structure "Term" has three elements. % type: this can be any of the 7 types listed below. % 1) The multiplicative constant. % 2) Real poles % 3) Real zeros % 4) Complex poles % 5) Complex zeros % 6) Poles at the origin % 7) Zeros at the origin % value: this is the location of the pole or zero (or in the case % of the multiplicative constant, its value). % multiplicity: this gives the multiplicity of the pole or zero. It has % no meaning in the case of the multiplicative constant. %The variable "Acc" is a relative accuracy used to determine whether or not %two poles (or zeros) are the same. Because Matlab uses an approximate %technique to find roots of an equation, it is likely to give slightly %different locations to identical roots. handles.Acc=1E-3; set(handles.TransferFunctionText,'String',... {' ','No transfer function chosen',' '}); guidata(handles.AsymBodePlot, handles); %save changes to handles. loadSystems(handles); handles=guidata(handles.AsymBodePlot); %Reload handles after function call. guidata(handles.AsymBodePlot, handles); %save changes to handles. end function simpleTF = makeSimple(origTF) simpleTF=minreal(origTF); %Get minimum realization. % Get numerator and denominator of two realizations. If their % lengths are unequal, it means that there were poles and zeros that % cancelled. [n1,d1]=tfdata(simpleTF,'v'); [n2,d2]=tfdata(origTF,'v'); if (length(n1)~=length(n2)), disp(' '); disp(' '); disp(' '); disp('************Warning******************'); disp('Original transfer function was:'); origTF disp('Some poles and zeros were equal. After cancellation:'); simpleTF disp('The simplified transfer function is the one that will be used.'); disp('*************************************'); disp(' '); beep; waitfor(warndlg('System has poles and zeros that cancel. See Command Window for caveats.')); end end function doBodeGUI(handles) handles.Sys=makeSimple(handles.Sys); handles.SysInc=handles.Sys; %The variable sysInc is that part of the transfer % function that will be plotted (with no poles or zeros excluded). Start with % it equal to Sys. This variable is modified in BodePlotSys %The function BodePlotTerms separates the transfer function into its consituent parts. % The variable DoQuit will come back as non-zero if there was a problem. DoQuit=BodePlotTerms(handles); handles=guidata(handles.AsymBodePlot); %Reload handles after function call. %if DoQuit is zero, there were no problems and we may continue. if ~DoQuit, BodePlotter(handles); %Make plot handles=guidata(handles.AsymBodePlot); %Reload handles after function call. %DoQuit was non-zero, so there was a problem. Quit program. else CloseButton_Callback(fig,'',handles,''); end end %| ABOUT CALLBACKS: %| GUIDE automatically appends subfunction prototypes to this file, and %| sets objects' callback properties to call them through the FEVAL %| switchyard above. This comment describes that mechanism. %| %| Each callback subfunction declaration has the following form: %| (H, EVENTDATA, HANDLES, VARARGIN) %| %| The subfunction name is composed using the object's Tag and the %| callback type separated by '_', e.g. 'slider2_Callback', %| 'figure1_CloseRequestFcn', 'axis1_ButtondownFcn'. %| %| H is the callback object's handle (obtained using GCBO). %| %| EVENTDATA is empty, but reserved for future use. %| %| HANDLES is a structure containing handles of components in GUI using %| tags as fieldnames, e.g. handles.figure1, handles.slider2. This %| structure is created at GUI startup using GUIHANDLES and stored in %| the figure's application data using GUIDATA. A copy of the structure %| is passed to each callback. You can store additional information in %| this structure at GUI startup, and you can change the structure %| during callbacks. Call guidata(h, handles) after changing your %| copy to replace the stored original so that subsequent callbacks see %| the updates. Type "help guihandles" and "help guidata" for more %| information. %| %| VARARGIN contains any extra arguments you have passed to the %| callback. Specify the extra arguments by editing the callback %| property in the inspector. By default, GUIDE sets the property to: %| ('', gcbo, [], guidata(gcbo)) %| Add any extra arguments after the last argument, before the final %| closing parenthesis. % -------------------------------------------------------------------- function varargout = IncludedElements_Callback(~, ~, handles, varargin) % Stub for Callback of the uicontrol handles.IncludedElements. % If a term in the "Included Elements" box is clicked, this callback is invoked. %Get index of element in box that is chosen.%If the index corresponds to one of the terms of the transfer function, deal with it. i=get(handles.IncludedElements,'Value'); % The alternative is that it corresponds to another string in the box (there is a blank % line, a line with dashes "----" and a line instructing the user to click on an element % to include it). if i<=length(handles.IncElem) TermsInd=handles.IncElem(i); %Get the index of the included element. handles.Terms(TermsInd).display=0; %Set display to 0 (to exclude it) guidata(handles.AsymBodePlot, handles); %save changes to handles. BodePlotter(handles); %Plot the Transfer function. handles=guidata(handles.AsymBodePlot); %Reload handles after function call. end end % ------------------End of function IncludedElements_Callback -------- % -------------------------------------------------------------------- function varargout = ExcludedElements_Callback(~, ~, handles, varargin) % Callback of the uicontrol handles.ExcludedElements. % If a term in the "Excluded Elements" box is clicked, this callback is invoked. i=get(handles.ExcludedElements,'Value'); %Get index of element in box that is chosen. %If the index corresponds to one of the terms of the transfer function, deal with it. % The alternative is that it corresponds to another string in the box (there is a blank % line, a line with dashes "----" and a line instructing the user to click on an element % to exclude it). if i<=length(handles.ExcElem) TermsInd=handles.ExcElem(i); %Get the index of the excluded element. handles.Terms(TermsInd).display=1; %Set display to 1 (to include it) guidata(handles.AsymBodePlot, handles); %save changes to handles. BodePlotter(handles); %Plot the Transfer function. handles=guidata(handles.AsymBodePlot); %Reload handles after function call. end end % ------------------End of function ExcludedElements_Callback -------- % -------------------------------------------------------------------- function varargout = CloseButton_Callback(~, ~, handles, varargin) % Callback for the uicontrol handles.CloseButton. %This function closes the window, and displays a message. disp(' '); disp('Asymptotic Bode Plotter closed.'); disp(' '); disp(' '); delete(handles.AsymBodePlot); end % ------------------End of function CloseButton_Callback ------------- % -------------------------------------------------------------------- function DoQuit=BodePlotTerms(handles) %This function takes a system and splits it up into terms that are plotted %individually when making a Bode plot by hand (it finds %1) The multiplicative constant. %2) All real poles %3) All real zeros %4) All complex poles %5) All complex zeros %6) All poles at the origin %7) All zeros at the origin % %In addition to finding the poles and zeros, it determines their multiplicity. %If two poles or zeros are very close they are determined to be the same pole %or zero. sys=handles.Sys; Acc=handles.Acc; %Relative accuracy. [z,p,k]=zpkdata(sys,'v'); %Get pole and zero data. %Find gain term. [n,d]=tfdata(sys,'v'); k=n(max(find(n~=0)))/d(max(find(d~=0))); term(1).type='Constant'; term(1).value=k; term(1).multiplicity=1; %Get all poles. j=length(term); for i=1:length(p), term(j+i).value=p(i); term(j+i).multiplicity=1; term(j+i).type='Pole'; end %Get all zeros. j=length(term); for i=1:length(z), term(j+i).value=z(i); term(j+i).multiplicity=1; term(j+i).type='Zero'; end %Check for multiplicity for i=2:length(term), for k=(i+1):length(term), %Handle pole or zero at origin as special case. if (term(i).value==0), %Multiple pole or zero at origin. if (term(k).value==0), term(i).multiplicity=term(i).multiplicity+term(k).multiplicity; %Set multiplicity of kth term to 0 to signify that it has been % subsumed by term(i) (by increasing the ith term's multiplicity). term(k).multiplicity=0; end %We know term is not at origin, so check for (approximate) equality % Since we know this term is not at origin, we can divide by value. elseif (abs((term(i).value-term(k).value)/term(i).value) < Acc), term(i).multiplicity=term(i).multiplicity+term(k).multiplicity; %Set multiplicity of kth term to 0 to signify that it has been % subsumed by term(i) (by increasing the ith term's multiplicity). term(k).multiplicity=0; end end end %Check for location of poles and zeros (and remove complex conjugates). i=2; while (i<=length(term)), %If root is at origin, handle it separately if (term(i).value==0), term(i).type=['Origin' term(i).type]; %If imaginary part is sufficiently small... elseif (abs(imag(term(i).value)/term(i).value)0), %Poles in RHP. beep; waitfor(errordlg('System has poles with positive real part, cannot make plot.')); DoQuit=1; return; end if any(real(z)>0), %Zeros in RHP. disp(' '); disp(' '); disp(' '); disp('************Warning******************'); handles.Sys disp('is a nonminimum phase system (zeros in right half plane).'); disp('The standard rules for phase do not apply.'); disp(' '); disp('Also - The plots produced may be different than the Matlab Bode plot'); disp(' by a factor of 360 degrees. So though the graphs don''t look'); disp(' the same, they are equivalent'); disp(' '); disp('Location(s) of zero(s):'); disp(z); disp('*************************************'); disp(' '); beep; waitfor(warndlg('System has zeros with positive real part. See Command Window for caveats.')); end %Check for terms near imaginary axis, or multiple poles or zeros at origin. for i=2:length(term), if (term(i).value~=0), if (abs(real(term(i).value)/term(i).value)1), disp(' '); disp(' '); disp(' '); disp('************Warning******************'); handles.Sys disp('has multiple poles or zeros at the origin.'); disp('Components of the phase plot may appear to disagree.'); disp('This is because the phase of a complex number is'); disp('not unique; the phase of -1 could be +180 degrees'); disp('or -180 or +/-540... Likewise the phase of 1/s^2'); disp('could be +180 degrees, or -180 degrees (or +/-540'); disp('Keep this in mind when looking at the phase plots.'); disp('*************************************'); disp(' '); beep; waitfor(warndlg('System has multiple poles or zeros at origin. See Command Window.')); end end handles.Terms=term; guidata(handles.AsymBodePlot, handles); %save changes to handles. end % ------------------End of function BodePlotTerms -------------------- % -------------------------------------------------------------------- function BodePlotter(handles) %Get the constituent terms and the system itself. Terms=handles.Terms; sys=handles.Sys; %Call function to get a system with only included poles and zeros. BodePlotSys(handles); handles=guidata(handles.AsymBodePlot); %Reload handles after function call. %Get systems with included poles and zeros. sysInc=handles.SysInc; %Find min and max freq. MinF=realmax; MaxF=-realmax; for i=2:length(Terms), if Terms(i).value~=0, MinF=min(MinF,abs(Terms(i).value)); MaxF=max(MaxF,abs(Terms(i).value)); end end %If there is exclusively a pole or zero at origin, MinF and MaxF will % not have changed. So set them arbitrarily to be near unity. if MaxF==-realmax, MinF=0.9; MaxF=1.1; end %MinFreq is a bit more than an order of magnitude below lowest break. MinFreq=10^(floor(log10(MinF)-1.01)); %MaxFreq is a bit more than an order of magnitude above highest break. MaxFreq=10^(ceil(log10(MaxF)+1.01)); %Calculate 500 frequency points for plotting. w=logspace(log10(MinFreq),log10(MaxFreq),1000); %%%%%%%%%%%%%%%%%%%% Start Magnitude Plot %%%%%%%%%% axes(handles.MagPlot); cla; %Plot line at 0 dB for reference. semilogx([MinFreq MaxFreq],[0 0],... 'Color',handles.zrefColor,... 'LineWidth',1.5); hold on; %For each term, plot the magnitude accordingly. %The variable mag_a has the combined asymptotic magnitude. mag_a=zeros(size(w)); %The variable peakinds holds the indices at which peaks in underdamped %responses occur. peakinds=[]; for i=1:length(Terms), if Terms(i).display, switch Terms(i).type, case 'Constant', %A constant term is unchanging from beginning to end. f=[MinFreq MaxFreq]; m=20*log10(abs([Terms(i).value Terms(i).value])); semilogx(f,m,... 'LineWidth',handles.LnWdth,... 'LineStyle',GetLineStyle(i,handles),... 'Color',GetLineColor(i,handles)); mag_a=mag_a+interp1(log(f),m,log(w)); %Build up asymptotic approx. case 'RealPole', %A real pole has a single break frequency and then %decreases at 20 dB per decade (Or more if pole is multiple). wo=-Terms(i).value; f=[MinFreq wo MaxFreq]; m=-20*log10([1 1 MaxFreq/wo])*Terms(i).multiplicity; semilogx(f,m,... 'LineWidth',handles.LnWdth,... 'LineStyle',GetLineStyle(i,handles),... 'Color',GetLineColor(i,handles)); mag_a=mag_a+interp1(log(f),m,log(w)); %Build up asymptotic approx. case 'RealZero', %Similar to real pole, but increases instead of decreasing. wo=abs(Terms(i).value); f=[MinFreq wo MaxFreq]; m=20*log10([1 1 MaxFreq/wo])*Terms(i).multiplicity; semilogx(f,m,... 'LineWidth',handles.LnWdth,... 'LineStyle',GetLineStyle(i,handles),... 'Color',GetLineColor(i,handles)); mag_a=mag_a+interp1(log(f),m,log(w)); %Build up asymptotic approx. case 'ComplexPole', %A complex pole has a single break frequency and then %decreases at 40 dB per decade (Or more if pole is multiple). %There is also a peak value whose height and location are %determined by the natural frequency and damping coefficient. %We will plot a circle ('o') at the location of the peak. wn=abs(Terms(i).value); theta=atan(abs(imag(Terms(i).value)/real(Terms(i).value))); zeta=cos(theta); if (zeta < 0.5), %Show peaking if zeta<0.5 peak=2*zeta; f=[MinFreq wn wn wn MaxFreq]; m=-20*log10([1 1 peak 1 (MaxFreq/wn)^2])*Terms(i).multiplicity; semilogx(f,m,... 'LineWidth',handles.LnWdth,... 'LineStyle',GetLineStyle(i,handles),... 'Color',GetLineColor(i,handles)); semilogx(wn,-20*log10(peak),'o','Color',GetLineColor(i,handles)); % Set up for interpolation (w/o peak) f=[MinFreq wn MaxFreq]; m=-20*log10([1 1 (MaxFreq/wn)^2])*Terms(i).multiplicity; mag_a=mag_a+interp1(log(f),m,log(w)); %Build up asymptotic approx. %Find location closest to peak, and adjust its amplitude. index=find(w>=wn,1,'first'); mag_a(index)=mag_a(index)-20*log10(peak); peakinds=[peakinds index]; %Save this index. else f=[MinFreq wn MaxFreq]; m=-20*log10([1 1 (MaxFreq/wn)^2])*Terms(i).multiplicity; semilogx(f,m,... 'LineWidth',handles.LnWdth,... 'LineStyle',GetLineStyle(i,handles),... 'Color',GetLineColor(i,handles)); % Set up for interpolation (w/o peak) mag_a=mag_a+interp1(log(f),m,log(w)); %Build up asymptotic approx. end case 'ComplexZero', %Similar to complex pole, but increases instead of decreasing. wn=abs(Terms(i).value); theta=atan(abs(imag(Terms(i).value)/real(Terms(i).value))); zeta=cos(theta); if (zeta < 0.5), %Show peaking if zeta<0.5 peak=2*zeta; f=[MinFreq wn wn wn MaxFreq]; m=20*log10([1 1 peak 1 (MaxFreq/wn)^2])*Terms(i).multiplicity; semilogx(f,m,... 'LineWidth',handles.LnWdth,... 'LineStyle',GetLineStyle(i,handles),... 'Color',GetLineColor(i,handles)); semilogx(wn,20*log10(peak),'o','Color',GetLineColor(i,handles)); % Set up for interpolation (w/o peak) f=[MinFreq wn MaxFreq]; m=20*log10([1 1 (MaxFreq/wn)^2])*Terms(i).multiplicity; mag_a=mag_a+interp1(log(f),m,log(w)); %Build up asymptotic approx. %Find location closest to peak, and adjust its amplitude. index=find(w>=wn,1,'first'); mag_a(index)=mag_a(index)+20*log10(peak); peakinds=[peakinds index]; %Save this index. else f=[MinFreq wn MaxFreq]; m=20*log10([1 1 (MaxFreq/wn)^2])*Terms(i).multiplicity; semilogx(f,m,... 'LineWidth',handles.LnWdth,... 'LineStyle',GetLineStyle(i,handles),... 'Color',GetLineColor(i,handles)); % Set up for interpolation (w/o peak) mag_a=mag_a+interp1(log(f),m,log(w)); %Build up asymptotic approx. end case 'OriginPole', %A pole at the origin is a monotonically decreasing straigh line. f=[MinFreq MaxFreq]; m=-20*log10([MinFreq MaxFreq])*Terms(i).multiplicity; semilogx(f,m,... 'LineWidth',handles.LnWdth,... 'LineStyle',GetLineStyle(i,handles),... 'Color',GetLineColor(i,handles)); mag_a=mag_a+interp1(log(f),m,log(w)); %Build up asymptotic approx. case 'OriginZero', %Similar to pole at origin, but increases instead of decreasing. f=[MinFreq MaxFreq]; m=20*log10([MinFreq MaxFreq])*Terms(i).multiplicity; semilogx(f,m,... 'LineWidth',handles.LnWdth,... 'LineStyle',GetLineStyle(i,handles),... 'Color',GetLineColor(i,handles)); mag_a=mag_a+interp1(log(f),m,log(w)); %Build up asymptotic approx. end end end %Set the x-axis limits to the minimum and maximum frequency. set(gca,'XLim',[MinFreq MaxFreq]); [mg,ph,w]=bode(sysInc,w); %Calculate the exact bode plot. semilogx(w,20*log10(mg(:)),... 'Color',handles.exactColor,... 'LineWidth',handles.LnWdth/2); %Plot it. if (get(handles.ShowAsymptoticCheckBox,'Value')~=0), semilogx(w,mag_a,... 'Color',handles.exactColor,... 'LineStyle',':',... 'LineWidth',handles.LnWdth); %Plot asymptotic approx. semilogx(w(peakinds),mag_a(peakinds),'o','Color',handles.exactColor); end if handles.FirstPlot, %If this is the first time, let Matlab do autoscaling, but save %the y-axis limits so that they will be unchanged as the plot changes. ylims=get(gca,'YLim')/20; ylims(1)=min(-20,ceil(ylims(1))*20); ylims(2)=max(20,floor(ylims(2))*20); handles.MagLims=ylims; else %If this is not the first time, retrieve the old y-axis limits. ylims=handles.MagLims; end set(gca,'YLim',ylims); set(gca,'YTick',ylims(1):20:ylims(2)); %Make ticks every 20 dB. ylabel('Magnitude - dB'); xlabel('Frequency - \omega, rad-sec^{-1}') title('Magnitude Plot','color','b','FontWeight','bold'); if get(handles.GridCheckBox,'Value')==1, grid on end hold off; %%%%%%%%%%%%%%%%%%%% End Magnitude Plot %%%%%%%%%% %%%%%%%%%%%%%%%%%%%% Start Phase Plot %%%%%%%%%% %Much of this section mirrors the previous section and is not commented. %One difference is that phase is calculated explicitly, rather than use %Matlab's "bode" command. Since phase is not unique (you can add or %subtract multiples of 360 degrees) There were sometimes discrepancies %between Matlab's phase calculations and mine axes(handles.PhasePlot); cla; %Plot line at 0 degrees for reference. semilogx([MinFreq MaxFreq],[0 0],... 'Color',handles.zrefColor,... 'LineWidth',1.5); hold on; %The variable phs_a has the combined asymptotic phase. phs_a=zeros(size(w)); for i=1:length(Terms), if Terms(i).display, switch Terms(i).type, case 'Constant', f=[MinFreq MaxFreq]; if Terms(i).value>0, p=[0 0]; else p=[180 180]; end if get(handles.RadianCheckBox,'Value')==1, p=p/180; end semilogx(f,p,... 'LineWidth',handles.LnWdth,... 'LineStyle',GetLineStyle(i,handles),... 'Color',GetLineColor(i,handles)); case 'RealPole', wo=-Terms(i).value; f=[MinFreq wo/10 wo*10 MaxFreq]; p=[0 0 -90 -90]*Terms(i).multiplicity; if get(handles.RadianCheckBox,'Value')==1, p=p/180; end semilogx(f,p,... 'LineWidth',handles.LnWdth,... 'LineStyle',GetLineStyle(i,handles),... 'Color',GetLineColor(i,handles)); case 'RealZero', if Terms(i).value>0, %Non-minimum phase wo=Terms(i).value; %Uncomment the next section to get agreement with Matlab plots. %if Terms(1).value>0, %Choose 0 or 360 to agree with MatLab plots % p=[0 0 0 0]; % (based on sign of constant term). % else % p=[360 360 360 360]; %end p=[0 0 -90 -90]*Terms(i).multiplicity; else %Minimum phase wo=-Terms(i).value; p=[0 0 90 90]*Terms(i).multiplicity; end f=[MinFreq wo/10 wo*10 MaxFreq]; if get(handles.RadianCheckBox,'Value')==1, p=p/180; end semilogx(f,p,... 'LineWidth',handles.LnWdth,... 'LineStyle',GetLineStyle(i,handles),... 'Color',GetLineColor(i,handles)); case 'ComplexPole', wo=abs(Terms(i).value); bf=0.2^zeta; f=[MinFreq wo*bf wo/bf MaxFreq]; p=[0 0 -180 -180]*Terms(i).multiplicity; if get(handles.RadianCheckBox,'Value')==1, p=p/180; end semilogx(f,p,... 'LineWidth',handles.LnWdth,... 'LineStyle',GetLineStyle(i,handles),... 'Color',GetLineColor(i,handles)); case 'ComplexZero', wo=abs(Terms(i).value); bf=0.2^zeta; f=[MinFreq wo*bf wo/bf MaxFreq]; if real(Terms(i).value)>0, %Non-minimum phase p=[0 0 -180 -180]*Terms(i).multiplicity; else p=[0 0 180 180]*Terms(i).multiplicity; end if get(handles.RadianCheckBox,'Value')==1, p=p/180; end semilogx(f,p,... 'LineWidth',handles.LnWdth,... 'LineStyle',GetLineStyle(i,handles),... 'Color',GetLineColor(i,handles)); case 'OriginPole', f=[MinFreq MaxFreq]; p=[-90 -90]*Terms(i).multiplicity; if get(handles.RadianCheckBox,'Value')==1, p=p/180; end semilogx(f,p,... 'LineWidth',handles.LnWdth,... 'LineStyle',GetLineStyle(i,handles),... 'Color',GetLineColor(i,handles)); case 'OriginZero', f=[MinFreq MaxFreq]; p=[90 90]*Terms(i).multiplicity; if get(handles.RadianCheckBox,'Value')==1, p=p/180; end semilogx(f,p,... 'LineWidth',handles.LnWdth,... 'LineStyle',GetLineStyle(i,handles),... 'Color',GetLineColor(i,handles)); end phs_a=phs_a+interp1(log(f),p,log(w)); %Build up asymptotic approx. end end if get(handles.RadianCheckBox,'Value')==1, ph=ph/180; end semilogx(w,ph(:),... 'Color',handles.exactColor,... 'LineWidth',handles.LnWdth/2); %Plot it. if (get(handles.ShowAsymptoticCheckBox,'Value')~=0), % There can be discrepancies between Matlabs calculation of phase and % the quantity "phs_a." This is because phase is not unique (you can % add or subtract multiples of 360 degrees). The next line shifts the % value of phs_a so that phs_a(1)=ph(1). Ensuring they are the same % at the beginning of the plot ensures that they align elsewhere. Note % that if the plots are already aligned (ph(1)=phs_a(1)), that the next % line does nothing. phs_a=phs_a+(ph(1)-phs_a(1)); semilogx(w,phs_a,... 'Color',handles.exactColor,... 'LineStyle',':',... 'LineWidth',handles.LnWdth); %Plot asymptotic approx. end set(gca,'XLim',[MinFreq MaxFreq]); if handles.FirstPlot, ylims=get(gca,'YLim')/45; ylims(1)=ceil(ylims(1)-1)*45; ylims(2)=floor(ylims(2)+1)*45; handles.DPhaseLims=ylims; %Find phase limits in degrees handles.RPhaseLims=ylims/180; %Find phase limits in radians/pi else if get(handles.RadianCheckBox,'Value')==1, ylims=handles.RPhaseLims; else ylims=handles.DPhaseLims; end end if get(handles.RadianCheckBox,'Value')==1, set(gca,'YLim',ylims); set(gca,'YTick',ylims(1):0.25:ylims(2)) ylabel('Phase - radians/\pi'); else set(gca,'YLim',ylims); set(gca,'YTick',ylims(1):45:ylims(2)) ylabel('Phase - degrees'); end xlabel('Frequency - \omega, rad-sec^{-1}'); title('Phase Plot','color','b','FontWeight','bold'); if get(handles.GridCheckBox,'Value')==1, grid on end hold off; %%%%%%%%%%%%%%%%%%%% End Phase Plot %%%%%%%%%% BodePlotDispTF(handles); %Display the transfer function BodePlotLegend(handles); %Display the legend. handles=guidata(handles.AsymBodePlot); %Reload handles after function call. %Set first plot to zero (so Matlab won't autoscale on subsequent calls). handles.FirstPlot=0; guidata(handles.AsymBodePlot, handles); %save changes to handles. end % ----------------- End of function BodePlotter ---------------------- % -------------------------------------------------------------------- function BodePlotSys(handles) %This function makes up a transfer function of all the terms that are not in %the "Excluded Elements" box in the GUI. Terms=handles.Terms; %Get all terms from original transfer function. p=[]; %Start with no poles, or zeros, and a constant of 1 z=[]; k=1; for i=1:length(Terms), %For each term. %If the term is not in "Excluded Elements". then we want to display it. if Terms(i).display, switch Terms(i).type, case 'Constant', k=Terms(i).value; %This is the constant. case 'RealPole', for j=1:Terms(i).multiplicity, p=[p Terms(i).value]; %Add poles. end case 'RealZero', for j=1:Terms(i).multiplicity, z=[z Terms(i).value]; %Add zeros. end case 'ComplexPole', for j=1:Terms(i).multiplicity, p=[p Terms(i).value]; %Add poles. p=[p conj(Terms(i).value)]; end case 'ComplexZero', for j=1:Terms(i).multiplicity, z=[z Terms(i).value]; %Add zeros. z=[z conj(Terms(i).value)]; end case 'OriginPole', for j=1:Terms(i).multiplicity, p=[p 0]; %Add poles. end case 'OriginZero', for j=1:Terms(i).multiplicity, z=[z 0]; %Add zeros. end end end end %Determine multiplicative constant in standard Bode Plot form. for i=1:length(p), if p(i)~=0 k=-k*p(i); end end for i=1:length(z), if z(i)~=0 k=-k/z(i); end end %If poles and/or zeros were complex conjugate pairs, there may be %some residual imaginary part due to finite precision. Remove it. k=real(k); handles.SysInc=zpk(z,p,k); guidata(handles.AsymBodePlot, handles); %save changes to handles. end % ------------------End of function BodePlotSys ---------------------- % -------------------------------------------------------------------- function BodePlotDispTF(handles) % This function displays a tranfer function that is a helper function for the % BodePlotGui routine. It takes the transfer function of the numerator and % splits it into three lines so that it can be displayed nicely. For example: % " s + 1" % "H(s) = ---------------" % " s^2 + 2 s + 1" % % The numerator string is in the variable nStr, % the second line is in divStr, % and the denominator string is in dStr. % Get numerator and denominator. [n,d]=tfdata(handles.SysInc,'v'); % Get string representations of numerator and denominator nStr=poly2str(n,'s'); dStr=poly2str(d,'s'); % Find length of strings. LnStr=length(nStr); LdStr=length(dStr); if LnStr>LdStr, %the numerator is longer than denominator string, so pad denominator. n=LnStr; %n is the length of the longer string. nStr=[' ' nStr]; %add spaces for characters at beginning of divStr. dStr=[' ' blanks(floor((LnStr-LdStr)/2)) dStr]; %pad denominator. else %the demoninator is longer than numerator, pad numerator. n=LdStr; nStr=[' ' blanks(floor((LdStr-LnStr)/2)) nStr]; dStr=[' ' dStr]; end divStr=[]; for i=1:n, divStr=[divStr '-']; end divStr=['H(s) = ' divStr]; set(handles.TransferFunctionText,'String',strvcat(nStr,divStr,dStr)); %Change type font and size. set(handles.TransferFunctionText,'FontName','Courier New') %set(handles.TransferFunctionText,'FontSize',10) guidata(handles.AsymBodePlot, handles); %save changes to handles. end % ------------------End of function BodePlotDispTF ------------------- % -------------------------------------------------------------------- function BodePlotLegend(handles) %This function creates the legends for the Bode plot being displayed. %It also makes four changes to the "handles" structure. % 1) It updates the array "IncElem" that holds the indices that determine % which elements are included in the Bode plot. % 2) It updates the sring array "IncStr" that hold the description of % each included elements. % 3) Updates ExcElem that holds indices of excluded elements. % 4) Updates ExcStr that hold descriptions of excluded elements. %Load the terms and the plotting strings into local variables for convenience. Terms=handles.Terms; axes(handles.LegendPlot); %Set axes to the legend widow, cla; % and clear it. Xleg=[0 0.1]; %Xleg holds start and end of line segment for legend. XlegText=0.125; %XlegText is location of text. FntSz=8; %Font Size of text. y=1-1/(length(Terms)+6); %Vertical location of first text item plot(Xleg,[y y],... 'Color',handles.exactColor,... 'LineWidth',handles.LnWdth/2); %Plot line for legend. text(XlegText,y,'Exact Bode Plot','FontSize',FntSz); %Place text hold on; if (get(handles.ShowAsymptoticCheckBox,'Value')~=0), y=1-2/(length(Terms)+6); %Vertical location of second item plot(Xleg,[y y],... 'Color',handles.exactColor,... 'LineStyle',':',... 'LineWidth',handles.LnWdth); %Plot line for legend. text(XlegText,y,'Asymptotic Plot','FontSize',FntSz); %Place text end y=1-3/(length(Terms)+6); %Vertical location of third item. plot(Xleg,[y y],... %Line. 'Color',handles.zrefColor,... 'LineWidth',2); text(XlegText,y,'Zero Value (for reference only)','FontSize',FntSz); %Text. IncElem=[]; %The indices of elements to be included in plot. ExcElem=[]; %The indices of elements to be excluded from plot. IncStr=''; %An array of strings describing included elements. ExcStr=''; %An array of strings describing excluded elements. %These variables are used as local counters later. Here they are initialized. i1=1; i2=1; for i=1:length(Terms), %For each term, %Tv is a local variable representing the pole location. It is used solely % for convenience. Tv=Terms(i).value; %Tm is a local variable representing the pole multiplicity. It is used solely % for convenience. Tm=Terms(i).multiplicity; %S2 is a blank string to be added to later in the loop. S2=''; %The next section of code ("switch" statement) plus a few lines, creates %a string that describes the pole or zero, its location, muliplicity... %The variable "DescStr" hold a Descriptive String for the pole or zero. The %string "S2" is a Second String that holds additional information (if needed) switch Terms(i).type, case 'Constant', %If the term is a consant, print its value in a string. DescStr=sprintf('Constant = %0.2g (%0.2g dB)',Tv,20*log10(abs(Tv))); if Tv>=0, DescStr=[DescStr ' phi=0']; else DescStr=[DescStr ' phi=180 (pi/2)']; end; case 'RealPole', %If the term is a real pole, print its value in string. DescStr=sprintf('Real Pole at %0.2g',Tv); case 'RealZero', %If the term is a real zero, print its value in string. DescStr=sprintf('Real Zero at %0.2g',Tv); if real(Tv)>0, DescStr=[DescStr ' RHP (Non-min phase)']; end; case 'ComplexPole', %If the term is a complex pole, print its value in string. %However, do this in terms of natural frequency and damping, as %well as the actual location of the pole (in S2). wn=abs(Tv); theta=atan(abs(imag(Tv)/real(Tv))); zeta=cos(theta); DescStr=sprintf('Complex Pole at wn=%0.2g, zeta=%0.2g',wn,zeta); if (zeta < 0.5), %peaking only if zeta<0.5 S2=sprintf('(%0.2g +/- %0.2gj) Circle shows peak height.',real(Tv),imag(Tv)); else S2=sprintf('(%0.2g +/- %0.2gj) (no peaking shown, zeta>0.5)',real(Tv),imag(Tv)); end case 'ComplexZero', %If the term is a complex zero, print its value in string. %However, do this in terms of natural frequency and damping, as %well as the actual location of the zero (in S2). %Also note if it is a non-minimum phase zero. wn=abs(Tv); theta=atan(abs(imag(Tv)/real(Tv))); zeta=cos(theta); DescStr=sprintf('Complex Zero at wn=%0.2g, zeta=%0.2g',wn,zeta); if real(Tv)>0, DescStr=[DescStr ' (RHP, Non-min phase)']; end; if (zeta < 0.5), %peaking only if zeta<0.5 S2=sprintf('(%0.2g +/- %0.2gj) Circle shows peak height.',real(Tv),imag(Tv)); else S2=sprintf('(%0.2g +/- %0.2gj) (no peaking shown, zeta>0.5)',real(Tv),imag(Tv)); end case 'OriginPole', %If pole is at origin, not this. DescStr=sprintf('Pole at origin'); case 'OriginZero', %If zero is at origin, not this. DescStr=sprintf('Zero at origin'); end %If multiplicity is greater than one, not this as well. if Tm>1, DescStr=[DescStr sprintf(', mult=%d',Tm)]; end %At this point we have a string (in "DescStr" and "S2"). if Terms(i).display, %If the term is to be included in plot.... IncStr=strvcat(IncStr,DescStr); %Add the Desriptive String to IncStr IncElem(i1)=i; %Add the appropriate index to the Included Elements list. i1=i1+1; %Increment the index counter y=1-(i1+2)/(length(Terms)+6); %Calculate the vertical position. plot(Xleg,[y y],... %Plot the line. 'LineWidth',handles.LnWdth,... 'LineStyle',GetLineStyle(i,handles),... 'Color',GetLineColor(i,handles)); text(XlegText,y,strvcat(DescStr,S2),'FontSize',FntSz); %Add the text. else %The term is *not* to be included in plot, so... ExcStr=strvcat(ExcStr,DescStr); %Add its Desriptive String to ExcStr ExcElem(i2)=i; %Add the appropriate index to the Excluded Elements list. i2=i2+1; %Increment the index counter. end end hold off; %Get rid of ticks around plot. axis([0 1 0 1]); set(gca,'Xtick',[]); set(gca,'Ytick',[]); %At this point the legend is completed. Next we will make up the strings %for the boxes that separately list included and excluded elements. IncStr=strvcat(IncStr,' '); %Add a blank line to IncStr. IncStr=strvcat(IncStr,'-------'); %Add a series of dashes. %If there are any included elements, we can click on box to exclude it. if i1~=1 IncStr=strvcat(IncStr,'Select element to exclude from plot'); end ExcStr=strvcat(ExcStr,' '); %Add a blank line to ExcStr ExcStr=strvcat(ExcStr,'-------'); %Add a series of dashes. %If there are any excluded elements, we can click on box to include it. if i2~=1 ExcStr=strvcat(ExcStr,'Select element to include in plot'); end %Set the strings for included and excluded elements. set(handles.IncludedElements,'String',IncStr); set(handles.ExcludedElements,'String',ExcStr); %Change the arrays holding included and excluded elements in the handles array. handles.IncElem=IncElem; handles.ExcElem=ExcElem; guidata(handles.AsymBodePlot, handles); %save changes to handles. end % ------------------End of function BodePlotLegend -------- % -------------------------------------------------------------------- % --- Executes during object creation, after setting all properties. function LineWidth_CreateFcn(hObject, ~, ~) % hObject handle to LineWidth (see GCBO) % eventdata reserved - to be defined in a future version of MATLAB % handles empty - handles not created until after all CreateFcns called if ispc set(hObject,'BackgroundColor','white'); else set(hObject,'BackgroundColor',get(0,'defaultUicontrolBackgroundColor')); end end % ------------------End of function BodePlotLegend -------- % -------------------------------------------------------------------- % Set the width of the lines used in plots. function LineWidth_Callback(~, ~, handles) % hObject handle to LineWidth (see GCBO) % eventdata reserved - to be defined in a future version of MATLAB % handles structure with handles and user data (see GUIDATA) handles.LnWdth=str2num(get(handles.LineWidth,'String')); guidata(handles.AsymBodePlot, handles); %save changes to handles. BodePlotter(handles); %Plot the Transfer function. handles=guidata(handles.AsymBodePlot); %Reload handles after function call. end % ------------------End of function BodePlotLegend -------- % -------------------------------------------------------------------- % Determines the line color of the ith plot. function linecolor=GetLineColor(i,handles) numColors=size(handles.colors,1); %Cycle through colors by using mod operator. linecolor=handles.colors(mod(i-1,numColors)+1,:); end % ------------------End of function GetLineColor -------- % -------------------------------------------------------------------- % Determines the line style of the ith plot. function linestyle=GetLineStyle(i,handles) numColors=size(handles.colors,1); numLnStl=size(handles.linestyle,2); %Cycle through line styles, incrementing after all colors are used. linestyle=handles.linestyle{mod(ceil(i/numColors)-1,numLnStl)+1}; end % ------------------End of function GetLineStyle -------- % -------------------------------------------------------------------- % --- Executes on button press in GrayCheckBox. % This function sets the sequence of colors used in plotting to gray scales. function GrayCheckBox_Callback(~, ~, handles) if get(handles.GrayCheckBox,'Value')==1, %If button is not set, handles.colors=handles.Gray; %and set colors to gray scale handles.zrefColor=handles.GrayZero; else handles.colors=handles.Color; %and set colors to RGB handles.zrefColor=handles.ColorZero; end guidata(handles.AsymBodePlot, handles); %save changes to handles. BodePlotter(handles); %Plot the Transfer function. handles=guidata(handles.AsymBodePlot); %Reload handles after function call. end % ------------------End of function BodePlotLegend -------- % --- Executes on button press in GridCheckBox. function GridCheckBox_Callback(~, ~, handles) BodePlotter(handles); %Plot the Transfer function. end % --- Executes on button press in RadianCheckBox. function RadianCheckBox_Callback(~, ~, handles) BodePlotter(handles); %Plot the Transfer function. end % --- Executes on button press in WebButton. function WebButton_Callback(~, ~, ~) web('http://lpsa.swarthmore.edu/Bode/Bode.html','-browser') end % --- Executes on button press in ShowAsymptoticCheckBox. function ShowAsymptoticCheckBox_Callback(~, ~, handles) BodePlotter(handles); %Plot the Transfer function. end % --- Executes on button press in limitButton. function limitButton_Callback(~, ~, ~) s{1}='Restrictions on systems:'; s{2}=' 1) SISO (Single Input Single Output);'; s{3}=' 2) Proper systems (order of num <= order of den);'; s{4}=' 4) System must be a transfer function (i.e., not state space...)'; s{5}=' 5) Time delays are ignored.'; helpdlg(s,'Valid Systems'); end % --- Executes on selection change in popupSystems. function popupSystems_Callback(hObject, ~, handles) i=get(hObject,'Value'); if i ~= 1, %If this is not the "User Systems" choice if i==2, %This is the refresh choice loadSystems(handles); handles=guidata(handles.AsymBodePlot); %Reload handles after function call. else %THis is a valid choice, pick transfer function. initBodePlotGui(handles); handles=guidata(handles.AsymBodePlot); %Reload handles after function call. handles.Sys=handles.WorkSpaceTFs{i}; doBodeGUI(handles); handles=guidata(handles.AsymBodePlot); %Reload handles after function call. end end guidata(hObject, handles); end % --- Executes during object creation, after setting all properties. function popupSystems_CreateFcn(hObject, ~, ~) % hObject handle to popupSystems (see GCBO) % eventdata reserved - to be defined in a future version of MATLAB % handles empty - handles not created until after all CreateFcns called % Hint: popupmenu controls usually have a white background on Windows. % See ISPC and COMPUTER. if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) set(hObject,'BackgroundColor','white'); end end function loadSystems(handles) [v_name, v_tf]=getBaseTFs; set(handles.popupSystems,'String',v_name); handles.WorkSpaceTFs=v_tf; guidata(handles.AsymBodePlot, handles); %save changes to handles. end function [varName, varTF]=getBaseTFs % Find all valid transfer functions in workspace s=evalin('base','whos(''*'')'); tfs=strvcat(s.class); %x=class of all variable tfs=strcmp(cellstr(tfs),'tf'); %Convert to cell array and find tf's s=s(tfs); %Get just tf's vname=strvcat(s.name); varName{1}='User Systems'; varTF{1}=[]; varName{2}='Refresh Systems'; varTF{2}=[]; j=2; for i=1:length(s) myTF=evalin('base',vname(i,:)); if (size(myTF.num)==[1 1]), %Check for siso j=j+1; varName{j}=vname(i,:); varTF{j}=myTF; end end end