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/__about__.py
ADDED
ee_toolkit/__init__.py
ADDED
ee_toolkit/cli.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main Click entry point for ee-calc.
|
|
3
|
+
|
|
4
|
+
Commands:
|
|
5
|
+
filter RC and LC filter cutoff frequencies
|
|
6
|
+
eseries E-series standard value lookup
|
|
7
|
+
oscillator LC/Colpitts/Hartley resonance frequencies
|
|
8
|
+
opamp Op-amp gain, integrator, differentiator
|
|
9
|
+
resistor Series / parallel resistor combinations
|
|
10
|
+
"""
|
|
11
|
+
import click
|
|
12
|
+
from .filters import filter_group
|
|
13
|
+
from .eseries import eseries_group
|
|
14
|
+
from .oscillator import oscillator_group
|
|
15
|
+
from .opamp import opamp_group
|
|
16
|
+
from .mosfet import mosfet_group
|
|
17
|
+
from .smps import smps_group
|
|
18
|
+
from .resistor import resistor_group
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@click.group()
|
|
22
|
+
@click.version_option(package_name='ee-calc')
|
|
23
|
+
def cli():
|
|
24
|
+
"""EE Calculator — quick electrical engineering calculations.
|
|
25
|
+
|
|
26
|
+
\b
|
|
27
|
+
Sub-commands:
|
|
28
|
+
filter RC / LC filter cutoff frequency
|
|
29
|
+
eseries Nearest E-series value / two-component decomposition
|
|
30
|
+
oscillator LC, Colpitts, Hartley resonance frequency
|
|
31
|
+
opamp Inverting / non-inverting gain, integrator, differentiator
|
|
32
|
+
mosfet Power MOSFET calculations (gate drive, switching)
|
|
33
|
+
smps Switching converter power-stage sizing (boost / buck)
|
|
34
|
+
resistor Series / parallel resistor combinations
|
|
35
|
+
|
|
36
|
+
Run ee-calc COMMAND --help for details on a command.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
cli.add_command(filter_group, name='filter')
|
|
41
|
+
cli.add_command(eseries_group, name='eseries')
|
|
42
|
+
cli.add_command(oscillator_group, name='oscillator')
|
|
43
|
+
cli.add_command(opamp_group, name='opamp')
|
|
44
|
+
cli.add_command(mosfet_group, name='mosfet')
|
|
45
|
+
cli.add_command(smps_group, name='smps')
|
|
46
|
+
cli.add_command(resistor_group, name='resistor')
|
ee_toolkit/eseries.py
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""
|
|
2
|
+
E-series resistor / capacitor standard value look-up.
|
|
3
|
+
|
|
4
|
+
Supported series: E12, E24, E48, E96, E192
|
|
5
|
+
"""
|
|
6
|
+
import math
|
|
7
|
+
from typing import List, Tuple, Literal
|
|
8
|
+
import click
|
|
9
|
+
from .units import parse_si, fmt_si
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# ---------------------------------------------------------------------------
|
|
13
|
+
# E-series base values (one decade, 3 significant figures)
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
_E12 = [1.0, 1.2, 1.5, 1.8, 2.2, 2.7, 3.3, 3.9, 4.7, 5.6, 6.8, 8.2]
|
|
17
|
+
|
|
18
|
+
_E24 = [
|
|
19
|
+
1.0, 1.1, 1.2, 1.3, 1.5, 1.6, 1.8, 2.0, 2.2, 2.4, 2.7, 3.0,
|
|
20
|
+
3.3, 3.6, 3.9, 4.3, 4.7, 5.1, 5.6, 6.2, 6.8, 7.5, 8.2, 9.1,
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
_E48 = [
|
|
24
|
+
1.00, 1.05, 1.10, 1.15, 1.21, 1.27, 1.33, 1.40, 1.47, 1.54, 1.62, 1.69,
|
|
25
|
+
1.78, 1.87, 1.96, 2.05, 2.15, 2.26, 2.37, 2.49, 2.61, 2.74, 2.87, 3.01,
|
|
26
|
+
3.16, 3.32, 3.48, 3.65, 3.83, 4.02, 4.22, 4.42, 4.64, 4.87, 5.11, 5.36,
|
|
27
|
+
5.62, 5.90, 6.19, 6.49, 6.81, 7.15, 7.50, 7.87, 8.25, 8.66, 9.09, 9.53,
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
_E96 = [
|
|
31
|
+
1.00, 1.02, 1.05, 1.07, 1.10, 1.13, 1.15, 1.18, 1.21, 1.24, 1.27, 1.30,
|
|
32
|
+
1.33, 1.37, 1.40, 1.43, 1.47, 1.50, 1.54, 1.58, 1.62, 1.65, 1.69, 1.74,
|
|
33
|
+
1.78, 1.82, 1.87, 1.91, 1.96, 2.00, 2.05, 2.10, 2.15, 2.21, 2.26, 2.32,
|
|
34
|
+
2.37, 2.43, 2.49, 2.55, 2.61, 2.67, 2.74, 2.80, 2.87, 2.94, 3.01, 3.09,
|
|
35
|
+
3.16, 3.24, 3.32, 3.40, 3.48, 3.57, 3.65, 3.74, 3.83, 3.92, 4.02, 4.12,
|
|
36
|
+
4.22, 4.32, 4.42, 4.53, 4.64, 4.75, 4.87, 4.99, 5.11, 5.23, 5.36, 5.49,
|
|
37
|
+
5.62, 5.76, 5.90, 6.04, 6.19, 6.34, 6.49, 6.65, 6.81, 6.98, 7.15, 7.32,
|
|
38
|
+
7.50, 7.68, 7.87, 8.06, 8.25, 8.45, 8.66, 8.87, 9.09, 9.31, 9.53, 9.76,
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
# E192 computed from IEC 60063 formula: round(10^(i/192), 2 sig figs)
|
|
42
|
+
def _make_e192() -> List[float]:
|
|
43
|
+
vals = []
|
|
44
|
+
for i in range(192):
|
|
45
|
+
raw = 10 ** (i / 192)
|
|
46
|
+
# round to 3 significant figures
|
|
47
|
+
mag = math.floor(math.log10(raw))
|
|
48
|
+
rounded = round(raw / 10**mag, 2) * 10**mag
|
|
49
|
+
vals.append(round(rounded, 6))
|
|
50
|
+
return vals
|
|
51
|
+
|
|
52
|
+
_E192 = _make_e192()
|
|
53
|
+
|
|
54
|
+
SERIES = {
|
|
55
|
+
'E12': _E12,
|
|
56
|
+
'E24': _E24,
|
|
57
|
+
'E48': _E48,
|
|
58
|
+
'E96': _E96,
|
|
59
|
+
'E192': _E192,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def all_values(series: str) -> List[float]:
|
|
64
|
+
"""Return all standard values across all decades (1Ω … 10MΩ typical)."""
|
|
65
|
+
base = SERIES[series.upper()]
|
|
66
|
+
result = []
|
|
67
|
+
for exp in range(-2, 8): # 0.01 … 10 000 000
|
|
68
|
+
for b in base:
|
|
69
|
+
result.append(round(b * (10 ** exp), 12))
|
|
70
|
+
return sorted(result)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _decade_candidates(target: float, base: List[float], span: int = 2) -> List[float]:
|
|
74
|
+
"""Generate E-series candidate values spanning ±*span* decades around *target*.
|
|
75
|
+
|
|
76
|
+
Decade-independent: works equally for resistors (kΩ), capacitors (nF) and
|
|
77
|
+
inductors (µH), unlike the fixed-range ``all_values``.
|
|
78
|
+
"""
|
|
79
|
+
decade = 10.0 ** math.floor(math.log10(target))
|
|
80
|
+
candidates = set()
|
|
81
|
+
for k in range(-span, span + 1):
|
|
82
|
+
scale = decade * (10.0 ** k)
|
|
83
|
+
for b in base:
|
|
84
|
+
candidates.add(b * scale)
|
|
85
|
+
return sorted(candidates)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def nearest_in_series(target: float, series: str) -> Tuple[float, float]:
|
|
89
|
+
"""Snap *target* to the single nearest E-series value (any decade).
|
|
90
|
+
|
|
91
|
+
Returns (value, error_pct).
|
|
92
|
+
"""
|
|
93
|
+
if target <= 0:
|
|
94
|
+
raise ValueError("target must be positive")
|
|
95
|
+
base = SERIES[series.upper()]
|
|
96
|
+
snapped = min(_decade_candidates(target, base), key=lambda v: abs(v - target))
|
|
97
|
+
return snapped, (snapped - target) / target * 100.0
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def find_nearest(target: float, series: str, n: int = 3) -> List[Tuple[float, float]]:
|
|
101
|
+
"""
|
|
102
|
+
Return the *n* closest standard values to *target* (value, error_pct).
|
|
103
|
+
|
|
104
|
+
Decade-independent across the full pF…GΩ range.
|
|
105
|
+
"""
|
|
106
|
+
if target <= 0:
|
|
107
|
+
raise ValueError("target must be positive")
|
|
108
|
+
base = SERIES[series.upper()]
|
|
109
|
+
candidates = _decade_candidates(target, base)
|
|
110
|
+
ranked = sorted(candidates, key=lambda v: abs(v - target))[:n]
|
|
111
|
+
return [(v, (v - target) / target * 100) for v in ranked]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def decompose(
|
|
115
|
+
target: float,
|
|
116
|
+
series: str,
|
|
117
|
+
mode: Literal['series', 'parallel'],
|
|
118
|
+
n_results: int = 5,
|
|
119
|
+
) -> List[Tuple[float, float, float]]:
|
|
120
|
+
"""
|
|
121
|
+
Find the best two-component (series or parallel) combination from *series*
|
|
122
|
+
that approximates *target*.
|
|
123
|
+
|
|
124
|
+
Returns list of (v1, v2, error_pct) sorted by |error|.
|
|
125
|
+
"""
|
|
126
|
+
base_vals = all_values(series)
|
|
127
|
+
# Limit search space: only values within a factor of target
|
|
128
|
+
lo, hi = target * 0.001, target * 1000
|
|
129
|
+
candidates = [v for v in base_vals if lo <= v <= hi]
|
|
130
|
+
|
|
131
|
+
results = []
|
|
132
|
+
seen = set()
|
|
133
|
+
|
|
134
|
+
for v1 in candidates:
|
|
135
|
+
if mode == 'series':
|
|
136
|
+
v2_ideal = target - v1
|
|
137
|
+
else: # parallel
|
|
138
|
+
if v1 == target:
|
|
139
|
+
continue
|
|
140
|
+
v2_ideal = (target * v1) / (v1 - target)
|
|
141
|
+
|
|
142
|
+
if v2_ideal <= 0:
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
# Find nearest E-series value for v2
|
|
146
|
+
nearest = min(candidates, key=lambda v: abs(v - v2_ideal))
|
|
147
|
+
v2 = nearest
|
|
148
|
+
|
|
149
|
+
if mode == 'series':
|
|
150
|
+
actual = v1 + v2
|
|
151
|
+
else:
|
|
152
|
+
actual = (v1 * v2) / (v1 + v2)
|
|
153
|
+
|
|
154
|
+
err = (actual - target) / target * 100
|
|
155
|
+
key = (round(min(v1, v2), 12), round(max(v1, v2), 12))
|
|
156
|
+
if key not in seen:
|
|
157
|
+
seen.add(key)
|
|
158
|
+
results.append((v1, v2, err))
|
|
159
|
+
|
|
160
|
+
results.sort(key=lambda x: abs(x[2]))
|
|
161
|
+
return results[:n_results]
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# ---------------------------------------------------------------------------
|
|
165
|
+
# Click commands
|
|
166
|
+
# ---------------------------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
_SERIES_CHOICE = click.Choice(['E12', 'E24', 'E48', 'E96', 'E192'],
|
|
169
|
+
case_sensitive=False)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@click.group()
|
|
173
|
+
def eseries_group():
|
|
174
|
+
"""E-series standard value look-up (E12 … E192)."""
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@eseries_group.command('find')
|
|
178
|
+
@click.option('--value', 'val_str', required=True, metavar='VALUE',
|
|
179
|
+
help='Target value, e.g. 10.5k, 220, 4.7M')
|
|
180
|
+
@click.option('--series', default='E24', show_default=True,
|
|
181
|
+
type=_SERIES_CHOICE, help='E-series to search.')
|
|
182
|
+
@click.option('-n', 'n', default=3, show_default=True,
|
|
183
|
+
help='Number of nearest values to show.')
|
|
184
|
+
@click.option('--unit', default='Ω', show_default=True,
|
|
185
|
+
help='Unit label for display (Ω, F, H…)')
|
|
186
|
+
def cmd_find(val_str, series, n, unit):
|
|
187
|
+
"""Find the closest E-series standard values to a target.
|
|
188
|
+
|
|
189
|
+
\b
|
|
190
|
+
Example:
|
|
191
|
+
ee-calc eseries find --value 10.5k
|
|
192
|
+
ee-calc eseries find --value 47n --series E48 --unit F
|
|
193
|
+
"""
|
|
194
|
+
target = parse_si(val_str)
|
|
195
|
+
results = find_nearest(target, series, n)
|
|
196
|
+
click.echo(f"Target : {fmt_si(target, unit)} (series {series.upper()})")
|
|
197
|
+
click.echo(f"{'Rank':<5} {'Value':<14} {'Error':>8}")
|
|
198
|
+
click.echo("─" * 30)
|
|
199
|
+
for i, (v, err) in enumerate(results, 1):
|
|
200
|
+
marker = " ◀" if i == 1 else ""
|
|
201
|
+
click.echo(f" {i:<3} {fmt_si(v, unit):<14} {err:>+7.3f}%{marker}")
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@eseries_group.command('decompose')
|
|
205
|
+
@click.option('--value', 'val_str', required=True, metavar='VALUE',
|
|
206
|
+
help='Target value, e.g. 10.5k')
|
|
207
|
+
@click.option('--series', default='E24', show_default=True,
|
|
208
|
+
type=_SERIES_CHOICE, help='E-series to use.')
|
|
209
|
+
@click.option('--combo', default='series',
|
|
210
|
+
type=click.Choice(['series', 'parallel'], case_sensitive=False),
|
|
211
|
+
show_default=True, help='Combination type.')
|
|
212
|
+
@click.option('-n', 'n', default=5, show_default=True,
|
|
213
|
+
help='Number of combinations to show.')
|
|
214
|
+
@click.option('--unit', default='Ω', show_default=True,
|
|
215
|
+
help='Unit label for display (Ω, F, H…)')
|
|
216
|
+
def cmd_decompose(val_str, series, combo, n, unit):
|
|
217
|
+
"""Find two E-series values (series or parallel) that approximate a target.
|
|
218
|
+
|
|
219
|
+
\b
|
|
220
|
+
Example:
|
|
221
|
+
ee-calc eseries decompose --value 10.5k
|
|
222
|
+
ee-calc eseries decompose --value 10.5k --combo parallel
|
|
223
|
+
"""
|
|
224
|
+
target = parse_si(val_str)
|
|
225
|
+
results = decompose(target, series, combo, n)
|
|
226
|
+
sym = '+' if combo == 'series' else '∥'
|
|
227
|
+
click.echo(f"Target : {fmt_si(target, unit)} "
|
|
228
|
+
f"(series {series.upper()}, {combo} combination)")
|
|
229
|
+
click.echo(f"{'Rank':<5} {'V1':<14} {'V2':<14} {'Result':<14} {'Error':>8}")
|
|
230
|
+
click.echo("─" * 58)
|
|
231
|
+
for i, (v1, v2, err) in enumerate(results, 1):
|
|
232
|
+
if combo == 'series':
|
|
233
|
+
actual = v1 + v2
|
|
234
|
+
else:
|
|
235
|
+
actual = (v1 * v2) / (v1 + v2)
|
|
236
|
+
marker = " ◀" if i == 1 else ""
|
|
237
|
+
click.echo(
|
|
238
|
+
f" {i:<3} {fmt_si(v1, unit):<14} {fmt_si(v2, unit):<14} "
|
|
239
|
+
f"{fmt_si(actual, unit):<14} {err:>+7.3f}%{marker}"
|
|
240
|
+
)
|
ee_toolkit/filters.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Filter cutoff / resonance frequency calculations.
|
|
3
|
+
|
|
4
|
+
RC filter: f = 1 / (2π · R · C)
|
|
5
|
+
LC filter: f = 1 / (2π · √(L · C))
|
|
6
|
+
RC band-pass: two cascaded RC stages (HPF + LPF)
|
|
7
|
+
"""
|
|
8
|
+
import math
|
|
9
|
+
from .units import parse_si, fmt_si
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def rc_cutoff(r: float, c: float) -> float:
|
|
13
|
+
"""Return the -3 dB cutoff frequency of an RC filter (Hz)."""
|
|
14
|
+
return 1.0 / (2.0 * math.pi * r * c)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def lc_resonance(l: float, c: float) -> float:
|
|
18
|
+
"""Return the resonance frequency of an LC network (Hz)."""
|
|
19
|
+
return 1.0 / (2.0 * math.pi * math.sqrt(l * c))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def rc_bandpass(r_hp: float, c_hp: float, r_lp: float, c_lp: float):
|
|
23
|
+
"""Return (f_low, f_high, f_centre, bandwidth) for a two-stage RC band-pass.
|
|
24
|
+
|
|
25
|
+
The high-pass stage (R_hp, C_hp) sets the lower -3 dB edge;
|
|
26
|
+
the low-pass stage (R_lp, C_lp) sets the upper -3 dB edge.
|
|
27
|
+
"""
|
|
28
|
+
f_low = rc_cutoff(r_hp, c_hp)
|
|
29
|
+
f_high = rc_cutoff(r_lp, c_lp)
|
|
30
|
+
if f_low > f_high:
|
|
31
|
+
raise ValueError(
|
|
32
|
+
f"High-pass cutoff ({f_low:.3g} Hz) must be lower than "
|
|
33
|
+
f"low-pass cutoff ({f_high:.3g} Hz)."
|
|
34
|
+
)
|
|
35
|
+
f_centre = math.sqrt(f_low * f_high)
|
|
36
|
+
bandwidth = f_high - f_low
|
|
37
|
+
return f_low, f_high, f_centre, bandwidth
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
# Click commands
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
import click
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@click.group()
|
|
47
|
+
def filter_group():
|
|
48
|
+
"""Filter frequency calculations (RC, LC, RC band-pass)."""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@filter_group.command('rc')
|
|
52
|
+
@click.option('--r', 'r_str', required=True, metavar='VALUE',
|
|
53
|
+
help='Resistance, e.g. 10k, 4.7M, 220')
|
|
54
|
+
@click.option('--c', 'c_str', required=True, metavar='VALUE',
|
|
55
|
+
help='Capacitance, e.g. 100n, 10u, 220p')
|
|
56
|
+
@click.option('--type', 'ftype', default='low',
|
|
57
|
+
type=click.Choice(['low', 'high'], case_sensitive=False),
|
|
58
|
+
show_default=True, help='Filter type (informational).')
|
|
59
|
+
def cmd_rc(r_str, c_str, ftype):
|
|
60
|
+
"""RC filter -3 dB cutoff frequency.
|
|
61
|
+
|
|
62
|
+
\b
|
|
63
|
+
f = 1 / (2π · R · C)
|
|
64
|
+
|
|
65
|
+
Example:
|
|
66
|
+
ee-calc filter rc --r 10k --c 100n
|
|
67
|
+
"""
|
|
68
|
+
r = parse_si(r_str)
|
|
69
|
+
c = parse_si(c_str)
|
|
70
|
+
f = rc_cutoff(r, c)
|
|
71
|
+
click.echo(f"RC {ftype}-pass filter")
|
|
72
|
+
click.echo(f" R = {fmt_si(r, 'Ω')}")
|
|
73
|
+
click.echo(f" C = {fmt_si(c, 'F')}")
|
|
74
|
+
click.echo(f" Cutoff frequency f₋₃dB = {fmt_si(f, 'Hz')}")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@filter_group.command('lc')
|
|
78
|
+
@click.option('--l', 'l_str', required=True, metavar='VALUE',
|
|
79
|
+
help='Inductance, e.g. 10u, 1m, 470n')
|
|
80
|
+
@click.option('--c', 'c_str', required=True, metavar='VALUE',
|
|
81
|
+
help='Capacitance, e.g. 100p, 10n, 1u')
|
|
82
|
+
def cmd_lc(l_str, c_str):
|
|
83
|
+
"""LC filter resonance frequency.
|
|
84
|
+
|
|
85
|
+
\b
|
|
86
|
+
f = 1 / (2π · √(L · C))
|
|
87
|
+
|
|
88
|
+
Example:
|
|
89
|
+
ee-calc filter lc --l 10u --c 100p
|
|
90
|
+
"""
|
|
91
|
+
l = parse_si(l_str)
|
|
92
|
+
c = parse_si(c_str)
|
|
93
|
+
f = lc_resonance(l, c)
|
|
94
|
+
click.echo("LC filter / resonator")
|
|
95
|
+
click.echo(f" L = {fmt_si(l, 'H')}")
|
|
96
|
+
click.echo(f" C = {fmt_si(c, 'F')}")
|
|
97
|
+
click.echo(f" Resonance frequency f₀ = {fmt_si(f, 'Hz')}")
|
|
98
|
+
|
|
99
|
+
@filter_group.command('bp')
|
|
100
|
+
@click.option('--r1', 'r1_str', required=True, metavar='VALUE',
|
|
101
|
+
help='High-pass resistance, e.g. 10k')
|
|
102
|
+
@click.option('--c1', 'c1_str', required=True, metavar='VALUE',
|
|
103
|
+
help='High-pass capacitance, e.g. 100n')
|
|
104
|
+
@click.option('--r2', 'r2_str', required=True, metavar='VALUE',
|
|
105
|
+
help='Low-pass resistance, e.g. 10k')
|
|
106
|
+
@click.option('--c2', 'c2_str', required=True, metavar='VALUE',
|
|
107
|
+
help='Low-pass capacitance, e.g. 10n')
|
|
108
|
+
def cmd_bp(r1_str, c1_str, r2_str, c2_str):
|
|
109
|
+
"""RC band-pass filter (cascaded high-pass + low-pass stages).
|
|
110
|
+
|
|
111
|
+
\b
|
|
112
|
+
f_low = 1 / (2π · R1 · C1) ← high-pass edge
|
|
113
|
+
f_high = 1 / (2π · R2 · C2) ← low-pass edge
|
|
114
|
+
f₀ = √(f_low · f_high) ← geometric centre
|
|
115
|
+
BW = f_high − f_low
|
|
116
|
+
|
|
117
|
+
Example:
|
|
118
|
+
ee-calc filter bp --r1 10k --c1 100n --r2 10k --c2 10n
|
|
119
|
+
"""
|
|
120
|
+
r1 = parse_si(r1_str)
|
|
121
|
+
c1 = parse_si(c1_str)
|
|
122
|
+
r2 = parse_si(r2_str)
|
|
123
|
+
c2 = parse_si(c2_str)
|
|
124
|
+
try:
|
|
125
|
+
f_low, f_high, f_centre, bw = rc_bandpass(r1, c1, r2, c2)
|
|
126
|
+
except ValueError as exc:
|
|
127
|
+
raise click.UsageError(str(exc)) from exc
|
|
128
|
+
click.echo("RC band-pass filter")
|
|
129
|
+
click.echo(f" High-pass stage R1 = {fmt_si(r1, 'Ω')}, C1 = {fmt_si(c1, 'F')}")
|
|
130
|
+
click.echo(f" Low-pass stage R2 = {fmt_si(r2, 'Ω')}, C2 = {fmt_si(c2, 'F')}")
|
|
131
|
+
click.echo(f" Lower edge f_low = {fmt_si(f_low, 'Hz')}")
|
|
132
|
+
click.echo(f" Upper edge f_high = {fmt_si(f_high, 'Hz')}")
|
|
133
|
+
click.echo(f" Centre freq f₀ = {fmt_si(f_centre, 'Hz')}")
|
|
134
|
+
click.echo(f" Bandwidth BW = {fmt_si(bw, 'Hz')}")
|
ee_toolkit/mosfet.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Power MOSFET calculations.
|
|
3
|
+
|
|
4
|
+
Gate Drive Requirements
|
|
5
|
+
-----------------------
|
|
6
|
+
Required Drive Current: Ig = Qg / t_sw
|
|
7
|
+
Switching Time: t_sw = Qg / Ig
|
|
8
|
+
Gate Drive Power: Pgate = Qg * Vdr * f_sw
|
|
9
|
+
Peak Current: Ig_peak = Vdr / Rg_total
|
|
10
|
+
"""
|
|
11
|
+
import click
|
|
12
|
+
from .units import parse_si, fmt_si
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def ig_from_tsw(qg: float, tsw: float) -> float:
|
|
16
|
+
"""Average gate current required for a specific switching time."""
|
|
17
|
+
return qg / tsw
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def tsw_from_ig(qg: float, ig: float) -> float:
|
|
21
|
+
"""Estimated switching time for a given average gate current."""
|
|
22
|
+
return qg / ig
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def pgate_power(qg: float, vdr: float, fsw: float) -> float:
|
|
26
|
+
"""Power dissipated in the gate driver."""
|
|
27
|
+
return qg * vdr * fsw
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def ig_peak_from_rg(vdr: float, rg: float) -> float:
|
|
31
|
+
"""Peak gate drive current limited by total gate resistance."""
|
|
32
|
+
return vdr / rg
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@click.group()
|
|
36
|
+
def mosfet_group():
|
|
37
|
+
"""Power MOSFET calculations (gate drive, switching)."""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@mosfet_group.command('gate')
|
|
41
|
+
@click.option('--qg', 'qg_str', required=True, metavar='VALUE',
|
|
42
|
+
help='Total gate charge (Qg), e.g. 50n')
|
|
43
|
+
@click.option('--vdr', 'vdr_str', required=True, metavar='VALUE',
|
|
44
|
+
help='Gate drive voltage (Vdr), e.g. 12')
|
|
45
|
+
@click.option('--tsw', 'tsw_str', default=None, metavar='VALUE',
|
|
46
|
+
help='Desired switching time (t_sw), e.g. 20n')
|
|
47
|
+
@click.option('--ig', 'ig_str', default=None, metavar='VALUE',
|
|
48
|
+
help='Available gate drive current (Ig), e.g. 1.5')
|
|
49
|
+
@click.option('--rg', 'rg_str', default=None, metavar='VALUE',
|
|
50
|
+
help='Total gate resistance (driver + ext + int), e.g. 10')
|
|
51
|
+
@click.option('--fsw', 'fsw_str', default=None, metavar='VALUE',
|
|
52
|
+
help='Switching frequency, e.g. 100k')
|
|
53
|
+
def cmd_gate(qg_str, vdr_str, tsw_str, ig_str, rg_str, fsw_str):
|
|
54
|
+
"""Gate driver requirements for power MOSFETs.
|
|
55
|
+
|
|
56
|
+
\b
|
|
57
|
+
Formulas:
|
|
58
|
+
Ig_req = Qg / t_sw
|
|
59
|
+
t_sw = Qg / Ig
|
|
60
|
+
P_gate = Qg * Vdr * f_sw
|
|
61
|
+
Ig_peak = Vdr / Rg_total
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
ee-calc mosfet gate --qg 50n --vdr 12 --tsw 20n --fsw 100k
|
|
65
|
+
"""
|
|
66
|
+
qg = parse_si(qg_str)
|
|
67
|
+
vdr = parse_si(vdr_str)
|
|
68
|
+
|
|
69
|
+
click.echo("Power MOSFET Gate Drive")
|
|
70
|
+
click.echo(f" Qg = {fmt_si(qg, 'C')}")
|
|
71
|
+
click.echo(f" Vdr = {fmt_si(vdr, 'V')}")
|
|
72
|
+
|
|
73
|
+
if tsw_str:
|
|
74
|
+
tsw = parse_si(tsw_str)
|
|
75
|
+
ig_req = ig_from_tsw(qg, tsw)
|
|
76
|
+
click.echo(f"\nTarget Switching Time: t_sw = {fmt_si(tsw, 's')}")
|
|
77
|
+
click.echo(f" Required average gate current (Ig) = {fmt_si(ig_req, 'A')}")
|
|
78
|
+
|
|
79
|
+
if ig_str:
|
|
80
|
+
ig = parse_si(ig_str)
|
|
81
|
+
tsw_est = tsw_from_ig(qg, ig)
|
|
82
|
+
click.echo(f"\nAvailable Gate Current: Ig = {fmt_si(ig, 'A')}")
|
|
83
|
+
click.echo(f" Estimated switching time (t_sw) = {fmt_si(tsw_est, 's')}")
|
|
84
|
+
|
|
85
|
+
if rg_str:
|
|
86
|
+
rg = parse_si(rg_str)
|
|
87
|
+
ig_peak = ig_peak_from_rg(vdr, rg)
|
|
88
|
+
click.echo(f"\nTotal Gate Resistance: Rg = {fmt_si(rg, 'Ω')}")
|
|
89
|
+
click.echo(f" Peak driver current (Ipk) = {fmt_si(ig_peak, 'A')}")
|
|
90
|
+
|
|
91
|
+
if fsw_str:
|
|
92
|
+
fsw = parse_si(fsw_str)
|
|
93
|
+
pd = pgate_power(qg, vdr, fsw)
|
|
94
|
+
click.echo(f"\nSwitching Frequency: f_sw = {fmt_si(fsw, 'Hz')}")
|
|
95
|
+
click.echo(f" Gate driver power (Pd) = {fmt_si(pd, 'W')}")
|