home *** CD-ROM | disk | FTP | other *** search
- function [gm,pm,Wcg,Wcp] = margin(a,b,c,d)
- %MARGIN Gain margin, phase margin, and crossover frequencies.
- %
- % [Gm,Pm,Wcg,Wcp] = MARGIN(A,B,C,D) returns gain margin Gm,
- % phase margin Pm, and associated frequencies Wcg and Wcp, given
- % the continuous state-space system (A,B,C,D).
- %
- % [Gm,Pm,Wcg,Wcp] = MARGIN(NUM,DEN) returns the gain and phase
- % margins for a system in s-domain transfer function form (NUM,DEN).
- %
- % [Gm,Pm,Wcg,Wcp] = MARGIN(MAG,PHASE,W) returns the gain and phase
- % margins given the Bode magnitude, phase, and frequency vectors
- % MAG, PHASE, and W from a system. In this case interpolation is
- % performed between frequency points to find the values. This works
- % for both continuous and discrete systems.
- %
- % When invoked without left hand arguments, MARGIN(A,B,C,D) plots
- % the Bode plot with the gain and phase margins marked with a
- % vertical line. The gain margin, Gm, is defined as 1/G where G
- % is the gain at the -180 phase frequency.
- % 20*log10(Gm) gives the gain margin in dB.
- %
- % See also, IMARGIN.
-
- % Note: if there is more than one crossover point, margin will
- % return the worst case gain and phase margins.
- %
-
- % Andrew Grace 12-5-91
- % Revised ACWG 6-21-92
- % Copyright (c) 1986-93 by the MathWorks, Inc.
-
-
- if nargin==0, eval('exresp(''margin'')'), return, end
- error(nargchk(2,4,nargin));
-
- % Convert to state-space if in state-space form
- if nargin == 4
- [mb,nb]=size(b); [mc,nc]=size(c);
- if nb > 1 | mc > 1
- error(...
- ['State-space matrices must be SISO, e.g, use: margin(a,b(:,1),c(1,:),d(1,1))']);
- end
- [numin,denin]=ss2tf(a,b,c,d);
- elseif nargin == 3
- if nargout > 0
- [gm,pm,Wcg,Wcp] = imargin(a,b,c);
- else
- imargin(a,b,c);
- end
- return;
- else
- numin = a; denin = b;
- end
-
- % Remove leading zeros
- while numin(1) == 0
- numin(1) = [];
- end
-
- % ----------------- Calculate: num(j)(w) and den(j)(w) --------------
- % Calculate num(j)
- % Less accurate: num = numin.*j.^[(length(numin)-1):-1:0];
- nl = length(numin);
- num = fliplr(numin);
- num(2:2:nl) = num(2:2:nl)*j;
- num([3:4:nl,4:4:nl]) = -num([3:4:nl,4:4:nl]);
- num = fliplr(num);
-
- % Calculate den(j)
- % Less accurate: den = denin.*j.^[(length(denin)-1):-1:0];
- dl = length(denin);
- den = fliplr(denin);
- den(2:2:dl) = den(2:2:dl)*j;
- den([3:4:dl,4:4:dl]) = -den([3:4:dl,4:4:dl]);
- den = fliplr(den);
-
-
-
- % ------------------------- Gain Margin ---------------------------
- % 1. Gain margin: find 180 degree crossovers
- %
- % Frequency response is purely real and -ve at crossover:
- %
- % num(jw)/den(jw) = -k
- %
- % num(jw)conj(den(jw))
- % ---------------------- = -k
- % den(jw)conj(den(jw))
- %
- % imag(num(jw)conj(den(jw))) = 0 i.e take roots
-
- % Multiply top and bottom of transfer function by
- % conjugate of denominator to get real part on bottom.
-
- rp = conv(num, conj(den));
- fphase = roots(imag(rp));
- [ind] = find(fphase >= 0 & abs(imag(fphase)) < 1e-10);
-
- % Work out worst case gain margin
- Gm = Inf;
- Wcg = NaN;
- if length(ind)
- if nargin == 2
- h = polyval(num, fphase(ind))./polyval(den,fphase(ind));
- else
- % More accurate to use state-space if available
- h = freqresp(a,b,c,d,1,j*fphase(ind));
- end
- % Find biggest value with real(h) less than zero.
- % Note: there should be no imaginary part to h unless
- % it is extremely badly conditioned.
- indr = find(real(h) < 0 & abs(imag(h)) < 100 );
- if any(abs(imag(h)) > 100)
- disp('Warning: gain margin may be inaccurate.')
- disp('Try using magnitude and phase data from bode: margin(mag,phase,w)')
- end
- if length(indr)
- [invGm, indm] = max(abs(h(indr)));
- hindm = h(indr(indm));
- ahindm = abs(angle(hindm));
- if ahindm < 0.9*pi | ahindm > 1.1*pi
- disp('Warning: gain margin is inaccurate.')
- disp('Try using magnitude and phase data from bode: margin(mag,phase,w)')
- end
- % Uncomment the next line if want gain margin in dB.
- % Gm = 20 * log10(Gm);
- Gm = 1/invGm;
- Wcg = fphase(ind(indr(indm)));
- end
- end
-
-
- % --------------------- Phase Margin ---------------------------
- % 2. Phase margin: find 0db crossovers
- %
- % i.e |num(jw)| / |den(jw)| = 1
- %
- % |den(jw)| - |num(jw)| = 0
- %
- % den(jw)conj(den(jw)) - num(jw)conj(num(jw)) = 0 i.e. take roots
-
- num2 = conv(num,conj(num));
- den2 = conv(den,conj(den));
-
- % Pad numerator
- num2 = [zeros(1,length(den2)-length(num2)),num2];
-
- cl = den2 - num2;
-
- fmags = roots(cl);
- [ind] = find(abs(imag(fmags)) < 1e-8 * real(fmags) & abs(imag(fmags)) < 1000);
- % Work out worst case phase margin
- if length(ind)
- if nargin == 2
- h = polyval(num, fmags(ind))./polyval(den,fmags(ind));
- else
- % More accurate to use state-space if available
- h = freqresp(a,b,c,d,1,j*fmags(ind));
- end
- ang = angle(h);
- fi = find(ang < 0);
- ang(fi) = ang(fi) + 2*pi;
- [Pm, indm] = min(abs(ang-pi));
- hindm = h(indm);
- % Comment out the next line if you don't want gain margin in degrees.
- if abs(hindm) < 0.9 | abs(hindm) > 1.1
- disp('Warning: phase margin is inaccurate.')
- disp('Try using magnitude and phase data from bode: margin(mag,phase,w)')
- end
- Pm = 180/pi * angle(hindm) - 180;
- % -180 < Pm < 180
- if Pm < -180, Pm = Pm + 360; end
- Wcp = real(fmags(ind(indm)));
- else
- Pm = Inf;
- Wcp = NaN;
- end
-
-
- % ------------------------------ Plots ----------------------------------
- % If no left hand arguments then plot graph and show location of margins.
- if nargout==0,
- if nargin == 2
- [mag,phase,w] = bode(numin,denin);
- else
- [mag,phase,w] = bode(a,b,c,d);
- end
- status = ishold;
-
- % Plot bode magnitudes
- subplot(211)
- if status
- hold on
- end
- semilogx(w,20*log10(mag))
- hold on
- xlabel('Frequency (rad/sec)'), ylabel('Gain dB')
-
- ylim = get(gca,'ylim');
- % Plot gain and phase margin lines
- if Wcg ~= 0 & finite(Wcg)
- semilogx([Wcg;Wcg],[-20*log10(Gm);zeros(1,length(Gm))],'w-')
- semilogx([Wcg;Wcg],ylim,'w:')
- end
- if finite(Wcp)
- semilogx([Wcp;Wcp],[0;ylim(1)],'w:')
- end
-
- limits = axis;
- plot(limits(1:2),[0,0],'w:') % 0 dB line
-
- title(['Gm=',num2str(20*log10(Gm)), ' dB,',...
- ' (w= ',num2str(Wcg), ...
- ') Pm=', num2str(Pm), ' deg.', ...
- ' (w=', num2str(Wcp),')' ]);
-
- if ~status, hold off, end;
-
-
- p = rem(phase-360, 360); % Phases modulo 360.
- subplot(212)
- semilogx(w,p)
- limits = axis;
- xlabel('Frequency (rad/sec)'), ylabel('Phase deg')
- hold on
-
- phase180 = -180*ones(1,length(Pm));
- if finite(Wcp)
- semilogx([Wcp;Wcp],[Pm-180;-180],'w-') % Plot phase margins
- semilogx([Wcp;Wcp],[0 -360],'w:')
- end
- if Wcg ~= 0 & finite(Wcg)
- semilogx([Wcg;Wcg],[0;-180],'w:')
- end
-
- limits = axis;
- plot(limits(1:2),[-180,-180],'w:') % 180 degree line
-
- set(gca,'ylim',[-360, 0]);
- set(gca,'ytick',[0 -90 -180 -270 -360])
-
- if ~status, hold off, end; % Return hold to previous status
- subplot(111)
- else
- gm = Gm;
- pm = Pm;
- end
-