#!/usr/bin/perl -w

use strict;

########################################

package SwPwr;
use Opt;
use Prefix;

our %disp; # hash of possible functions
my $Usage;

# Generic doc:
# Erickson Maksimovic / Fundamentals of Power Electronics / Springer

# special IC handling

sub IC_mc33063($) {
    my $data = shift;
    # A, http://www.onsemi.com/pub/Collateral/MC34063A-D.PDF
    # B, http://www.onsemi.com/pub/Collateral/AN920-D.PDF
    # C, http://www.ti.com/lit/ds/symlink/mc33063a.pdf
    # D, http://www.ti.com/lit/an/slva252b/slva252b.pdf

    # mc33063 is a variable frequency design, and theese formulas are for fix freq.
    # so some breakage is to be expected
    # E.g. formula below for Ts is for inductor working fulltime (CCM).

    my $kCt = 40e-6; # Amp. per Volt
    my $str = "";
    $$data{Vref} = 1.25;

    voltages($data);
    if ($$data{Ct}) {
	if (20e-12 <= $$data{Ct} && $$data{Ct} <= 5e-9) {
	    my $T1 = $$data{Ct} / $kCt;
	    $$data{Ts} = $T1 * (1+$$data{ML})/$$data{ML};
	    $$data{frq} = 1 / $$data{Ts};
	} else {
	    $str = Prefix::pfx_en($$data{Ct}, "F", "%s%s");
	    &$Usage(" ERR: Ct ($str) should be greater than 20pF and less than 5nF");
	}
    }
    $$data{Vsc} = 0.33 - 0.03 * ( $$data{Vi} - 5 ) / ( 40 - 5 ); # 300mV / Vcc = 40V and 330mV / 5V
    $$data{Vrmin} = 1.5e-3 * abs($$data{Vo}) / $$data{Vref}; # see B p5; D p8
}
sub IC_mc33063_check($) {
    my $data = shift;

    my $str = "";
    if ($$data{PA} > 6/7) { $str .= " PA > 6/7"; }
    if ($$data{frq} > 100e3) { $str .= " frq > 100kHz"; }
    if ($str) {
	$str = &$Usage($str);
    }
}

sub options_defaults($) {
    my $data = shift;

    %$data = (
	IC    => "",

	verb  => 0,
	Vtype => undef,
	Itype => undef,
	Ctype => undef,

	frq   => undef,
	Ts    => undef,
	T1    => undef,

	Vi    => undef,
	Vo    => undef,
	Io    => undef,
	Ii    => undef,
	Vsat  => 0,
	Vf    => 0,

	L     => undef,
	Ci    => 0,
	Co    => 0,
	Rl    => 0,
	Rci   => 0,
	Rco   => 0,

	VA    => 0,
	VB    => 0,
	IA    => 0,
	IB    => 0,
	kt    => 0,
	kA    => 0,
	kB    => 0,

	M     => 0,
	ML    => 0,
	PA    => 0,
	PB    => 0,
	Icr   => 0,
	Lcr   => 0,		# L for which Io = Icr
	L20   => 0,		# L for which ( ILpp/2 ) / IM = 20%
	LE    => 0,
	L3i   => 0,		# border between current level II o III, input
	L3o   => 0,		# border between current level II o III, output
	ILPP  => 0,
	ILpp  => 0,

	Vsat  => 0,		# transistor saturation voltage
	Vf    => 0,		# diode forward voltage


	IL    => 0,
	ILmax => 0,
	ILmin => 0,
	IM    => 0,

	ICi   => 0,
	dUCiq => 0,
	dUCir => 0,

	ICo   => 0,
	dUCoq => 0,
	dUCor => 0,

	EL    => 0,		# energy in L = ILmax^2 L / 2
    );
}

sub options_check($) {
    my $data = shift;

    Opt::Err_eval( $data, undef, ">  0", qw/ Vi L /);
    Opt::Err_eval( $data, undef, "!= 0", qw/ Vo Io /);
    Opt::Err_eval( $data, undef, ">= 0", qw/ Vf Vsat Ci Co Rl Rci Rco /);
    if ($$data{IC} eq "mc33063") {
	Opt::Err_count( $data, undef, 1, qw/ frq Ts Ct /);
    } else {
	Opt::Err_count( $data, undef, 1, qw/ frq Ts /);
    }
    # frq and Ts checked later
}

