Code covered by the BSD License  

Highlights from
Analog Filter Design Toolbox

image thumbnail
from Analog Filter Design Toolbox by James Squire
GUI to design and simulate active (opamp) LP and HP Bessel, Butter, Cheby, and Elliptic filters.

BuildCircuit_Zpk2Circuit(strFilterObject)
function strCircuit = BuildCircuit_Zpk2Circuit(strFilterObject)
% Zpk2Circuit is a subfile of the AnalogFilter GUI collection
%
% James C. Squire, 2002
% Assistant Professor, Virginia Military Institute
% ver 1.0

% Zpk2Circuit converts the zero and pole vectors and k scalar into 
% a set of (zero or more) biquads followed by (zero or one) final stage.
% The final stage implements a first or zero order stage.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                              Create Structure                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
z = cplxpair(strFilterObject.vZeros);
p = cplxpair(strFilterObject.vPoles);
k = strFilterObject.fK;
nStages = ceil(length(p)/2);
nBiquads = floor(length(p)/2);
bFirstOrderStage = nStages - nBiquads;
bGainStage = 0;
sPolarity = 'd';  % don't care
nRTol = 0; % exact
nCTol = 0;
if isequal(strFilterObject.sType,'Elliptic') || isequal(strFilterObject.sType,'Chebychev II')
    sPurpose = 'Notch';
elseif isequal(strFilterObject.sPurpose,'Lowpass')
    sPurpose = 'LP';
elseif isequal(strFilterObject.sPurpose,'Highpass')
    sPurpose = 'HP';
else
    error(['Problem with filter object: not a normal definition in ' mfilename]);
end
tStage.z = [];
tStage.p = [];
tStage.k = 1;
tStage.Q = [];
tStage.wp = [];
tStage.wz = [];
tStage.z1 = [];
tStage.p1 = [];
tStage.k1 = 1;
tStage.Q1 = [];
tStage.wp1 = [];
tStage.wz1 = [];
tStage.schName = '';
tStage.recName = '';
tStage.schTitle = '';
tStage.recTitle = '';
tStage.vfCSelect = [];    % vector of capacitors that the user chooses (NaN means does not exist)
tStage.vnCSelectExp = []; % vector of cap value exponents.  May be -3, -6, -9, or -12
tStage.csCSelectMan = {}; % cell array of the mantissa string.  E.g. 0.004 has a mantissa of -3 and Exp of 4
tStage.vfRSelect = [];    % vector of resistors that the user chooses
tStage.vnRSelectExp = []; % vector of resistor value exponents.  Either 0,3, or 6 for ohms, k, or M
tStage.csRSelectMan = {}; % cell array of the mantissa string.  E.g. 6040 has a mantissa of 6.04 and Exp of 3
tStage.vfCCalc = [];
tStage.csCCalc = {};
tStage.vfRCalc = [];   % vector of resistors that the program calculates
tStage.csRCalc = {};   % cell array of strings describing the computed values in engineering form
for i=1:5 % can't have more than 5 stages for a 8th order filter plus a gain
    vStage(i) = tStage;
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                     Pair poles with zeros into biquads                    %
%                  by pairing similar-sized absolute values                 %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% sort complex pairs of poles into stages by order of increasing wp
for i=1:nBiquads
    tempP(i).p = p([-1 0]+(i*2));
    tp = tempP(i).p(1);
    tempP(i).wp = abs(tp);
    tempP(i).Q = abs(tp)/(-2*real(tp));
    tempToSortWp(i) = tempP(i).wp;
end
if nBiquads
    [dummy,index]=sort(tempToSortWp);
end
for i=1:nBiquads
    vStage(i).p = tempP(index(i)).p;
    vStage(i).Q = tempP(index(i)).Q;
    vStage(i).wp =tempP(index(i)).wp;
end
% sort complex pairs of zeros into stages by order of increasing wz
if length(z)==0  % if a LP filter
    for i=1:nBiquads
        vStage(i).z = [];
        vStage(i).wz = [];
    end
else
    for i=1:nBiquads
        tempZ(i).z = z([-1 0]+(i*2));
        tz = tempZ(i).z(1);
        tempZ(i).wz = abs(tz);
        tempToSortWz(i) = tempZ(i).wz;
    end
    if nBiquads
        [dummy,index]=sort(tempToSortWz);
    end
    for i=1:nBiquads
        vStage(i).z = tempZ(index(i)).z;
        vStage(i).wz =tempZ(index(i)).wz;
    end
