/*
Copyright (C) Paul Falstad and Iain Sharp
This file is part of CircuitJS1.
CircuitJS1 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
CircuitJS1 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CircuitJS1. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lushprojects.circuitjs1.client;
import com.google.gwt.i18n.client.NumberFormat;
class TransistorElm extends CircuitElm {
int pnp;
double beta;
double fgain;
double gmin;
final int FLAG_FLIP = 1;
TransistorElm(int xx, int yy, boolean pnpflag) {
super(xx, yy);
pnp = (pnpflag) ? -1 : 1;
beta = 100;
setup();
}
public TransistorElm(int xa, int ya, int xb, int yb, int f, StringTokenizer st) {
super(xa, ya, xb, yb, f);
pnp = new Integer(st.nextToken()).intValue();
beta = 100;
try {
lastvbe = new Double(st.nextToken()).doubleValue();
lastvbc = new Double(st.nextToken()).doubleValue();
volts[0] = 0;
volts[1] = -lastvbe;
volts[2] = -lastvbc;
beta = new Double(st.nextToken()).doubleValue();
} catch (Exception e) {
}
setup();
}
void setup() {
vcrit = vt * Math.log(vt/(Math.sqrt(2)*leakage));
fgain = beta/(beta+1);
noDiagonal = true;
}
boolean nonLinear() { return true; }
void reset() {
volts[0] = volts[1] = volts[2] = 0;
lastvbc = lastvbe = curcount_c = curcount_e = curcount_b = 0;
}
int getDumpType() { return 't'; }
String dump() {
return super.dump() + " " + pnp + " " + (volts[0]-volts[1]) + " " +
(volts[0]-volts[2]) + " " + beta;
}
double ic, ie, ib, curcount_c, curcount_e, curcount_b;
Polygon rectPoly, arrowPoly;
void draw(Graphics g) {
setBbox(point1, point2, 16);
setPowerColor(g, true);
// draw collector
setVoltageColor(g, volts[1]);
drawThickLine(g, coll[0], coll[1]);
// draw emitter
setVoltageColor(g, volts[2]);
drawThickLine(g, emit[0], emit[1]);
// draw arrow
g.setColor(lightGrayColor);
g.fillPolygon(arrowPoly);
// draw base
setVoltageColor(g, volts[0]);
if (sim.powerCheckItem.getState())
g.setColor(Color.gray);
drawThickLine(g, point1, base);
// draw dots
curcount_b = updateDotCount(-ib, curcount_b);
drawDots(g, base, point1, curcount_b);
curcount_c = updateDotCount(-ic, curcount_c);
drawDots(g, coll[1], coll[0], curcount_c);
curcount_e = updateDotCount(-ie, curcount_e);
drawDots(g, emit[1], emit[0], curcount_e);
// draw base rectangle
setVoltageColor(g, volts[0]);
setPowerColor(g, true);
g.fillPolygon(rectPoly);
if ((needsHighlight() || sim.dragElm == this) && dy == 0) {
g.setColor(Color.white);
// IES
// g.setFont(unitsFont);
int ds = sign(dx);
g.drawString("B", base.x-10*ds, base.y-5);
g.drawString("C", coll[0].x-3+9*ds, coll[0].y+4); // x+6 if ds=1, -12 if -1
g.drawString("E", emit[0].x-3+9*ds, emit[0].y+4);
}
drawPosts(g);
}
Point getPost(int n) {
return (n == 0) ? point1 : (n == 1) ? coll[0] : emit[0];
}
int getPostCount() { return 3; }
double getPower() {
return (volts[0]-volts[2])*ib + (volts[1]-volts[2])*ic;
}
Point rect[], coll[], emit[], base;
void setPoints() {
super.setPoints();
int hs = 16;
if ((flags & FLAG_FLIP) != 0)
dsign = -dsign;
int hs2 = hs*dsign*pnp;
// calc collector, emitter posts
coll = newPointArray(2);
emit = newPointArray(2);
interpPoint2(point1, point2, coll[0], emit[0], 1, hs2);
// calc rectangle edges
rect = newPointArray(4);
interpPoint2(point1, point2, rect[0], rect[1], 1-16/dn, hs);
interpPoint2(point1, point2, rect[2], rect[3], 1-13/dn, hs);
// calc points where collector/emitter leads contact rectangle
interpPoint2(point1, point2, coll[1], emit[1], 1-13/dn, 6*dsign*pnp);
// calc point where base lead contacts rectangle
base = new Point();
interpPoint (point1, point2, base, 1-16/dn);
// rectangle
rectPoly = createPolygon(rect[0], rect[2], rect[3], rect[1]);
// arrow
if (pnp == 1)
arrowPoly = calcArrow(emit[1], emit[0], 8, 4);
else {
Point pt = interpPoint(point1, point2, 1-11/dn, -5*dsign*pnp);
arrowPoly = calcArrow(emit[0], pt, 8, 4);
}
}
static final double leakage = 1e-13; // 1e-6;
static final double vt = .025;
static final double vdcoef = 1/vt;
static final double rgain = .5;
double vcrit;
double lastvbc, lastvbe;
double limitStep(double vnew, double vold) {
double arg;
double oo = vnew;
if (vnew > vcrit && Math.abs(vnew - vold) > (vt + vt)) {
if(vold > 0) {
arg = 1 + (vnew - vold) / vt;
if(arg > 0) {
vnew = vold + vt * Math.log(arg);
} else {
vnew = vcrit;
}
} else {
vnew = vt *Math.log(vnew/vt);
}
sim.converged = false;
//System.out.println(vnew + " " + oo + " " + vold);
}
return(vnew);
}
void stamp() {
sim.stampNonLinear(nodes[0]);
sim.stampNonLinear(nodes[1]);
sim.stampNonLinear(nodes[2]);
}
void doStep() {
double vbc = volts[0]-volts[1]; // typically negative
double vbe = volts[0]-volts[2]; // typically positive
if (Math.abs(vbc-lastvbc) > .01 || // .01
Math.abs(vbe-lastvbe) > .01)
sim.converged = false;
gmin = 0;
if (sim.subIterations > 100) {
// if we have trouble converging, put a conductance in parallel with all P-N junctions.
// Gradually increase the conductance value for each iteration.
gmin = Math.exp(-9*Math.log(10)*(1-sim.subIterations/300.));
if (gmin > .1)
gmin = .1;
// sim.console("gmin " + gmin + " vbc " + vbc + " vbe " + vbe);
}
//System.out.print("T " + vbc + " " + vbe + "\n");
vbc = pnp*limitStep(pnp*vbc, pnp*lastvbc);
vbe = pnp*limitStep(pnp*vbe, pnp*lastvbe);
lastvbc = vbc;
lastvbe = vbe;
double pcoef = vdcoef*pnp;
double expbc = Math.exp(vbc*pcoef);
/*if (expbc > 1e13 || Double.isInfinite(expbc))
expbc = 1e13;*/
double expbe = Math.exp(vbe*pcoef);
if (expbe < 1)
expbe = 1;
/*if (expbe > 1e13 || Double.isInfinite(expbe))
expbe = 1e13;*/
ie = pnp*leakage*(-(expbe-1)+rgain*(expbc-1));
ic = pnp*leakage*(fgain*(expbe-1)-(expbc-1));
ib = -(ie+ic);
//System.out.println("gain " + ic/ib);
//System.out.print("T " + vbc + " " + vbe + " " + ie + " " + ic + "\n");
double gee = -leakage*vdcoef*expbe;
double gec = rgain*leakage*vdcoef*expbc;
double gce = -gee*fgain;
double gcc = -gec*(1/rgain);
/*System.out.print("gee = " + gee + "\n");
System.out.print("gec = " + gec + "\n");
System.out.print("gce = " + gce + "\n");
System.out.print("gcc = " + gcc + "\n");
System.out.print("gce+gcc = " + (gce+gcc) + "\n");
System.out.print("gee+gec = " + (gee+gec) + "\n");*/
// stamps from page 302 of Pillage. Node 0 is the base,
// node 1 the collector, node 2 the emitter. Also stamp
// minimum conductance (gmin) between b,e and b,c
sim.stampMatrix(nodes[0], nodes[0], -gee-gec-gce-gcc + gmin*2);
sim.stampMatrix(nodes[0], nodes[1], gec+gcc - gmin);
sim.stampMatrix(nodes[0], nodes[2], gee+gce - gmin);
sim.stampMatrix(nodes[1], nodes[0], gce+gcc - gmin);
sim.stampMatrix(nodes[1], nodes[1], -gcc + gmin);
sim.stampMatrix(nodes[1], nodes[2], -gce);
sim.stampMatrix(nodes[2], nodes[0], gee+gec - gmin);
sim.stampMatrix(nodes[2], nodes[1], -gec);
sim.stampMatrix(nodes[2], nodes[2], -gee + gmin);
// we are solving for v(k+1), not delta v, so we use formula
// 10.5.13, multiplying J by v(k)
sim.stampRightSide(nodes[0], -ib - (gec+gcc)*vbc - (gee+gce)*vbe);
sim.stampRightSide(nodes[1], -ic + gce*vbe + gcc*vbc);
sim.stampRightSide(nodes[2], -ie + gee*vbe + gec*vbc);
}
@Override String getScopeText(int x) {
String t ="";
switch (x) {
case Scope.VAL_IB: t = "Ib"; break;
case Scope.VAL_IC: t = "Ic"; break;
case Scope.VAL_IE: t = "Ie"; break;
case Scope.VAL_VBE: t = "Vbe"; break;
case Scope.VAL_VBC: t = "Vbc"; break;
case Scope.VAL_VCE: t = "Vce"; break;
}
return sim.LS("transistor") + ", " + t;
}
void getInfo(String arr[]) {
arr[0] = sim.LS("transistor") + " (" + ((pnp == -1) ? "PNP)" : "NPN)") + " beta=" + showFormat.format(beta);
double vbc = volts[0]-volts[1];
double vbe = volts[0]-volts[2];
double vce = volts[1]-volts[2];
if (vbc*pnp > .2)
arr[1] = vbe*pnp > .2 ? "saturation" : "reverse active";
else
arr[1] = vbe*pnp > .2 ? "fwd active" : "cutoff";
arr[1] = sim.LS(arr[1]);
arr[2] = "Ic = " + getCurrentText(ic);
arr[3] = "Ib = " + getCurrentText(ib);
arr[4] = "Vbe = " + getVoltageText(vbe);
arr[5] = "Vbc = " + getVoltageText(vbc);
arr[6] = "Vce = " + getVoltageText(vce);
}
double getScopeValue(int x) {
switch (x) {
case Scope.VAL_IB: return ib;
case Scope.VAL_IC: return ic;
case Scope.VAL_IE: return ie;
case Scope.VAL_VBE: return volts[0]-volts[2];
case Scope.VAL_VBC: return volts[0]-volts[1];
case Scope.VAL_VCE: return volts[1]-volts[2];
}
return 0;
}
int getScopeUnits(int x) {
switch (x) {
case Scope.VAL_IB: case Scope.VAL_IC:
case Scope.VAL_IE: return Scope.UNITS_A;
default: return Scope.UNITS_V;
}
}
public EditInfo getEditInfo(int n) {
if (n == 0)
return new EditInfo("Beta/hFE", beta, 10, 1000).
setDimensionless();
if (n == 1) {
EditInfo ei = new EditInfo("", 0, -1, -1);
ei.checkbox = new Checkbox("Swap E/C", (flags & FLAG_FLIP) != 0);
return ei;
}
return null;
}
public void setEditValue(int n, EditInfo ei) {
if (n == 0) {
beta = ei.value;
setup();
}
if (n == 1) {
if (ei.checkbox.getState())
flags |= FLAG_FLIP;
else
flags &= ~FLAG_FLIP;
setPoints();
}
}
void stepFinished() {
// stop for huge currents that make simulator act weird
if (Math.abs(ic) > 1e12 || Math.abs(ib) > 1e12)
sim.stop("max current exceeded", this);
}
boolean canViewInScope() { return true; }
double getCurrentIntoNode(int n) {
if (n==0)
return -ib;
if (n==1)
return -ic;
return -ie;
}
double getCurrentIntoPoint(int xa, int ya) {
if (xa == x && ya == y)
return -ib;
if (xa == coll[0].x && ya == coll[0].y)
return -ic;
return -ie;
}
}