Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions edg/abstract_parts/Jumper.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,20 @@ def contents(self) -> None:
self.assign(self.input.current_draw, self.output.link().current_draw) # for model purposes, treat as connected
self.connect(self.input.net, self.device.a)
self.connect(self.output.net, self.device.b)


class PoeJumper(TypedJumper, Block):
def __init__(self) -> None:
super().__init__()
self.jack = self.Port(PoeDevicePort(), [Input]) # jack-facing, device-presenting port
self.device = self.Port(PoePowerPort(), [Output]) # device-facing, power-presenting port

@override
def contents(self) -> None:
super().contents()
self.pos = self.Block(Jumper())
self.connect(self.jack.pos, self.pos.a)
self.connect(self.device.pos, self.pos.b)
self.neg = self.Block(Jumper())
self.connect(self.jack.neg, self.neg.a)
self.connect(self.device.neg, self.neg.b)
2 changes: 1 addition & 1 deletion edg/abstract_parts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
CanDiffTestPoint,
)
from .TestPoint import AnalogCoaxTestPoint
from .Jumper import Jumper, GroundJumper, VoltageJumper, DigitalJumper
from .Jumper import Jumper, GroundJumper, VoltageJumper, DigitalJumper, PoeJumper
from .PassiveConnector import PassiveConnector, FootprintPassiveConnector

from .UsbConnectors import UsbConnector, UsbHostConnector, UsbDeviceConnector, UsbEsdDiode
Expand Down
116 changes: 116 additions & 0 deletions edg/electronics_interfaces/EthernetPort.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from typing_extensions import override

from ..electronics_model import *


class EthernetMdiPairLink(Link):
"""Single pair ethernet twisted-pair MDI connection, between the PHY and magnetics."""

def __init__(self) -> None:
super().__init__()
self.phy = self.Port(EthernetMdiPhyPairPort.empty())
self.mag = self.Port(EthernetMdiMagPairPort.empty())

@override
def contents(self) -> None:
# KiCad diffpair-friendly naming
self.dp_P = self.connect(self.phy.pos, self.mag.pos)
self.dp_N = self.connect(self.phy.neg, self.mag.neg)
self.center = self.connect(self.phy.center, self.mag.center)


class EthernetMdiPhyPairPort(Port[EthernetMdiPairLink]):
"""PHY-side port of an ethernet twisted-pair MDI connection"""

link_type = EthernetMdiPairLink

def __init__(self) -> None:
super().__init__()
self.pos = self.Port(Passive())
self.neg = self.Port(Passive())
self.center = self.Port(Passive())


class EthernetMdiMagPairPort(Port[EthernetMdiPairLink]):
"""Magnetics-side port of a twisted-pair MDI connection"""

link_type = EthernetMdiPairLink

def __init__(self) -> None:
super().__init__()
self.pos = self.Port(Passive())
self.neg = self.Port(Passive())
self.center = self.Port(Passive())


class EthernetMdiLink(Link):
"""Full (multi-pair) connection for ethernet twisted-pair MDI connection, between the PHY and magnetics.
Currently supports only 10/100Mbps (100BASE-TX) connections with TX/RX pairs."""

def __init__(self) -> None:
super().__init__()
self.phy = self.Port(EthernetMdi100BaseTxPhyPort.empty())
self.mag = self.Port(EthernetMdi100BaseTxMagPort.empty())

@override
def contents(self) -> None:
self.tx = self.connect(self.phy.tx, self.mag.tx)
self.rx = self.connect(self.phy.rx, self.mag.rx)


class EthernetMdi100BaseTxPhyPort(Port[EthernetMdiLink]):
"""PHY-side MDI port for 100BASE-TX / Fast Ethernet"""

link_type = EthernetMdiLink

def __init__(self) -> None:
super().__init__()
self.tx = self.Port(EthernetMdiPhyPairPort())
self.rx = self.Port(EthernetMdiPhyPairPort())


class EthernetMdi100BaseTxMagPort(Port[EthernetMdiLink]):
"""Magnetics-side MDI port for 100BASE-TX / Fast Ethernet"""