sub SwInit($;$) {
    my $data = shift;
    $Usage = shift;

    options_defaults($data);
    @ARGV = Opt::Opt($data, $Usage);
    Prefix::decode_hash($data);
    options_check($data);
}

########################################
# the rest...

sub voltages($) {
    my $data = shift;

    # voltages
    if ($$data{VA} < $$data{Vsat}) {
	&$Usage("VA must be > Vsat");
    }
    $$data{VA} -= $$data{Vsat};
    $$data{VB} -= $$data{Vf};
    $$data{M} = $$data{Vo} / $$data{Vi};
    $$data{ML} = -$$data{VB} / $$data{VA};
}

sub common($) {
    my $data = shift;

    voltages($data);
    # timings
    if      ($$data{frq}) {
	$$data{Ts}  = 1 / $$data{frq};
    } elsif ($$data{Ts}) {
	$$data{frq} = 1 / $$data{Ts};
    }

    # the rest
    $$data{IA} = $$data{VA} * $$data{Ts} / $$data{L};
    $$data{IB} = $$data{VB} * $$data{Ts} / $$data{L};

    my $MLP = 1 + $$data{ML};
    $$data{ILPP} = $$data{ML} * $$data{VA} * $$data{Ts} / ($MLP*$$data{L});
    my $LtoI = - $$data{VB} * $$data{Ts} * (1+$$data{kA} * $$data{ML}) / (2*$MLP*$MLP);
    $$data{Icr} = $LtoI / $$data{L};
    $$data{Lcr} = $$data{kt} * $LtoI / $$data{Io};
    $$data{L20} = 5 * $$data{Lcr};

    my $kk = abs($$data{Io}) / $$data{Icr};
    if      ($kk < 1 - 1e-5) {
	$$data{Itype} = "parttime";
	$$data{PB} = sqrt(2 * $$data{kt} * $$data{Io} / (-$$data{IB} * (1 + $$data{kA} * $$data{ML})));

    } elsif ($kk < 1 + 1e-5) {
	$$data{Itype} = "bordertime";
	$$data{PB} = 1 / $MLP;
    } else {
	$$data{Itype} = "fulltime";
	$$data{PB} = 1 / $MLP;
    }
    $$data{PA} = $$data{ML} * $$data{PB};
    $$data{T1} = $$data{PA} * $$data{Ts};
    $$data{T2} = $$data{PB} * $$data{Ts};

    $$data{ILpp} = - $$data{PB} * $$data{IB};
    $$data{IM} = $$data{kt} * $$data{Io} / ((1 + $$data{kA} * $$data{ML})*$$data{PB});
    $$data{ILmin} = $$data{IM} - $$data{ILpp}/2;
    if ($$data{Itype} eq "parttime") { $$data{ILmin} = 0; }
    $$data{ILmax} = $$data{IM} + $$data{ILpp}/2;

    $$data{EL} = $$data{ILmax}**2 * $$data{L} / 2;
    $$data{LE} = - $$data{VB} * $$data{Ts} / (2 * $MLP * $$data{IM});

    # voltages over kapacitances
    $$data{Ii} = $$data{M} * $$data{Io};
    my $i1 = $$data{Ii} - $$data{ILmin};
    my $i2 = $$data{Ii} - $$data{ILmax};
    my $i3 = $$data{kt} * $$data{ILmin} - $$data{Io};
    my $i4 = $$data{kt} * $$data{ILmax} - $$data{Io};

    $$data{ICi} = abs($i2);
    if ($$data{ICi} < abs($i1)) { $$data{ICi} = abs($i1); }
    $$data{ICo} = abs($i3);
    if ($$data{ICo} < abs($i4)) { $$data{ICo} = abs($i4); }

    # input
    if (defined($$data{Ci})) {
	# ripple over Ci due to charge diffs
	my $q;
	if ($$data{kB}) {
	    # double engaged
	    if ($$data{Itype} eq 'parttime') {
		$q = $i2*$i2 * $$data{Ts} * (1/$$data{IA} - 1/$$data{IB}) / 2;
		$$data{Ctype} = "p-";
	    } else {
		$q = $$data{ILPP} * $$data{Ts} / 8;
		$$data{Ctype} = "f-";
	    }
	} else {
	    # single engaged
	    if ($$data{ILmin} < $$data{Ii}) {
		$q = $i2*$i2 * $$data{Ts} / (2*$$data{IA});
		$$data{Ctype} = "I-";
	    } else {
		$q = $$data{PB} * $$data{Ts} * $$data{Ii};
		$$data{Ctype} = "III-";
	    }
	}
	$$data{dUCiq} = $q/$$data{Ci};

	# ripple over Ci due to internal resistance
	if ($$data{kB} && $$data{Itype} eq 'fulltime') {
	    $$data{dUCir} = $$data{Rci} * $$data{ILpp};
	} else {
	    $$data{dUCir} = $$data{Rci} * $$data{ILmax};
	}
    }
    # output
    if (defined($$data{Co})) {
	# ripple over Ci due to charge diffs
	my $q;
	if ($$data{kA}) {
	    # double engaged
	    if ($$data{Itype} eq 'parttime') {
		$q = $i4*$i4 * $$data{Ts} * (1/$$data{IA} - 1/$$data{IB}) / 2;
		$$data{Ctype} .= "p";
	    } else {
		$q = $$data{ILPP} * $$data{Ts} / 8;
		$$data{Ctype} .= "f";
	    }
	} else {
	    # single engaged
	    if ($$data{ILmin} < $$data{Io}) {
		$q = - $i4*$i4 * $$data{Ts} / (2*$$data{IB});
		$$data{Ctype} .= "I";
	    } else {
		$q = abs($$data{PA} * $$data{Ts} * $$data{Io});
		$$data{Ctype} .= "III";
	    }
	}
	$$data{dUCoq} = $q/$$data{Co};

	# ripple over Co due to internal resistance
	if ($$data{kA} && $$data{Itype} eq 'fulltime') {
	    $$data{dUCor} = $$data{Rco} * $$data{ILpp};
	} else {
	    $$data{dUCor} = $$data{Rco} * $$data{ILmax};
	}
    }

    if (!$$data{kB}) {
	$$data{L3i} = $$data{Lcr}/$$data{PB};
    }
    if (!$$data{kA}) {
	$$data{L3o} = $$data{Lcr}/$$data{PA};
    }

}

