velxio/test/test_circuit/test/spice_examples.test.js

708 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, it, expect } from 'vitest';
import { runNetlist } from '../src/spice/SpiceEngine.js';
/**
* SPICE behavior tests for each circuit example shipped in
* frontend/src/data/examples-circuits.ts.
*
* Each test builds the netlist for the example's analog topology and verifies
* voltages/currents match expectations. These tests guard against regressions
* when component models change (e.g., LED Vf, BJT beta, op-amp Vsat).
*/
const NTC_R0 = 10000, NTC_T0 = 298.15, NTC_BETA = 3950;
function ntcR(Tc) {
const T = Tc + 273.15;
return NTC_R0 * Math.exp(NTC_BETA * (1 / T - 1 / NTC_T0));
}
// ════════════════════════════════════════════════════════════════════════════
// PASSIVE / ANALOG
// ════════════════════════════════════════════════════════════════════════════
describe('Example: voltage-divider', () => {
it('R1=R2=10k → V_out = 2.5V (half of 5V)', { timeout: 30_000 }, async () => {
const { dcValue } = await runNetlist(`Voltage divider
V1 vcc 0 DC 5
R1 vcc out 10k
R2 out 0 10k
.op
.end`);
expect(dcValue('v(out)')).toBeCloseTo(2.5, 2);
});
});
describe('Example: rc-low-pass-filter', () => {
it('PWM 50% duty avg = 2.5V, RC=100ms filters to ~2.5V DC', { timeout: 30_000 }, async () => {
// Simulate steady-state DC equivalent: PWM avg = 2.5V → R → output (open in DC)
const { dcValue } = await runNetlist(`RC low-pass DC equivalent
V1 in 0 DC 2.5
R1 in out 10k
Rload out 0 10Meg
.op
.end`);
expect(dcValue('v(out)')).toBeCloseTo(2.5, 1);
});
});
describe('Example: wheatstone-bridge', () => {
it('Unbalanced bridge (R3=11k vs R4=10k) gives ~119mV diff', { timeout: 30_000 }, async () => {
const { dcValue } = await runNetlist(`Wheatstone unbalanced
V1 vcc 0 DC 5
R1 vcc a 10k
R2 vcc b 10k
R3 a 0 11k
R4 b 0 10k
.op
.end`);
const diff = dcValue('v(a)') - dcValue('v(b)');
expect(diff).toBeGreaterThan(0.10);
expect(diff).toBeLessThan(0.14);
});
});
describe('Example: ntc-temperature', () => {
for (const T of [0, 25, 50]) {
it(`T=${T}°C → V depends on NTC R(T)`, { timeout: 30_000 }, async () => {
const r = ntcR(T);
const { dcValue } = await runNetlist(`NTC at ${T}C
V1 vcc 0 DC 5
Rpull vcc out 10k
Rntc out 0 ${r}
.op
.end`);
const v = dcValue('v(out)');
const expected = 5 * r / (10000 + r);
expect(v).toBeCloseTo(expected, 2);
});
}
});
describe('Example: led-current-limiting', () => {
it('5V through 330Ω + LED → V_anode in typical Vf range', { timeout: 30_000 }, async () => {
const { dcValue } = await runNetlist(`LED with 330R
V1 vcc 0 DC 5
R1 vcc anode 330
D1 anode 0 DLED
.model DLED D(Is=1e-14 N=1.8 Rs=1)
.op
.end`);
const va = dcValue('v(anode)');
// Generic-diode model gives Vf ≈ 0.71.5V depending on current. The
// current is well-defined: I ≈ (5 Vf)/330 ≈ 1013 mA.
expect(va).toBeGreaterThan(0.7);
expect(va).toBeLessThan(2.5);
});
});
describe('Example: parallel-resistors', () => {
it('3× 10k in parallel = 3.33k → V_out = 5·3.33/(10+3.33) = 1.25V', { timeout: 30_000 }, async () => {
const { dcValue } = await runNetlist(`Parallel R
V1 vcc 0 DC 5
Rs vcc mid 10k
R1 mid 0 10k
R2 mid 0 10k
R3 mid 0 10k
.op
.end`);
expect(dcValue('v(mid)')).toBeCloseTo(1.25, 2);
});
});
describe('Example: pot-adc-reader', () => {
it('Potentiometer at 50% = 2.5V', { timeout: 30_000 }, async () => {
const { dcValue } = await runNetlist(`Pot at 50%
V1 vcc 0 DC 5
Rtop vcc wiper 5k
Rbot wiper 0 5k
.op
.end`);
expect(dcValue('v(wiper)')).toBeCloseTo(2.5, 2);
});
});
describe('Example: photoresistor-light', () => {
it('LDR at 500 lux + 10k pull-down', { timeout: 30_000 }, async () => {
// LDR: R(lux) = 1M / (1 + 5*500/1000) = 1M/3.5 ≈ 286k
const Rldr = 1e6 / (1 + 5 * 500 / 1000);
const { dcValue } = await runNetlist(`LDR
V1 vcc 0 DC 5
Rldr vcc sig ${Rldr}
Rpull sig 0 10k
.op
.end`);
const expected = 5 * 10000 / (Rldr + 10000);
expect(dcValue('v(sig)')).toBeCloseTo(expected, 2);
});
});
describe('Example: capacitor-charge-curve', () => {
it('RC charging: V(τ) ≈ 63% of V_supply', { timeout: 30_000 }, async () => {
const { vec } = await runNetlist(`RC charge
V1 vcc 0 PULSE(0 5 0 1n 1n 10 20)
R1 vcc out 10k
C1 out 0 100u IC=0
.tran 10m 3
.ic v(out)=0
.end`);
const t = vec('time');
const v = vec('v(out)');
const tau = 10000 * 100e-6; // 1s
let bestI = 0, dist = Infinity;
for (let i = 0; i < t.length; i++) {
if (Math.abs(t[i] - tau) < dist) { dist = Math.abs(t[i] - tau); bestI = i; }
}
expect(v[bestI]).toBeGreaterThan(5 * (1 - 1/Math.E) * 0.95);
expect(v[bestI]).toBeLessThan(5 * (1 - 1/Math.E) * 1.05);
});
});
describe('Example: multi-led-bar', () => {
it('LED + 220Ω at 5V conducts ~14 mA', { timeout: 30_000 }, async () => {
const { vec } = await runNetlist(`LED 220R
V1 vcc 0 DC 5
R1 vcc anode 220
D1 anode 0 DLED
.model DLED D(Is=1e-14 N=1.8 Rs=1)
.op
.end`);
const i = Math.abs(vec('i(v1)')[0]);
expect(i).toBeGreaterThan(0.005);
expect(i).toBeLessThan(0.020);
});
});
// ════════════════════════════════════════════════════════════════════════════
// TRANSISTOR / SEMICONDUCTOR
// ════════════════════════════════════════════════════════════════════════════
describe('Example: npn-led-switch', () => {
it('2N2222 ON: collector pulled to ~0V', { timeout: 30_000 }, async () => {
const { dcValue } = await runNetlist(`NPN switch
V1 vcc 0 DC 5
Vdrv drv 0 DC 5
RB drv b 1k
RC vcc c 220
Q1 c b 0 Q2N2222
.model Q2N2222 NPN(Is=14.34f Bf=200 Vaf=74)
.op
.end`);
expect(dcValue('v(c)')).toBeLessThan(0.5);
});
});
describe('Example: pnp-high-side-switch', () => {
it('2N3906 ON when base LOW: load voltage near Vcc', { timeout: 30_000 }, async () => {
const { dcValue } = await runNetlist(`PNP high-side
V1 vcc 0 DC 5
Vdrv drv 0 DC 0
RB drv b 1k
Q1 c b vcc Q2N3906
RL c 0 220
.model Q2N3906 PNP(Is=1.41f Bf=180 Vaf=18.7)
.op
.end`);
expect(dcValue('v(c)')).toBeGreaterThan(4.0);
});
});
describe('Example: mosfet-pwm-led', () => {
it('2N7000 with Vgs=5V drives load, drain low', { timeout: 30_000 }, async () => {
// 220Ω load + low W/L → drain ≈ 1.2V (still well below 5V → fully ON)
const { dcValue } = await runNetlist(`NMOS switch
V1 vcc 0 DC 5
Vg gate 0 DC 5
RL vcc drain 220
M1 drain gate 0 0 NMOS L=2u W=200u
.model NMOS NMOS(Level=1 Vto=1.6 Kp=50u)
.op
.end`);
expect(dcValue('v(drain)')).toBeLessThan(2.0);
});
});
describe('Example: diode-rectifier', () => {
it('Half-wave: positive cycle passes, negative blocked', { timeout: 30_000 }, async () => {
const { vec } = await runNetlist(`Half-wave rectifier
V1 in 0 SIN(0 5 50)
D1 in out DRECT
RL out 0 1k
.model DRECT D(Is=1e-14 N=1)
.tran 0.1m 40m
.end`);
const t = vec('time');
const vout = vec('v(out)');
let posMax = -Infinity, negMin = Infinity;
for (let i = 0; i < t.length; i++) {
if (t[i] < 20e-3) continue;
if (vout[i] > posMax) posMax = vout[i];
if (vout[i] < negMin) negMin = vout[i];
}
expect(posMax).toBeGreaterThan(3.5);
expect(negMin).toBeGreaterThan(-0.2); // negative blocked
});
});
describe('Example: zener-regulator', () => {
it('5.1V Zener clamps output regardless of input variation', { timeout: 30_000 }, async () => {
for (const vin of [7, 9, 12]) {
const { dcValue } = await runNetlist(`Zener V_in=${vin}
V1 vin 0 DC ${vin}
Rs vin out 220
Dz 0 out DZ
.model DZ D(Is=1n N=1 Rs=5 Bv=5.1 Ibv=50m)
.op
.end`);
const v = dcValue('v(out)');
expect(v).toBeGreaterThan(4.8);
expect(v).toBeLessThan(5.4);
}
});
});
describe('Example: schottky-reverse-protection', () => {
it('1N5817 forward Vf < 0.5V at 100mA', { timeout: 30_000 }, async () => {
const { dcValue } = await runNetlist(`Schottky forward
V1 in 0 DC 5
Rs in d 47
D1 d 0 D1N5817
.model D1N5817 D(Is=3.3u N=1 Rs=0.025)
.op
.end`);
const vf = dcValue('v(d)');
expect(vf).toBeLessThan(0.5);
});
});
describe('Example: bjt-common-emitter', () => {
it('Common-emitter biased at Vcc/2, gain × small AC input', { timeout: 30_000 }, async () => {
const { dcValue } = await runNetlist(`CE amp DC bias
V1 vcc 0 DC 5
RB1 vcc b 47k
RB2 b 0 10k
RC vcc c 4.7k
RE e 0 1k
Q1 c b e Q2N2222
.model Q2N2222 NPN(Is=14.34f Bf=200 Vaf=74)
.op
.end`);
const vc = dcValue('v(c)');
// Should be biased somewhere in mid-range (not saturated, not cutoff)
expect(vc).toBeGreaterThan(1.0);
expect(vc).toBeLessThan(4.5);
});
});
describe('Example: darlington-high-current', () => {
it('Darlington saturates with very small base current', { timeout: 30_000 }, async () => {
const { dcValue } = await runNetlist(`Darlington
V1 vcc 0 DC 5
Vdrv drv 0 DC 5
RB drv b 100k
RC vcc c 100
Q1 c b e1 Q2N2222
Q2 c e1 0 Q2N2222
.model Q2N2222 NPN(Is=14.34f Bf=200 Vaf=74)
.op
.end`);
expect(dcValue('v(c)')).toBeLessThan(2.0);
});
});
// ════════════════════════════════════════════════════════════════════════════
// OP-AMP
// ════════════════════════════════════════════════════════════════════════════
describe('Example: opamp-inverting', () => {
it('LM358 inverter gain=-10: Vin=0.2V → Vout=2.52*(0.22.5)=2.5+0.5×... approx', { timeout: 30_000 }, async () => {
// Using ideal op-amp for clean test
const { dcValue } = await runNetlist(`Inverter
V1 vin 0 DC 0.2
Rin vin n 1k
Rf n out 10k
E1 out 0 0 n 1e6
.op
.end`);
expect(dcValue('v(out)')).toBeCloseTo(-2.0, 1);
});
});
describe('Example: opamp-voltage-follower', () => {
it('Follower: Vout tracks Vin exactly', { timeout: 30_000 }, async () => {
for (const vin of [1.0, 2.5, 4.0]) {
const { dcValue } = await runNetlist(`Follower vin=${vin}
V1 vin 0 DC ${vin}
E1 out 0 vin out 1e6
.op
.end`);
expect(dcValue('v(out)')).toBeCloseTo(vin, 2);
}
});
});
describe('Example: opamp-comparator', () => {
it('Comparator: V+ > V- → output HIGH; V+ < V- → output LOW', { timeout: 30_000 }, async () => {
const Vcc = 5;
const A = 1e5;
const vHi = Vcc - 1.5;
const vLo = 0.05;
// Test 1: input above threshold
const r1 = await runNetlist(`Comparator HIGH
V_pos vp 0 DC 3
V_ref vr 0 DC 2.5
B1 out 0 V = max(${vLo}, min(${vHi}, ${A}*(V(vp)-V(vr))))
Rload out 0 1Meg
.op
.end`);
expect(r1.dcValue('v(out)')).toBeGreaterThan(3);
// Test 2: input below threshold
const r2 = await runNetlist(`Comparator LOW
V_pos vp 0 DC 2
V_ref vr 0 DC 2.5
B1 out 0 V = max(${vLo}, min(${vHi}, ${A}*(V(vp)-V(vr))))
Rload out 0 1Meg
.op
.end`);
expect(r2.dcValue('v(out)')).toBeLessThan(0.2);
});
});
describe('Example: opamp-difference', () => {
it('Diff amp gain=10: V_out = 10·(V2V1)', { timeout: 30_000 }, async () => {
const { dcValue } = await runNetlist(`Diff amp
V1 v1 0 DC 0.5
V2 v2 0 DC 0.3
R1 v1 n 10k
R2 n out 100k
R3 v2 p 10k
R4 p 0 100k
E1 out 0 p n 1e6
.op
.end`);
expect(dcValue('v(out)')).toBeCloseTo(-2.0, 1);
});
});
describe('Example: opamp-schmitt-trigger', () => {
it('Non-inverting Schmitt with Rin=10k Rfb=100k flips at ±1V', { timeout: 30_000 }, async () => {
// Test: input above hi threshold → output HIGH
const { dcValue } = await runNetlist(`Schmitt HIGH input
Vin in 0 DC 3
Rin in p 10k
Rfb p out 100k
B1 out 0 V = 20 * u(V(p)) - 10
Rload out 0 1Meg
.op
.end`);
expect(dcValue('v(out)')).toBeGreaterThan(5);
});
});
// ════════════════════════════════════════════════════════════════════════════
// LOGIC GATES
// ════════════════════════════════════════════════════════════════════════════
describe('Example: and-gate-alarm', () => {
it('AND truth table: HIGH only when both inputs HIGH', { timeout: 30_000 }, async () => {
for (const [a, b, exp] of [[0,0,0],[0,5,0],[5,0,0],[5,5,5]]) {
const { dcValue } = await runNetlist(`AND
Va a 0 DC ${a}
Vb b 0 DC ${b}
B1 y 0 V = 5 * u(V(a)-2.5) * u(V(b)-2.5)
Rload y 0 1Meg
.op
.end`);
expect(dcValue('v(y)')).toBeCloseTo(exp, 0);
}
});
});
describe('Example: xor-toggle-detector', () => {
it('XOR truth table', { timeout: 30_000 }, async () => {
for (const [a, b, exp] of [[0,0,0],[5,0,5],[0,5,5],[5,5,0]]) {
const { dcValue } = await runNetlist(`XOR
Va a 0 DC ${a}
Vb b 0 DC ${b}
B1 y 0 V = 5 * (u(V(a)-2.5) + u(V(b)-2.5) - 2*u(V(a)-2.5)*u(V(b)-2.5))
Rload y 0 1Meg
.op
.end`);
expect(dcValue('v(y)')).toBeCloseTo(exp, 0);
}
});
});
describe('Example: nand-sr-latch', () => {
it('NAND truth table', { timeout: 30_000 }, async () => {
for (const [a, b, exp] of [[0,0,5],[0,5,5],[5,0,5],[5,5,0]]) {
const { dcValue } = await runNetlist(`NAND
Va a 0 DC ${a}
Vb b 0 DC ${b}
B1 y 0 V = 5 * (1 - u(V(a)-2.5) * u(V(b)-2.5))
Rload y 0 1Meg
.op
.end`);
expect(dcValue('v(y)')).toBeCloseTo(exp, 0);
}
});
});
describe('Example: full-adder', () => {
it('Sum = A XOR B XOR Cin, Cout = AB + Cin(A XOR B)', { timeout: 90_000 }, async () => {
const cases = [
{ a: 0, b: 0, cin: 0, sum: 0, cout: 0 },
{ a: 0, b: 0, cin: 5, sum: 5, cout: 0 },
{ a: 5, b: 5, cin: 0, sum: 0, cout: 5 },
{ a: 5, b: 5, cin: 5, sum: 5, cout: 5 },
];
const G = `
.subckt XOR_G a b y
B y 0 V = 5 * (u(V(a)-2.5) + u(V(b)-2.5) - 2*u(V(a)-2.5)*u(V(b)-2.5))
Rl y 0 1Meg
.ends
.subckt AND_G a b y
B y 0 V = 5 * u(V(a)-2.5) * u(V(b)-2.5)
Rl y 0 1Meg
.ends
.subckt OR_G a b y
B y 0 V = 5 * (1 - (1-u(V(a)-2.5)) * (1-u(V(b)-2.5)))
Rl y 0 1Meg
.ends`;
for (const c of cases) {
const { dcValue } = await runNetlist(`Full adder
Va a 0 DC ${c.a}
Vb b 0 DC ${c.b}
Vcin cin 0 DC ${c.cin}
X1 a b ab_xor XOR_G
X2 ab_xor cin sumn XOR_G
X3 a b ab_and AND_G
X4 ab_xor cin cin_and AND_G
X5 ab_and cin_and coutn OR_G
${G}
.op
.end`);
expect(dcValue('v(sumn)')).toBeCloseTo(c.sum, 0);
expect(dcValue('v(coutn)')).toBeCloseTo(c.cout, 0);
}
});
});
describe('Example: binary-counter-leds', () => {
it('LED + 220Ω driven HIGH conducts', { timeout: 30_000 }, async () => {
const { vec } = await runNetlist(`Counter LED
V1 pin 0 DC 5
R1 pin anode 220
D1 anode 0 DLED
.model DLED D(Is=1e-14 N=1.8 Rs=1)
.op
.end`);
const i = Math.abs(vec('i(v1)')[0]);
expect(i).toBeGreaterThan(0.005);
});
});
describe('Example: logic-probe', () => {
it('Green LED conducts when pin12 HIGH', { timeout: 30_000 }, async () => {
const { dcValue } = await runNetlist(`Green LED
V1 pin12 0 DC 5
R1 pin12 anode 220
D1 anode 0 DLED
.model DLED D(Is=1e-14 N=2.0 Rs=1)
.op
.end`);
const va = dcValue('v(anode)');
expect(va).toBeGreaterThan(1.0);
expect(va).toBeLessThan(3.0);
});
});
// ════════════════════════════════════════════════════════════════════════════
// ELECTROMECHANICAL
// ════════════════════════════════════════════════════════════════════════════
describe('Example: relay-led-switch', () => {
it('Relay coil energised at 5V draws ~71mA through 70Ω', { timeout: 30_000 }, async () => {
const { vec } = await runNetlist(`Relay coil
V1 cp 0 DC 5
R_coil cp 0 70
.op
.end`);
const i = Math.abs(vec('i(v1)')[0]);
expect(i).toBeGreaterThan(0.06);
expect(i).toBeLessThan(0.08);
});
});
describe('Example: optocoupler-signal', () => {
it('Optocoupler 4N25 with LED ON: phototransistor conducts (CTR=0.5)', { timeout: 30_000 }, async () => {
const { dcValue, vec } = await runNetlist(`4N25 ON
Vin vin 0 DC 5
Rled vin an 270
Vcat cat 0 DC 0
Vcc vcc 0 DC 5
Rload vcc col 470
Vemit emit 0 DC 0
Dled an mid DLED
Vsense mid cat DC 0
F_pt col emit Vsense 0.5
Rleak col emit 100Meg
.model DLED D(Is=1e-14 N=2 Rs=5)
.op
.end`);
const iLed = Math.abs(vec('i(vsense)')[0]);
const vcol = dcValue('v(col)');
expect(iLed).toBeGreaterThan(0.005);
expect(vcol).toBeLessThan(3.5);
});
});
describe('Example: l293d-motor-control', () => {
it('L293D forward: OUT1=Vmotor, OUT2=0', { timeout: 30_000 }, async () => {
const { dcValue } = await runNetlist(`L293D forward
Vmot vmot 0 DC 9
Ven en 0 DC 5
Vin1 in1 0 DC 5
Vin2 in2 0 DC 0
B_a out1 0 V = u(V(en)-2.5) * u(V(in1)-2.5) * V(vmot)
B_b out2 0 V = u(V(en)-2.5) * u(V(in2)-2.5) * V(vmot)
R_a out1 0 10Meg
R_b out2 0 10Meg
Rmotor out1 out2 10
.op
.end`);
expect(dcValue('v(out1)')).toBeGreaterThan(7);
expect(dcValue('v(out2)')).toBeLessThan(2);
});
});
describe('Example: l293d-speed-pwm', () => {
it('L293D with EN=PWM (avg 50%): output averages V_motor/2', { timeout: 30_000 }, async () => {
// DC equivalent of PWM 50%: EN sees 2.5V (average) → at threshold
// Use EN=5V (representing PWM HIGH duty 100%) to verify full-on case
const { dcValue } = await runNetlist(`L293D speed
Vmot vmot 0 DC 9
Ven en 0 DC 5
Vin in 0 DC 5
B_o out 0 V = u(V(en)-2.5) * u(V(in)-2.5) * V(vmot)
R_o out 0 10Meg
.op
.end`);
expect(dcValue('v(out)')).toBeCloseTo(9, 1);
});
});
// ════════════════════════════════════════════════════════════════════════════
// POWER / REGULATOR
// ════════════════════════════════════════════════════════════════════════════
describe('Example: power-supply-7805', () => {
it('7805 with V_in=9V → V_out=5V', { timeout: 30_000 }, async () => {
const { dcValue } = await runNetlist(`7805
Vin vin 0 DC 9
B_u1 vout 0 V = min(V(vin)-V(0)-2, 5)
R_load vout 0 1k
.op
.end`);
expect(dcValue('v(vout)')).toBeCloseTo(5, 1);
});
it('7805 dropout when V_in too low', { timeout: 30_000 }, async () => {
const { dcValue } = await runNetlist(`7805 dropout
Vin vin 0 DC 4
B_u1 vout 0 V = min(V(vin)-V(0)-2, 5)
R_load vout 0 1k
.op
.end`);
expect(dcValue('v(vout)')).toBeLessThan(3);
});
});
describe('Example: lm317-adjustable-psu', () => {
it('LM317 with R1=240, R2=720: V_out ≈ 5V', { timeout: 30_000 }, async () => {
const { dcValue } = await runNetlist(`LM317
Vin vin 0 DC 12
B_u1 vout 0 V = V(adj) + min(V(vin)-V(adj)-2, 1.25)
R1 vout adj 240
R2 adj 0 720
Rload vout 0 10k
.op
.end`);
expect(dcValue('v(vout)')).toBeCloseTo(5, 1);
});
});
describe('Example: battery-voltage-monitor', () => {
it('20k+10k divider scales 9V → 3V into ADC', { timeout: 30_000 }, async () => {
const { dcValue } = await runNetlist(`Battery monitor
Vbat vbat 0 DC 9
R1 vbat mid 20k
R2 mid 0 10k
.op
.end`);
expect(dcValue('v(mid)')).toBeCloseTo(3.0, 2);
});
});
// ════════════════════════════════════════════════════════════════════════════
// ESP32 / MEGA / NANO board-specific
// ════════════════════════════════════════════════════════════════════════════
describe('Example: esp32-dual-adc', () => {
it('Two pots at 3.3V supply: each leg is independent', { timeout: 30_000 }, async () => {
const { dcValue } = await runNetlist(`ESP32 dual pot
V1 vcc 0 DC 3.3
Rt1 vcc s1 5k
Rb1 s1 0 5k
Rt2 vcc s2 3k
Rb2 s2 0 7k
.op
.end`);
expect(dcValue('v(s1)')).toBeCloseTo(1.65, 2);
expect(dcValue('v(s2)')).toBeCloseTo(2.31, 2);
});
});
describe('Example: mega-multi-led', () => {
it('Mega 5V LED with 220Ω limit', { timeout: 30_000 }, async () => {
const { vec } = await runNetlist(`Mega LED
V1 pin 0 DC 5
R1 pin anode 220
D1 anode 0 DLED
.model DLED D(Is=1e-14 N=1.8 Rs=1)
.op
.end`);
expect(Math.abs(vec('i(v1)')[0])).toBeGreaterThan(0.005);
});
});
describe('Example: nano-sensor-station', () => {
for (const T of [10, 25, 40]) {
it(`Nano NTC at ${T}°C: V_out depends on R_ntc`, { timeout: 30_000 }, async () => {
const r = ntcR(T);
const { dcValue } = await runNetlist(`Nano NTC
V1 vcc 0 DC 5
Rntc vcc out ${r}
Rpull out 0 10k
.op
.end`);
const expected = 5 * 10000 / (r + 10000);
expect(dcValue('v(out)')).toBeCloseTo(expected, 2);
});
}
});
describe('Example: esp32-pwm-led-rgb', () => {
it('RGB LED full red @3.3V through 220Ω: red anode conducts', { timeout: 30_000 }, async () => {
const { vec } = await runNetlist(`RGB red full
V1 pin 0 DC 3.3
R1 pin anode 220
D1 anode 0 DLED
.model DLED D(Is=1e-14 N=1.8 Rs=1)
.op
.end`);
const i = Math.abs(vec('i(v1)')[0]);
expect(i).toBeGreaterThan(0.001);
expect(i).toBeLessThan(0.012);
});
});