Skip to content

Commit 7ec5ea4

Browse files
committed
update
2 parents 3e23023 + d07a176 commit 7ec5ea4

18 files changed

+1806
-25
lines changed

edg/abstract_parts/AbstractFets.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ class FetStandardFootprint(StandardFootprint['Fet']):
3838
'7': block.drain,
3939
'8': block.drain,
4040
},
41-
'Package_SO:PowerPAK_SO-8_Single': lambda block: {
41+
(
42+
'Package_SO:PowerPAK_SO-8_Single',
43+
'Package_DFN_QFN:PQFN-8-EP_6x5mm_P1.27mm_Generic',
44+
): lambda block: {
4245
'1': block.source,
4346
'2': block.source,
4447
'3': block.source,
@@ -62,6 +65,12 @@ class Fet(KiCadImportableBlock, DiscreteSemiconductor, HasStandardFootprint):
6265
"""Base class for untyped MOSFETs
6366
Drain voltage, drain current, and gate voltages are positive (absolute).
6467
68+
The gate voltage is only checked against maximum ratings.
69+
Optionally, the gate threshold voltage can also be specified.
70+
71+
The actual gate drive voltage is specified as (threshold voltage, gate drive voltage), where the top end of that
72+
is either the voltage at Rds,on or the specified driving voltage level.
73+
6574
MOSFET equations
6675
- https://inst.eecs.berkeley.edu/~ee105/fa05/handouts/discussions/Discussion5.pdf (cutoff/linear/saturation regions)
6776
@@ -90,7 +99,8 @@ def PFet(*args, **kwargs) -> 'Fet':
9099

91100
@init_in_parent
92101
def __init__(self, drain_voltage: RangeLike, drain_current: RangeLike, *,
93-
gate_voltage: RangeLike = (0, 0), rds_on: RangeLike = Range.all(),
102+
gate_voltage: RangeLike = (0, 0), gate_threshold_voltage: RangeLike = Range.all(),
103+
rds_on: RangeLike = Range.all(),
94104
gate_charge: RangeLike = Range.all(), power: RangeLike = Range.exact(0),
95105
channel: StringLike = StringExpr()) -> None:
96106
super().__init__()
@@ -102,6 +112,7 @@ def __init__(self, drain_voltage: RangeLike, drain_current: RangeLike, *,
102112
self.drain_voltage = self.ArgParameter(drain_voltage)
103113
self.drain_current = self.ArgParameter(drain_current)
104114
self.gate_voltage = self.ArgParameter(gate_voltage)
115+
self.gate_threshold_voltage = self.ArgParameter(gate_threshold_voltage)
105116
self.rds_on = self.ArgParameter(rds_on)
106117
self.gate_charge = self.ArgParameter(gate_charge)
107118
self.power = self.ArgParameter(power)
@@ -151,15 +162,16 @@ class TableFet(PartsTableSelector, BaseTableFet):
151162
@init_in_parent
152163
def __init__(self, *args, **kwargs):
153164
super().__init__(*args, **kwargs)
154-
self.generator_param(self.drain_voltage, self.drain_current, self.gate_voltage, self.rds_on, self.gate_charge,
155-
self.power, self.channel)
165+
self.generator_param(self.drain_voltage, self.drain_current, self.gate_voltage, self.gate_threshold_voltage,
166+
self.rds_on, self.gate_charge, self.power, self.channel)
156167

157168
def _row_filter(self, row: PartsTableRow) -> bool:
158169
return super()._row_filter(row) and \
159170
row[self.CHANNEL] == self.get(self.channel) and \
160171
self.get(self.drain_voltage).fuzzy_in(row[self.VDS_RATING]) and \
161172
self.get(self.drain_current).fuzzy_in(row[self.IDS_RATING]) and \
162173
self.get(self.gate_voltage).fuzzy_in(row[self.VGS_RATING]) and \
174+
(row[self.VGS_DRIVE].lower in self.get(self.gate_threshold_voltage)) and \
163175
row[self.RDS_ON].fuzzy_in(self.get(self.rds_on)) and \
164176
row[self.GATE_CHARGE].fuzzy_in(self.get(self.gate_charge)) and \
165177
self.get(self.power).fuzzy_in(row[self.POWER_RATING])
@@ -193,7 +205,7 @@ def PFet(*args, **kwargs):
193205

194206

195207
@init_in_parent
196-
def __init__(self, frequency: RangeLike, drive_current: RangeLike, **kwargs) -> None:
208+
def __init__(self, *, frequency: RangeLike = 0*Hertz(tol=0), drive_current: RangeLike = Range.all(), **kwargs) -> None:
197209
super().__init__(**kwargs)
198210

199211
self.frequency = self.ArgParameter(frequency)
@@ -209,8 +221,9 @@ class TableSwitchFet(PartsTableSelector, SwitchFet, BaseTableFet):
209221
@init_in_parent
210222
def __init__(self, *args, **kwargs):
211223
super().__init__(*args, **kwargs)
212-
self.generator_param(self.frequency, self.drain_voltage, self.drain_current, self.gate_voltage, self.rds_on,
213-
self.gate_charge, self.power, self.channel, self.drive_current)
224+
self.generator_param(self.frequency, self.drain_voltage, self.drain_current,
225+
self.gate_voltage, self.gate_threshold_voltage,
226+
self.rds_on, self.gate_charge, self.power, self.channel, self.drive_current)
214227

215228
self.actual_static_power = self.Parameter(RangeExpr())
216229
self.actual_switching_power = self.Parameter(RangeExpr())
@@ -222,6 +235,7 @@ def _row_filter(self, row: PartsTableRow) -> bool: # here this is just a pre-fi
222235
self.get(self.drain_voltage).fuzzy_in(row[self.VDS_RATING]) and \
223236
self.get(self.drain_current).fuzzy_in(row[self.IDS_RATING]) and \
224237
self.get(self.gate_voltage).fuzzy_in(row[self.VGS_RATING]) and \
238+
(row[self.VGS_DRIVE].lower in self.get(self.gate_threshold_voltage)) and \
225239
row[self.RDS_ON].fuzzy_in(self.get(self.rds_on)) and \
226240
row[self.GATE_CHARGE].fuzzy_in(self.get(self.gate_charge)) and \
227241
self.get(self.power).fuzzy_in(row[self.POWER_RATING])
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from ..abstract_parts import *
1+
from ..electronics_model import *
2+
from .AbstractDiodes import Diode
23

34

45
class CustomDiode(Diode, FootprintBlock, GeneratorBlock):
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
from ..abstract_parts import *
1+
from ..electronics_model import *
2+
from .AbstractFets import SwitchFet
23

34

4-
class CustomFet(Fet, FootprintBlock, GeneratorBlock):
5+
class CustomFet(SwitchFet, FootprintBlock, GeneratorBlock):
56
@init_in_parent
67
def __init__(self, *args, footprint_spec: StringLike = "",
78
manufacturer_spec: StringLike = "", part_spec: StringLike = "", **kwargs):

edg/abstract_parts/DummyDevices.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,18 @@ def __init__(self, voltage_limit: RangeLike = RangeExpr.ALL,
4545
self.current_limits = self.Parameter(RangeExpr(self.pwr.link().current_limits))
4646

4747

48+
class DummyDigitalSource(DummyDevice):
49+
@init_in_parent
50+
def __init__(self, voltage_out: RangeLike = RangeExpr.ZERO,
51+
current_limits: RangeLike = RangeExpr.ALL) -> None:
52+
super().__init__()
53+
54+
self.io = self.Port(DigitalSource(
55+
voltage_out=voltage_out,
56+
current_limits=current_limits
57+
), [InOut])
58+
59+
4860
class DummyDigitalSink(DummyDevice):
4961
@init_in_parent
5062
def __init__(self, voltage_limit: RangeLike = RangeExpr.ALL,
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
from typing import NamedTuple, Dict, Optional
22
import math
33

4-
from ..abstract_parts import *
4+
from ..electronics_model import *
5+
from .AbstractCapacitor import Capacitor, DummyCapacitorFootprint
6+
from .SelectorArea import SelectorArea
7+
from .ESeriesUtil import ESeriesUtil
58

69

710
class GenericMlcc(Capacitor, SelectorArea, FootprintBlock, GeneratorBlock):
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
from typing import List, Tuple
22

3-
from ..abstract_parts import *
3+
from ..electronics_model import *
4+
from .AbstractResistor import Resistor
5+
from .ESeriesUtil import ESeriesUtil
6+
from .SelectorArea import SelectorArea
47

58

69
@non_library

edg/abstract_parts/PowerCircuits.py

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from ..electronics_model import *
22
from .Resettable import Resettable
33
from .AbstractResistor import Resistor, SeriesPowerResistor
4-
from .AbstractFets import SwitchFet
4+
from .AbstractFets import SwitchFet, Fet
5+
from .AbstractCapacitor import Capacitor
56
from .GateDrivers import HalfBridgeDriver, HalfBridgeDriverIndependent, HalfBridgeDriverPwm
67
from .DigitalAmplifiers import HighSideSwitch
8+
from .ResistiveDivider import VoltageDivider, ResistiveDivider
79
from .Categories import PowerConditioner
810
from .MergedBlocks import MergedVoltageSource
911
from .DummyDevices import ForcedVoltageCurrentDraw
@@ -123,6 +125,111 @@ def generate(self):
123125
self.connect(self.reset, self.driver.with_mixin(Resettable()).reset)
124126

125127

128+
class RampLimiter(KiCadSchematicBlock):
129+
"""PMOS-based ramp limiter that roughly targets a constant-dV/dt ramp.
130+
The cgd should be specified to swamp (10x+) the parasitic Cgd of the FET to get more controlled parameters.
131+
The target ramp rate is in volts/second, and for a capacitive load this can be calculated from a target current with
132+
I = C * dV/dt => dV/dt = I / C
133+
The actual ramp rate will vary substantially, the values calculated are based on many assertions.
134+
135+
A target Vgs can also be specified, this is the final Vgs of the FET after the ramp completes.
136+
The FET will be constrained to have a Vgs,th below the minimum of this range and a Vgs,max above the maximum.
137+
138+
A capacitive divider with Cgs will be generated so the target initial Vgs at less than half the FET Vgs,th
139+
(targeting half Vgs,th at Vin,max).
140+
141+
TODO: allow control to be optional, eliminating the NMOS with a short
142+
143+
HOW THIS WORKS:
144+
When the input voltage rises, the capacitive divider of Cgs, Cgd brings the gate to a subthreshold voltage.
145+
The gate voltage charges via the divider until it gets to the threshold voltage.
146+
At around the threshold voltage, the FET begins to turn on, with current flowing into (and charging) the output.
147+
As the output rises, Cgd causes the gate to be pulled up with the output, keeping Vgs roughly constant.
148+
(this also keeps the current roughly constant, mostly regardless of transconductance)
149+
During this stage, if we assume Vgs is constant, then Cgs is constant and can be disregarded.
150+
For the output to rise, Vgd must rise, which means Cgd must charge, and the current must go through the divider.
151+
Assuming a constant Vgs (and absolute gate voltage), the current into the divider is constant,
152+
and this is how the voltage ramp rate is controlled.
153+
Once the output gets close to the input voltage, Cgd stops charging and Vgs rises, turning the FET fully on.
154+
155+
Note that Vgs,th is an approximate parameter and the ramp current is likely larger than the Vgs,th current.
156+
Vgs also may rise during the ramp, meaning some current goes into charging Cgs.
157+
158+
References: https://www.ti.com/lit/an/slva156/slva156.pdf, https://www.ti.com/lit/an/slyt096/slyt096.pdf,
159+
https://youtu.be/bOka13RtOXM
160+
161+
Additional more complex circuits
162+
https://electronics.stackexchange.com/questions/294061/p-channel-mosfet-inrush-current-limiting
163+
"""
164+
@init_in_parent
165+
def __init__(self, *, cgd: RangeLike = 10*nFarad(tol=0.5), target_ramp: RangeLike = 1000*Volt(tol=0.25),
166+
target_vgs: RangeLike = (4, 10)*Volt, max_rds: FloatLike = 1*Ohm,
167+
_cdiv_vgs_factor: RangeLike = (0.05, 0.75)):
168+
super().__init__()
169+
170+
self.gnd = self.Port(Ground.empty(), [Common])
171+
self.pwr_in = self.Port(VoltageSink.empty(), [Input])
172+
self.pwr_out = self.Port(VoltageSource.empty(), [Output])
173+
self.control = self.Port(DigitalSink.empty())
174+
175+
self.cgd = self.ArgParameter(cgd)
176+
self.target_ramp = self.ArgParameter(target_ramp)
177+
self.target_vgs = self.ArgParameter(target_vgs)
178+
self.max_rds = self.ArgParameter(max_rds)
179+
self._cdiv_vgs_factor = self.ArgParameter(_cdiv_vgs_factor)
180+
181+
def contents(self):
182+
super().contents()
183+
184+
pwr_voltage = self.pwr_in.link().voltage
185+
self.drv = self.Block(SwitchFet.PFet(
186+
drain_voltage=pwr_voltage,
187+
drain_current=self.pwr_out.link().current_drawn,
188+
gate_voltage=(0 * Volt(tol=0)).hull(self.target_vgs.upper()),
189+
gate_threshold_voltage=(0 * Volt(tol=0)).hull(self.target_vgs.lower()),
190+
rds_on=(0, self.max_rds)
191+
))
192+
193+
self.cap_gd = self.Block(Capacitor(
194+
capacitance=self.cgd,
195+
voltage=(0 * Volt(tol=0)).hull(self.pwr_in.link().voltage)
196+
))
197+
# treat Cgs and Cgd as a capacitive divider with Cgs on the bottom
198+
self.cap_gs = self.Block(Capacitor(
199+
capacitance=(
200+
(1/(self.drv.actual_gate_drive.lower()*self._cdiv_vgs_factor)).shrink_multiply(self.pwr_in.link().voltage) - 1
201+
).shrink_multiply(
202+
self.cap_gd.actual_capacitance
203+
),
204+
voltage=(0 * Volt(tol=0)).hull(self.pwr_in.link().voltage)
205+
))
206+
# dV/dt over a capacitor is I / C => I = Cgd * dV/dt
207+
# then calculate to get the target I: Vgs,th = I * Reff => Reff = Vgs,th / I = Vgs,th / (Cgd * dV/dt)
208+
# we assume Vgs,th is exact, and only contributing sources come from elsewhere
209+
self.div = self.Block(ResistiveDivider(ratio=self.target_vgs.shrink_multiply(1/self.pwr_in.link().voltage),
210+
impedance=(1 / self.target_ramp).shrink_multiply(self.drv.actual_gate_drive.lower() / (self.cap_gd.actual_capacitance))
211+
))
212+
div_current_draw = (self.pwr_in.link().voltage/self.div.actual_impedance).hull(0)
213+
self.ctl_fet = self.Block(SwitchFet.NFet(
214+
drain_voltage=pwr_voltage,
215+
drain_current=div_current_draw,
216+
gate_voltage=(self.control.link().output_thresholds.upper(), self.control.link().voltage.upper())
217+
))
218+
219+
self.import_kicad(
220+
self.file_path("resources", f"{self.__class__.__name__}.kicad_sch"),
221+
conversions={
222+
'pwr_in': VoltageSink(
223+
current_draw=self.pwr_out.link().current_drawn + div_current_draw
224+
),
225+
'pwr_out': VoltageSource(
226+
voltage_out=self.pwr_in.link().voltage
227+
),
228+
'control': DigitalSink(),
229+
'gnd': Ground(),
230+
})
231+
232+
126233
class FetPrecharge(Block):
127234
"""Precharge circuit that limits inrush current with an resistor, then provides low supply impedance
128235
by closing a power FET.
@@ -163,4 +270,4 @@ def contents(self):
163270
self.merge = self.Block(MergedVoltageSource()).connected_from(
164271
self.switch.output, self.res_forceout.pwr_out
165272
)
166-
self.connect(self.merge.pwr_out, self.pwr_out)
273+
self.connect(self.merge.pwr_out, self.pwr_out)

edg/abstract_parts/ResistiveDivider.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def generate(self) -> None:
135135

136136

137137
@non_library
138-
class BaseVoltageDivider(KiCadImportableBlock, Block):
138+
class BaseVoltageDivider(KiCadImportableBlock):
139139
"""Base class that defines a resistive divider that takes in a voltage source and ground, and outputs
140140
an analog constant-voltage signal.
141141
The actual output voltage is defined as a ratio of the input voltage, and the divider is specified by

edg/abstract_parts/__init__.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
from .AbstractPowerConverters import BuckConverter, DiscreteBuckConverter, BoostConverter, DiscreteBoostConverter
7474
from .AbstractPowerConverters import BuckConverterPowerPath, BoostConverterPowerPath, BuckBoostConverterPowerPath
7575
from .PowerCircuits import HalfBridge, FetHalfBridge, HalfBridgeIndependent, HalfBridgePwm, FetHalfBridgeIndependent,\
76-
FetHalfBridgePwmReset, FetPrecharge
76+
FetHalfBridgePwmReset, RampLimiter, FetPrecharge
7777
from .AbstractLedDriver import LedDriver, LedDriverPwm, LedDriverSwitchingConverter
7878
from .AbstractFuse import Fuse, SeriesPowerFuse, PptcFuse, FuseStandardFootprint, TableFuse, SeriesPowerPptcFuse
7979
from .AbstractCrystal import Crystal, TableCrystal, OscillatorReference, CeramicResonator
@@ -107,8 +107,13 @@
107107
from .PinMappable import PinResource, PeripheralFixedPin, PeripheralAnyResource, PeripheralFixedResource
108108
from .VariantPinRemapper import VariantPinRemapper
109109

110-
from .DummyDevices import DummyPassive, DummyGround, DummyVoltageSource, DummyVoltageSink, DummyDigitalSink, \
111-
DummyAnalogSource, DummyAnalogSink
110+
from .CustomDiode import CustomDiode
111+
from .CustomFet import CustomFet
112+
from .GenericResistor import ESeriesResistor, GenericChipResistor, GenericAxialResistor, GenericAxialVerticalResistor
113+
from .GenericCapacitor import GenericMlcc
114+
115+
from .DummyDevices import DummyPassive, DummyGround, DummyVoltageSource, DummyVoltageSink, DummyDigitalSource, \
116+
DummyDigitalSink, DummyAnalogSource, DummyAnalogSink
112117
from .DummyDevices import ForcedVoltageCurrentDraw, ForcedVoltageCurrentLimit, ForcedVoltage, ForcedVoltageCurrent, \
113118
ForcedAnalogVoltage, ForcedAnalogSignal, ForcedDigitalSinkCurrentDraw
114119
from .MergedBlocks import MergedVoltageSource, MergedDigitalSource, MergedAnalogSource, MergedSpiController
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
Version 4
2+
SHEET 1 880 680
3+
WIRE 48 128 -64 128
4+
WIRE 160 128 48 128
5+
WIRE 224 128 160 128
6+
WIRE 384 128 320 128
7+
WIRE 496 128 384 128
8+
WIRE 576 128 496 128
9+
WIRE 160 208 160 192
10+
WIRE 160 208 48 208
11+
WIRE 240 208 240 176
12+
WIRE 240 208 160 208
13+
WIRE 384 208 384 192
14+
WIRE 384 208 240 208
15+
WIRE 496 208 496 192
16+
WIRE 576 208 496 208
17+
WIRE 496 304 496 208
18+
WIRE -64 384 -64 288
19+
WIRE 48 384 -64 384
20+
WIRE -64 432 -64 384
21+
FLAG -64 432 0
22+
FLAG 576 128 Vout
23+
FLAG -64 128 Vin
24+
FLAG 240 208 Vg
25+
FLAG 496 304 0
26+
FLAG 0 448 0
27+
SYMBOL cap 144 128 R0
28+
SYMATTR InstName C1
29+
SYMATTR Value 470n
30+
SYMBOL cap 368 128 R0
31+
SYMATTR InstName C2
32+
SYMATTR Value 10n
33+
SYMBOL pmos 320 176 M270
34+
SYMATTR InstName M1
35+
SYMATTR Value AO6407
36+
SYMBOL res 32 112 R0
37+
SYMATTR InstName R1
38+
SYMATTR Value 200k
39+
SYMBOL res 32 192 R0
40+
SYMATTR InstName R2
41+
SYMATTR Value 200k
42+
SYMBOL voltage -64 192 R0
43+
WINDOW 3 -363 77 Left 2
44+
WINDOW 123 0 0 Left 0
45+
WINDOW 39 0 0 Left 0
46+
SYMATTR InstName V1
47+
SYMATTR Value PULSE(0 12 0 0.000001 0 1)
48+
SYMBOL res 560 112 R0
49+
SYMATTR InstName R3
50+
SYMATTR Value 1000
51+
SYMBOL cap 480 128 R0
52+
SYMATTR InstName C3
53+
SYMATTR Value 200�
54+
SYMBOL voltage -64 112 R0
55+
WINDOW 3 -363 78 Left 2
56+
WINDOW 123 0 0 Left 0
57+
WINDOW 39 0 0 Left 0
58+
SYMATTR InstName V2
59+
SYMATTR Value PULSE(0 8 0.050 .000001 0 1)
60+
SYMBOL nmos 0 288 R0
61+
SYMATTR InstName M2
62+
SYMATTR Value AO6408
63+
SYMBOL voltage 0 352 R0
64+
WINDOW 123 0 0 Left 0
65+
WINDOW 39 0 0 Left 0
66+
SYMATTR InstName V3
67+
SYMATTR Value PULSE(3.3 0 .01 .000001 .000001 .045)
68+
TEXT -194 330 Left 2 !.tran .1

0 commit comments

Comments
 (0)