sub K1($) {
    my $data = shift;
    $$data{Vtype} = "up";
    Opt::Err_eval( $data, "For $$data{Vtype}-converter, theese must be", "> $$data{Vi}", qw/Vo/);
    Opt::Err_eval( $data, "For $$data{Vtype}-converter, theese must be", "> 0", qw/Io Ci Co/);

    $$data{VA} = $$data{Vi};
    $$data{VB} = $$data{Vi} - $$data{Vo};
    $$data{kt} = 1;
    $$data{kA} = 0;
    $$data{kB} = 1;
}
my $K1_doc = "make a higher voltage, also called step-up, boost, Aufwärtswandler";
$disp{up} = [ \&K1, $K1_doc ];

sub K2($) {
    my $data = shift;
    $$data{Vtype} = "down";
    Opt::Err_eval( $data, "For $$data{Vtype}-converter, theese must be", "< $$data{Vi}", qw/Vo/);
    Opt::Err_eval( $data, "For $$data{Vtype}-converter, theese must be", "> 0", qw/Io Ci Co/);

    $$data{VA} = $$data{Vi} - $$data{Vo};
    $$data{VB} = - $$data{Vo};
    $$data{kt} = 1;
    $$data{kA} = 1;
    $$data{kB} = 0;
}
my $K2_doc = "make a lower voltage, also called step-down, buck, Abwärtswandler";
$disp{down} = [ \&K2, $K2_doc ];

