#!/usr/bin/perl -w
#
# ampsr.pl v0.02        --crb3 30may03
#
# given input, output and reference voltages (high and low),
# calculate a feedback (attenuator) network for a noninverting
# op-amp buffer which will produce that amplifer behavior, first
# as ideal values, then as best values found by searching a
# standard array of resistor values.
#       program         picture (ampsr.png)
#       -------         -------
#       Rf (feedback)   R3
#       Rh (high)       R2
#       Rl (low)        R1
#
# This program is copyright (C) 1983-2003 Carroll R. Bryan III
# (crb3@stormbringer.org), All Rights Reserved. It is made
# available under the terms of the GNU General Public License,
# version 2 (http://www.gnu.org/licenses/gpl.txt).
#
# v0.01 --crb3 24may03  initial Perl version.
# v0.02 --crb3 30may03  put in argfile handling.
#

use POSIX qw(log10 pow);

$|=1;

$chatty=0;

@cfvals = (
    1.0,1.1,1.2,1.3,
    1.5,1.6,1.8,2.0,
    2.2,2.4,2.7,3.0,
    3.3,3.6,3.9,4.3,
    4.7,5.1,5.6,6.2,
    6.8,7.5,8.2,9.1,
    10.0
    );

@milvals = (
    1.00,1.02,1.05,1.07,
    1.10,1.13,1.15,1.18,
    1.21,1.24,1.27,1.30,
    1.33,1.37,1.40,1.43,
    1.47,1.50,1.54,1.58,
    1.62,1.65,1.69,1.74,
    1.78,1.82,1.87,1.91,
    1.96,2.00,2.05,2.10,
    2.15,2.21,2.26,2.32,
    2.37,2.43,2.49,2.55,
    2.61,2.67,2.74,2.80,
    2.87,2.94,3.01,3.09,
    3.16,3.24,3.32,3.40,
    3.48,3.57,3.65,3.74,
    3.83,3.92,4.02,4.12,
    4.22,4.32,4.42,4.53,
    4.64,4.75,4.87,4.99,
    5.11,5.23,5.36,5.49,
    5.62,5.76,5.90,6.04,
    6.19,6.34,6.49,6.65,
    6.81,6.98,7.15,7.32,
    7.50,7.68,7.87,8.06,
    8.25,8.45,8.66,8.87,
    9.09,9.31,9.53,9.76,
    10.0
    );

#
# what params are in already?
#
%gots = (
          vh => 0,
          vl => 0,
          ih => 0,
          il => 0,
          oh => 0,
          ol => 0,
          pr => 0,
        );

sub argswitch {
  my($key,$arg)=(@_);

  if($key eq "vh"){
    $Vrh=0+$arg;                # coerce all these strings to numeric
    $gots{"vh"}=1;
  }elsif($key eq "vl"){
    $Vrl=0+$arg;
    $gots{"vl"}=1;
  }elsif($key eq "ih"){
    $Vih=0+$arg;
    $gots{"ih"}=1;
  }elsif($key eq "il"){
    $Vil=0+$arg;
    $gots{"il"}=1;
  }elsif($key eq "oh"){
    $Voh=0+$arg;
    $gots{"oh"}=1;
  }elsif($key eq "ol"){
    $Vol=0+$arg;
    $gots{"ol"}=1;
  }elsif($key eq "pr"){
    $pr=0+$arg;
    $gots{"pr"}=1;
  }elsif($key eq '@'){
    $argfname=$arg;
    $gots{'@'}=1;
  }else{
    die "switch not recognized: -$key $arg\n";
  }
}

$argfname="";

