From 47ac586488f3e5324952606162a37af6950caf81 Mon Sep 17 00:00:00 2001 From: Suke0811 <49264928+Suke0811@users.noreply.github.com> Date: Mon, 1 Apr 2024 12:22:48 -0700 Subject: [PATCH] Add: test_protected_charger for testing reverse protection Fix: revert test_pcbbot changes Fix: PMOS charger protection m1 being flipped --- electronics_lib/PowerConditioning.py | 215 +----------------- .../PmosChargerReverseProtection.kicad_sch | 28 +-- examples/test_pcbbot.py | 10 +- examples/test_protected_charger.py | 72 ++++++ 4 files changed, 93 insertions(+), 232 deletions(-) create mode 100644 examples/test_protected_charger.py diff --git a/electronics_lib/PowerConditioning.py b/electronics_lib/PowerConditioning.py index 995a36269..a97874d3e 100644 --- a/electronics_lib/PowerConditioning.py +++ b/electronics_lib/PowerConditioning.py @@ -265,156 +265,6 @@ def connected_from(self, gnd: Optional[Port[VoltageLink]] = None, pwr_hi: Option cast(Block, builder.get_enclosing_block()).connect(pwr_lo, self.pwr_lo) return self -class PmosReverseProtection(PowerConditioner, Block): - """ - , here is a tradeoff between the Gate discharge time and Zener biasing. - In most cases, 100R-330R is good if there are chances for the appearance of sudden reverse voltage in the circuit. - But if there are no chances of sudden reverse voltage during the continuous working of the circuit, anything from the 1k-50k resistor value can be used. - """ - @init_in_parent - def __init__(self, clamp_voltage: RangeLike, gate_resistor: RangeLike): - super().__init__() - self.gnd = self.Port(Ground.empty(), [Common]) - self.pwr_in = self.Port(VoltageSink.empty()) # high-priority higher-voltage source - self.pwr_out = self.Port(VoltageSource.empty()) - - self.clamp_voltage = self.ArgParameter(clamp_voltage) - self.gate_resistor = self.ArgParameter(gate_resistor) - - def contents(self): - super().contents() - - output_current_draw = self.pwr_out.link().current_drawn - # Pfet - self.fet = self.Block(Fet.PFet( - drain_voltage=(0, self.pwr_out.link().voltage.upper()), - drain_current=output_current_draw, - # gate voltage accounts for a possible power on transient - gate_voltage=(self.clamp_voltage.upper(), (self.clamp_voltage.upper())), - )) - - self.res = self.Block(Resistor(self.gate_resistor)) - self.diode = self.Block(ZenerDiode(self.clamp_voltage)) - # PFet gate to res to gnd - self.connect(self.fet.gate, self.res.a) - self.connect(self.gnd, self.res.b.adapt_to(Ground())) - # pwr going through the PFet - self.connect(self.pwr_in, self.fet.source.adapt_to(VoltageSink())) - self.connect(self.pwr_out, self.fet.drain.adapt_to(VoltageSource())) - # Connectign the diode from the gate to the pwr out side - self.connect(self.fet.drain, self.diode.cathode) - self.connect(self.fet.gate, self.diode.anode) - - - - -class PmosChargerProtection(PowerConditioner, Block): - @init_in_parent - def __init__(self, r1: RangeLike = 100*kOhm(tol=0.01), r2:RangeLike = 100*kOhm(tol=0.01), rds_on: RangeLike =(0, 0.1)*Ohm): - super().__init__() - - self.gnd = self.Port(Ground.empty(), [Common]) - self.pwr_out = self.Port(VoltageSource.empty(),) # Load - self.vbatt = self.Port(VoltageSink.empty(),) # Battery - self.vcharger = self.Port(VoltageSink.empty(),) # Charger - - self.r1_val = self.ArgParameter(r1) - self.r2_val = self.ArgParameter(r2) - self.rds_on = self.ArgParameter(rds_on) - - def contents(self): - super().contents() - max_vcharge_voltage = self.vcharger.link().voltage.upper() - max_vcharge_current = self.vcharger.link().current_drawn.upper() - max_vbatt_voltage = self.vbatt.link().voltage.upper() - max_vbatt_current = self.vbatt.link().current_drawn.upper() - # Create the PMOS transistors and resistors based on the provided schematic - self.mp1 = self.Block(Fet.PFet( - drain_voltage=(0, max_vcharge_voltage), drain_current=(0, max_vcharge_current), - gate_voltage=(- max_vbatt_voltage, max_vbatt_voltage), - rds_on=self.rds_on, - power=(0, max_vcharge_voltage * max_vcharge_current) - )) - self.mp2 = self.Block(Fet.PFet( - drain_voltage=(0, max_vbatt_voltage), drain_current=(0, max_vbatt_current), - gate_voltage=(0, max_vcharge_voltage), - rds_on=self.rds_on, - power=(0, max_vbatt_voltage * max_vbatt_current) - )) - self.r1 = self.Block(Resistor(resistance=self.r1_val)) - self.r2 = self.Block(Resistor(resistance=self.r2_val)) - - self.connect(self.mp1.source.adapt_to(VoltageSink()), self.vcharger) - self.connect(self.mp2.source.adapt_to(VoltageSink()), self.vcharger) - self.connect(self.r2.a.adapt_to(VoltageSink()), self.vcharger) - - # Connect the source of MP1 to the battery, gate of MP1 to the charger through R1 - self.connect(self.mp2.drain.adapt_to(VoltageSink()), self.vbatt) - self.connect(self.mp1.gate.adapt_to(VoltageSink()), self.vbatt) - self.connect(self.r2.b.adapt_to(VoltageSink()), self.vbatt) - - self.mp1_drain = self.connect(self.mp1.drain.adapt_to(VoltageSink()), self.r1.a.adapt_to(VoltageSink())) - self.connect(self.mp2.gate.adapt_to(VoltageSink()), self.mp1_drain) - #self.mp1_drain = self.connect(self.mp1.drain.adapt_to(VoltageSink()), self.mp2.gate.adapt_to(VoltageSink())) - # self.connect(self.mp1_drain, self.r1.a.adapt_to(VoltageSink())) - - self.connect(self.r1.b.adapt_to(Ground()), self.gnd) - self.connect(self.pwr_out, self.vcharger) - - - -class PmosChargerReverseProtection(PowerConditioner, KiCadSchematicBlock, Block): - @init_in_parent - def __init__(self, r1: RangeLike = 100*kOhm(tol=0.01), r2:RangeLike = 100*kOhm(tol=0.01), rds_on: RangeLike =(0, 0.1)*Ohm): - super().__init__() - - self.gnd = self.Port(Ground.empty(), [Common]) - self.pwr_out = self.Port(VoltageSource.empty(),) # Load - self.vbatt = self.Port(VoltageSink.empty(),) # Battery - self.vcharger = self.Port(VoltageSink.empty(),) # Charger - - self.r1_val = self.ArgParameter(r1) - self.r2_val = self.ArgParameter(r2) - self.rds_on = self.ArgParameter(rds_on) - - def contents(self): - super().contents() - max_vcharge_voltage = self.vcharger.link().voltage.upper() - max_vcharge_current = self.vcharger.link().current_drawn.upper() - max_vbatt_voltage = self.vbatt.link().voltage.upper() - max_vbatt_current = self.vbatt.link().current_drawn.upper() - # Create the PMOS transistors and resistors based on the provided schematic - self.mp1 = self.Block(Fet.PFet( - drain_voltage=(0, max_vcharge_voltage), drain_current=(0, max_vcharge_current), - gate_voltage=(- max_vbatt_voltage, max_vbatt_voltage), - rds_on=self.rds_on, - power=(0, max_vcharge_voltage * max_vcharge_current) - )) - self.mp2 = self.Block(Fet.PFet( - drain_voltage=(0, max_vbatt_voltage), drain_current=(0, max_vbatt_current), - gate_voltage=(0, max_vcharge_voltage), - rds_on=self.rds_on, - power=(0, max_vbatt_voltage * max_vbatt_current) - )) - self.r1 = self.Block(Resistor(resistance=self.r1_val)) - self.r2 = self.Block(Resistor(resistance=self.r2_val)) - - - - self.import_kicad( - self.file_path("resources", f"{self.__class__.__name__}.kicad_sch"), - conversions={ - 'vbatt': VoltageSink( - current_draw=max_vbatt_current - ), - 'vcharger': VoltageSink( - current_draw=max_vcharge_current - ), - 'pwr_out': VoltageSource( - voltage_out=max_vcharge_voltage), - 'gnd': Ground(), - }) - class PmosReverseProtection(PowerConditioner, Block): """ @@ -459,64 +309,9 @@ def contents(self): -class PmosChargerProtection(PowerConditioner, Block): - @init_in_parent - def __init__(self, r1: RangeLike = 100*kOhm(tol=0.01), r2:RangeLike = 100*kOhm(tol=0.01), rds_on: RangeLike =(0, 0.1)*Ohm): - super().__init__() - - self.gnd = self.Port(Ground.empty(), [Common]) - self.pwr_out = self.Port(VoltageSource.empty(),) # Load - self.vbatt = self.Port(VoltageSink.empty(),) # Battery - self.vcharger = self.Port(VoltageSink.empty(),) # Charger - - self.r1_val = self.ArgParameter(r1) - self.r2_val = self.ArgParameter(r2) - self.rds_on = self.ArgParameter(rds_on) - - def contents(self): - super().contents() - max_vcharge_voltage = self.vcharger.link().voltage.upper() - max_vcharge_current = self.vcharger.link().current_drawn.upper() - max_vbatt_voltage = self.vbatt.link().voltage.upper() - max_vbatt_current = self.vbatt.link().current_drawn.upper() - # Create the PMOS transistors and resistors based on the provided schematic - self.mp1 = self.Block(Fet.PFet( - drain_voltage=(0, max_vcharge_voltage), drain_current=(0, max_vcharge_current), - gate_voltage=(- max_vbatt_voltage, max_vbatt_voltage), - rds_on=self.rds_on, - power=(0, max_vcharge_voltage * max_vcharge_current) - )) - self.mp2 = self.Block(Fet.PFet( - drain_voltage=(0, max_vbatt_voltage), drain_current=(0, max_vbatt_current), - gate_voltage=(0, max_vcharge_voltage), - rds_on=self.rds_on, - power=(0, max_vbatt_voltage * max_vbatt_current) - )) - self.r1 = self.Block(Resistor(resistance=self.r1_val)) - self.r2 = self.Block(Resistor(resistance=self.r2_val)) - - self.connect(self.mp1.source.adapt_to(VoltageSink()), self.vcharger) - self.connect(self.mp2.source.adapt_to(VoltageSink()), self.vcharger) - self.connect(self.r2.a.adapt_to(VoltageSink()), self.vcharger) - - # Connect the source of MP1 to the battery, gate of MP1 to the charger through R1 - self.connect(self.mp2.drain.adapt_to(VoltageSink()), self.vbatt) - self.connect(self.mp1.gate.adapt_to(VoltageSink()), self.vbatt) - self.connect(self.r2.b.adapt_to(VoltageSink()), self.vbatt) - - self.mp1_drain = self.connect(self.mp1.drain.adapt_to(VoltageSink()), self.r1.a.adapt_to(VoltageSink())) - self.connect(self.mp2.gate.adapt_to(VoltageSink()), self.mp1_drain) - #self.mp1_drain = self.connect(self.mp1.drain.adapt_to(VoltageSink()), self.mp2.gate.adapt_to(VoltageSink())) - # self.connect(self.mp1_drain, self.r1.a.adapt_to(VoltageSink())) - - self.connect(self.r1.b.adapt_to(Ground()), self.gnd) - self.connect(self.pwr_out, self.vcharger) - - - class PmosChargerReverseProtection(PowerConditioner, KiCadSchematicBlock, Block): @init_in_parent - def __init__(self, r1: RangeLike = 100*kOhm(tol=0.01), r2:RangeLike = 100*kOhm(tol=0.01), rds_on: RangeLike =(0, 0.1)*Ohm): + def __init__(self, r1_val: RangeLike = 100*kOhm(tol=0.01), r2_val: RangeLike = 100*kOhm(tol=0.01), rds_on: RangeLike =(0, 0.1)*Ohm): super().__init__() self.gnd = self.Port(Ground.empty(), [Common]) @@ -524,8 +319,8 @@ def __init__(self, r1: RangeLike = 100*kOhm(tol=0.01), r2:RangeLike = 100*kOhm(t self.vbatt = self.Port(VoltageSink.empty(),) # Battery self.chg = self.Port(VoltageSink.empty(),) # Charger - self.r1_val = self.ArgParameter(r1) - self.r2_val = self.ArgParameter(r2) + self.r1_val = self.ArgParameter(r1_val) + self.r2_val = self.ArgParameter(r2_val) self.rds_on = self.ArgParameter(rds_on) def contents(self): @@ -533,7 +328,7 @@ def contents(self): max_vcharge_voltage = self.chg.link().voltage.upper() max_vcharge_current = self.chg.link().current_drawn.upper() max_vbatt_voltage = self.vbatt.link().voltage.upper() - max_vbatt_current = self.vbatt.link().current_drawn.upper() + max_vbatt_current = self.vbatt.link().current_limits.upper() # TODO: check if we should use current_limit or current_draw # Create the PMOS transistors and resistors based on the provided schematic self.mp1 = self.Block(Fet.PFet( drain_voltage=(0, max_vcharge_voltage), drain_current=(0, max_vcharge_current), @@ -552,7 +347,7 @@ def contents(self): chg_adapter = self.Block(PassiveAdapterVoltageSink()) setattr(self, '(adapter)chg', chg_adapter) # hack so the netlister recognizes this as an adapter - self.connect(self.mp1.drain, chg_adapter.src) + self.connect(self.mp1.source, chg_adapter.src) self.connect(self.chg, chg_adapter.dst) self.import_kicad( diff --git a/electronics_lib/resources/PmosChargerReverseProtection.kicad_sch b/electronics_lib/resources/PmosChargerReverseProtection.kicad_sch index f10b7e905..182f7346d 100644 --- a/electronics_lib/resources/PmosChargerReverseProtection.kicad_sch +++ b/electronics_lib/resources/PmosChargerReverseProtection.kicad_sch @@ -241,10 +241,10 @@ (uuid 414f0070-564b-405d-b71b-214b1d92e219) ) (junction (at 152.4 86.36) (diameter 0) (color 0 0 0 0) - (uuid 940d6e88-14e8-4a8c-9086-4044ac4fc7f5) + (uuid 886a151f-c6c3-4d9e-a86d-cacbfd10639e) ) (junction (at 152.4 73.66) (diameter 0) (color 0 0 0 0) - (uuid 9aa805d7-cf25-448e-85e5-54dcd69acaac) + (uuid 921f02ca-3043-464e-8d30-5a83209e363c) ) (wire (pts (xy 165.1 83.82) (xy 175.26 83.82)) @@ -259,10 +259,6 @@ (stroke (width 0) (type default)) (uuid 1d5f332d-27e6-4f0c-ac09-a1a0e71bc341) ) - (wire (pts (xy 152.4 83.82) (xy 152.4 86.36)) - (stroke (width 0) (type default)) - (uuid 2ddbb025-427a-41c6-bfa4-0c2e5acdbb2c) - ) (wire (pts (xy 160.02 78.74) (xy 165.1 78.74)) (stroke (width 0) (type default)) (uuid 3dd7ff86-72ff-43b5-b894-0e5a62f25699) @@ -283,10 +279,6 @@ (stroke (width 0) (type default)) (uuid 7c3d3d02-ed81-48d3-a207-98a8631ab8ed) ) - (wire (pts (xy 143.51 73.66) (xy 152.4 73.66)) - (stroke (width 0) (type default)) - (uuid 7cab5b0d-9153-424d-9095-a6419ddc6b5d) - ) (wire (pts (xy 124.46 101.6) (xy 127 101.6)) (stroke (width 0) (type default)) (uuid 864c531c-87c9-485b-a44e-d9aa537f522b) @@ -295,17 +287,25 @@ (stroke (width 0) (type default)) (uuid 9817d9d6-40c8-4a6e-8baa-964fd7e97de8) ) + (wire (pts (xy 152.4 83.82) (xy 152.4 86.36)) + (stroke (width 0) (type default)) + (uuid 9848f6af-1634-4e92-bab4-6b1ff52cdda6) + ) (wire (pts (xy 143.51 66.04) (xy 143.51 73.66)) (stroke (width 0) (type default)) (uuid a087cf6a-b08b-4dbd-9e78-ded1fa1740e3) ) + (wire (pts (xy 143.51 73.66) (xy 152.4 73.66)) + (stroke (width 0) (type default)) + (uuid b6506fce-b46b-42ad-84fe-68ff496aed77) + ) (wire (pts (xy 182.88 83.82) (xy 187.96 83.82)) (stroke (width 0) (type default)) (uuid c7edf471-f510-4ced-ae0e-fa0b4de75aca) ) (wire (pts (xy 152.4 73.66) (xy 175.26 73.66)) (stroke (width 0) (type default)) - (uuid ebdc34cc-c823-4107-bc50-c07b68d3b9bc) + (uuid e141ea76-4b1b-417c-ae27-ff704152d25e) ) (wire (pts (xy 185.42 66.04) (xy 143.51 66.04)) (stroke (width 0) (type default)) @@ -363,16 +363,16 @@ ) ) - (symbol (lib_id "Device:Q_PMOS_DGS") (at 154.94 78.74 0) (mirror y) (unit 1) + (symbol (lib_id "Device:Q_PMOS_DGS") (at 154.94 78.74 180) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid 39f44928-01d2-4e2e-9e28-a7bb035b6c2b) - (property "Reference" "mp1" (at 157.48 81.28 90) + (property "Reference" "mp1" (at 157.48 76.2 90) (effects (font (size 1.27 1.27))) ) (property "Value" "~" (at 146.05 78.74 90) (effects (font (size 1.27 1.27))) ) - (property "Footprint" "" (at 149.86 76.2 0) + (property "Footprint" "" (at 149.86 81.28 0) (effects (font (size 1.27 1.27)) hide) ) (property "Datasheet" "~" (at 154.94 78.74 0) diff --git a/examples/test_pcbbot.py b/examples/test_pcbbot.py index 10e418973..36ccc18d7 100644 --- a/examples/test_pcbbot.py +++ b/examples/test_pcbbot.py @@ -70,15 +70,9 @@ def contents(self) -> None: ) self.v3v3 = self.connect(self.reg_3v3.pwr_out) - self.pmos = imp.Block(PmosChargerReverseProtection()) - # self.pmos = imp.Block(PmosChargerReverseProtection()) - self.pmos = imp.Block(PmosChargerReverseProtection()) - (self.charger, ), _ = self.chain( - self.vusb, imp.Block(Mcp73831(200*mAmp(tol=0.2))), self.pmos.chg # self.pmos.vcharger, + self.vusb, imp.Block(Mcp73831(200*mAmp(tol=0.2))), self.batt.chg ) - self.connect(self.pmos.vbatt, self.batt.chg) - self.pvbatt = self.connect(self.pmos.pwr_out) (self.charge_led, ), _ = self.chain( self.Block(IndicatorSinkLed(Led.Yellow)), self.charger.stat ) @@ -245,4 +239,4 @@ def refinements(self) -> Refinements: class PcbBotTestCase(unittest.TestCase): def test_design(self) -> None: - compile_board_inplace(PcbBot) + compile_board_inplace(PcbBot) \ No newline at end of file diff --git a/examples/test_protected_charger.py b/examples/test_protected_charger.py new file mode 100644 index 000000000..9fcdb8d25 --- /dev/null +++ b/examples/test_protected_charger.py @@ -0,0 +1,72 @@ +import unittest + +from edg import * + +from .test_robotdriver import PwmConnector +from .test_multimeter import FetPowerGate + + +class ProtectedCharger(JlcBoardTop): + """A Lipo charger that does not blowup with reverse polarity from the battery + + Key features: + - Type C Lipo charger + - Reverse polarity protection with PMOS + - A Port for load + """ + def contents(self) -> None: + super().contents() + + self.usb = self.Block(UsbCReceptacle(current_limits=(0, 3)*Amp)) + self.vusb = self.connect(self.usb.pwr) + + self.batt = self.Block(LipoConnector(actual_voltage=(3.7, 4.2)*Volt)) + + self.gnd_merge = self.Block(MergedVoltageSource()).connected_from( + self.usb.gnd, self.batt.gnd + ) + self.gnd = self.connect(self.gnd_merge.pwr_out) + self.tp_gnd = self.Block(VoltageTestPoint()).connected(self.gnd_merge.pwr_out) + + with self.implicit_connect( + ImplicitConnect(self.gnd, [Common]), + ) as imp: + (self.tp,), _ = self.chain(self.batt.pwr, self.Block(VoltageTestPoint()), ) + self.pmos = imp.Block(PmosChargerReverseProtection()) + + (self.charger, ), _ = self.chain( + self.vusb, imp.Block(Mcp73831(200*mAmp(tol=0.2))), self.pmos.chg + ) + self.connect(self.pmos.vbatt, self.batt.pwr) + + (self.charge_led, ), _ = self.chain( + self.Block(IndicatorSinkLed(Led.Yellow)), self.charger.stat + ) + self.connect(self.vusb, self.charge_led.pwr) + + self.pwr_pins = self.Block(PassiveConnector(length=2)) + self.connect(self.pwr_pins.pins.request('1').adapt_to(Ground())) + self.connect(self.pmos.pwr_out, self.pwr_pins.pins.request('2').adapt_to(VoltageSink())) + + + + def refinements(self) -> Refinements: + return super().refinements() + Refinements( + instance_refinements=[ + ], + instance_values=[ + (['prot_batt', 'diode', 'footprint_spec'], 'Diode_SMD:D_SMA'), # big diodes to dissipate more power + ], + class_refinements=[ + (PassiveConnector, JstPhKHorizontal), # default connector series unless otherwise specified + (TestPoint, CompactKeystone5015), + ], + class_values=[ + (Diode, ['footprint_spec'], 'Diode_SMD:D_SOD-323'), + ], + ) + + +class PcbBotTestCase(unittest.TestCase): + def test_design(self) -> None: + compile_board_inplace(ProtectedCharger)