sub K3($) {
    my $data = shift;
    $$data{Vtype} = "inv";
    Opt::Err_eval( $data, "For $$data{Vtype}-converter, theese must be", "< 0", qw/Vo Io/);
    Opt::Err_eval( $data, "For $$data{Vtype}-converter, theese must be", "> 0", qw/Ci Co/);

    $$data{VA} = $$data{Vi};
    $$data{VB} = $$data{Vo};
    $$data{kt} = -1;
    $$data{kA} = 0;
    $$data{kB} = 0;
}
my $K3_doc = "make a negative voltage, also called buck-boost, Inverswandler";
$disp{inv} = [ \&K3, $K3_doc ];

sub K4($) {
    my $data = shift;
    $$data{Vtype} = "updown";
    Opt::Err_eval( $data, "For $$data{Vtype}-converter, theese must be", "> 0", qw/Vo Io Ci Co/);

    $$data{VA} = $$data{Vi};
    $$data{VB} = - $$data{Vo};
    $$data{kt} = 1;
    $$data{kA} = 0;
    $$data{kB} = 0;
}
my $K4_doc = "make a positive voltage, also called boost-buck";
$disp{updown} = [ \&K4, $K4_doc ];

sub printIt(@) {
    my @arr = @_;

    my $fst = $arr[0];
    my $verb = $$fst{verb};
    my $str = "";
    my $ref = [ @arr ];
    my $sep = " ";

    for my $data (@arr) {
	return if (!defined($$data{Vtype}));
    }

    printf("$$fst{Vtype}-converter ");
    for my $data (@$ref) {
	printf($str);
	printf("$$data{Itype} $$data{Ctype}");
	if (!$str) { $str = " to "; }
    }
    printf(":\n");
    if ($verb > 0) {
	Prefix::println( 1, $ref, $sep, qw/ frq   Hz  Ts    s   EL    J / );
	Prefix::println( 1, $ref, $sep, qw/ T1    s   T2    s   LE    H /);
    }
    Prefix::println( 1, $ref, $sep, qw/ PA    -f  PB    -f/);
    if ($verb) {
	Prefix::println( 1, $ref, $sep, qw/ Vi    V   M     -f  Vo    V /);
	Prefix::println( 1, $ref, $sep, qw/ VA    V   ML    -f  VB    V /);
    }
    Prefix::println( 1, $ref, $sep, qw/ Ii    A   IM    A   Io    A /);
    if ($verb) {
	Prefix::println( 1, $ref, $sep, qw/ IA    A   ILPP  A   IB    A /);
    }
    Prefix::println( 1, $ref, $sep, qw/ ILmin A   ILpp  A   ILmax A /);
    if ($verb) {
	Prefix::println( 1, $ref, $sep, qw/ Lcr   H   L20   H   Icr   A /);
    }
    if (defined($$fst{Ci})) {
	Prefix::println( 1, $ref, $sep, qw/ ICi A  dUCiq V  dUCir V /);
    }
    if (defined($$fst{Co})) {
	Prefix::println( 1, $ref, $sep, qw/ ICo A  dUCoq V  dUCor V /);
    }
    if ($verb) {
	if ($$fst{L3i}) {
	    Prefix::println( 1, $ref, $sep, qw/ L3i H /);
	}
	if ($$fst{L3o}) {
	    Prefix::println( 1, $ref, $sep, qw/ L3o H /);
	}
    }
}
my $pr_doc = "print out current result";
$disp{pr} = [ \&printIt, $pr_doc ];

sub test($) {
    my $data = shift;
    #printit();
    printf(" ILmax = %s, Io = %s, %5.3f\n",
	   Prefix::pfx_en($$data{ILmax},"A"), Prefix::pfx_en($$data{Io},"A"), - $$data{ILmax} / $$data{Io});
}
my $test_doc = "edit file to run some test";
$disp{test} = [ \&test, $test_doc ];

sub show($) {
    my $data = shift;
    print Opt::Show($data, "  ");
}
my $show_doc = "show command line options";
$disp{show} = [ \&show, $show_doc ];

#############3

1;
