ee-toolkit 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ee_toolkit/__about__.py +4 -0
- ee_toolkit/__init__.py +3 -0
- ee_toolkit/cli.py +46 -0
- ee_toolkit/eseries.py +240 -0
- ee_toolkit/filters.py +134 -0
- ee_toolkit/mosfet.py +95 -0
- ee_toolkit/opamp.py +180 -0
- ee_toolkit/oscillator.py +107 -0
- ee_toolkit/resistor.py +258 -0
- ee_toolkit/smps.py +330 -0
- ee_toolkit/units.py +77 -0
- ee_toolkit-0.0.2.dist-info/METADATA +111 -0
- ee_toolkit-0.0.2.dist-info/RECORD +16 -0
- ee_toolkit-0.0.2.dist-info/WHEEL +4 -0
- ee_toolkit-0.0.2.dist-info/entry_points.txt +2 -0
- ee_toolkit-0.0.2.dist-info/licenses/LICENSE.txt +673 -0
ee_toolkit/smps.py
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Switching converter power-stage sizing (boost / buck).
|
|
3
|
+
|
|
4
|
+
All formulas are the standard CCM (continuous-conduction-mode) first-order
|
|
5
|
+
approximations: the input/output voltages are assumed constant within a
|
|
6
|
+
switching cycle, the converter is assumed to stay in CCM, and ESR/ESL are
|
|
7
|
+
ignored (negligible for ceramics, add ΔV_ESR = ΔI·ESR for electrolytics).
|
|
8
|
+
Good to ~10–20 % — the right accuracy for picking parts.
|
|
9
|
+
|
|
10
|
+
Inductor (V = L·dI/dt over the charging interval)
|
|
11
|
+
-------------------------------------------------
|
|
12
|
+
Boost duty: D = 1 - Vin/Vrail (volt-second balance)
|
|
13
|
+
Buck duty: D = Vout/Vrail
|
|
14
|
+
Boost ripple: ΔI = Vin·D / (L·fsw) = Vin(1-Vin/Vrail)/(L·fsw)
|
|
15
|
+
Buck ripple: ΔI = Vout(1-D) / (L·fsw) = Vout(1-Vout/Vrail)/(L·fsw)
|
|
16
|
+
|
|
17
|
+
The ripple term V(1-V/Vrail) is a downward parabola, maximised at V = Vrail/2,
|
|
18
|
+
so that is the worst-case ripple operating point used for sizing L.
|
|
19
|
+
|
|
20
|
+
Capacitor (I = C·dV/dt)
|
|
21
|
+
-----------------------
|
|
22
|
+
Buck output: C = ΔI / (8·fsw·ΔV) (cap absorbs only the triangular ripple)
|
|
23
|
+
Boost output: C = Iout·D / (fsw·ΔV) (cap supplies the FULL load during D·T)
|
|
24
|
+
Buck input RMS: I_rms = Iout·√(D(1-D)) (pulsed input current heating)
|
|
25
|
+
"""
|
|
26
|
+
import math
|
|
27
|
+
|
|
28
|
+
import click
|
|
29
|
+
|
|
30
|
+
from .units import parse_si, fmt_si
|
|
31
|
+
from .eseries import nearest_in_series
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
# Duty cycle (steady-state volt-second balance)
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
def boost_duty(vin: float, vrail: float) -> float:
|
|
39
|
+
"""Boost duty cycle D = 1 - Vin/Vrail."""
|
|
40
|
+
return 1.0 - vin / vrail
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def buck_duty(vrail: float, vout: float) -> float:
|
|
44
|
+
"""Buck duty cycle D = Vout/Vrail."""
|
|
45
|
+
return vout / vrail
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# Inductor ripple / sizing
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
def boost_inductor_ripple(vin: float, vrail: float, l: float, fsw: float) -> float:
|
|
53
|
+
"""Peak-to-peak inductor ripple current of a boost stage (A)."""
|
|
54
|
+
return vin * boost_duty(vin, vrail) / (l * fsw)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def boost_inductance(vin: float, vrail: float, ripple_i: float, fsw: float) -> float:
|
|
58
|
+
"""Inductance (H) giving *ripple_i* peak-to-peak ripple at this operating point."""
|
|
59
|
+
return vin * boost_duty(vin, vrail) / (ripple_i * fsw)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def buck_inductor_ripple(vrail: float, vout: float, l: float, fsw: float) -> float:
|
|
63
|
+
"""Peak-to-peak inductor ripple current of a buck stage (A)."""
|
|
64
|
+
return vout * (1.0 - buck_duty(vrail, vout)) / (l * fsw)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def buck_inductance(vrail: float, vout: float, ripple_i: float, fsw: float) -> float:
|
|
68
|
+
"""Inductance (H) giving *ripple_i* peak-to-peak ripple at this operating point."""
|
|
69
|
+
return vout * (1.0 - buck_duty(vrail, vout)) / (ripple_i * fsw)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
# Capacitor sizing
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
def buck_output_cap(ripple_i: float, fsw: float, ripple_v: float) -> float:
|
|
77
|
+
"""Output capacitance (F) bounding buck output ripple to *ripple_v* (capacitive term)."""
|
|
78
|
+
return ripple_i / (8.0 * fsw * ripple_v)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def boost_output_cap(iout: float, duty: float, fsw: float, ripple_v: float) -> float:
|
|
82
|
+
"""Output capacitance (F) bounding boost output ripple to *ripple_v*.
|
|
83
|
+
|
|
84
|
+
*iout* is the boost stage output current (= P_rail / Vrail); the cap alone
|
|
85
|
+
supplies the load during the D·T on-time.
|
|
86
|
+
"""
|
|
87
|
+
return iout * duty / (fsw * ripple_v)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def buck_input_rms(iout: float, duty: float) -> float:
|
|
91
|
+
"""RMS ripple current drawn by a buck stage from its input rail (A)."""
|
|
92
|
+
return iout * math.sqrt(duty * (1.0 - duty))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
# Worst-case helper
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
def worst_ripple_voltage(v_low: float, v_high: float, vrail: float) -> float:
|
|
100
|
+
"""Voltage in [v_low, v_high] that maximises ripple V·(1-V/Vrail).
|
|
101
|
+
|
|
102
|
+
Unconstrained maximum is at Vrail/2; clamp to the supplied range.
|
|
103
|
+
"""
|
|
104
|
+
v_star = vrail / 2.0
|
|
105
|
+
if v_star < v_low:
|
|
106
|
+
return v_low
|
|
107
|
+
if v_star > v_high:
|
|
108
|
+
return v_high
|
|
109
|
+
return v_star
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def inductor_rms(i_avg: float, ripple_i: float) -> float:
|
|
113
|
+
"""RMS current of a triangular-ripple inductor waveform (A)."""
|
|
114
|
+
return math.sqrt(i_avg ** 2 + (ripple_i ** 2) / 12.0)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def snap_e12(value: float):
|
|
118
|
+
"""Snap *value* to the nearest E12 standard value in any decade.
|
|
119
|
+
|
|
120
|
+
Thin wrapper over :func:`eseries.nearest_in_series` for L/C component picks.
|
|
121
|
+
Returns (snapped_value, error_percent).
|
|
122
|
+
"""
|
|
123
|
+
return nearest_in_series(value, 'E12')
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
# Click commands
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
def _snap(value: float, unit: str):
|
|
131
|
+
"""Snap *value* to the nearest E12 standard value; return (snapped, text)."""
|
|
132
|
+
snapped, err = snap_e12(value)
|
|
133
|
+
return snapped, f"{fmt_si(snapped, unit)} ({err:+.1f}% vs {fmt_si(value, unit)} ideal)"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _hdr(text: str):
|
|
137
|
+
click.secho(text, fg='cyan', bold=True)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _row(cells, widths, color=None, marker=''):
|
|
141
|
+
line = ' ' + ' '.join(f"{c:<{w}}" for c, w in zip(cells, widths))
|
|
142
|
+
click.secho(line + marker, fg=color)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@click.group()
|
|
146
|
+
def smps_group():
|
|
147
|
+
"""Switching converter power-stage sizing (boost / buck)."""
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@smps_group.command('boost')
|
|
151
|
+
@click.option('--vin-min', 'vin_min_str', required=True, metavar='VALUE',
|
|
152
|
+
help='Minimum input voltage (pack empty), e.g. 9')
|
|
153
|
+
@click.option('--vin-max', 'vin_max_str', required=True, metavar='VALUE',
|
|
154
|
+
help='Maximum input voltage (pack full), e.g. 25.2')
|
|
155
|
+
@click.option('--vrail', 'vrail_str', required=True, metavar='VALUE',
|
|
156
|
+
help='Boost output / intermediate-rail voltage, e.g. 30')
|
|
157
|
+
@click.option('--pout', 'pout_str', required=True, metavar='VALUE',
|
|
158
|
+
help='Power delivered to the rail (boost output power), e.g. 105')
|
|
159
|
+
@click.option('--fsw', 'fsw_str', required=True, metavar='VALUE',
|
|
160
|
+
help='Switching frequency, e.g. 250k')
|
|
161
|
+
@click.option('--ripple', default=0.4, show_default=True,
|
|
162
|
+
help='Target peak-to-peak inductor ripple as a fraction of I_avg.')
|
|
163
|
+
@click.option('--eff', default=0.95, show_default=True,
|
|
164
|
+
help='Boost stage efficiency (for input-current estimate).')
|
|
165
|
+
@click.option('--dvrail', 'dvrail_str', default='300m', show_default=True, metavar='VALUE',
|
|
166
|
+
help='Allowed rail voltage ripple, e.g. 300m')
|
|
167
|
+
@click.option('--inductance', 'l_str', default=None, metavar='VALUE',
|
|
168
|
+
help='Analyse a fixed inductor instead of sizing one, e.g. 10u')
|
|
169
|
+
@click.option('--margin', default=1.3, show_default=True,
|
|
170
|
+
help='Safety margin applied to peak current for I_sat selection.')
|
|
171
|
+
def cmd_boost(vin_min_str, vin_max_str, vrail_str, pout_str, fsw_str,
|
|
172
|
+
ripple, eff, dvrail_str, l_str, margin):
|
|
173
|
+
"""Size a boost stage (Vin range → fixed rail).
|
|
174
|
+
|
|
175
|
+
\b
|
|
176
|
+
D = 1 - Vin/Vrail
|
|
177
|
+
ΔI = Vin·D / (L·fsw) (worst case at Vin = Vrail/2)
|
|
178
|
+
C_rail = Iout·D / (fsw·ΔV) (cap supplies full load during D·T)
|
|
179
|
+
|
|
180
|
+
Example (3S-6S pack → 30 V rail, 105 W):
|
|
181
|
+
ee-calc smps boost --vin-min 9 --vin-max 25.2 --vrail 30 --pout 105 --fsw 250k
|
|
182
|
+
"""
|
|
183
|
+
vin_min = parse_si(vin_min_str)
|
|
184
|
+
vin_max = parse_si(vin_max_str)
|
|
185
|
+
vrail = parse_si(vrail_str)
|
|
186
|
+
pout = parse_si(pout_str)
|
|
187
|
+
fsw = parse_si(fsw_str)
|
|
188
|
+
dvrail = parse_si(dvrail_str)
|
|
189
|
+
|
|
190
|
+
if vrail <= vin_max:
|
|
191
|
+
raise click.UsageError(
|
|
192
|
+
f"Rail ({fmt_si(vrail, 'V')}) must exceed max input "
|
|
193
|
+
f"({fmt_si(vin_max, 'V')}) — a boost can only step up.")
|
|
194
|
+
|
|
195
|
+
p_in = pout / eff
|
|
196
|
+
v_star = worst_ripple_voltage(vin_min, vin_max, vrail)
|
|
197
|
+
|
|
198
|
+
if l_str:
|
|
199
|
+
l = parse_si(l_str)
|
|
200
|
+
l_note = f"{fmt_si(l, 'H')} (as supplied)"
|
|
201
|
+
else:
|
|
202
|
+
i_avg_star = p_in / v_star
|
|
203
|
+
l_ideal = boost_inductance(v_star, vrail, ripple * i_avg_star, fsw)
|
|
204
|
+
l, l_note = _snap(l_ideal, 'H')
|
|
205
|
+
|
|
206
|
+
_hdr("Boost stage")
|
|
207
|
+
click.echo(f" Vin = {fmt_si(vin_min, 'V')} … {fmt_si(vin_max, 'V')}")
|
|
208
|
+
click.echo(f" Vrail = {fmt_si(vrail, 'V')} Pout(rail) = {fmt_si(pout, 'W')} η = {eff:.0%}")
|
|
209
|
+
click.echo(f" fsw = {fmt_si(fsw, 'Hz')} Pin ≈ {fmt_si(p_in, 'W')}")
|
|
210
|
+
click.secho(f" L = {l_note}", fg='green', bold=True)
|
|
211
|
+
|
|
212
|
+
_hdr("\nOperating sweep")
|
|
213
|
+
widths = (10, 7, 10, 10, 9, 10)
|
|
214
|
+
_row(('Vin', 'Duty', 'I_avg', 'ΔI(pp)', 'ripple', 'I_peak'), widths)
|
|
215
|
+
click.echo(" " + "─" * 62)
|
|
216
|
+
peak_max = 0.0
|
|
217
|
+
irms_max = 0.0
|
|
218
|
+
for vin in (vin_min, v_star, vin_max):
|
|
219
|
+
d = boost_duty(vin, vrail)
|
|
220
|
+
i_avg = p_in / vin
|
|
221
|
+
di = boost_inductor_ripple(vin, vrail, l, fsw)
|
|
222
|
+
i_peak = i_avg + di / 2.0
|
|
223
|
+
peak_max = max(peak_max, i_peak)
|
|
224
|
+
irms_max = max(irms_max, inductor_rms(i_avg, di))
|
|
225
|
+
marker = ' ◀ worst ripple' if abs(vin - v_star) < 1e-9 else ''
|
|
226
|
+
_row((f"{vin:.2f} V", f"{d:.3f}", f"{i_avg:.2f} A",
|
|
227
|
+
f"{di:.2f} A", f"{di / i_avg:.0%}", f"{i_peak:.2f} A"),
|
|
228
|
+
widths, marker=marker)
|
|
229
|
+
|
|
230
|
+
i_out = pout / vrail
|
|
231
|
+
c_rail_ideal = boost_output_cap(i_out, boost_duty(vin_min, vrail), fsw, dvrail)
|
|
232
|
+
_, c_note = _snap(c_rail_ideal, 'F')
|
|
233
|
+
|
|
234
|
+
_hdr("\nComponent selection")
|
|
235
|
+
click.secho(f" Inductor : ≥ {fmt_si(peak_max * margin, 'A')} I_sat "
|
|
236
|
+
f"(peak {fmt_si(peak_max, 'A')} × {margin:g}), "
|
|
237
|
+
f"≥ {fmt_si(irms_max, 'A')} I_rms", fg='green')
|
|
238
|
+
click.secho(f" Rail cap : {c_note}", fg='green')
|
|
239
|
+
click.secho(" (size for RMS current too — the buck input adds to this rail)",
|
|
240
|
+
fg='yellow')
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@smps_group.command('buck')
|
|
244
|
+
@click.option('--vrail', 'vrail_str', required=True, metavar='VALUE',
|
|
245
|
+
help='Input / intermediate-rail voltage, e.g. 30')
|
|
246
|
+
@click.option('--vout-min', 'vout_min_str', required=True, metavar='VALUE',
|
|
247
|
+
help='Minimum output voltage, e.g. 3.3')
|
|
248
|
+
@click.option('--vout-max', 'vout_max_str', required=True, metavar='VALUE',
|
|
249
|
+
help='Maximum output voltage, e.g. 21')
|
|
250
|
+
@click.option('--iout', 'iout_str', required=True, metavar='VALUE',
|
|
251
|
+
help='Maximum output current, e.g. 5')
|
|
252
|
+
@click.option('--fsw', 'fsw_str', required=True, metavar='VALUE',
|
|
253
|
+
help='Switching frequency, e.g. 250k')
|
|
254
|
+
@click.option('--ripple', default=0.4, show_default=True,
|
|
255
|
+
help='Target peak-to-peak inductor ripple as a fraction of I_out.')
|
|
256
|
+
@click.option('--dvout', 'dvout_str', default='50m', show_default=True, metavar='VALUE',
|
|
257
|
+
help='Allowed output voltage ripple, e.g. 50m')
|
|
258
|
+
@click.option('--inductance', 'l_str', default=None, metavar='VALUE',
|
|
259
|
+
help='Analyse a fixed inductor instead of sizing one, e.g. 15u')
|
|
260
|
+
@click.option('--margin', default=1.3, show_default=True,
|
|
261
|
+
help='Safety margin applied to peak current for I_sat selection.')
|
|
262
|
+
def cmd_buck(vrail_str, vout_min_str, vout_max_str, iout_str, fsw_str,
|
|
263
|
+
ripple, dvout_str, l_str, margin):
|
|
264
|
+
"""Size a buck stage (fixed rail → Vout range).
|
|
265
|
+
|
|
266
|
+
\b
|
|
267
|
+
D = Vout/Vrail
|
|
268
|
+
ΔI = Vout(1-D) / (L·fsw) (worst case at Vout = Vrail/2)
|
|
269
|
+
C_out = ΔI / (8·fsw·ΔV)
|
|
270
|
+
I_rail,rms = Iout·√(D(1-D)) (rail cap RMS, max at D=0.5)
|
|
271
|
+
|
|
272
|
+
Example (30 V rail → PD/PPS 3.3-21 V, 5 A):
|
|
273
|
+
ee-calc smps buck --vrail 30 --vout-min 3.3 --vout-max 21 --iout 5 --fsw 250k
|
|
274
|
+
"""
|
|
275
|
+
vrail = parse_si(vrail_str)
|
|
276
|
+
vout_min = parse_si(vout_min_str)
|
|
277
|
+
vout_max = parse_si(vout_max_str)
|
|
278
|
+
iout = parse_si(iout_str)
|
|
279
|
+
fsw = parse_si(fsw_str)
|
|
280
|
+
dvout = parse_si(dvout_str)
|
|
281
|
+
|
|
282
|
+
if vout_max >= vrail:
|
|
283
|
+
raise click.UsageError(
|
|
284
|
+
f"Max output ({fmt_si(vout_max, 'V')}) must be below the rail "
|
|
285
|
+
f"({fmt_si(vrail, 'V')}) — a buck can only step down.")
|
|
286
|
+
|
|
287
|
+
v_star = worst_ripple_voltage(vout_min, vout_max, vrail)
|
|
288
|
+
|
|
289
|
+
if l_str:
|
|
290
|
+
l = parse_si(l_str)
|
|
291
|
+
l_note = f"{fmt_si(l, 'H')} (as supplied)"
|
|
292
|
+
else:
|
|
293
|
+
l_ideal = buck_inductance(vrail, v_star, ripple * iout, fsw)
|
|
294
|
+
l, l_note = _snap(l_ideal, 'H')
|
|
295
|
+
|
|
296
|
+
_hdr("Buck stage")
|
|
297
|
+
click.echo(f" Vrail = {fmt_si(vrail, 'V')}")
|
|
298
|
+
click.echo(f" Vout = {fmt_si(vout_min, 'V')} … {fmt_si(vout_max, 'V')} Iout = {fmt_si(iout, 'A')}")
|
|
299
|
+
click.echo(f" fsw = {fmt_si(fsw, 'Hz')}")
|
|
300
|
+
click.secho(f" L = {l_note}", fg='green', bold=True)
|
|
301
|
+
|
|
302
|
+
_hdr("\nOperating sweep")
|
|
303
|
+
widths = (10, 7, 10, 9, 10, 11)
|
|
304
|
+
_row(('Vout', 'Duty', 'ΔI(pp)', 'ripple', 'I_peak', 'I_rail,rms'), widths)
|
|
305
|
+
click.echo(" " + "─" * 64)
|
|
306
|
+
di_max = 0.0
|
|
307
|
+
irms_max = 0.0
|
|
308
|
+
for vout in (vout_min, v_star, vout_max):
|
|
309
|
+
d = buck_duty(vrail, vout)
|
|
310
|
+
di = buck_inductor_ripple(vrail, vout, l, fsw)
|
|
311
|
+
i_peak = iout + di / 2.0
|
|
312
|
+
i_rms = buck_input_rms(iout, d)
|
|
313
|
+
di_max = max(di_max, di)
|
|
314
|
+
irms_max = max(irms_max, i_rms)
|
|
315
|
+
marker = ' ◀ worst ripple' if abs(vout - v_star) < 1e-9 else ''
|
|
316
|
+
_row((f"{vout:.2f} V", f"{d:.3f}", f"{di:.2f} A",
|
|
317
|
+
f"{di / iout:.0%}", f"{i_peak:.2f} A", f"{i_rms:.2f} A"),
|
|
318
|
+
widths, marker=marker)
|
|
319
|
+
|
|
320
|
+
c_out_ideal = buck_output_cap(di_max, fsw, dvout)
|
|
321
|
+
_, c_note = _snap(c_out_ideal, 'F')
|
|
322
|
+
|
|
323
|
+
_hdr("\nComponent selection")
|
|
324
|
+
click.secho(f" Inductor : ≥ {fmt_si((iout + di_max / 2) * margin, 'A')} I_sat, "
|
|
325
|
+
f"≥ {fmt_si(iout, 'A')} I_rms", fg='green')
|
|
326
|
+
click.secho(f" Out cap : {c_note}", fg='green')
|
|
327
|
+
click.secho(f" Rail cap : carries ≥ {fmt_si(irms_max, 'A')} RMS from the buck side",
|
|
328
|
+
fg='green')
|
|
329
|
+
click.secho(" (don't oversize C_out — it slows PPS voltage slewing)",
|
|
330
|
+
fg='yellow')
|
ee_toolkit/units.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared utilities: SI prefix parsing and pretty-printing.
|
|
3
|
+
"""
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
_PREFIX_MAP = {
|
|
7
|
+
'G': 1e9,
|
|
8
|
+
'M': 1e6,
|
|
9
|
+
'k': 1e3,
|
|
10
|
+
'K': 1e3,
|
|
11
|
+
'': 1.0,
|
|
12
|
+
'm': 1e-3,
|
|
13
|
+
'u': 1e-6,
|
|
14
|
+
'µ': 1e-6,
|
|
15
|
+
'n': 1e-9,
|
|
16
|
+
'p': 1e-12,
|
|
17
|
+
'f': 1e-15,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
_SI_SCALE = [
|
|
21
|
+
(1e9, 'G'),
|
|
22
|
+
(1e6, 'M'),
|
|
23
|
+
(1e3, 'k'),
|
|
24
|
+
(1.0, ''),
|
|
25
|
+
(1e-3, 'm'),
|
|
26
|
+
(1e-6, 'µ'),
|
|
27
|
+
(1e-9, 'n'),
|
|
28
|
+
(1e-12,'p'),
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
_UNIT_RE = re.compile(
|
|
32
|
+
r'^\s*([+-]?[0-9]*\.?[0-9]+(?:[eE][+-]?[0-9]+)?)'
|
|
33
|
+
r'\s*([GMkKmuµnpf]?)\s*$'
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def parse_si(text: str) -> float:
|
|
38
|
+
"""
|
|
39
|
+
Parse a string with an optional SI prefix into a float.
|
|
40
|
+
|
|
41
|
+
Examples
|
|
42
|
+
--------
|
|
43
|
+
>>> parse_si('10k') # 10 000
|
|
44
|
+
10000.0
|
|
45
|
+
>>> parse_si('100n') # 100e-9
|
|
46
|
+
1e-07
|
|
47
|
+
>>> parse_si('3.3M') # 3 300 000
|
|
48
|
+
3300000.0
|
|
49
|
+
"""
|
|
50
|
+
m = _UNIT_RE.match(text)
|
|
51
|
+
if not m:
|
|
52
|
+
raise ValueError(
|
|
53
|
+
f"Cannot parse '{text}' – expected a number with an optional SI prefix "
|
|
54
|
+
"(G M k m u µ n p f)."
|
|
55
|
+
)
|
|
56
|
+
number = float(m.group(1))
|
|
57
|
+
prefix = m.group(2)
|
|
58
|
+
return number * _PREFIX_MAP[prefix]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def fmt_si(value: float, unit: str = '', decimals: int = 4) -> str:
|
|
62
|
+
"""
|
|
63
|
+
Format *value* with an SI prefix and optional unit string.
|
|
64
|
+
|
|
65
|
+
Examples
|
|
66
|
+
--------
|
|
67
|
+
>>> fmt_si(15920.0, 'Hz')
|
|
68
|
+
'15.92 kHz'
|
|
69
|
+
>>> fmt_si(1e-9, 'F')
|
|
70
|
+
'1.0 nF'
|
|
71
|
+
"""
|
|
72
|
+
for scale, prefix in _SI_SCALE:
|
|
73
|
+
if abs(value) >= scale:
|
|
74
|
+
return f"{value / scale:.{decimals}g} {prefix}{unit}"
|
|
75
|
+
# Fallback for very small values
|
|
76
|
+
scale, prefix = _SI_SCALE[-1]
|
|
77
|
+
return f"{value / scale:.{decimals}g} {prefix}{unit}"
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ee-toolkit
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Project-URL: Documentation, https://git.sr.ht/~laplace/ee-toolkit#readme
|
|
5
|
+
Project-URL: Issues, https://git.sr.ht/~laplace/ee-toolkit/todo
|
|
6
|
+
Project-URL: Source, https://git.sr.ht/~laplace/ee-toolkit
|
|
7
|
+
Author-email: Alexander Becker <nabla.becker@mailbox.org>
|
|
8
|
+
License-Expression: GPL-3.0-or-later
|
|
9
|
+
License-File: LICENSE.txt
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
18
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
19
|
+
Requires-Python: >=3.8
|
|
20
|
+
Requires-Dist: click>=8.0
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# ee-toolkit
|
|
24
|
+
|
|
25
|
+
[](https://pypi.org/project/ee-toolkit)
|
|
26
|
+
[](https://pypi.org/project/ee-toolkit)
|
|
27
|
+
|
|
28
|
+
A command-line EE calculator
|
|
29
|
+
|
|
30
|
+
This is essentially the cli version of many of the online 'electronics' focussed calculators
|
|
31
|
+
for all the usual things like filter cutoff frequencies or resonances or resistor approximations
|
|
32
|
+
|
|
33
|
+
## License
|
|
34
|
+
|
|
35
|
+
`ee-toolkit` is licensed under the terms of the GPL-v3 license.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install ee-toolkit
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Commands
|
|
44
|
+
|
|
45
|
+
### `filter`
|
|
46
|
+
```bash
|
|
47
|
+
ee-calc filter rc --r 10k --c 100n # RC cutoff
|
|
48
|
+
ee-calc filter lc --l 10u --c 100p # LC resonance
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### `eseries`
|
|
52
|
+
```bash
|
|
53
|
+
ee-calc eseries find --value 10.5k # nearest E24 values
|
|
54
|
+
ee-calc eseries find --value 47n --series E96 --unit F # E96 capacitor
|
|
55
|
+
ee-calc eseries find --value 10u --series E12 --unit H # E12 inductor
|
|
56
|
+
ee-calc eseries decompose --value 10.5k --combo series # two-resistor series
|
|
57
|
+
ee-calc eseries decompose --value 10.5k --combo parallel # two-resistor parallel
|
|
58
|
+
```
|
|
59
|
+
Value snapping is decade-independent — it works across the full pF…GΩ/µH range,
|
|
60
|
+
not just resistor decades.
|
|
61
|
+
|
|
62
|
+
### `smps`
|
|
63
|
+
Switching-converter power-stage sizing (inductor, capacitor, frequency trade).
|
|
64
|
+
```bash
|
|
65
|
+
# Boost stage: 3S-6S pack (9-25.2 V) → 30 V intermediate rail, 105 W
|
|
66
|
+
ee-calc smps boost --vin-min 9 --vin-max 25.2 --vrail 30 --pout 105 --fsw 250k
|
|
67
|
+
|
|
68
|
+
# Buck stage: 30 V rail → USB-PD/PPS output (3.3-21 V), 5 A
|
|
69
|
+
ee-calc smps buck --vrail 30 --vout-min 3.3 --vout-max 21 --iout 5 --fsw 250k
|
|
70
|
+
|
|
71
|
+
# Analyse a fixed inductor instead of sizing one
|
|
72
|
+
ee-calc smps buck --vrail 30 --vout-min 3.3 --vout-max 21 --iout 5 --fsw 250k --inductance 15u
|
|
73
|
+
```
|
|
74
|
+
Each command reports the recommended inductance (snapped to E12), an operating
|
|
75
|
+
sweep across the voltage range (duty, ripple, peak current) marking the
|
|
76
|
+
worst-case ripple point, and the inductor/capacitor selection including
|
|
77
|
+
saturation-current and RMS-current targets. Override ripple/voltage-ripple
|
|
78
|
+
targets with `--ripple`, `--dvrail`/`--dvout`.
|
|
79
|
+
|
|
80
|
+
### `oscillator`
|
|
81
|
+
```bash
|
|
82
|
+
ee-calc oscillator lc --l 10u --c 100p # LC tank
|
|
83
|
+
ee-calc oscillator colpitts --l 10u --c1 100p --c2 100p # Colpitts
|
|
84
|
+
ee-calc oscillator hartley --l1 5u --l2 5u --c 100p # Hartley
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### `opamp`
|
|
88
|
+
```bash
|
|
89
|
+
ee-calc opamp noninv --rf 100k --r1 10k # non-inverting gain
|
|
90
|
+
ee-calc opamp inv --rf 100k --r1 10k # inverting gain
|
|
91
|
+
ee-calc opamp integrator --r 10k --c 100n # integrator corner freq
|
|
92
|
+
ee-calc opamp integrator --r 10k --c 100n --freq 500 # gain at 500 Hz
|
|
93
|
+
ee-calc opamp differentiator --r 10k --c 100n # differentiator corner freq
|
|
94
|
+
ee-calc opamp differentiator --r 10k --c 100n --freq 2k # gain at 2 kHz
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## SI prefixes
|
|
98
|
+
|
|
99
|
+
All value inputs accept SI prefixes: `G M k m u µ n p f`
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
10k → 10 000
|
|
103
|
+
100n → 100 × 10⁻⁹
|
|
104
|
+
4.7M → 4 700 000
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Run tests
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
python -m pytest tests/ -v
|
|
111
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
ee_toolkit/__about__.py,sha256=KW48ZmlfhCUsX-7BLzk9VWpZljnynWruMdpV1Jmk7pI,138
|
|
2
|
+
ee_toolkit/__init__.py,sha256=Bl_M7kRF_a5EMK50H2KzTycOZvwxTYwX0QjDALGcV9U,107
|
|
3
|
+
ee_toolkit/cli.py,sha256=X3OJcqFnglJMpltlGEX59vlSWb0IMWRiS3BQCCm1tK8,1631
|
|
4
|
+
ee_toolkit/eseries.py,sha256=hnggGB_NsPYeO6qHVL9lFiy8wHlsomkvuTB7KU-eg7M,8510
|
|
5
|
+
ee_toolkit/filters.py,sha256=1VvJ94Q-0OoKOzuF-4kGbb80LVXT1A8_dUivAOXDLHY,4731
|
|
6
|
+
ee_toolkit/mosfet.py,sha256=8xSy4x7fNvF6kyF3OlSV7l1RWLavF3ZuLrLrLYUD0fM,3232
|
|
7
|
+
ee_toolkit/opamp.py,sha256=D-jhX1oBdTiQJdU_j7Lty7_SVo2Of6HI_xLt_F7gJl0,6819
|
|
8
|
+
ee_toolkit/oscillator.py,sha256=oaBM5-ATJt28qm091Ag3ZAeXCzYspJzqyt5IWEWb8Nc,3576
|
|
9
|
+
ee_toolkit/resistor.py,sha256=qzuCt6Zg5KRBDBZ9fU4QXyGJyUZQJrxfG_yTDg_y-EM,8680
|
|
10
|
+
ee_toolkit/smps.py,sha256=PP0ph2l4xEOqDaMrkDnYhNalxTMQDexSck970Kf0smA,13952
|
|
11
|
+
ee_toolkit/units.py,sha256=5j--iuTUnG3kllXeX8Z6VxgJQq1SiWg4h6Rq_EazM8k,1625
|
|
12
|
+
ee_toolkit-0.0.2.dist-info/METADATA,sha256=6OFWCorL6AlF7bRq-YgSYnt_yQD2rpiuhJ_B0GzSsHA,4005
|
|
13
|
+
ee_toolkit-0.0.2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
14
|
+
ee_toolkit-0.0.2.dist-info/entry_points.txt,sha256=B1HnQx9H8BtZXlIGnluo5Skav4demPDscKPWhibrnWw,47
|
|
15
|
+
ee_toolkit-0.0.2.dist-info/licenses/LICENSE.txt,sha256=769qERkGHy-UfR4fBzJY83KqNle54WBx1PQxdrIDEww,34961
|
|
16
|
+
ee_toolkit-0.0.2.dist-info/RECORD,,
|