├── 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 |
--------------------------------------------------------------------------------