#!/usr/bin/perl -w

use strict;
use lib '.';
use Opt;
use Eseries;
use Rdiv;
use Data::Dumper;

# from diagrams in pdf
my @temp      = ( -50, -25,   0,  25,  50,  75, 100, 125, 150 ); # °C
my @lm317Iadj = (  42,  46,  51,  53,  56,  57,  57,  57,  57 ); # uA
my @lm337Iadj = (  69,  67,  66,  65,  66,  67,  69,  70,  72 ); # uA
my @lm317Vref = (  60,  80, 100, 100,  95,  80,  55,  30,   0 ); # 0.1 mV +1.24V
my @lm337Vref = (  25,  20,  20,  20,  20,  15,  10,  05, -10 ); # 0.1 mV +1.25V
@lm317Iadj = map { $_ * 1e-6        } @lm317Iadj;
@lm337Iadj = map { $_ * 1e-6        } @lm337Iadj;
@lm317Vref = map { 1.24 + $_ * 1e-4 } @lm317Vref;
@lm337Vref = map { 1.25 + $_ * 1e-4 } @lm337Vref;

my %opt = (
    Tmin => $temp[0],
    Tmax => $temp[$#temp],
    Rmin => "10k",
    Rmax => "1M",
    Rtop => "1k",
    E    => "E6",
);

sub get_ix($) {
    my $ix = shift;
    my @res;

    if ($opt{Vout} < 0) {
	@res = ( $lm337Iadj[$ix], $lm337Vref[$ix], );
    } else {
	@res = ( $lm317Iadj[$ix], $lm317Vref[$ix], );
    }
    @res;
}

sub get($) {
    my $t = shift;
    my @res;

    if ($t < $temp[0]) {
	warn("$t to low temperature");
	return get_ix(0);
    } elsif ($temp[$#temp] < $t) {
	warn("$t to high temperature");
	return get_ix($#temp);
    }

    $t += 50;
    my $ix = int($t / 25);
    my $fr = $t - 25 * $ix;

    if ($fr == 0) {
	return get_ix($ix);
    }

    my @a = get_ix($ix);
    my @b = get_ix($ix+1);

    my @c;
    my $q = $fr / 25;
    #print "$t $ix $fr $q\n";
    for (my $jx = 0; $jx < 2; $jx++) {
	$c[$jx] = $a[$jx] + ($b[$jx] - $a[$jx]) * $q;
	#print join(", ", $a[$jx], $b[$jx], $c[$jx]), "\n";
    }

    @c;
}

sub vout($$) {
    my $t = shift;
    my $Rbot = shift;

    my ($Iadj, $Vref) = get($t);
    my $vout = $Vref * ( 1 + $Rbot/$opt{Rtop}) + $Iadj * $Rbot;

    if ($opt{Vout} < 0) { $vout = -$vout; }
    $vout;
}
sub rbot($) {
    my $t = shift;

    my ($Iadj, $Vref) = get($t);

# Vout = Vref ( 1 + Rbot/Rtop) + Iadj Rbot
#      = Vref + ( Vref/Rtop + Iadj ) Rbot
# ( Vout - Vref ) / (Vref / Rtop + Iadj ) = Rbot
    my $Rbot = (abs($opt{Vout}) - $Vref)/($Vref/$opt{Rtop} + $Iadj);
}

sub vout_boundries($) {
    my $Rbot = shift;

    my @bot;
    my @top;
    @top = @bot = ($opt{Tmin}, vout($opt{Tmin}, $Rbot));
    for (my $t = $opt{Tmin}+1; $t < $opt{Vout}; $t++) {
	my $vout = vout($t, $Rbot);
	if ($vout < $bot[1]) {
	    @bot = ($t, $vout);
	} elsif ($top[1] < $vout) {
	    @top = ($t, $vout);
	}
    }

    ([@bot], [@top]);
}

sub rbot_boundries() {
    my @bot;
    my @top;
    @top = @bot = ($opt{Tmin}, rbot($opt{Tmin}));
    for (my $t = $opt{Tmin}+1; $t < $opt{Vout}; $t++) {
	my $Rbot = rbot($t);
	if ($Rbot < $bot[1]) {
	    @bot = ($t, $Rbot);
	} elsif ($top[1] < $Rbot) {
	    @top = ($t, $Rbot);
	}
    }

    ([@bot], [@top]);
}

sub find_vout(;$) {
    my $Rbot = shift;

    my $Rw;
    my $flag = defined($Rbot);

    printf(" Vout = %.1f Rtop = %.0f I = %.3fmA\n",
	   $opt{Vout}, $opt{Rtop}, 1000 * 1.25 / $opt{Rtop});

    if (defined($Rbot)) {
	$Rw = 0;
	printf(" Rbot = %.3f\n", $Rbot);
    } else {
	my @rr = rbot_boundries();
	my @bot = @{$rr[0]};
	my @top = @{$rr[1]};
	$Rbot = ($bot[1] + $top[1])/2;
	$Rw = ($top[1] - $bot[1])/$Rbot;
	printf(" Rmin = %.3f (%.0f°C) Rbot = %.3f Rmax = %.3f (%.0f°C), variation = %.3f%%\n",
	       $bot[1], $bot[0], $Rbot, $top[1], $top[0], $Rw*100);
    }

    my @rr = vout_boundries($Rbot);
    my @bot = @{$rr[0]};
    my @top = @{$rr[1]};
    my $Vmid = ($bot[1] + $top[1])/2;
    my $Vw = ($top[1] - $bot[1])/$Vmid;

    printf(" Vmin = %.3f (%.0f°C) Vmid = %.3f Vmax = %.3f (%.0f°C), variation = %.3f%%\n",
	   $bot[1], $bot[0], $Vmid, $top[1], $top[0], $Vw*100);


    if (!$flag) {
	print("Suggested Rbot realisations\n");
	print("  err type   Rbot           R1        R2        R3\n");
	Rdiv::r1($Rbot);
	Rdiv::r2($Rbot);
	Rdiv::r3($Rbot);
    }
}

sub main() {
    @ARGV = Opt::opt(\%opt, 0, @ARGV);
    Prefix::decode_hash(\%opt);

    my $str = "";
    my $usagetxt =
"Usage:
  lm3x7.pl [Tmin=...] [Tmax=...] [Rbot=...] <Vout=...> <Rtop=...>
Where
  Tmin = lowest  temperature to check ($opt{Tmin})
  Tmax = highest temperature to check ($opt{Tmax})
  Rbot = resistance between 'Adjust' and ground
  Vout = output voltage
  Rtop = resistance between 'Vout' and 'Adjust' ($opt{Rtop})
";

    if (!defined($opt{Vout})) {
        Opt::usage(0, $str, $usagetxt );
    }

    my @E = Eseries::find($opt{E});
    Rdiv::val_init($opt{Rmin}, $opt{Rmax}, @E);
    find_vout($opt{Rbot});
}

main();

__END__

sub show() {
    printf("%.1fV %dOhm %d°C %d°C\n", $opt{Vout}, $opt{Rtop}, $opt{Tmin}, $opt{Tmax});
    printf("%6.0f, %6.0f, %6.0f, %6.0f, %6.0f, %6.0f, %6.0f, %6.0f, %6.0f\n", @temp);
    printf("%6.0f, %6.0f, %6.0f, %6.0f, %6.0f, %6.0f, %6.0f, %6.0f, %6.0f\n", map {$_ * 1e6} @lm317Iadj);
    printf("%6.0f, %6.0f, %6.0f, %6.0f, %6.0f, %6.0f, %6.0f, %6.0f, %6.0f\n", map {$_ * 1e6} @lm337Iadj);
    printf("%.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f\n", @lm317Vref);
    printf("%.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f\n", @lm337Vref);
}

sub print_octave() {
    # print code for gnu octave mathamatical program
    printf("1;\n");
    printf("global Vout = 15;\n");
    printf("global Rtop = 1000;\n");
    printf("global %s = [ %g, %g, %g, %g, %g, %g, %g, %g, %g];\n", "temp", @temp);
    printf("global %s = [ %g, %g, %g, %g, %g, %g, %g, %g, %g];\n", "lm317Iadj", @lm317Iadj);
    printf("global %s = [ %g, %g, %g, %g, %g, %g, %g, %g, %g];\n", "lm337Iadj", @lm337Iadj);
    printf("global %s = [ %g, %g, %g, %g, %g, %g, %g, %g, %g];\n", "lm317Vref", @lm317Vref);
    printf("global %s = [ %g, %g, %g, %g, %g, %g, %g, %g, %g];\n", "lm337Vref", @lm337Vref);
    print "\n";

    print
"function Iadj = lm3x7Iadj(t)
  global Vout;
  global temp;
  global lm317Iadj;
  global lm337Iadj;

  if (Vout < 0)
    arr = lm337Iadj;
  else
   arr = lm317Iadj;
  endif

  Iadj = interp1(temp, arr, t, \"*linear\");
endfunction

function Vref = lm3x7Vref(t)
  global Vout;
  global temp;
  global lm317Vref;
  global lm337Vref;

  if (Vout < 0)
    arr = lm337Vref;
  else
   arr = lm317Vref;
  endif

  Vref = interp1(temp, arr, t, \"*linear\");
endfunction

function rbot = rbot(t)
  global Vout;
  global Rtop;

  vref = lm3x7Vref(t);
  iadj = lm3x7Iadj(t);

  rbot = (Vout - vref) ./ ( vref ./ Rtop + iadj);
endfunction

function vout = vout(t,rbot)
  global Vout;
  global Rtop;

  vref = lm3x7Vref(t);
  iadj = lm3x7Iadj(t);

  vout = vref .* ( 1 + rbot / Rtop ) + rbot * iadj;
endfunction
";
}