link_type = EthernetMdiLink

def __init__(self) -> None:
super().__init__()
self.tx = self.Port(EthernetMdiMagPairPort())
self.rx = self.Port(EthernetMdiMagPairPort())


class PoeLink(Link):
"""Power over Ethernet connection between the powered device and the post-rectification jack-facing circuit."""

def __init__(self) -> None:
super().__init__()
self.jack = self.Port(PoePowerPort.empty())
self.poe = self.Port(PoeDevicePort.empty())

@override
def contents(self) -> None:
self.connect(self.jack.pos, self.poe.pos)
self.connect(self.jack.neg, self.poe.neg)


class PoePowerPort(Port[PoeLink]):
"""Jack side port for Power over Ethernet, post-rectification."""

link_type = PoeLink

def __init__(self) -> None:
super().__init__()
self.pos = self.Port(Passive())
self.neg = self.Port(Passive())


class PoeDevicePort(Port[PoeLink]):
"""Powered device side port for Power over Ethernet. Generally exposed by a PoE controller subcircuit"""

link_type = PoeLink

def __init__(self) -> None:
super().__init__()
self.pos = self.Port(Passive())
self.neg = self.Port(Passive())
3 changes: 0 additions & 3 deletions edg/electronics_interfaces/UsbPort.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
from typing import *

from typing_extensions import override

from ..electronics_model import *
from .DigitalPorts import DigitalBidir
from ..electronics_model.PassivePort import PassiveBridge


Expand Down
3 changes: 3 additions & 0 deletions edg/electronics_interfaces/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
from .UsbPort import UsbHostPort, UsbDevicePort, UsbPassivePort, UsbCcPort, UsbLink
from .DvpPort import Dvp8Host, Dvp8Camera, Dvp8Link
from .I2sPort import I2sController, I2sTargetReceiver, I2sLink
from .EthernetPort import EthernetMdiPairLink, EthernetMdiPhyPairPort, EthernetMdiMagPairPort
from .EthernetPort import EthernetMdiLink, EthernetMdi100BaseTxPhyPort, EthernetMdi100BaseTxMagPort
from .EthernetPort import PoeLink, PoePowerPort, PoeDevicePort

# model-breaking constructs, including for unit testing
from .GroundDummy import DummyGround
Expand Down
134 changes: 134 additions & 0 deletions edg/parts/connector/Ethernet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
from typing_extensions import override

from ...circuits import *
from ...vendor_parts.jlc.JlcPart import JlcPart


class Hy931147c_Device(InternalSubcircuit, FootprintBlock, JlcPart):
def __init__(self) -> None:
super().__init__()

self.eth = self.Port(EthernetMdi100BaseTxMagPort.empty(), optional=True)
self.poe = self.Port(PoePowerPort.empty(), optional=True)

self.led_grn_anode = self.Port(Passive(), optional=True)
self.led_grn_cathode = self.Port(Passive(), optional=True)

self.led_yel_anode = self.Port(Passive(), optional=True)
self.led_yel_cathode = self.Port(Passive(), optional=True)

self.shield = self.Port(Passive())

@override
def contents(self) -> None:
super().contents()

self.require(self.led_grn_anode.is_connected() == self.led_grn_cathode.is_connected())
self.require(self.led_yel_anode.is_connected() == self.led_yel_cathode.is_connected())

self.footprint(
"J",
"Connector_RJ:RJ45_Wuerth_7499111446_Horizontal",
{
"1": self.eth.rx.pos,
"2": self.eth.rx.neg,
"3": self.eth.rx.center,
"6": self.eth.tx.neg,
"5": self.eth.tx.pos,
"4": self.eth.tx.center,
"9": self.poe.pos,
"10": self.poe.neg,
"11": self.led_yel_anode,
"12": self.led_yel_cathode,
"13": self.led_grn_anode,
"14": self.led_grn_cathode,
"SH": self.shield,
},
"Hanrun",
"HY931147C",
pnp_rot=90,
pnp_offset=(5.6, 6.4),
)
self.assign(self.lcsc_part, "C91754")
self.assign(self.actual_basic_part, False)