while(defined($ARGV[0])){
  $arg=shift(@ARGV);
  if(substr($arg,0,1eq '-'){
    substr($arg,0,1)="";
    if(index($arg,'=')>-1){
      ($key,$arg)=split('=',$arg,2);
      $key=lc($key);
    }else{
      $key=lc($arg);
      $arg=shift(@ARGV);
    }
    &argswitch($key,$arg);
  }else{
    die "commandline arg $arg not recognized\n";
  }
}
#
# we allow for implicit includes here, by looping on fresh argfiles.
#
while($gots{'@'}){
  $gots{'@'}=0;
  open(AFIL,"<$argfname"or die "can't open argfile $argfname\n";
  while(defined($inline=<AFIL>)){
    chomp $inline;
    next if $inline =~ /^\s*$/;
    next if $inline =~ /^\s*\#/;
    ($key,$arg)=split('=',$inline,2);
    &argswitch($key,$arg);
  }
  close(AFIL);
}

#
# fill in with prompt-and-fetch, using %gots to say what's needed.
#
$Vih=query("highest input voltage"unless $gots{"ih"};
$Vil=query("lowest input voltage"unless $gots{"il"};
$Voh=query("highest output voltage"unless $gots{"oh"};
$Vol=query("lowest output voltage"unless $gots{"ol"};
$Vrh=query("high reference voltage"unless $gots{"vh"};
$Vrl=query("low reference voltage"unless $gots{"vl"};
$pr=query("5% or 1% array values"unless $gots{"pr"};

#
# now, just get on with it. first, the network calculations.
#

if($pr==1){     # use 1% value array?
  $precise=1;
  $endv = 96;
}else{          # use 5% array.
  $precise=0;
  $endv = 24;
  $pr=5;        # nail down the default
}

# start by deriving normalized ideals, working from a 10k Rf.

$Av = (($Voh-$Vol) / ($Vih-$Vil));
$Rf = 10E3;
$Ih = ($Voh - $Vih) / $Rf;
$Il = ($Vol - $Vil) / $Rf;
$Rp = ($Vih - $Vil) / ($Ih - $Il);
$V2 = $Vih - ($Ih * $Rp);

die "\areference voltages not usable (V2==$V2)\n" if($V2<=$Vrl || $V2>=$Vrh);

$N = ($V2 - $Vrl) / ($Vrh - $Vrl);
$Rh = 1 / ( (1 / $Rp* $N);
$Rl = 1 / ( (1/$Rp- (1/$Rh) );

printf("Av=\t%Lf\nRf=\t%Lf\nIh=\t%Lf\nIl=\t%Lf\nRp=\t%Lf\nV2=\t%Lf\n",
            $Av,$Rf,$Ih,$Il,$Rp,$V2if $chatty;
printf("N=\t%Lf\nidealRh=%Lf\nidealRl=%Lf\n",$N,$Rh,$Rlif $chatty;

#
# ideal values found. now the scaling search, to find the best
# resistor set among the chosen value array.
#
$besterr=9999.9999$s_besterr=sprintf("%Lf",$besterr);
for($b=0;$b<$endv;$b++){
  if($precise==1){
    $scaler=$milvals[$b];
    $tryRh=get_best_mil($Rh*$scaler);
    $tryRl=get_best_mil($Rl*$scaler);
    $tryRf=get_best_mil($Rf*$scaler);
  }else{
    $scaler=$cfvals[$b];
    $tryRh=get_best_cf($Rh*$scaler);
    $tryRl=get_best_cf($Rl*$scaler);
    $tryRf=get_best_cf($Rf*$scaler);
  }

#
# values selected, now try em on for size, see if they fit better than the
# last set.
#

  $tryV2 = ( ($tryRl / ($tryRl + $tryRh)) * ($Vrh-$Vrl) ) + $Vrl;
  $tryRp= (1 / ((1/$tryRh)+(1/$tryRl)) );
  $tryAv1 + ($tryRf / $tryRp);
  $tryVoh = $tryV2 + (($Vih-$tryV2)*$tryAv);
  $tryVol = $tryV2 + (($Vil-$tryV2)*$tryAv);
  if($tryVoh>$Voh){
    $tryerr = abslut(pc_ch($tryVoh,$Voh));
  }else{
    $tryerr = abslut(pc_ch($Voh,$tryVoh));
  }
  if($tryVol>$Vol){
    $tryerr += abslut(pc_ch($tryVol,$Vol));
  }else{
    $tryerr += abslut(pc_ch($Vol,$tryVol));
  }

  $s_tryerr=sprintf("%Lf",$tryerr);

  print "Rf=$tryRf, tryerr=$s_tryerr...\n" if $chatty;

  if($b==0 or $s_tryerr lt $s_besterr){

    print "tryerr=$tryerr < besterr=$besterr\n"
        ."Rh\t$tryRh\tRl\t$tryRl\tRf\t$tryRf\n"
        ."Voh\t$tryVoh\tVol\t$tryVol\tAv\t$tryAv\n" if $chatty;

    $bestRl=$tryRl;
    $bestRh=$tryRh;
    $bestRf=$tryRf;
    $bestVoh=$tryVoh;
    $bestVol=$tryVol;
    $bestAv=$tryAv;
    $besterr=$tryerr;
    $s_besterr=sprintf("%Lf",$tryerr);
  }
}

# announce the best selection set, and go home.

print "Best fits:\tAv=$bestAv\terr=$besterr\n"
        ."Rh=$bestRh\tVih=$Vih\tVoh=$bestVoh\n"
        ."Rl=$bestRl\tVil=$Vil\tVol=$bestVol\n"
        ."Rf=$bestRf\tVrefhi=$Vrh\tVreflo=$Vrl\n";

if(0){
printf"\nBest fits:\n"
        ."Rl=%Lf\nRh=%Lf\nRf=%Lf\n"
        ."Voh=%Lf\tVol=%Lf\tAv=%Lf\terr=%Lf\n",
        $bestRl,
        $bestRh,
        $bestRf,
        $bestVoh,
        $bestVol,
        $bestAv,
        $besterr
        );
}

exit(0);

#----------------------------------------

# get_best_mil.
# Get the closest fit, in the standard 1% array of values, to the provided
# ideal calculated value. There are 96 values in the array. Rather than a
# simple linear iterative search, a modified binary-tree search (modified
# because there's a '3' in the components, so there's a triplet in there
# somewhere) ... kind of an SAR.
#
sub get_best_mil {
  my $ideal = shift(@_);
  my($v,$a,$b,$sel);
  my($err,$lasterr,$try,$ord,$mult);

  $v = int(log10($ideal));      # crack off the fraction

  $mult = pow10($v);

  $ord = $ideal / $mult;        # just the array-size val

# get the triplet out of the way first

  for($sel=$a=0;$a<3;$a++){
    $try=$milvals[$a*32];
    $sel = $a*32 if($try < $ord);
  }

# now the bit-shift part.

  for($a=0,$b=16;$a<5;$a++,$b>>=1){
    $try=$milvals[$sel+$b];
    $sel += $b if($try < $ord);
  }

# one final pass to knock out quantization error and include the '10' in the
# search

  $try = $milvals[$sel];


  $lasterr=pc_ch($ord,$try);
  $try=$milvals[$sel+1];
  if($try>$ord){
    $err=pc_ch($try,$ord);
  }else{
    $err=pc_ch($ord,$try);
  }
  if($err<$lasterr){
    $sel+=1;
  }
  return($milvals[$sel* $mult);

}

#---------------------------------------

# get_best_cf.
# Get the closest fit, in the standard 5% array of values, to the provided
# ideal calculated value. There are 24 values in the array. Rather than a
# simple linear iterative search, a modified binary-tree search (modified
# because there's a '3' in the components, so there's a triplet in there
# somewhere) ... kind of an SAR.
#

sub get_best_cf {
  my $ideal = shift(@_);
  my($v,$a,$b,$sel);
  my($err,$lasterr,$try,$ord,$mult);

  $v = int(log10($ideal));      # crack off the fraction

  $mult = pow10($v);

  $ord = $ideal / $mult;        # just the array-size val

print "ideal=$ideal, mult=$mult, ord=$ord\n" if $chatty;

# get the triplet out of the way first

  for($sel=$a=0;$a<3;$a++){
    $try=$cfvals[$a*8];
    $sel = $a*8 if($try < $ord);
  }

# now the bit-shift part.

  for($a=0,$b=4;$a<3;$a++,$b>>=1){
    $try=$cfvals[$sel+$b];
    $sel += $b if($try < $ord);

print "a=$a, b=$b, sel=$sel, try=$try, ord=$ord\n" if $chatty;

  }

# one final pass to knock out quantization error and include the '10' in the
# search

  $try = $cfvals[$sel];

  $lasterr=pc_ch($ord,$try);
  $try=$cfvals[$sel+1];
  if($try>$ord){
    $err=pc_ch($try,$ord);
  }else{
    $err=pc_ch($ord,$try);
  }
  if($err<$lasterr){
    $sel+=1;
  }

  return($cfvals[$sel* $mult);

}

#--------------------------------------

# abslut.
# this was a macro in the C version.
# #define abslut(x)  (x>=0 ? x : (0-x))
#
sub abslut {
  my $x=shift(@_);

  return($x >= 0 ? $x : (0-$x));
}

#--------------------------------------

# pc_ch.
# this was a macro in the C version.
# #define pc_ch(x,y)  (((x-y) / x) * 100)
#
sub pc_ch {
  my($x,$y)=(@_);

  return( ( ($x-$y / $x* 100) );
}

#--------------------------------------

# pow10.

sub pow10 {
  my $x = shift(@_);

  return( pow(10,$x) );
}

#--------------------------------------

# query

sub query {
  my ($ask)=(@_);

  print "$ask";
  my $resp = <STDIN>;


  return($resp);
}

__END__


Grab a
gzipped
copy
here
 
Syntax highlighting using Syntax::Highlight::Engine::Kate