feat(components): Regulated Power Supply with per-instance current limit
Adds a new picker entry 'Regulated Power Supply' under the analog
category. Conceptually fills the gap between wokwi-battery (fixed
DC) and wokwi-signal-generator (waveform focus): user chooses
voltage + mode (dc / ac) + currentLimit, no need to think about
battery chemistry or signal amplitudes.
Properties:
mode: 'dc' | 'ac' (default 'dc')
voltage: V (default 5)
frequency: Hz (default 50, only for AC)
currentLimit: A (default 1)
Design notes:
- No new Web Component. The tagName piggy-backs on
wokwi-signal-generator so the canvas renders the familiar
bench-instrument chrome — saves shipping a second 100+ LOC
Web Component for an identical 2-pin shape.
- SPICE: ideal V-source + ESR sized so a near-short reads
I ≈ 1.5·limit. ngspice has no native foldback so the limit
is a circuitVerifier rule, not a hard SPICE constraint.
- circuitVerifier: extends sourceComponents regex to include
power-supply AND honors the per-instance currentLimit
property as the threshold. Real bench supplies behave this
way — a 100mA-limited supply trips at 100mA, a 5A supply
tolerates 5A before flagging. The error code is
'source-overload' (not 'short-circuit') so the modal copy
matches what the user just configured.
The board GND / VCC pins of Arduino / ESP32 / etc. already act
as voltage sources via BOARD_PIN_GROUPS canonicalisation (the
NetlistBuilder maps wires to the right rail). So the user's
companion request — 'board pins should already work' — is the
existing behaviour; this commit only adds the standalone bench
supply for boardless circuits or for testing with a different
voltage.
This commit is contained in:
parent
3b527f3dce
commit
305170aeb9
|
|
@ -517,6 +517,66 @@
|
||||||
"category": "analog",
|
"category": "analog",
|
||||||
"description": "Single-channel optocoupler. CTR 80–600% (typ. 100%). Higher drive than 4N25, commonly used for MCU-to-mains isolation."
|
"description": "Single-channel optocoupler. CTR 80–600% (typ. 100%). Higher drive than 4N25, commonly used for MCU-to-mains isolation."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"thumbnail": "<svg width=\"64\" height=\"64\" xmlns=\"http://www.w3.org/2000/svg\">\n <rect width=\"64\" height=\"64\" fill=\"#1a2332\" rx=\"4\"/>\n <rect x=\"10\" y=\"14\" width=\"44\" height=\"30\" fill=\"#2a3548\" rx=\"3\" stroke=\"#4a5878\"/>\n <text x=\"32\" y=\"30\" text-anchor=\"middle\" font-size=\"9\" font-family=\"monospace\" fill=\"#4ade80\" font-weight=\"bold\">5.00V</text>\n <text x=\"32\" y=\"40\" text-anchor=\"middle\" font-size=\"7\" font-family=\"monospace\" fill=\"#fbbf24\">1.00A</text>\n <circle cx=\"18\" cy=\"52\" r=\"3\" fill=\"#dc2626\"/>\n <circle cx=\"46\" cy=\"52\" r=\"3\" fill=\"#0f172a\" stroke=\"#64748b\"/>\n <text x=\"32\" y=\"60\" text-anchor=\"middle\" font-size=\"6\" fill=\"#9d9d9d\">PSU</text>\n </svg>",
|
||||||
|
"tags": [
|
||||||
|
"power-supply",
|
||||||
|
"regulated",
|
||||||
|
"source",
|
||||||
|
"psu",
|
||||||
|
"bench",
|
||||||
|
"lab",
|
||||||
|
"dc",
|
||||||
|
"ac",
|
||||||
|
"analog"
|
||||||
|
],
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"name": "mode",
|
||||||
|
"type": "string",
|
||||||
|
"defaultValue": "dc",
|
||||||
|
"control": "select",
|
||||||
|
"options": [
|
||||||
|
"dc",
|
||||||
|
"ac"
|
||||||
|
],
|
||||||
|
"description": "DC for fixed rails, AC for sine waveform output"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "voltage",
|
||||||
|
"type": "number",
|
||||||
|
"defaultValue": 5,
|
||||||
|
"control": "text",
|
||||||
|
"description": "Output voltage in V (DC mode) or peak amplitude (AC mode)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "frequency",
|
||||||
|
"type": "number",
|
||||||
|
"defaultValue": 50,
|
||||||
|
"control": "text",
|
||||||
|
"description": "Frequency in Hz (AC mode only)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "currentLimit",
|
||||||
|
"type": "number",
|
||||||
|
"defaultValue": 1,
|
||||||
|
"control": "text",
|
||||||
|
"description": "Maximum sourced current in A. The verifier warns when exceeded."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"defaultValues": {
|
||||||
|
"mode": "dc",
|
||||||
|
"voltage": 5,
|
||||||
|
"frequency": 50,
|
||||||
|
"currentLimit": 1
|
||||||
|
},
|
||||||
|
"pinCount": 2,
|
||||||
|
"id": "power-supply",
|
||||||
|
"tagName": "wokwi-signal-generator",
|
||||||
|
"name": "Regulated Power Supply",
|
||||||
|
"category": "analog",
|
||||||
|
"description": "Bench DC/AC power supply with optional current limit. DC mode for fixed rails; AC mode for sine. Verifier enforces the current limit per instance."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"thumbnail": "<svg width=\"64\" height=\"64\" xmlns=\"http://www.w3.org/2000/svg\">\n <rect width=\"64\" height=\"64\" fill=\"#e0e0e0\" rx=\"4\"/>\n <text x=\"50%\" y=\"50%\" text-anchor=\"middle\" dy=\".3em\" font-size=\"10\" fill=\"#666\">\n SIGNAL-GENERATOR\n </text>\n </svg>",
|
"thumbnail": "<svg width=\"64\" height=\"64\" xmlns=\"http://www.w3.org/2000/svg\">\n <rect width=\"64\" height=\"64\" fill=\"#e0e0e0\" rx=\"4\"/>\n <text x=\"50%\" y=\"50%\" text-anchor=\"middle\" dy=\".3em\" font-size=\"10\" fill=\"#666\">\n SIGNAL-GENERATOR\n </text>\n </svg>",
|
||||||
"tags": [
|
"tags": [
|
||||||
|
|
|
||||||
|
|
@ -548,6 +548,41 @@ const MAPPERS: Record<string, Mapper> = {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ── Regulated power supply (DC / AC, current-limit aware) ───────────────
|
||||||
|
// Properties:
|
||||||
|
// mode: 'dc' | 'ac' (default 'dc')
|
||||||
|
// voltage: V (default 5)
|
||||||
|
// frequency: Hz (default 50, only used in AC mode)
|
||||||
|
// currentLimit: A (default 1; enforced by circuitVerifier,
|
||||||
|
// NOT SPICE — ngspice has no native
|
||||||
|
// foldback limiter)
|
||||||
|
// The visual element reuses wokwi-signal-generator (no new Web Component
|
||||||
|
// needed). ESR is derived from the currentLimit so a near-short reads
|
||||||
|
// as I ≈ 1.5·limit, which the verifier then flags as source-overload.
|
||||||
|
'power-supply': (comp, netLookup) => {
|
||||||
|
const pos = netLookup('+') ?? netLookup('SIG') ?? netLookup('VCC');
|
||||||
|
const neg = netLookup('−') ?? netLookup('-') ?? netLookup('GND');
|
||||||
|
if (!pos || !neg) return null;
|
||||||
|
const mode = String(comp.properties.mode ?? 'dc').toLowerCase();
|
||||||
|
const voltage = Number(comp.properties.voltage ?? 5);
|
||||||
|
const currentLimit = Math.max(0.01, Number(comp.properties.currentLimit ?? 1));
|
||||||
|
const esr = Math.max(0.01, voltage / (currentLimit * 1.5));
|
||||||
|
let source: string;
|
||||||
|
if (mode === 'ac') {
|
||||||
|
const freq = Number(comp.properties.frequency ?? 50);
|
||||||
|
source = `SIN(0 ${voltage} ${freq})`;
|
||||||
|
} else {
|
||||||
|
source = `DC ${voltage}`;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
cards: [
|
||||||
|
`V_${comp.id} ${pos} ${comp.id}_int ${source}`,
|
||||||
|
`R_${comp.id}_esr ${comp.id}_int ${neg} ${esr}`,
|
||||||
|
],
|
||||||
|
modelsUsed: new Set(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
// ── Signal generator (AC / pulse / DC) ───────────────────────────────────
|
// ── Signal generator (AC / pulse / DC) ───────────────────────────────────
|
||||||
// Properties:
|
// Properties:
|
||||||
// waveform: 'sine' | 'square' | 'dc' (default 'sine')
|
// waveform: 'sine' | 'square' | 'dc' (default 'sine')
|
||||||
|
|
|
||||||
|
|
@ -128,22 +128,36 @@ export async function verifyCircuit(
|
||||||
const branchCurrents = solve.branchCurrents;
|
const branchCurrents = solve.branchCurrents;
|
||||||
|
|
||||||
// ── Rule 1: short circuit / power source overload ──────────────────────
|
// ── Rule 1: short circuit / power source overload ──────────────────────
|
||||||
// Every voltage source (battery / signal-generator) emits a branch current
|
// Every voltage source (battery / signal-generator / power-supply) emits
|
||||||
// `i(v_<id>)`. SPICE convention: V-source's current is measured + → −
|
// a branch current `i(v_<id>)`. SPICE convention: V-source's current is
|
||||||
// INTERNALLY, so external current draw is the absolute value.
|
// measured + → − INTERNALLY, so external current draw is the absolute
|
||||||
|
// value.
|
||||||
|
//
|
||||||
|
// power-supply components carry a per-instance `currentLimit` property
|
||||||
|
// that overrides the global short-circuit threshold — that matches what
|
||||||
|
// a real bench supply does: a 100mA-limited supply trips at 100mA, a
|
||||||
|
// 5A-limited supply tolerates up to 5A before flagging fault.
|
||||||
const sourceComponents = input.components.filter((c) =>
|
const sourceComponents = input.components.filter((c) =>
|
||||||
/^(battery|signal-generator)/.test(c.metadataId),
|
/^(battery|signal-generator|power-supply)/.test(c.metadataId),
|
||||||
);
|
);
|
||||||
for (const src of sourceComponents) {
|
for (const src of sourceComponents) {
|
||||||
const i = Math.abs(branchCurrents[`v_${src.id}`] ?? 0);
|
const i = Math.abs(branchCurrents[`v_${src.id}`] ?? 0);
|
||||||
if (i >= config.shortCircuitAmps) {
|
const perInstanceLimit =
|
||||||
|
src.metadataId === 'power-supply'
|
||||||
|
? Number(src.properties?.currentLimit ?? config.shortCircuitAmps)
|
||||||
|
: config.shortCircuitAmps;
|
||||||
|
const threshold = Number.isFinite(perInstanceLimit) && perInstanceLimit > 0
|
||||||
|
? perInstanceLimit
|
||||||
|
: config.shortCircuitAmps;
|
||||||
|
if (i >= threshold) {
|
||||||
|
const isPsu = src.metadataId === 'power-supply';
|
||||||
errors.push({
|
errors.push({
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
code: 'short-circuit',
|
code: isPsu ? 'source-overload' : 'short-circuit',
|
||||||
componentId: src.id,
|
componentId: src.id,
|
||||||
message: `Possible short circuit — ${src.metadataId} ${src.id} is delivering ${formatAmps(
|
message: isPsu
|
||||||
i,
|
? `Power supply ${src.id} is being asked for ${formatAmps(i)} — past its ${formatAmps(threshold)} current limit. A real bench supply would foldback or cut out. Raise the currentLimit or add more series resistance to the load.`
|
||||||
)} (threshold ${formatAmps(config.shortCircuitAmps)}). Check for 5 V tied directly to GND.`,
|
: `Possible short circuit — ${src.metadataId} ${src.id} is delivering ${formatAmps(i)} (threshold ${formatAmps(threshold)}). Check for power tied directly to GND.`,
|
||||||
metric: i,
|
metric: i,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1044,6 +1044,65 @@
|
||||||
"analog"
|
"analog"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "power-supply",
|
||||||
|
"tagName": "wokwi-signal-generator",
|
||||||
|
"name": "Regulated Power Supply",
|
||||||
|
"category": "analog",
|
||||||
|
"description": "Bench DC/AC power supply with optional current limit. DC mode for fixed rails (5V, 12V, etc.); AC mode for sine sources. Visually shares the signal-generator chassis so no new Web Component is needed. The current limit is enforced by the circuit verifier — if any load pulls more than the configured amperage, Run is blocked with a 'source-overload' warning, mimicking a real bench supply's foldback behavior.",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"name": "mode",
|
||||||
|
"type": "string",
|
||||||
|
"defaultValue": "dc",
|
||||||
|
"control": "select",
|
||||||
|
"options": [
|
||||||
|
"dc",
|
||||||
|
"ac"
|
||||||
|
],
|
||||||
|
"description": "DC for fixed rails, AC for sine waveform output"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "voltage",
|
||||||
|
"type": "number",
|
||||||
|
"defaultValue": 5,
|
||||||
|
"control": "text",
|
||||||
|
"description": "Output voltage in V (DC mode) or peak amplitude (AC mode)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "frequency",
|
||||||
|
"type": "number",
|
||||||
|
"defaultValue": 50,
|
||||||
|
"control": "text",
|
||||||
|
"description": "Frequency in Hz (AC mode only)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "currentLimit",
|
||||||
|
"type": "number",
|
||||||
|
"defaultValue": 1,
|
||||||
|
"control": "text",
|
||||||
|
"description": "Maximum sourced current in A. The verifier warns when exceeded."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"defaultValues": {
|
||||||
|
"mode": "dc",
|
||||||
|
"voltage": 5,
|
||||||
|
"frequency": 50,
|
||||||
|
"currentLimit": 1
|
||||||
|
},
|
||||||
|
"pinCount": 2,
|
||||||
|
"tags": [
|
||||||
|
"power-supply",
|
||||||
|
"regulated",
|
||||||
|
"source",
|
||||||
|
"psu",
|
||||||
|
"bench",
|
||||||
|
"lab",
|
||||||
|
"dc",
|
||||||
|
"ac",
|
||||||
|
"analog"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "signal-generator",
|
"id": "signal-generator",
|
||||||
"tagName": "wokwi-signal-generator",
|
"tagName": "wokwi-signal-generator",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue