Skip to content

Commit

Permalink
New library parts and example boards: SX1262 LoRa, PN7160 NFC, FLIR L…
Browse files Browse the repository at this point in the history
…epton thermal camera sensor (#384)

Adds these libraries:
- Bidirectional level shifter using two FETs , requires a directionality
hint
- A lot of RF library components / generators, including L networks, and
a L network overlaid with a second-harmonic notch
  - Unit tests for these verified against online calculators
- DiscreteRfWarning mixin, override-able assertion warning for discrete
RF blocks
- TLV757P high current SOT-23 LDOs
- PN7160 NFC controller with analog frontend generator. Experimental,
but tested working (though not necessary optimally tuned)
  - Unit tests for these verified against reference documents
- SX1262 LoRa transceiver, based on the reference analog frontend
architecture but with generators. The component values don't line up
exactly but are in the right ballpark.

Example boards:
- Standing desk controller - flipped RX/TX as sent to fab, corrected in
libraries and generator but not layout. Layout is still good enough
since the level shifters are bidirectional. Level shifter resistor for
the Neopixels also need to be 1k not 4.7k.
- SX1262 LoRa transceiver / PN7160 NFC omnibus RF test board - missing
the SX1262 BUSY pin connection, missing the PN7160 IRQ pin connection as
sent to fab, corrected in libraries and generator but not layout.
- FLIR Lepton test board - works with no modifications.

Changes to existing libraries:
- Add gnd to Antenna interface
- Add RF connectors as antennas, including Amphenol901143 SMA-F
connector
- Add SOT-323 to BJTs, FETs
- Un-comment DCR criteria for inductors
- Fixes for new JlcParts parts selector libraries
- Optional reset pull for OV2640, only generated if reset not externally
connected
- Expansion of SG8101 crystals to also 3.2x2.5mm devices
- Add support for using strapping pins of ESP32-C3-WROOM module
- More common default threshold for APX803s POR generator

TODO - future work
- Separate Shutdown-able and Resettable
  • Loading branch information
ducky64 authored Oct 19, 2024
1 parent 2080f8a commit 275b102
Show file tree
Hide file tree
Showing 77 changed files with 106,642 additions and 221 deletions.
3 changes: 2 additions & 1 deletion edg/abstract_parts/AbstractAntenna.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ def __init__(self, frequency: RangeLike, impedance: RangeLike = Range.all(), pow
self.power = self.ArgParameter(power)
self.actual_power_rating = self.Parameter(RangeExpr())

self.a = self.Port(Passive.empty())
self.a = self.Port(Passive.empty(), [Input])
self.gnd = self.Port(Ground.empty(), [Common])


@non_library
Expand Down
10 changes: 4 additions & 6 deletions edg/abstract_parts/AbstractBjt.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ class BjtStandardFootprint(StandardFootprint['Bjt']):
REFDES_PREFIX = 'Q'

FOOTPRINT_PINNING_MAP = {
'Package_TO_SOT_SMD:SOT-23': lambda block: {
(
'Package_TO_SOT_SMD:SOT-23',
'Package_TO_SOT_SMD:SOT-323_SC-70',
): lambda block: {
'1': block.base,
'2': block.emitter,
'3': block.collector,
Expand All @@ -21,11 +24,6 @@ class BjtStandardFootprint(StandardFootprint['Bjt']):
'2': block.collector,
'3': block.emitter,
},
'Package_TO_SOT_SMD:SOT-323_SC-70': lambda block: {
'1': block.base,
'2': block.emitter,
'3': block.collector,
},
}


Expand Down
30 changes: 29 additions & 1 deletion edg/abstract_parts/AbstractConnector.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from ..electronics_model import *
from .Categories import Connector
from .AbstractAntenna import Antenna


@abstract_block
Expand All @@ -21,7 +22,7 @@ class RfConnector(Connector):
"""Base class for a RF connector, with a signal and ground. Signal is passive-typed."""
def __init__(self) -> None:
super().__init__()
self.sig = self.Port(Passive.empty())
self.sig = self.Port(Passive.empty(), [Input])
self.gnd = self.Port(Ground(), [Common])


Expand All @@ -33,5 +34,32 @@ def __init__(self, name: StringLike):
self.tp_name = self.ArgParameter(name)


class RfConnectorAntenna(Antenna):
"""RF connector used as an antenna"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.conn = self.Block(RfConnector())
self.connect(self.conn.sig, self.a)
self.connect(self.conn.gnd, self.gnd)


@abstract_block
class UflConnector(RfConnector):
"""Base class for a U.FL / IPEX / UMCC connector, miniature RF connector."""


@abstract_block
class SmaConnector(RfConnector):
"""Base class for a SMA coax connector."""


@abstract_block
class SmaMConnector(SmaConnector):
"""Base class for a SMA M connector, pin with internal threads.
Typically used on the antenna itself."""


@abstract_block
class SmaFConnector(SmaConnector):
"""Base class for a SMA F connector, socket with external threads.
Typically used for an antenna connector for sub-2.4GHz applications; 2.4GHz uses RP-SMA."""
5 changes: 4 additions & 1 deletion edg/abstract_parts/AbstractFets.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ class FetStandardFootprint(StandardFootprint['Fet']):
REFDES_PREFIX = 'Q'

FOOTPRINT_PINNING_MAP = {
'Package_TO_SOT_SMD:SOT-23': lambda block: {
(
'Package_TO_SOT_SMD:SOT-23',
'Package_TO_SOT_SMD:SOT-323_SC-70',
): lambda block: {
'1': block.gate,
'2': block.source,
'3': block.drain,
Expand Down
17 changes: 11 additions & 6 deletions edg/abstract_parts/AbstractInductor.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ def symbol_pinning(self, symbol_name: str) -> Dict[str, BasePort]:
@init_in_parent
def __init__(self, inductance: RangeLike,
current: RangeLike = RangeExpr.ZERO,
frequency: RangeLike = RangeExpr.ZERO) -> None:
frequency: RangeLike = RangeExpr.ZERO,
resistance_dc: RangeLike = (0, 1)*Ohm # generic sane choice?
) -> None:
super().__init__()

self.a = self.Port(Passive.empty())
Expand All @@ -102,12 +104,12 @@ def __init__(self, inductance: RangeLike,
self.inductance = self.ArgParameter(inductance)
self.current = self.ArgParameter(current) # defined as operating current range, non-directioned
self.frequency = self.ArgParameter(frequency) # defined as operating frequency range
# TODO: in the future, when we consider efficiency - for now, use current ratings
# self.resistance_dc = self.Parameter(RangeExpr())
self.resistance_dc = self.ArgParameter(resistance_dc)

self.actual_inductance = self.Parameter(RangeExpr())
self.actual_current_rating = self.Parameter(RangeExpr())
self.actual_frequency_rating = self.Parameter(RangeExpr())
self.actual_resistance_dc = self.Parameter(RangeExpr())

def contents(self):
super().contents()
Expand All @@ -118,7 +120,9 @@ def contents(self):
"<b>current rating:</b> ", DescriptionString.FormatUnits(self.actual_current_rating, "A"),
" <b>of operating:</b> ", DescriptionString.FormatUnits(self.current, "A"), "\n",
"<b>frequency rating:</b> ", DescriptionString.FormatUnits(self.actual_frequency_rating, "Hz"),
" <b>of operating:</b> ", DescriptionString.FormatUnits(self.frequency, "Hz")
" <b>of operating:</b> ", DescriptionString.FormatUnits(self.frequency, "Hz"), "\n",
"<b>dc resistance:</b> ", DescriptionString.FormatUnits(self.actual_resistance_dc, "Ω"),
" <b>of spec:</b> ", DescriptionString.FormatUnits(self.resistance_dc, "Ω"),
)


Expand All @@ -132,21 +136,22 @@ class TableInductor(PartsTableSelector, Inductor):
@init_in_parent
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.generator_param(self.inductance, self.current, self.frequency)
self.generator_param(self.inductance, self.current, self.frequency, self.resistance_dc)

def _row_filter(self, row: PartsTableRow) -> bool:
# TODO eliminate arbitrary DCR limit in favor of exposing max DCR to upper levels
return super()._row_filter(row) and \
row[self.INDUCTANCE].fuzzy_in(self.get(self.inductance)) and \
self.get(self.current).fuzzy_in(row[self.CURRENT_RATING]) and \
row[self.DC_RESISTANCE].fuzzy_in(Range.zero_to_upper(1.0)) and \
row[self.DC_RESISTANCE].fuzzy_in(self.get(self.resistance_dc)) and \
self.get(self.frequency).fuzzy_in(row[self.FREQUENCY_RATING])

def _row_generate(self, row: PartsTableRow) -> None:
super()._row_generate(row)
self.assign(self.actual_inductance, row[self.INDUCTANCE])
self.assign(self.actual_current_rating, row[self.CURRENT_RATING])
self.assign(self.actual_frequency_rating, row[self.FREQUENCY_RATING])
self.assign(self.actual_resistance_dc, row[self.DC_RESISTANCE])


class SeriesPowerInductor(DiscreteApplication):
Expand Down
78 changes: 78 additions & 0 deletions edg/abstract_parts/LevelShifter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from ..electronics_model import *
from .Categories import *
from .AbstractResistor import PullupResistor
from .AbstractFets import Fet
from .DummyDevices import DummyVoltageSink

class BidirectionaLevelShifter(Interface, GeneratorBlock):
"""Bidirectional level shifter for low(ish) frequency signals.
Circuit design from Phillips AN97055, https://cdn-shop.adafruit.com/datasheets/an97055.pdf
When both sides are floating or driving high, the FET is off and the pullups provide the high signal.
When the LV side drives low, the FET source goes to ground, putting the FET into conduction and pulling HV low.
When the HV side drives low, the body diode pulls the FET source low, then goes into conduction.
Use infinity resistance to not generate a resistor, for example if it is known there is already a resistor
on that side.
src_hint = 'lv' | 'hv' | '' determines the 'source' side to help the electronics model resolve directionality
and does not affect circuit generation or functionality.
If empty, both sides are assumed to be able to drive the shifter and must have voltages and output thresholds
modeled. TODO: this mode may be brittle
"""
@init_in_parent
def __init__(self, lv_res: RangeLike = 4.7*kOhm(tol=0.05), hv_res: RangeLike = 4.7*kOhm(tol=0.05),
src_hint: StringLike = '') -> None:
super().__init__()
self.lv_pwr = self.Port(VoltageSink.empty())
self.lv_io = self.Port(DigitalBidir.empty())
self.hv_pwr = self.Port(VoltageSink.empty())
self.hv_io = self.Port(DigitalBidir.empty())

self.lv_res = self.ArgParameter(lv_res)
self.hv_res = self.ArgParameter(hv_res)
self.src_hint = self.ArgParameter(src_hint)
self.generator_param(self.lv_res, self.hv_res, self.src_hint)

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

self.fet = self.Block(Fet.NFet(
drain_voltage=self.hv_pwr.link().voltage.hull(self.hv_io.link().voltage),
drain_current=self.lv_io.link().current_drawn.hull(self.hv_io.link().current_drawn),
gate_voltage=self.lv_pwr.link().voltage - self.lv_io.link().voltage,
rds_on=(0, 1)*Ohm # arbitrary
))

if self.get(self.src_hint) == 'lv': # LV is source, HV model is incomplete
lv_io_model = DigitalBidir(
voltage_out=self.lv_pwr.link().voltage, # this is not driving, effectively only a pullup
output_thresholds=self.lv_pwr.link().voltage.hull(-float('inf'))
)
else: # HV model is complete, can use its thresholds
lv_io_model = DigitalBidir(
voltage_out=self.lv_pwr.link().voltage.hull(self.hv_io.link().voltage.lower()),
output_thresholds=self.lv_pwr.link().voltage.hull(self.hv_io.link().voltage.lower())
)

if self.get(self.src_hint) == 'hv': # HV is source, LV model is incomplete
hv_io_model = DigitalBidir(
voltage_out=self.hv_pwr.link().voltage, # this is not driving, effectively only a pullup
output_thresholds=self.hv_pwr.link().voltage.hull(-float('inf'))
)
else: # HV model is complete, can use its thresholds
hv_io_model = DigitalBidir(
voltage_out=self.hv_pwr.link().voltage.hull(self.lv_io.link().voltage.lower()),
output_thresholds=self.hv_pwr.link().voltage.hull(self.lv_io.link().voltage.lower())
)

self.connect(self.lv_io, self.fet.source.adapt_to(lv_io_model))
self.connect(self.hv_io, self.fet.drain.adapt_to(hv_io_model))
self.connect(self.lv_pwr, self.fet.gate.adapt_to(VoltageSink()))

if self.get(self.lv_res) != RangeExpr.INF:
self.lv_pu = self.Block(PullupResistor(self.lv_res)).connected(self.lv_pwr, self.lv_io)
if self.get(self.hv_res) != RangeExpr.INF:
self.hv_pu = self.Block(PullupResistor(self.hv_res)).connected(self.hv_pwr, self.hv_io)
else:
self.dummy_hv = self.Block(DummyVoltageSink()) # must be connected
self.connect(self.dummy_hv.pwr, self.hv_pwr)
Loading

0 comments on commit 275b102

Please sign in to comment.