end
% fill up the single order stage, if any
if bFirstOrderStage
    vStage(nStages).p = p(end);
    vStage(nStages).Q = 0;
    vStage(nStages).wp = abs(p(end));
    if length(z)==0 || length(z)<length(p)
        vStage(nStages).z = [];
        vStage(nStages).wz = [];
    else
        if vStage(nStages).z~=0, error('unexpected zero value'), end
        vStage(nStages).z = 0;
        vStage(nStages).wz = 0;        
    end
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                        Order biquads by increasing Q                      %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if nBiquads
    for i=1:nBiquads
        Q(i) = vStage(i).Q;
    end
    [dummy,index]=sort(Q);
    for i=1:nBiquads
        vNewStage(i) = vStage(index(i));
    end
    vStage(1:nBiquads) = vNewStage;
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                            Assign k's to each stage                       %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Spread the passband gain out evenly so each stage gets kAdditional in its passband.
% Note that each lowpass stage already has a built-in passband gain of 1/abs(p)^2 
% gain in its passband, so kAdditional is above and beyond this value.
kPassband = abs(k);
if isequal(sPurpose,'LP')
    for i=1:nBiquads
        kPassband = 1/abs(vStage(i).p(1))^2 * kPassband;
    end
    if bFirstOrderStage
        kPassband = 1/abs(vStage(nStages).p) * kPassband;
    end
end
kAdditional = kPassband^(1/nStages);
for i=1:nBiquads
    if isequal(sPurpose,'LP')
        vStage(i).k = kAdditional * abs((vStage(i).p(1)))^2;
    else
        vStage(i).k = kAdditional;
    end
end % updated May 03 so each stage has kAdditional extra passband gain
if bFirstOrderStage
    if isequal(sPurpose,'LP')
        vStage(nStages).k = kAdditional * abs(vStage(nStages).p);
    else
        vStage(nStages).k = kAdditional;
    end
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                      Recommend circuit for each biquad                    %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
switch sPurpose
    case 'LP'
        for i=1:nBiquads
            if vStage(i).Q < 3
                vStage(i).schTitle = 'Sallen-Key';
                % kDC = k / pp* for LP or k otherwise - important because SK uses a different 
                % configuration for kDC <=1 and >1 
                kDC = abs(vStage(i).k/(abs(vStage(i).p(1))^2));
                if abs(kDC-1) < 0.00001 % make it exact
                    kDC = 1;
                end
                if kDC <= 1
                    vStage(i).schName = 'SK_LP_kLE1';
                else
                    vStage(i).schName = 'SK_LP_kGT1';
                end
            else
                vStage(i).schTitle = 'Ackerberg-Mossberg';
                vStage(i).schName = 'AM_LP_N';
            end
        end
    case 'HP'
        for i=1:nBiquads
            if vStage(i).Q < 3
                vStage(i).schTitle = 'Sallen-Key';
                if abs(vStage(i).k-1) < 0.00001 % make it exact
                    vStage(i).k = 1;
                end
                if abs(vStage(i).k) <= 1
                    vStage(i).schName = 'SK_HP_kLE1';
                else
                    vStage(i).schName = 'SK_HP_kGT1';
                end
            else
                vStage(i).schTitle = 'Ackerberg-Mossberg';
                vStage(i).schName = 'AM_HP';
            end
        end
    case 'Notch'
        for i=1:nBiquads
            if vStage(i).Q < 3
                if abs(vStage(i).p) < abs(vStage(i).z) % LP
                    vStage(i).schTitle = 'Multiple Feedback';
                    vStage(i).schName = 'MFB_Z_LP';
                else % HP
                    vStage(i).schTitle = 'Multiple Feedback';
                    vStage(i).schName = 'MFB_Z_HP';
                end
            else
                vStage(i).schTitle = 'Ackerberg-Mossberg';
                vStage(i).schName = 'AM_Z';
            end
        end
    otherwise
        error('Unrecognized sPurpose')
end
% make the recommended name the same as the current schematic name
for i=1:nStages
    vStage(i).recName = vStage(i).schName;
    vStage(i).recTitle = vStage(i).schTitle;
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                fill up the strCircuit structure with results              %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
strCircuit.z = z;
strCircuit.p = p;
strCircuit.k = k;
strCircuit.nStages = nStages;
strCircuit.nBiquads = nBiquads;
strCircuit.bFirstOrderStage = bFirstOrderStage;
strCircuit.bGainStage = bGainStage;
strCircuit.sPolarity = sPolarity;
strCircuit.nRTol = nRTol;
strCircuit.nCTol = nCTol;
strCircuit.sPurpose = sPurpose;
strCircuit.vStage = vStage;

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                fill up each biquad with default components                %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% do biquads 1st because some biquads have a k1 that influence gain stage
for i=1:nBiquads
    strCircuit.vStage(i) = BuildCircuit_UpdateComponents(strCircuit.vStage(i),nRTol,nCTol);
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%          create the last stage (zero or first order), if any              %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% assumption at this point: never a final gain stage.  LastStage will add one if needed
% BuildCircuit_LastStage calls BuildCircuit_UpdateComponents
strCircuit = BuildCircuit_LastStage(strCircuit);

Contact us at files@mathworks.com