class Hy931147c(Connector, GeneratorBlock):
"""Commonly available RJ45 magjack with PoE support.
Footprint and pin-compatible with Wuerth 7499211121A.

This uses the footprint for the Wuerth 7499111446, which shares the same pattern
but is not functionally compatible.

TODO should define and implement an abstract base class, EthernetConnector, which defines the
magnetics-side ports and can also be implemented by DiscreteMagneticsEthernetConnector,
which has a passive-typed RJ45, discrete magnetics, and optional PoE diode bridge generator.

TODO: allow LEDs to be driven in source mode

TODO: support LED connection by multipacking"""

_LED_CURRENT_LIMITS = (0, 20) * mAmp

def __init__(self, *, led_target_current: RangeLike = (1, 10) * mAmp) -> None:
super().__init__()
self.led_target_current = self.ArgParameter(led_target_current)

self.conn = self.Block(Hy931147c_Device())

self.eth = self.Export(self.conn.eth, optional=True)
self.poe = self.Export(self.conn.poe, optional=True)

self.gnd = self.Port(Ground()) # for termination
self.pwr_led = self.Port(VoltageSink(), optional=True) # for LED power
self.led_yel_sink = self.Port(
DigitalSink(current_draw=RangeExpr()), optional=True, doc="Yellow LED cathode connection"
)
self.led_grn_sink = self.Port(
DigitalSink(current_draw=RangeExpr()), optional=True, doc="Green LED cathode connection"
)
self.generator_param(self.led_yel_sink.is_connected(), self.led_grn_sink.is_connected())

@override
def generate(self) -> None:
super().generate()

self.require(self.eth.is_connected() | self.poe.is_connected(), "must use ethernet or PoE")

self.require(
(self.led_yel_sink.is_connected() | self.led_grn_sink.is_connected()).implies(self.pwr_led.is_connected()),
"power required when LEDs used",
)
if self.get(self.led_yel_sink.is_connected()):
self.led_yel_res = self.Block(
Resistor(
(1 / self.led_target_current).shrink_multiply(
self.pwr_led.link().voltage - self.led_yel_sink.link().output_thresholds.lower()
)
)
)
self.connect(self.pwr_led.net, self.conn.led_yel_anode)
self.connect(self.conn.led_yel_cathode, self.led_yel_res.a)
self.connect(self.led_yel_res.b, self.led_yel_sink.net)
self.assign(
self.led_yel_sink.current_draw, -self.pwr_led.link().voltage / self.led_yel_res.actual_resistance
)

if self.get(self.led_grn_sink.is_connected()):
self.led_grn_res = self.Block(
Resistor(
(1 / self.led_target_current).shrink_multiply(
self.pwr_led.link().voltage - self.led_grn_sink.link().output_thresholds.lower()
)
)
)
self.connect(self.pwr_led.net, self.conn.led_grn_anode)
self.connect(self.conn.led_grn_cathode, self.led_grn_res.a)
self.connect(self.led_grn_res.b, self.led_grn_sink.net)
self.assign(
self.led_grn_sink.current_draw, -self.pwr_led.link().voltage / self.led_grn_res.actual_resistance
)

self.cap = self.Block(Capacitor(1 * nFarad(tol=0.2), voltage=(0, 1000) * Volt)) # termination
self.connect(self.cap.neg, self.gnd.net)
self.connect(self.cap.pos, self.conn.shield)
2 changes: 2 additions & 0 deletions edg/parts/connector/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
from .FanConnector import CpuFanConnector, CpuFanPwmControl
from .Connectors import PowerBarrelJack, Pj_102ah, Pj_036ah, LipoConnector, QwiicTarget

from .Ethernet import Hy931147c

from .UsbPorts import UsbAReceptacle, UsbCReceptacle, UsbMicroBReceptacle
from .UsbPorts import Tpd2e009, Pesd5v0x1bt, Pgb102st23

Expand Down
Loading