├── BodePlotGui.fig ├── .gitattributes ├── .gitignore ├── README.md ├── LICENSE ├── BodeMagPaper.m ├── BodePaper.m └── BodePlotGui.m /BodePlotGui.fig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echeever/BodePlotGui/HEAD/BodePlotGui.fig -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # ========================= 18 | # Operating System Files 19 | # ========================= 20 | 21 | # OSX 22 | # ========================= 23 | 24 | .DS_Store 25 | .AppleDouble 26 | .LSOverride 27 | 28 | # Icon must end with two \r 29 | Icon 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | I have stopped working on BodePlotGui and have developed a similar tool in JavaScript to make it more accessible (https://lpsa.swarthmore.edu/Bode/bodeDraw.html). While MATLAB is extremely powerful, it is also very expensive. 2 | 3 | BodePlotGui 4 | =========== 5 | 6 | A MATLAB GUI for drawing asymptotic Bode diagrams 7 | 8 | The code here is useful for people trying to learn how to draw asymptotic Bode diagrams. The code takes as input a single-input single-output transfer function and generates the asymptotic approximation along with a description of how the approximation was developed. 9 | 10 | For help see: http://lpsa.swarthmore.edu/Bode/BodePlotGui.html and http://lpsa.swarthmore.edu/Bode/BodePaper.html 11 | 12 | Note: the MATLAB GUI doesn't display well on all devices (some elements of the GUI may not show up). If you have this problem, simply run the MATLAB command "guide" and open the file BodePlotGui.fig. You can edit the size and layout of the GUI for your machine. Save it, and then rerun the BodePlotGui.m file. 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Erik Cheever 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /BodeMagPaper.m: -------------------------------------------------------------------------------- 1 | function BodeMagPaper(om_lo, om_hi, dB_lo, dB_hi) 2 | %BodePaper is Matlab code to generate graph paper for Bode plots. It generates 3 | %a semilog graphs for making Bode plots of magnitude with the 4 | %units on the vertical axis is set to dB. 5 | % 6 | %Correct calling syntax is: 7 | % BodeMagPaper(om_lo, om_hi, dB_lo, dB_hi) 8 | % om_lo the low end of the frequency scale. This can be either 9 | % rad/sec or Hz. No units are displayed on the graph. 10 | % om_hi the high end of the frequency scale. 11 | % dB_lo the bottom end of the dB scale. 12 | % dB_hi the top end of the dB scale. 13 | % 14 | 15 | %Ensure proper number of arguments 16 | if (nargin~=4), 17 | beep; 18 | disp(' '); 19 | disp('BodeMagPaper needs four argmuments)'); 20 | disp('Enter "help BodeMagPaper" for more information'); 21 | disp(' '); 22 | return 23 | end 24 | 25 | 26 | %This next set of lines makes the magnitude graph. 27 | subplot(211); 28 | semilogx(0,0); %Create a set of axes 29 | axis([om_lo, om_hi, dB_lo, dB_hi]) 30 | title('Bode Plot Magnitude (Axes 1)'); 31 | ylabel('Magnitude (dB)'); 32 | grid 33 | 34 | %This next section of code sets ticks every 20 dB. 35 | h=gca; 36 | ytick=dB_lo+20*[0:(dB_hi-dB_lo)/20]; 37 | set(h,'YTick',ytick); 38 | 39 | 40 | %This next set of lines makes the phase graph. 41 | subplot(212); 42 | semilogx(0,0); %Create a set of axes 43 | axis([om_lo, om_hi, dB_lo, dB_hi]) 44 | title('Bode Plot Magnitude (Axes 2 - in case of error with Axes 1)'); 45 | ylabel('Magnitude (dB)'); 46 | grid 47 | 48 | %This next section of code sets ticks every 20 dB. 49 | h=gca; 50 | ytick=dB_lo+20*[0:(dB_hi-dB_lo)/20]; 51 | set(h,'YTick',ytick); 52 | -------------------------------------------------------------------------------- /BodePaper.m: -------------------------------------------------------------------------------- 1 | function BodePaper(om_lo, om_hi, dB_lo, dB_hi, ph_lo, ph_hi, UseRad) 2 | %BodePaper is Matlab code to generate graph paper for Bode plots. It generates 3 | %two semilog graphs for making Bode plots. The top plot is for magnitude, the 4 | %units on the vertical axis is set to dB. The bottom plot shows phase. The 5 | %units on the phase plot can be radians or degrees, at the discretion of the 6 | %user. The default is degrees. 7 | % 8 | %The correct calling syntax is: 9 | % BodePaper(om_lo, om_hi, dB_lo, dB_hi, ph_lo, ph_hi, UseRad) 10 | % om_lo the low end of the frequency scale. This can be either 11 | % rad/sec or Hz. No units are displayed on the graph. 12 | % om_hi the high end of the frequency scale. 13 | % dB_lo the bottom end of the dB scale. 14 | % dB_hi the top end of the dB scale. 15 | % ph_lo the bottom end of the phase scale. 16 | % ph_hi the top end of the phase scale. 17 | % UseRad an optional argument. If this argument is non-zero the units 18 | % on the phase plot are radians. If this argument is left off 19 | % or set to zero, the units are degrees. 20 | % 21 | 22 | %Ensure proper number of arguments 23 | if (nargin~=6) & (nargin~=7), 24 | beep; 25 | disp(' '); 26 | disp('BodePaper needs six argmuments (or seven)'); 27 | disp('Enter "help BodePaper" for more information'); 28 | disp(' '); 29 | return 30 | end 31 | 32 | if nargin==6 %If there is no 7th argument 33 | UseRad=0; % UseRad=0, (i.e., phase units are degrees.) 34 | end 35 | 36 | %This next set of lines makes the magnitude graph. 37 | subplot(211); 38 | semilogx(0,0); %Create a set of axes 39 | axis([om_lo, om_hi, dB_lo, dB_hi]) 40 | title('Bode Plot'); 41 | ylabel('Magnitude (dB)'); 42 | grid 43 | 44 | %This next section of code sets ticks every 20 dB. 45 | h=gca; 46 | ytick=dB_lo+20*[0:(dB_hi-dB_lo)/20]; 47 | set(h,'YTick',ytick); 48 | 49 | 50 | %This next set of lines makes the phase graph. 51 | subplot(212); 52 | semilogx(0,0); %Create a set of axes 53 | axis([om_lo, om_hi, ph_lo, ph_hi]) 54 | xlabel('Frequency - log scale'); 55 | grid 56 | 57 | %Put in ticks on y axis 58 | h=gca; 59 | if UseRad==0, 60 | %This next section of code sets ticks every 45 degrees. 61 | ytick=ph_lo+45*[0:(ph_hi-ph_lo)/45]; 62 | set(h,'YTick',ytick); 63 | ylabel('Phase (degrees)'); 64 | else 65 | %If radians, make ticks every 0.25 radians. 66 | ytick=ph_lo+(pi/4)*[0:(ph_hi-ph_lo)/(pi/4)]; 67 | set(h,'YTick',ytick); 68 | %Set the tick labels to be fractions of pi. 69 | set(h,'YTickLabel',num2str(ytick'/pi)); 70 | ylabel('Phase/\pi (radians/\pi)'); 71 | end -------------------------------------------------------------------------------- /BodePlotGui.m: -------------------------------------------------------------------------------- 1 | function varargout = BodePlotGui(varargin) 2 | % BODEPLOTGUI Application M-file for BodePlotGui.fig 3 | % FIG = BODEPLOTGUI launch BodePlotGui GUI. 4 | % BODEPLOTGUI('callback_name', ...) invoke the named callback. 5 | 6 | % Last Modified by GUIDE v2.5 17-Oct-2014 11:41:15 7 | 8 | %Written by Erik Cheever (Copyright 2002) 9 | %Contact: erik_cheever@swarthmore.edu 10 | % Erik Cheever 11 | % Dept. of Engineering 12 | % Swarthmore College 13 | % 500 College Avenue 14 | % Swarthmore, PA 19081 USA 15 | 16 | %This function acts as a switchyard for several callbacks. It also intializes 17 | %the variables used by the GUI. Note that all variables are initialized here to 18 | %default values. A brief description of each variable is included. 19 | 20 | if (nargin == 0) || (isa(varargin{1},'tf')) %If no arguments, or first 21 | fig = openfig(mfilename,'new'); 22 | handles = guihandles(fig); %Get handles structure. 23 | initBodePlotGui(handles); 24 | handles=guidata(handles.AsymBodePlot); %Reload handles after function call. 25 | 26 | if nargout > 0 %If output argument is used, set it to figure. 27 | varargout{1} = fig; 28 | end 29 | 30 | if ((nargin~=0) && (isa(varargin{1},'tf'))), 31 | %Transfer function chosen. 32 | handles.Sys=varargin{1}; %The variable Sys is the transfer function. 33 | doBodeGUI(handles); 34 | end 35 | 36 | elseif ischar(varargin{1}) % INVOKE NAMED SUBFUNCTION OR CALLBACK 37 | try 38 | [varargout{1:nargout}] = feval(varargin{:}); % FEVAL switchyard 39 | catch 40 | disp(lasterr); 41 | end 42 | end 43 | % ------------------End of function BodePlotGui ---------------------- 44 | 45 | 46 | function initBodePlotGui(handles) 47 | handles.IncludeString=[]; %An array of strings representing terms to include in the plot. 48 | handles.ExcludeString=[]; %An array of strings representing terms to exclude from plot. 49 | handles.IncElem=[]; %An array of indices of terms corresponding to their 50 | % location in the IncludeString array. 51 | handles.ExcElem=[]; %An array of indices of terms corresponding to their 52 | % location in the ExcludeString array. 53 | handles.FirstPlot=1; %This term is 1 the first time a plot is made. This lets 54 | % Matlab do the original autoscaling. The scales are then 55 | % saved and reused. 56 | handles.MagLims=[]; %The limits on the magnitude plot determined by MatLab autoscaling. 57 | handles.PhaseLims=[]; %The limits on the phase plot determined by MatLab autoscaling. 58 | handles.LnWdth=2; 59 | set(handles.LineWidth,'String',num2str(handles.LnWdth)); 60 | 61 | %Set the color of lines used in gray scale. The plotting functions 62 | %cycle through these colors (and then cycle through the linestyles). 63 | handles.Gray=[0.75 0.75 0.75; 0.5 0.5 0.5; 0.25 0.25 0.25]; 64 | handles.GrayZero=[0.9 0.9 0.9]; %This is the color used for the zero reference. 65 | 66 | %Set the color of lines used in color plots. The plotting functions 67 | %cycle through these colors (and then cycle through the linestyles). 68 | handles.Color=[0 1 1; 0 0 1; 0 1 0; 1 0 0; 1 0 1;1 0.52 0.40]; 69 | handles.ColorZero=[1 1 0]; %Yellow, this is the color used for the zero reference. 70 | 71 | %Sets order of linestyles used. 72 | handles.linestyle={':','--','-.'}; 73 | 74 | %This sets the default scheme to color (GUI can set them to gray scale). 75 | handles.colors=handles.Color; 76 | handles.zrefColor=handles.ColorZero; 77 | handles.exactColor=[0 0 0]; %Black 78 | 79 | handles.Sys=[]; 80 | handles.SysInc=[]; 81 | 82 | handles.Terms=[]; 83 | %The structure "Term" has three elements. 84 | % type: this can be any of the 7 types listed below. 85 | % 1) The multiplicative constant. 86 | % 2) Real poles 87 | % 3) Real zeros 88 | % 4) Complex poles 89 | % 5) Complex zeros 90 | % 6) Poles at the origin 91 | % 7) Zeros at the origin 92 | % value: this is the location of the pole or zero (or in the case 93 | % of the multiplicative constant, its value). 94 | % multiplicity: this gives the multiplicity of the pole or zero. It has 95 | % no meaning in the case of the multiplicative constant. 96 | 97 | %The variable "Acc" is a relative accuracy used to determine whether or not 98 | %two poles (or zeros) are the same. Because Matlab uses an approximate 99 | %technique to find roots of an equation, it is likely to give slightly 100 | %different locations to identical roots. 101 | handles.Acc=1E-3; 102 | set(handles.TransferFunctionText,'String',... 103 | {' ','No transfer function chosen',' '}); 104 | 105 | guidata(handles.AsymBodePlot, handles); %save changes to handles. 106 | loadSystems(handles); 107 | handles=guidata(handles.AsymBodePlot); %Reload handles after function call. 108 | 109 | guidata(handles.AsymBodePlot, handles); %save changes to handles. 110 | 111 | function simpleTF = makeSimple(origTF) 112 | simpleTF=minreal(origTF); %Get minimum realization. 113 | % Get numerator and denominator of two realizations. If their 114 | % lengths are unequal, it means that there were poles and zeros that 115 | % cancelled. 116 | [n1,d1]=tfdata(simpleTF,'v'); 117 | [n2,d2]=tfdata(origTF,'v'); 118 | if (length(n1)~=length(n2)), 119 | disp(' '); 120 | disp(' '); 121 | disp(' '); 122 | disp('************Warning******************'); 123 | disp('Original transfer function was:'); 124 | origTF 125 | disp('Some poles and zeros were equal. After cancellation:'); 126 | simpleTF 127 | disp('The simplified transfer function is the one that will be used.'); 128 | disp('*************************************'); 129 | disp(' '); 130 | beep; 131 | waitfor(warndlg('System has poles and zeros that cancel. See Command Window for caveats.')); 132 | end 133 | 134 | function doBodeGUI(handles) 135 | handles.Sys=makeSimple(handles.Sys); 136 | handles.SysInc=handles.Sys; %The variable sysInc is that part of the transfer 137 | % function that will be plotted (with no poles or zeros excluded). Start with 138 | % it equal to Sys. This variable is modified in BodePlotSys 139 | 140 | %The function BodePlotTerms separates the transfer function into its consituent parts. 141 | % The variable DoQuit will come back as non-zero if there was a problem. 142 | DoQuit=BodePlotTerms(handles); 143 | handles=guidata(handles.AsymBodePlot); %Reload handles after function call. 144 | 145 | %if DoQuit is zero, there were no problems and we may continue. 146 | if ~DoQuit, 147 | BodePlotter(handles); %Make plot 148 | handles=guidata(handles.AsymBodePlot); %Reload handles after function call. 149 | %DoQuit was non-zero, so there was a problem. Quit program. 150 | else 151 | CloseButton_Callback(fig,'',handles,''); 152 | end 153 | 154 | % -------------------------------------------------------------------- 155 | function varargout = IncludedElements_Callback(~, ~, handles, varargin) 156 | % Stub for Callback of the uicontrol handles.IncludedElements. 157 | % If a term in the "Included Elements" box is clicked, this callback is invoked. 158 | %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. 159 | i=get(handles.IncludedElements,'Value'); 160 | % The alternative is that it corresponds to another string in the box (there is a blank 161 | % line, a line with dashes "----" and a line instructing the user to click on an element 162 | % to include it). 163 | if i<=length(handles.IncElem) 164 | TermsInd=handles.IncElem(i); %Get the index of the included element. 165 | handles.Terms(TermsInd).display=0; %Set display to 0 (to exclude it) 166 | guidata(handles.AsymBodePlot, handles); %save changes to handles. 167 | BodePlotter(handles); %Plot the Transfer function. 168 | handles=guidata(handles.AsymBodePlot); %Reload handles after function call. 169 | end 170 | % ------------------End of function IncludedElements_Callback -------- 171 | 172 | 173 | % -------------------------------------------------------------------- 174 | function varargout = ExcludedElements_Callback(~, ~, handles, varargin) 175 | % Callback of the uicontrol handles.ExcludedElements. 176 | % If a term in the "Excluded Elements" box is clicked, this callback is invoked. 177 | i=get(handles.ExcludedElements,'Value'); %Get index of element in box that is chosen. 178 | %If the index corresponds to one of the terms of the transfer function, deal with it. 179 | % The alternative is that it corresponds to another string in the box (there is a blank 180 | % line, a line with dashes "----" and a line instructing the user to click on an element 181 | % to exclude it). 182 | if i<=length(handles.ExcElem) 183 | TermsInd=handles.ExcElem(i); %Get the index of the excluded element. 184 | handles.Terms(TermsInd).display=1; %Set display to 1 (to include it) 185 | guidata(handles.AsymBodePlot, handles); %save changes to handles. 186 | BodePlotter(handles); %Plot the Transfer function. 187 | handles=guidata(handles.AsymBodePlot); %Reload handles after function call. 188 | end 189 | % ------------------End of function ExcludedElements_Callback -------- 190 | 191 | 192 | % -------------------------------------------------------------------- 193 | function varargout = CloseButton_Callback(~, ~, handles, varargin) 194 | % Callback for the uicontrol handles.CloseButton. 195 | %This function closes the window, and displays a message. 196 | disp(' '); disp('Asymptotic Bode Plotter closed.'); disp(' '); disp(' '); 197 | delete(handles.AsymBodePlot); 198 | % ------------------End of function CloseButton_Callback ------------- 199 | 200 | 201 | % -------------------------------------------------------------------- 202 | function DoQuit=BodePlotTerms(handles) 203 | %This function takes a system and splits it up into terms that are plotted 204 | %individually when making a Bode plot by hand (it finds 205 | %1) The multiplicative constant. 206 | %2) All real poles 207 | %3) All real zeros 208 | %4) All complex poles 209 | %5) All complex zeros 210 | %6) All poles at the origin 211 | %7) All zeros at the origin 212 | % 213 | %In addition to finding the poles and zeros, it determines their multiplicity. 214 | %If two poles or zeros are very close they are determined to be the same pole 215 | %or zero. 216 | 217 | sys=handles.Sys; 218 | Acc=handles.Acc; %Relative accuracy. 219 | 220 | [z,p,k]=zpkdata(sys,'v'); %Get pole and zero data. 221 | 222 | %Find gain term. 223 | [n,d]=tfdata(sys,'v'); 224 | k=n(max(find(n~=0)))/d(max(find(d~=0))); 225 | term(1).type='Constant'; 226 | term(1).value=k; 227 | term(1).multiplicity=1; 228 | 229 | %Get all poles. 230 | j=length(term); 231 | for i=1:length(p), 232 | term(j+i).value=p(i); 233 | term(j+i).multiplicity=1; 234 | term(j+i).type='Pole'; 235 | end 236 | %Get all zeros. 237 | j=length(term); 238 | for i=1:length(z), 239 | term(j+i).value=z(i); 240 | term(j+i).multiplicity=1; 241 | term(j+i).type='Zero'; 242 | end 243 | 244 | %Check for multiplicity 245 | for i=2:length(term), 246 | for k=(i+1):length(term), 247 | %Handle pole or zero at origin as special case. 248 | if (term(i).value==0), 249 | %Multiple pole or zero at origin. 250 | if (term(k).value==0), 251 | term(i).multiplicity=term(i).multiplicity+term(k).multiplicity; 252 | %Set multiplicity of kth term to 0 to signify that it has been 253 | % subsumed by term(i) (by increasing the ith term's multiplicity). 254 | term(k).multiplicity=0; 255 | end 256 | %We know term is not at origin, so check for (approximate) equality 257 | % Since we know this term is not at origin, we can divide by value. 258 | elseif (abs((term(i).value-term(k).value)/term(i).value) < Acc), 259 | term(i).multiplicity=term(i).multiplicity+term(k).multiplicity; 260 | %Set multiplicity of kth term to 0 to signify that it has been 261 | % subsumed by term(i) (by increasing the ith term's multiplicity). 262 | term(k).multiplicity=0; 263 | end 264 | end 265 | end 266 | 267 | %Check for location of poles and zeros (and remove complex conjugates). 268 | i=2; 269 | while (i<=length(term)), 270 | %If root is at origin, handle it separately 271 | if (term(i).value==0), 272 | term(i).type=['Origin' term(i).type]; 273 | %If imaginary part is sufficiently small... 274 | elseif (abs(imag(term(i).value)/term(i).value)0), %Poles in RHP. 302 | beep; 303 | waitfor(errordlg('System has poles with positive real part, cannot make plot.')); 304 | DoQuit=1; 305 | return; 306 | end 307 | if any(real(z)>0), %Zeros in RHP. 308 | disp(' '); 309 | disp(' '); 310 | disp(' '); 311 | disp('************Warning******************'); 312 | handles.Sys 313 | disp('is a nonminimum phase system (zeros in right half plane).'); 314 | disp('The standard rules for phase do not apply.'); 315 | disp(' '); 316 | disp('Also - The plots produced may be different than the Matlab Bode plot'); 317 | disp(' by a factor of 360 degrees. So though the graphs don''t look'); 318 | disp(' the same, they are equivalent'); 319 | disp(' '); 320 | disp('Location(s) of zero(s):'); 321 | disp(z); 322 | disp('*************************************'); 323 | disp(' '); 324 | beep; 325 | waitfor(warndlg('System has zeros with positive real part. See Command Window for caveats.')); 326 | end 327 | %Check for terms near imaginary axis, or multiple poles or zeros at origin. 328 | for i=2:length(term), 329 | if (term(i).value~=0), 330 | if (abs(real(term(i).value)/term(i).value)1), 353 | disp(' '); 354 | disp(' '); 355 | disp(' '); 356 | disp('************Warning******************'); 357 | handles.Sys 358 | disp('has multiple poles or zeros at the origin.'); 359 | disp('Components of the phase plot may appear to disagree.'); 360 | disp('This is because the phase of a complex number is'); 361 | disp('not unique; the phase of -1 could be +180 degrees'); 362 | disp('or -180 or +/-540... Likewise the phase of 1/s^2'); 363 | disp('could be +180 degrees, or -180 degrees (or +/-540'); 364 | disp('Keep this in mind when looking at the phase plots.'); 365 | disp('*************************************'); 366 | disp(' '); 367 | beep; 368 | waitfor(warndlg('System has multiple poles or zeros at origin. See Command Window.')); 369 | end 370 | end 371 | 372 | handles.Terms=term; 373 | guidata(handles.AsymBodePlot, handles); %save changes to handles. 374 | % ------------------End of function BodePlotTerms -------------------- 375 | 376 | 377 | % -------------------------------------------------------------------- 378 | function BodePlotter(handles) 379 | 380 | %Get the constituent terms and the system itself. 381 | Terms=handles.Terms; 382 | sys=handles.Sys; 383 | 384 | %Call function to get a system with only included poles and zeros. 385 | BodePlotSys(handles); 386 | handles=guidata(handles.AsymBodePlot); %Reload handles after function call. 387 | 388 | %Get systems with included poles and zeros. 389 | sysInc=handles.SysInc; 390 | 391 | %Find min and max freq. 392 | MinF=realmax; 393 | MaxF=-realmax; 394 | for i=2:length(Terms), 395 | if Terms(i).value~=0, 396 | MinF=min(MinF,abs(Terms(i).value)); 397 | MaxF=max(MaxF,abs(Terms(i).value)); 398 | end 399 | end 400 | 401 | %If there is exclusively a pole or zero at origin, MinF and MaxF will 402 | % not have changed. So set them arbitrarily to be near unity. 403 | if MaxF==-realmax, 404 | MinF=0.9; 405 | MaxF=1.1; 406 | end 407 | 408 | %MinFreq is a bit more than an order of magnitude below lowest break. 409 | MinFreq=10^(floor(log10(MinF)-1.01)); 410 | %MaxFreq is a bit more than an order of magnitude above highest break. 411 | MaxFreq=10^(ceil(log10(MaxF)+1.01)); 412 | %Calculate 500 frequency points for plotting. 413 | w=logspace(log10(MinFreq),log10(MaxFreq),1000); 414 | 415 | %%%%%%%%%%%%%%%%%%%% Start Magnitude Plot %%%%%%%%%% 416 | axes(handles.MagPlot); 417 | cla; 418 | 419 | %Plot line at 0 dB for reference. 420 | semilogx([MinFreq MaxFreq],[0 0],... 421 | 'Color',handles.zrefColor,... 422 | 'LineWidth',1.5); 423 | 424 | hold on; 425 | 426 | %For each term, plot the magnitude accordingly. 427 | %The variable mag_a has the combined asymptotic magnitude. 428 | mag_a=zeros(size(w)); 429 | %The variable peakinds holds the indices at which peaks in underdamped 430 | %responses occur. 431 | peakinds=[]; 432 | for i=1:length(Terms), 433 | if Terms(i).display, 434 | switch Terms(i).type, 435 | case 'Constant', 436 | %A constant term is unchanging from beginning to end. 437 | f=[MinFreq MaxFreq]; 438 | m=20*log10(abs([Terms(i).value Terms(i).value])); 439 | semilogx(f,m,... 440 | 'LineWidth',handles.LnWdth,... 441 | 'LineStyle',GetLineStyle(i,handles),... 442 | 'Color',GetLineColor(i,handles)); 443 | mag_a=mag_a+interp1(log(f),m,log(w)); %Build up asymptotic approx. 444 | case 'RealPole', 445 | %A real pole has a single break frequency and then 446 | %decreases at 20 dB per decade (Or more if pole is multiple). 447 | wo=-Terms(i).value; 448 | f=[MinFreq wo MaxFreq]; 449 | m=-20*log10([1 1 MaxFreq/wo])*Terms(i).multiplicity; 450 | semilogx(f,m,... 451 | 'LineWidth',handles.LnWdth,... 452 | 'LineStyle',GetLineStyle(i,handles),... 453 | 'Color',GetLineColor(i,handles)); 454 | mag_a=mag_a+interp1(log(f),m,log(w)); %Build up asymptotic approx. 455 | case 'RealZero', 456 | %Similar to real pole, but increases instead of decreasing. 457 | wo=abs(Terms(i).value); 458 | f=[MinFreq wo MaxFreq]; 459 | m=20*log10([1 1 MaxFreq/wo])*Terms(i).multiplicity; 460 | semilogx(f,m,... 461 | 'LineWidth',handles.LnWdth,... 462 | 'LineStyle',GetLineStyle(i,handles),... 463 | 'Color',GetLineColor(i,handles)); 464 | mag_a=mag_a+interp1(log(f),m,log(w)); %Build up asymptotic approx. 465 | case 'ComplexPole', 466 | %A complex pole has a single break frequency and then 467 | %decreases at 40 dB per decade (Or more if pole is multiple). 468 | %There is also a peak value whose height and location are 469 | %determined by the natural frequency and damping coefficient. 470 | %We will plot a circle ('o') at the location of the peak. 471 | wn=abs(Terms(i).value); 472 | theta=atan(abs(imag(Terms(i).value)/real(Terms(i).value))); 473 | zeta=cos(theta); 474 | if (zeta < 0.5), %Show peaking if zeta<0.5 475 | peak=2*zeta; 476 | f=[MinFreq wn wn wn MaxFreq]; 477 | m=-20*log10([1 1 peak 1 (MaxFreq/wn)^2])*Terms(i).multiplicity; 478 | semilogx(f,m,... 479 | 'LineWidth',handles.LnWdth,... 480 | 'LineStyle',GetLineStyle(i,handles),... 481 | 'Color',GetLineColor(i,handles)); 482 | semilogx(wn,-20*log10(peak),'o','Color',GetLineColor(i,handles)); 483 | % Set up for interpolation (w/o peak) 484 | f=[MinFreq wn MaxFreq]; 485 | m=-20*log10([1 1 (MaxFreq/wn)^2])*Terms(i).multiplicity; 486 | mag_a=mag_a+interp1(log(f),m,log(w)); %Build up asymptotic approx. 487 | %Find location closest to peak, and adjust its amplitude. 488 | index=find(w>=wn,1,'first'); 489 | mag_a(index)=mag_a(index)-20*log10(peak); 490 | peakinds=[peakinds index]; %Save this index. 491 | else 492 | f=[MinFreq wn MaxFreq]; 493 | m=-20*log10([1 1 (MaxFreq/wn)^2])*Terms(i).multiplicity; 494 | semilogx(f,m,... 495 | 'LineWidth',handles.LnWdth,... 496 | 'LineStyle',GetLineStyle(i,handles),... 497 | 'Color',GetLineColor(i,handles)); 498 | % Set up for interpolation (w/o peak) 499 | mag_a=mag_a+interp1(log(f),m,log(w)); %Build up asymptotic approx. 500 | end 501 | case 'ComplexZero', 502 | %Similar to complex pole, but increases instead of decreasing. 503 | wn=abs(Terms(i).value); 504 | theta=atan(abs(imag(Terms(i).value)/real(Terms(i).value))); 505 | zeta=cos(theta); 506 | if (zeta < 0.5), %Show peaking if zeta<0.5 507 | peak=2*zeta; 508 | f=[MinFreq wn wn wn MaxFreq]; 509 | m=20*log10([1 1 peak 1 (MaxFreq/wn)^2])*Terms(i).multiplicity; 510 | semilogx(f,m,... 511 | 'LineWidth',handles.LnWdth,... 512 | 'LineStyle',GetLineStyle(i,handles),... 513 | 'Color',GetLineColor(i,handles)); 514 | semilogx(wn,20*log10(peak),'o','Color',GetLineColor(i,handles)); 515 | % Set up for interpolation (w/o peak) 516 | f=[MinFreq wn MaxFreq]; 517 | m=20*log10([1 1 (MaxFreq/wn)^2])*Terms(i).multiplicity; 518 | mag_a=mag_a+interp1(log(f),m,log(w)); %Build up asymptotic approx. 519 | %Find location closest to peak, and adjust its amplitude. 520 | index=find(w>=wn,1,'first'); 521 | mag_a(index)=mag_a(index)+20*log10(peak); 522 | peakinds=[peakinds index]; %Save this index. 523 | else 524 | f=[MinFreq wn MaxFreq]; 525 | m=20*log10([1 1 (MaxFreq/wn)^2])*Terms(i).multiplicity; 526 | semilogx(f,m,... 527 | 'LineWidth',handles.LnWdth,... 528 | 'LineStyle',GetLineStyle(i,handles),... 529 | 'Color',GetLineColor(i,handles)); 530 | % Set up for interpolation (w/o peak) 531 | mag_a=mag_a+interp1(log(f),m,log(w)); %Build up asymptotic approx. 532 | end 533 | case 'OriginPole', 534 | %A pole at the origin is a monotonically decreasing straigh line. 535 | f=[MinFreq MaxFreq]; 536 | m=-20*log10([MinFreq MaxFreq])*Terms(i).multiplicity; 537 | semilogx(f,m,... 538 | 'LineWidth',handles.LnWdth,... 539 | 'LineStyle',GetLineStyle(i,handles),... 540 | 'Color',GetLineColor(i,handles)); 541 | mag_a=mag_a+interp1(log(f),m,log(w)); %Build up asymptotic approx. 542 | case 'OriginZero', 543 | %Similar to pole at origin, but increases instead of decreasing. 544 | f=[MinFreq MaxFreq]; 545 | m=20*log10([MinFreq MaxFreq])*Terms(i).multiplicity; 546 | semilogx(f,m,... 547 | 'LineWidth',handles.LnWdth,... 548 | 'LineStyle',GetLineStyle(i,handles),... 549 | 'Color',GetLineColor(i,handles)); 550 | mag_a=mag_a+interp1(log(f),m,log(w)); %Build up asymptotic approx. 551 | end 552 | end 553 | end 554 | 555 | %Set the x-axis limits to the minimum and maximum frequency. 556 | set(gca,'XLim',[MinFreq MaxFreq]); 557 | 558 | [mg,ph,w]=bode(sysInc,w); %Calculate the exact bode plot. 559 | semilogx(w,20*log10(mg(:)),... 560 | 'Color',handles.exactColor,... 561 | 'LineWidth',handles.LnWdth/2); %Plot it. 562 | if (get(handles.ShowAsymptoticCheckBox,'Value')~=0), 563 | semilogx(w,mag_a,... 564 | 'Color',handles.exactColor,... 565 | 'LineStyle',':',... 566 | 'LineWidth',handles.LnWdth); %Plot asymptotic approx. 567 | semilogx(w(peakinds),mag_a(peakinds),'o','Color',handles.exactColor); 568 | end 569 | 570 | if handles.FirstPlot, 571 | %If this is the first time, let Matlab do autoscaling, but save 572 | %the y-axis limits so that they will be unchanged as the plot changes. 573 | ylims=get(gca,'YLim')/20; 574 | ylims(1)=min(-20,ceil(ylims(1))*20); 575 | ylims(2)=max(20,floor(ylims(2))*20); 576 | handles.MagLims=ylims; 577 | else 578 | %If this is not the first time, retrieve the old y-axis limits. 579 | ylims=handles.MagLims; 580 | end 581 | set(gca,'YLim',ylims); 582 | set(gca,'YTick',ylims(1):20:ylims(2)); %Make ticks every 20 dB. 583 | 584 | ylabel('Magnitude - dB'); 585 | xlabel('Frequency - \omega, rad-sec^{-1}') 586 | title('Magnitude Plot','color','b','FontWeight','bold'); 587 | if get(handles.GridCheckBox,'Value')==1, 588 | grid on 589 | end 590 | hold off; 591 | %%%%%%%%%%%%%%%%%%%% End Magnitude Plot %%%%%%%%%% 592 | 593 | 594 | %%%%%%%%%%%%%%%%%%%% Start Phase Plot %%%%%%%%%% 595 | %Much of this section mirrors the previous section and is not commented. 596 | %One difference is that phase is calculated explicitly, rather than use 597 | %Matlab's "bode" command. Since phase is not unique (you can add or 598 | %subtract multiples of 360 degrees) There were sometimes discrepancies 599 | %between Matlab's phase calculations and mine 600 | axes(handles.PhasePlot); 601 | cla; 602 | 603 | %Plot line at 0 degrees for reference. 604 | semilogx([MinFreq MaxFreq],[0 0],... 605 | 'Color',handles.zrefColor,... 606 | 'LineWidth',1.5); 607 | hold on; 608 | 609 | %The variable phs_a has the combined asymptotic phase. 610 | phs_a=zeros(size(w)); 611 | for i=1:length(Terms), 612 | if Terms(i).display, 613 | switch Terms(i).type, 614 | case 'Constant', 615 | f=[MinFreq MaxFreq]; 616 | if Terms(i).value>0, 617 | p=[0 0]; 618 | else 619 | p=[180 180]; 620 | end 621 | if get(handles.RadianCheckBox,'Value')==1, 622 | p=p/180; 623 | end 624 | semilogx(f,p,... 625 | 'LineWidth',handles.LnWdth,... 626 | 'LineStyle',GetLineStyle(i,handles),... 627 | 'Color',GetLineColor(i,handles)); 628 | case 'RealPole', 629 | wo=-Terms(i).value; 630 | f=[MinFreq wo/10 wo*10 MaxFreq]; 631 | p=[0 0 -90 -90]*Terms(i).multiplicity; 632 | if get(handles.RadianCheckBox,'Value')==1, 633 | p=p/180; 634 | end 635 | semilogx(f,p,... 636 | 'LineWidth',handles.LnWdth,... 637 | 'LineStyle',GetLineStyle(i,handles),... 638 | 'Color',GetLineColor(i,handles)); 639 | case 'RealZero', 640 | if Terms(i).value>0, %Non-minimum phase 641 | wo=Terms(i).value; 642 | %Uncomment the next section to get agreement with Matlab plots. 643 | %if Terms(1).value>0, %Choose 0 or 360 to agree with MatLab plots 644 | % p=[0 0 0 0]; % (based on sign of constant term). 645 | % else 646 | % p=[360 360 360 360]; 647 | %end 648 | p=[0 0 -90 -90]*Terms(i).multiplicity; 649 | else 650 | %Minimum phase 651 | wo=-Terms(i).value; 652 | p=[0 0 90 90]*Terms(i).multiplicity; 653 | end 654 | f=[MinFreq wo/10 wo*10 MaxFreq]; 655 | if get(handles.RadianCheckBox,'Value')==1, 656 | p=p/180; 657 | end 658 | semilogx(f,p,... 659 | 'LineWidth',handles.LnWdth,... 660 | 'LineStyle',GetLineStyle(i,handles),... 661 | 'Color',GetLineColor(i,handles)); 662 | case 'ComplexPole', 663 | wo=abs(Terms(i).value); 664 | bf=0.1^zeta; 665 | f=[MinFreq wo*bf wo/bf MaxFreq]; 666 | p=[0 0 -180 -180]*Terms(i).multiplicity; 667 | if get(handles.RadianCheckBox,'Value')==1, 668 | p=p/180; 669 | end 670 | semilogx(f,p,... 671 | 'LineWidth',handles.LnWdth,... 672 | 'LineStyle',GetLineStyle(i,handles),... 673 | 'Color',GetLineColor(i,handles)); 674 | case 'ComplexZero', 675 | wo=abs(Terms(i).value); 676 | bf=0.1^zeta; 677 | f=[MinFreq wo*bf wo/bf MaxFreq]; 678 | if real(Terms(i).value)>0, %Non-minimum phase 679 | p=[0 0 -180 -180]*Terms(i).multiplicity; 680 | else 681 | p=[0 0 180 180]*Terms(i).multiplicity; 682 | end 683 | if get(handles.RadianCheckBox,'Value')==1, 684 | p=p/180; 685 | end 686 | semilogx(f,p,... 687 | 'LineWidth',handles.LnWdth,... 688 | 'LineStyle',GetLineStyle(i,handles),... 689 | 'Color',GetLineColor(i,handles)); 690 | case 'OriginPole', 691 | f=[MinFreq MaxFreq]; 692 | p=[-90 -90]*Terms(i).multiplicity; 693 | if get(handles.RadianCheckBox,'Value')==1, 694 | p=p/180; 695 | end 696 | semilogx(f,p,... 697 | 'LineWidth',handles.LnWdth,... 698 | 'LineStyle',GetLineStyle(i,handles),... 699 | 'Color',GetLineColor(i,handles)); 700 | 701 | case 'OriginZero', 702 | f=[MinFreq MaxFreq]; 703 | p=[90 90]*Terms(i).multiplicity; 704 | if get(handles.RadianCheckBox,'Value')==1, 705 | p=p/180; 706 | end 707 | semilogx(f,p,... 708 | 'LineWidth',handles.LnWdth,... 709 | 'LineStyle',GetLineStyle(i,handles),... 710 | 'Color',GetLineColor(i,handles)); 711 | end 712 | phs_a=phs_a+interp1(log(f),p,log(w)); %Build up asymptotic approx. 713 | end 714 | end 715 | 716 | if get(handles.RadianCheckBox,'Value')==1, 717 | ph=ph/180; 718 | end 719 | 720 | semilogx(w,ph(:),... 721 | 'Color',handles.exactColor,... 722 | 'LineWidth',handles.LnWdth/2); %Plot it. 723 | if (get(handles.ShowAsymptoticCheckBox,'Value')~=0), 724 | % There can be discrepancies between Matlabs calculation of phase and 725 | % the quantity "phs_a." This is because phase is not unique (you can 726 | % add or subtract multiples of 360 degrees). The next line shifts the 727 | % value of phs_a so that phs_a(1)=ph(1). Ensuring they are the same 728 | % at the beginning of the plot ensures that they align elsewhere. Note 729 | % that if the plots are already aligned (ph(1)=phs_a(1)), that the next 730 | % line does nothing. 731 | phs_a=phs_a+(ph(1)-phs_a(1)); 732 | semilogx(w,phs_a,... 733 | 'Color',handles.exactColor,... 734 | 'LineStyle',':',... 735 | 'LineWidth',handles.LnWdth); %Plot asymptotic approx. 736 | end 737 | 738 | set(gca,'XLim',[MinFreq MaxFreq]); 739 | if handles.FirstPlot, 740 | ylims=get(gca,'YLim')/45; 741 | ylims(1)=ceil(ylims(1)-1)*45; 742 | ylims(2)=floor(ylims(2)+1)*45; 743 | handles.DPhaseLims=ylims; %Find phase limits in degrees 744 | handles.RPhaseLims=ylims/180; %Find phase limits in radians/pi 745 | else 746 | if get(handles.RadianCheckBox,'Value')==1, 747 | ylims=handles.RPhaseLims; 748 | else 749 | ylims=handles.DPhaseLims; 750 | end 751 | end 752 | 753 | if get(handles.RadianCheckBox,'Value')==1, 754 | set(gca,'YLim',ylims); 755 | set(gca,'YTick',ylims(1):0.25:ylims(2)) 756 | ylabel('Phase - radians/\pi'); 757 | else 758 | set(gca,'YLim',ylims); 759 | set(gca,'YTick',ylims(1):45:ylims(2)) 760 | ylabel('Phase - degrees'); 761 | end 762 | 763 | xlabel('Frequency - \omega, rad-sec^{-1}'); 764 | title('Phase Plot','color','b','FontWeight','bold'); 765 | if get(handles.GridCheckBox,'Value')==1, 766 | grid on 767 | end 768 | hold off; 769 | %%%%%%%%%%%%%%%%%%%% End Phase Plot %%%%%%%%%% 770 | 771 | BodePlotDispTF(handles); %Display the transfer function 772 | BodePlotLegend(handles); %Display the legend. 773 | handles=guidata(handles.AsymBodePlot); %Reload handles after function call. 774 | 775 | %Set first plot to zero (so Matlab won't autoscale on subsequent calls). 776 | handles.FirstPlot=0; 777 | 778 | guidata(handles.AsymBodePlot, handles); %save changes to handles. 779 | % ----------------- End of function BodePlotter ---------------------- 780 | 781 | 782 | % -------------------------------------------------------------------- 783 | function BodePlotSys(handles) 784 | %This function makes up a transfer function of all the terms that are not in 785 | %the "Excluded Elements" box in the GUI. 786 | 787 | Terms=handles.Terms; %Get all terms from original transfer function. 788 | p=[]; %Start with no poles, or zeros, and a constant of 1 789 | z=[]; 790 | k=1; 791 | 792 | for i=1:length(Terms), %For each term. 793 | %If the term is not in "Excluded Elements". then we want to display it. 794 | if Terms(i).display, 795 | switch Terms(i).type, 796 | case 'Constant', 797 | k=Terms(i).value; %This is the constant. 798 | case 'RealPole', 799 | for j=1:Terms(i).multiplicity, 800 | p=[p Terms(i).value]; %Add poles. 801 | end 802 | case 'RealZero', 803 | for j=1:Terms(i).multiplicity, 804 | z=[z Terms(i).value]; %Add zeros. 805 | end 806 | case 'ComplexPole', 807 | for j=1:Terms(i).multiplicity, 808 | p=[p Terms(i).value]; %Add poles. 809 | p=[p conj(Terms(i).value)]; 810 | end 811 | case 'ComplexZero', 812 | for j=1:Terms(i).multiplicity, 813 | z=[z Terms(i).value]; %Add zeros. 814 | z=[z conj(Terms(i).value)]; 815 | end 816 | case 'OriginPole', 817 | for j=1:Terms(i).multiplicity, 818 | p=[p 0]; %Add poles. 819 | end 820 | case 'OriginZero', 821 | for j=1:Terms(i).multiplicity, 822 | z=[z 0]; %Add zeros. 823 | end 824 | end 825 | end 826 | end 827 | 828 | %Determine multiplicative constant in standard Bode Plot form. 829 | for i=1:length(p), 830 | if p(i)~=0 831 | k=-k*p(i); 832 | end 833 | end 834 | for i=1:length(z), 835 | if z(i)~=0 836 | k=-k/z(i); 837 | end 838 | end 839 | %If poles and/or zeros were complex conjugate pairs, there may be 840 | %some residual imaginary part due to finite precision. Remove it. 841 | k=real(k); 842 | 843 | handles.SysInc=zpk(z,p,k); 844 | guidata(handles.AsymBodePlot, handles); %save changes to handles. 845 | % ------------------End of function BodePlotSys ---------------------- 846 | 847 | 848 | % -------------------------------------------------------------------- 849 | function BodePlotDispTF(handles) 850 | % This function displays a tranfer function that is a helper function for the 851 | % BodePlotGui routine. It takes the transfer function of the numerator and 852 | % splits it into three lines so that it can be displayed nicely. For example: 853 | % " s + 1" 854 | % "H(s) = ---------------" 855 | % " s^2 + 2 s + 1" 856 | % 857 | % The numerator string is in the variable nStr, 858 | % the second line is in divStr, 859 | % and the denominator string is in dStr. 860 | 861 | % Get numerator and denominator. 862 | [n,d]=tfdata(handles.SysInc,'v'); 863 | 864 | % Get string representations of numerator and denominator 865 | nStr=poly2str(n,'s'); 866 | dStr=poly2str(d,'s'); 867 | 868 | % Find length of strings. 869 | LnStr=length(nStr); 870 | LdStr=length(dStr); 871 | 872 | if LnStr>LdStr, 873 | %the numerator is longer than denominator string, so pad denominator. 874 | n=LnStr; %n is the length of the longer string. 875 | nStr=[' ' nStr]; %add spaces for characters at beginning of divStr. 876 | dStr=[' ' blanks(floor((LnStr-LdStr)/2)) dStr]; %pad denominator. 877 | else 878 | %the demoninator is longer than numerator, pad numerator. 879 | n=LdStr; 880 | nStr=[' ' blanks(floor((LdStr-LnStr)/2)) nStr]; 881 | dStr=[' ' dStr]; 882 | end 883 | 884 | divStr=[]; 885 | for i=1:n, 886 | divStr=[divStr '-']; 887 | end 888 | divStr=['H(s) = ' divStr]; 889 | 890 | set(handles.TransferFunctionText,'String',strvcat(nStr,divStr,dStr)); 891 | 892 | %Change type font and size. 893 | set(handles.TransferFunctionText,'FontName','Courier New') 894 | %set(handles.TransferFunctionText,'FontSize',10) 895 | 896 | guidata(handles.AsymBodePlot, handles); %save changes to handles. 897 | % ------------------End of function BodePlotDispTF ------------------- 898 | 899 | 900 | % -------------------------------------------------------------------- 901 | function BodePlotLegend(handles) 902 | %This function creates the legends for the Bode plot being displayed. 903 | %It also makes four changes to the "handles" structure. 904 | % 1) It updates the array "IncElem" that holds the indices that determine 905 | % which elements are included in the Bode plot. 906 | % 2) It updates the sring array "IncStr" that hold the description of 907 | % each included elements. 908 | % 3) Updates ExcElem that holds indices of excluded elements. 909 | % 4) Updates ExcStr that hold descriptions of excluded elements. 910 | 911 | %Load the terms and the plotting strings into local variables for convenience. 912 | Terms=handles.Terms; 913 | 914 | axes(handles.LegendPlot); %Set axes to the legend widow, 915 | cla; % and clear it. 916 | Xleg=[0 0.1]; %Xleg holds start and end of line segment for legend. 917 | XlegText=0.125; %XlegText is location of text. 918 | FntSz=8; %Font Size of text. 919 | 920 | y=1-1/(length(Terms)+6); %Vertical location of first text item 921 | plot(Xleg,[y y],... 922 | 'Color',handles.exactColor,... 923 | 'LineWidth',handles.LnWdth/2); %Plot line for legend. 924 | text(XlegText,y,'Exact Bode Plot','FontSize',FntSz); %Place text 925 | hold on; 926 | 927 | if (get(handles.ShowAsymptoticCheckBox,'Value')~=0), 928 | y=1-2/(length(Terms)+6); %Vertical location of second item 929 | plot(Xleg,[y y],... 930 | 'Color',handles.exactColor,... 931 | 'LineStyle',':',... 932 | 'LineWidth',handles.LnWdth); %Plot line for legend. 933 | text(XlegText,y,'Asymptotic Plot','FontSize',FntSz); %Place text 934 | end 935 | 936 | y=1-3/(length(Terms)+6); %Vertical location of third item. 937 | plot(Xleg,[y y],... %Line. 938 | 'Color',handles.zrefColor,... 939 | 'LineWidth',2); 940 | text(XlegText,y,'Zero Value (for reference only)','FontSize',FntSz); %Text. 941 | 942 | IncElem=[]; %The indices of elements to be included in plot. 943 | ExcElem=[]; %The indices of elements to be excluded from plot. 944 | IncStr=''; %An array of strings describing included elements. 945 | ExcStr=''; %An array of strings describing excluded elements. 946 | 947 | %These variables are used as local counters later. Here they are initialized. 948 | i1=1; 949 | i2=1; 950 | 951 | for i=1:length(Terms), %For each term, 952 | 953 | %Tv is a local variable representing the pole location. It is used solely 954 | % for convenience. 955 | Tv=Terms(i).value; 956 | %Tm is a local variable representing the pole multiplicity. It is used solely 957 | % for convenience. 958 | Tm=Terms(i).multiplicity; 959 | %S2 is a blank string to be added to later in the loop. 960 | S2=''; 961 | 962 | %The next section of code ("switch" statement) plus a few lines, creates 963 | %a string that describes the pole or zero, its location, muliplicity... 964 | %The variable "DescStr" hold a Descriptive String for the pole or zero. The 965 | %string "S2" is a Second String that holds additional information (if needed) 966 | switch Terms(i).type, 967 | case 'Constant', 968 | %If the term is a consant, print its value in a string. 969 | DescStr=sprintf('Constant = %0.2g (%0.2g dB)',Tv,20*log10(abs(Tv))); 970 | if Tv>=0, 971 | DescStr=[DescStr ' phi=0']; 972 | else 973 | DescStr=[DescStr ' phi=180 (pi/2)']; 974 | end; 975 | case 'RealPole', 976 | %If the term is a real pole, print its value in string. 977 | DescStr=sprintf('Real Pole at %0.2g',Tv); 978 | case 'RealZero', 979 | %If the term is a real zero, print its value in string. 980 | DescStr=sprintf('Real Zero at %0.2g',Tv); 981 | if real(Tv)>0, 982 | DescStr=[DescStr ' RHP (Non-min phase)']; 983 | end; 984 | case 'ComplexPole', 985 | %If the term is a complex pole, print its value in string. 986 | %However, do this in terms of natural frequency and damping, as 987 | %well as the actual location of the pole (in S2). 988 | wn=abs(Tv); 989 | theta=atan(abs(imag(Tv)/real(Tv))); 990 | zeta=cos(theta); 991 | DescStr=sprintf('Complex Pole at wn=%0.2g, zeta=%0.2g',wn,zeta); 992 | if (zeta < 0.5), %peaking only if zeta<0.5 993 | S2=sprintf('(%0.2g +/- %0.2gj) Circle shows peak height.',real(Tv),imag(Tv)); 994 | else 995 | S2=sprintf('(%0.2g +/- %0.2gj) (no peaking shown, zeta>0.5)',real(Tv),imag(Tv)); 996 | end 997 | case 'ComplexZero', 998 | %If the term is a complex zero, print its value in string. 999 | %However, do this in terms of natural frequency and damping, as 1000 | %well as the actual location of the zero (in S2). 1001 | %Also note if it is a non-minimum phase zero. 1002 | wn=abs(Tv); 1003 | theta=atan(abs(imag(Tv)/real(Tv))); 1004 | zeta=cos(theta); 1005 | DescStr=sprintf('Complex Zero at wn=%0.2g, zeta=%0.2g',wn,zeta); 1006 | if real(Tv)>0, 1007 | DescStr=[DescStr ' (RHP, Non-min phase)']; 1008 | end; 1009 | if (zeta < 0.5), %peaking only if zeta<0.5 1010 | S2=sprintf('(%0.2g +/- %0.2gj) Circle shows peak height.',real(Tv),imag(Tv)); 1011 | else 1012 | S2=sprintf('(%0.2g +/- %0.2gj) (no peaking shown, zeta>0.5)',real(Tv),imag(Tv)); 1013 | end 1014 | case 'OriginPole', 1015 | %If pole is at origin, not this. 1016 | DescStr=sprintf('Pole at origin'); 1017 | case 'OriginZero', 1018 | %If zero is at origin, not this. 1019 | DescStr=sprintf('Zero at origin'); 1020 | end 1021 | %If multiplicity is greater than one, not this as well. 1022 | if Tm>1, 1023 | DescStr=[DescStr sprintf(', mult=%d',Tm)]; 1024 | end 1025 | 1026 | %At this point we have a string (in "DescStr" and "S2"). 1027 | if Terms(i).display, %If the term is to be included in plot.... 1028 | IncStr=strvcat(IncStr,DescStr); %Add the Desriptive String to IncStr 1029 | IncElem(i1)=i; %Add the appropriate index to the Included Elements list. 1030 | i1=i1+1; %Increment the index counter 1031 | y=1-(i1+2)/(length(Terms)+6); %Calculate the vertical position. 1032 | plot(Xleg,[y y],... %Plot the line. 1033 | 'LineWidth',handles.LnWdth,... 1034 | 'LineStyle',GetLineStyle(i,handles),... 1035 | 'Color',GetLineColor(i,handles)); 1036 | text(XlegText,y,strvcat(DescStr,S2),'FontSize',FntSz); %Add the text. 1037 | else %The term is *not* to be included in plot, so... 1038 | ExcStr=strvcat(ExcStr,DescStr); %Add its Desriptive String to ExcStr 1039 | ExcElem(i2)=i; %Add the appropriate index to the Excluded Elements list. 1040 | i2=i2+1; %Increment the index counter. 1041 | end 1042 | end 1043 | hold off; 1044 | %Get rid of ticks around plot. 1045 | axis([0 1 0 1]); 1046 | set(gca,'Xtick',[]); 1047 | set(gca,'Ytick',[]); 1048 | 1049 | %At this point the legend is completed. Next we will make up the strings 1050 | %for the boxes that separately list included and excluded elements. 1051 | 1052 | IncStr=strvcat(IncStr,' '); %Add a blank line to IncStr. 1053 | IncStr=strvcat(IncStr,'-------'); %Add a series of dashes. 1054 | %If there are any included elements, we can click on box to exclude it. 1055 | if i1~=1 1056 | IncStr=strvcat(IncStr,'Select element to exclude from plot'); 1057 | end 1058 | 1059 | ExcStr=strvcat(ExcStr,' '); %Add a blank line to ExcStr 1060 | ExcStr=strvcat(ExcStr,'-------'); %Add a series of dashes. 1061 | %If there are any excluded elements, we can click on box to include it. 1062 | if i2~=1 1063 | ExcStr=strvcat(ExcStr,'Select element to include in plot'); 1064 | end 1065 | 1066 | %Set the strings for included and excluded elements. 1067 | set(handles.IncludedElements,'String',IncStr); 1068 | set(handles.ExcludedElements,'String',ExcStr); 1069 | 1070 | %Change the arrays holding included and excluded elements in the handles array. 1071 | handles.IncElem=IncElem; 1072 | handles.ExcElem=ExcElem; 1073 | 1074 | guidata(handles.AsymBodePlot, handles); %save changes to handles. 1075 | % ------------------End of function BodePlotLegend -------- 1076 | 1077 | 1078 | % -------------------------------------------------------------------- 1079 | % --- Executes during object creation, after setting all properties. 1080 | function LineWidth_CreateFcn(hObject, ~, ~) 1081 | % hObject handle to LineWidth (see GCBO) 1082 | % eventdata reserved - to be defined in a future version of MATLAB 1083 | % handles empty - handles not created until after all CreateFcns called 1084 | if ispc 1085 | set(hObject,'BackgroundColor','white'); 1086 | else 1087 | set(hObject,'BackgroundColor',get(0,'defaultUicontrolBackgroundColor')); 1088 | end 1089 | % ------------------End of function BodePlotLegend -------- 1090 | 1091 | 1092 | % -------------------------------------------------------------------- 1093 | % Set the width of the lines used in plots. 1094 | function LineWidth_Callback(~, ~, handles) 1095 | % hObject handle to LineWidth (see GCBO) 1096 | % eventdata reserved - to be defined in a future version of MATLAB 1097 | % handles structure with handles and user data (see GUIDATA) 1098 | handles.LnWdth=str2num(get(handles.LineWidth,'String')); 1099 | guidata(handles.AsymBodePlot, handles); %save changes to handles. 1100 | BodePlotter(handles); %Plot the Transfer function. 1101 | handles=guidata(handles.AsymBodePlot); %Reload handles after function call. 1102 | % ------------------End of function BodePlotLegend -------- 1103 | 1104 | 1105 | % -------------------------------------------------------------------- 1106 | % Determines the line color of the ith plot. 1107 | function linecolor=GetLineColor(i,handles) 1108 | numColors=size(handles.colors,1); 1109 | %Cycle through colors by using mod operator. 1110 | linecolor=handles.colors(mod(i-1,numColors)+1,:); 1111 | % ------------------End of function GetLineColor -------- 1112 | 1113 | 1114 | % -------------------------------------------------------------------- 1115 | % Determines the line style of the ith plot. 1116 | function linestyle=GetLineStyle(i,handles) 1117 | numColors=size(handles.colors,1); 1118 | numLnStl=size(handles.linestyle,2); 1119 | %Cycle through line styles, incrementing after all colors are used. 1120 | linestyle=handles.linestyle{mod(ceil(i/numColors)-1,numLnStl)+1}; 1121 | % ------------------End of function GetLineStyle -------- 1122 | 1123 | 1124 | % -------------------------------------------------------------------- 1125 | % --- Executes on button press in GrayCheckBox. 1126 | % This function sets the sequence of colors used in plotting to gray scales. 1127 | function GrayCheckBox_Callback(~, ~, handles) 1128 | if get(handles.GrayCheckBox,'Value')==1, %If button is not set, 1129 | handles.colors=handles.Gray; %and set colors to gray scale 1130 | handles.zrefColor=handles.GrayZero; 1131 | else 1132 | handles.colors=handles.Color; %and set colors to RGB 1133 | handles.zrefColor=handles.ColorZero; 1134 | end 1135 | guidata(handles.AsymBodePlot, handles); %save changes to handles. 1136 | BodePlotter(handles); %Plot the Transfer function. 1137 | handles=guidata(handles.AsymBodePlot); %Reload handles after function call. 1138 | % ------------------End of function BodePlotLegend -------- 1139 | 1140 | 1141 | % --- Executes on button press in GridCheckBox. 1142 | function GridCheckBox_Callback(~, ~, handles) 1143 | BodePlotter(handles); %Plot the Transfer function. 1144 | 1145 | % --- Executes on button press in RadianCheckBox. 1146 | function RadianCheckBox_Callback(~, ~, handles) 1147 | BodePlotter(handles); %Plot the Transfer function. 1148 | 1149 | % --- Executes on button press in WebButton. 1150 | function WebButton_Callback(~, ~, ~) 1151 | web('http://lpsa.swarthmore.edu/Bode/Bode.html','-browser') 1152 | 1153 | % --- Executes on button press in ShowAsymptoticCheckBox. 1154 | function ShowAsymptoticCheckBox_Callback(~, ~, handles) 1155 | BodePlotter(handles); %Plot the Transfer function. 1156 | 1157 | 1158 | % --- Executes on button press in limitButton. 1159 | function limitButton_Callback(~, ~, ~) 1160 | s{1}='Restrictions on systems:'; 1161 | s{2}=' 1) SISO (Single Input Single Output);'; 1162 | s{3}=' 2) Proper systems (order of num <= order of den);'; 1163 | s{4}=' 4) System must be a transfer function (i.e., not state space...)'; 1164 | s{5}=' 5) Time delays are ignored.'; 1165 | helpdlg(s,'Valid Systems'); 1166 | 1167 | 1168 | % --- Executes on selection change in popupSystems. 1169 | function popupSystems_Callback(hObject, ~, handles) 1170 | i=get(hObject,'Value'); 1171 | if i ~= 1, %If this is not the "User Systems" choice 1172 | if i==2, %This is the refresh choice 1173 | loadSystems(handles); 1174 | handles=guidata(handles.AsymBodePlot); %Reload handles after function call. 1175 | else %THis is a valid choice, pick transfer function. 1176 | initBodePlotGui(handles); 1177 | handles=guidata(handles.AsymBodePlot); %Reload handles after function call. 1178 | handles.Sys=handles.WorkSpaceTFs{i}; 1179 | doBodeGUI(handles); 1180 | handles=guidata(handles.AsymBodePlot); %Reload handles after function call. 1181 | end 1182 | end 1183 | guidata(hObject, handles); 1184 | 1185 | 1186 | % --- Executes during object creation, after setting all properties. 1187 | function popupSystems_CreateFcn(hObject, ~, ~) 1188 | % hObject handle to popupSystems (see GCBO) 1189 | % eventdata reserved - to be defined in a future version of MATLAB 1190 | % handles empty - handles not created until after all CreateFcns called 1191 | 1192 | % Hint: popupmenu controls usually have a white background on Windows. 1193 | % See ISPC and COMPUTER. 1194 | if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) 1195 | set(hObject,'BackgroundColor','white'); 1196 | end 1197 | 1198 | 1199 | function loadSystems(handles) 1200 | [v_name, v_tf]=getBaseTFs; 1201 | set(handles.popupSystems,'String',v_name); 1202 | handles.WorkSpaceTFs=v_tf; 1203 | guidata(handles.AsymBodePlot, handles); %save changes to handles. 1204 | 1205 | 1206 | function [varName, varTF]=getBaseTFs 1207 | % Find all valid transfer functions in workspace 1208 | s=evalin('base','whos(''*'')'); 1209 | tfs=strvcat(s.class); %x=class of all variable 1210 | tfs=strcmp(cellstr(tfs),'tf'); %Convert to cell array and find tf's 1211 | s=s(tfs); %Get just tf's 1212 | vname=strvcat(s.name); 1213 | 1214 | varName{1}='User Systems'; 1215 | varTF{1}=[]; 1216 | varName{2}='Refresh Systems'; 1217 | varTF{2}=[]; 1218 | j=2; 1219 | for i=1:length(s) 1220 | myTF=evalin('base',vname(i,:)); 1221 | if (size(myTF.num)==[1 1]), %Check for siso 1222 | j=j+1; 1223 | varName{j}=vname(i,:); 1224 | varTF{j}=myTF; 1225 | end 1226 | end 1227 | 1228 | 1229 | 1230 | % -------------------------------------------------------------------- 1231 | function menuHelp_Callback(~, ~, ~) 1232 | % hObject handle to menuHelp (see GCBO) 1233 | % eventdata reserved - to be defined in a future version of MATLAB 1234 | % handles structure with handles and user data (see GUIDATA) 1235 | 1236 | 1237 | % -------------------------------------------------------------------- 1238 | function menuLimits_Callback(~, ~, ~) 1239 | s{1}='Restrictions on systems:'; 1240 | s{2}=' 1) SISO (Single Input Single Output);'; 1241 | s{3}=' 2) Proper systems (order of num <= order of den);'; 1242 | s{4}=' 4) System must be a transfer function (i.e., not state space...)'; 1243 | s{5}=' 5) Time delays are ignored.'; 1244 | msgbox(s,'Valid Systems'); 1245 | 1246 | % -------------------------------------------------------------------- 1247 | function menuWeb_Callback(~, ~, ~) %#ok<*DEFNU> 1248 | web('http://lpsa.swarthmore.edu/Bode/Bode.html','-browser') 1249 | 1250 | 1251 | % -------------------------------------------------------------------- 1252 | function menuAbout_Callback(~, ~, ~) 1253 | s{1}='Copyright 2014-2015, Erik Cheever, Swarthmore College'; 1254 | s{2}='Last edited October, 2014'; 1255 | s{3}='Written with MATLAB 2014b, may be incompatible with older versions.'; 1256 | msgbox(s,'About'); 1257 | --------------------------------------------------------------------------------