Skip to content

Commit 0354529

Browse files
authored
IoT + More Test Panel (#408)
A panel of mostly misc IoT devices, meant to be a fabrication test of the recent compiler and infrastructural changes. And the sublayout KiCad plugin. Top-level boards: - add BLE joystick - replace can-adapter with OBD adapter - add IoT rollerblinds and curtain crawler, all variations on a IoT brushed-DC motor driver - replace IoT LED driver with a puck design and higher frequency LED drivers Library additions: - Change JLC postprocessing script to support merging BoMs, for panelization - Add SeriesPowerFuse to support non-PPTC fuses - Add base categories MagneticSensor and MagneticSwitch, distinct from Magnetometer which has a analog response - Add A1304 linear hall-effect sensor - Add AH1806 hall effect switch - Add I2C bitbang adpater, to convert digital lines to I2C lines - Add Nano2 fuseholder - Add XBox Elite joystick block and footprint - Add TPS92200 buck LED driver, higher frequency (and smaller inductor) than the current one - Add DRV8870 DC motor driver - Add JST XH, Molex Picoblade connectors. - Add JST PH connector super-class. - Add J1962 (OBDII) footprint - Add Mp2722 bidirectional buck converter for single-cell LiIon Fixes: - Add optional signal_out_abs to AnalogSource.from_supply - MCP73831: make stat output line optional - Add CAN (TWAI) to ESP32C3
1 parent 723b6e7 commit 0354529

File tree

75 files changed

+105209
-66245
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+105209
-66245
lines changed

edg/BoardTop.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def refinements(self) -> Refinements:
3434
(Fpc050Bottom, HiroseFh12sh),
3535
(UsbEsdDiode, Tpd2e009),
3636
(CanEsdDiode, Pesd1can),
37+
(Fuse, Nano2Fuseholder),
3738
(TestPoint, TeRc),
3839

3940
(SwdCortexTargetConnector, SwdCortexTargetHeader),
@@ -77,6 +78,7 @@ def refinements(self) -> Refinements:
7778
(Resistor, JlcResistor),
7879
(Capacitor, JlcCapacitor),
7980
(Inductor, JlcInductor),
81+
(AluminumCapacitor, JlcAluminumCapacitor),
8082
(FerriteBead, JlcFerriteBead),
8183
(PptcFuse, JlcPptcFuse),
8284
(ResistorArray, JlcResistorArray),

edg/abstract_parts/AbstractFuse.py

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,41 @@ def contents(self):
6868
"operating voltage not within rating")
6969

7070

71+
class SeriesPowerFuse(Protection):
72+
"""Series fuse for power applications"""
73+
FUSE_TYPE = Fuse
74+
75+
@init_in_parent
76+
def __init__(self, trip_current: RangeLike) -> None:
77+
super().__init__()
78+
79+
self.pwr_out = self.Port(VoltageSource.empty(), [Output]) # forward declaration
80+
self.pwr_in = self.Port(VoltageSink.empty(), [Power, Input]) # forward declaration
81+
82+
self.fuse = self.Block(self.FUSE_TYPE(
83+
trip_current=trip_current,
84+
hold_current=(self.pwr_out.link().current_drawn.upper(), float('inf')),
85+
voltage=self.pwr_in.link().voltage
86+
))
87+
self.connect(self.pwr_in, self.fuse.a.adapt_to(VoltageSink(
88+
voltage_limits=self.fuse.actual_voltage_rating, # TODO: eventually needs a ground ref
89+
current_draw=self.pwr_out.link().current_drawn
90+
)))
91+
self.connect(self.pwr_out, self.fuse.b.adapt_to(VoltageSource(
92+
voltage_out=self.pwr_in.link().voltage, # ignore voltage drop
93+
current_limits=(0, self.fuse.actual_hold_current.lower())
94+
)))
95+
96+
def connected(self, pwr_in: Optional[Port[VoltageLink]] = None, pwr_out: Optional[Port[VoltageLink]] = None) -> \
97+
'SeriesPowerFuse':
98+
"""Convenience function to connect both ports, returning this object so it can still be given a name."""
99+
if pwr_in is not None:
100+
cast(Block, builder.get_enclosing_block()).connect(pwr_in, self.pwr_in)
101+
if pwr_out is not None:
102+
cast(Block, builder.get_enclosing_block()).connect(pwr_out, self.pwr_out)
103+
return self
104+
105+
71106
@abstract_block
72107
class PptcFuse(Fuse):
73108
"""PPTC self-resetting fuse"""
@@ -97,34 +132,5 @@ def _row_generate(self, row: PartsTableRow) -> None:
97132
self.assign(self.actual_voltage_rating, row[self.VOLTAGE_RATING])
98133

99134

100-
class SeriesPowerPptcFuse(Protection):
101-
"""Series fuse for power applications"""
102-
@init_in_parent
103-
def __init__(self, trip_current: RangeLike) -> None:
104-
super().__init__()
105-
106-
self.pwr_out = self.Port(VoltageSource.empty(), [Output]) # forward declaration
107-
self.pwr_in = self.Port(VoltageSink.empty(), [Power, Input]) # forward declaration
108-
109-
self.fuse = self.Block(PptcFuse(
110-
trip_current=trip_current,
111-
hold_current=(self.pwr_out.link().current_drawn.upper(), float('inf')),
112-
voltage=self.pwr_in.link().voltage
113-
))
114-
self.connect(self.pwr_in, self.fuse.a.adapt_to(VoltageSink(
115-
voltage_limits=self.fuse.actual_voltage_rating, # TODO: eventually needs a ground ref
116-
current_draw=self.pwr_out.link().current_drawn
117-
)))
118-
self.connect(self.pwr_out, self.fuse.b.adapt_to(VoltageSource(
119-
voltage_out=self.pwr_in.link().voltage, # ignore voltage drop
120-
current_limits=(0, self.fuse.actual_hold_current.lower())
121-
)))
122-
123-
def connected(self, pwr_in: Optional[Port[VoltageLink]] = None, pwr_out: Optional[Port[VoltageLink]] = None) -> \
124-
'SeriesPowerPptcFuse':
125-
"""Convenience function to connect both ports, returning this object so it can still be given a name."""
126-
if pwr_in is not None:
127-
cast(Block, builder.get_enclosing_block()).connect(pwr_in, self.pwr_in)
128-
if pwr_out is not None:
129-
cast(Block, builder.get_enclosing_block()).connect(pwr_out, self.pwr_out)
130-
return self
135+
class SeriesPowerPptcFuse(SeriesPowerFuse):
136+
FUSE_TYPE = PptcFuse

edg/abstract_parts/AbstractInductor.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Dict, Optional, cast
1+
from typing import Dict, Optional, cast, Any
22

33
from ..electronics_model import *
44
from .PartsTable import PartsTableColumn, PartsTableRow, ExperimentalUserFnPartsTable
@@ -143,7 +143,6 @@ def __init__(self, *args, **kwargs) -> None:
143143
self.experimental_filter_fn)
144144

145145
def _row_filter(self, row: PartsTableRow) -> bool:
146-
# TODO eliminate arbitrary DCR limit in favor of exposing max DCR to upper levels
147146
filter_fn_str = self.get(self.experimental_filter_fn)
148147
if filter_fn_str:
149148
filter_fn = ExperimentalUserFnPartsTable.deserialize_fn(filter_fn_str)
@@ -164,6 +163,10 @@ def _row_generate(self, row: PartsTableRow) -> None:
164163
self.assign(self.actual_frequency_rating, row[self.FREQUENCY_RATING])
165164
self.assign(self.actual_resistance_dc, row[self.DC_RESISTANCE])
166165

166+
@classmethod
167+
def _row_sort_by(cls, row: PartsTableRow) -> Any:
168+
return row[cls.DC_RESISTANCE].center()
169+
167170

168171
class SeriesPowerInductor(DiscreteApplication):
169172
"""VoltageSource/Sink-typed series inductor for power filtering"""

edg/abstract_parts/Categories.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,19 @@ class LightSensor(Sensor):
238238

239239

240240
@abstract_block
241-
class Magnetometer(Sensor):
241+
class MagneticSensor(Sensor):
242+
pass
243+
244+
245+
@abstract_block
246+
class MagneticSwitch(MagneticSensor):
247+
"""A switch that is activated by a magnetic field, including omnipolar and bipolar devices."""
248+
pass
249+
250+
251+
@abstract_block
252+
class Magnetometer(MagneticSensor):
253+
"""Linear response magnetic field sensor, potentially with multiple axes"""
242254
pass
243255

244256

edg/abstract_parts/I2cBitBang.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from typing import cast
2+
3+
from ..electronics_model import *
4+
from .Categories import *
5+
6+
7+
class I2cControllerBitBang(BitBangAdapter, Block):
8+
"""Bit-bang adapter for I2C controller"""
9+
@staticmethod
10+
def digital_external_from_link(link_port: DigitalBidir) -> DigitalBidir:
11+
"""Creates a DigitalBidir model that is the external-facing port that exports from
12+
an internal-facing (link-side) port. The internal-facing port should be ideal.
13+
These are basically the semantics of a DigitalBidir bridge.
14+
TODO: unify code w/ DigitalBidir bridge?"""
15+
return DigitalBidir(
16+
voltage_out=link_port.link().voltage, current_draw=link_port.link().current_drawn,
17+
voltage_limits=link_port.link().voltage_limits, current_limits=link_port.link().current_limits,
18+
output_thresholds=link_port.link().output_thresholds, input_thresholds=link_port.link().input_thresholds,
19+
pulldown_capable=link_port.link().pulldown_capable, pullup_capable=link_port.link().pullup_capable
20+
)
21+
22+
def __init__(self) -> None:
23+
super().__init__()
24+
self.i2c = self.Port(I2cController.empty(), [Output])
25+
self.scl = self.Port(DigitalBidir.empty())
26+
self.sda = self.Port(DigitalBidir.empty())
27+
28+
def contents(self) -> None:
29+
super().contents()
30+
self.connect(self.i2c.scl, self.scl)
31+
self.connect(self.i2c.sda, self.sda)
32+
33+
def connected_from(self, scl: Port[DigitalLink], sda: Port[DigitalLink]) -> 'I2cControllerBitBang':
34+
cast(Block, builder.get_enclosing_block()).connect(scl, self.scl)
35+
cast(Block, builder.get_enclosing_block()).connect(sda, self.sda)
36+
return self

edg/abstract_parts/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
from .Categories import PowerConditioner, PowerSwitch, MotorDriver, BrushedMotorDriver, BldcDriver
1616
from .Categories import PowerSource, Connector, ProgrammingConnector
1717
from .Categories import HumanInterface, Display, Lcd, Oled, EInk, Light
18-
from .Categories import Sensor, CurrentSensor, Accelerometer, Gyroscope, Magnetometer, DistanceSensor, Microphone, \
19-
Camera, LightSensor
18+
from .Categories import Sensor, CurrentSensor, Accelerometer, Gyroscope, MagneticSensor, MagneticSwitch, Magnetometer,\
19+
DistanceSensor, Microphone, Camera, LightSensor
2020
from .Categories import EnvironmentalSensor, TemperatureSensor, HumiditySensor, PressureSensor, GasSensor
2121
from .Categories import Label, Testing, TypedJumper, TypedTestPoint, InternalSubcircuit, DeprecatedBlock, Mechanical
2222
from .Categories import MultipackDevice
@@ -74,7 +74,7 @@
7474
from .PowerCircuits import HalfBridge, FetHalfBridge, HalfBridgeIndependent, HalfBridgePwm, FetHalfBridgeIndependent,\
7575
FetHalfBridgePwmReset
7676
from .AbstractLedDriver import LedDriver, LedDriverPwm, LedDriverSwitchingConverter
77-
from .AbstractFuse import Fuse, PptcFuse, FuseStandardFootprint, TableFuse, SeriesPowerPptcFuse
77+
from .AbstractFuse import Fuse, SeriesPowerFuse, PptcFuse, FuseStandardFootprint, TableFuse, SeriesPowerPptcFuse
7878
from .AbstractCrystal import Crystal, TableCrystal, OscillatorReference, CeramicResonator
7979
from .AbstractOscillator import Oscillator, TableOscillator
8080
from .AbstractDebugHeaders import SwdCortexTargetConnector, SwdCortexTargetConnectorReset, \
@@ -92,6 +92,7 @@
9292
from .DigitalIsolator import DigitalIsolator
9393
from .I2cPullup import I2cPullup
9494
from .UsbBitBang import UsbBitBang
95+
from .I2cBitBang import I2cControllerBitBang
9596

9697
from .IoController import BaseIoController, IoController, IoControllerPowerRequired, BaseIoControllerPinmapGenerator
9798
from .IoControllerExportable import BaseIoControllerExportable

edg/electronics_model/AnalogPort.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,14 +175,19 @@ class AnalogSource(AnalogBase):
175175
@staticmethod
176176
def from_supply(neg: Port[GroundLink], pos: Port[VoltageLink], *,
177177
signal_out_bound: Optional[Tuple[FloatLike, FloatLike]] = None,
178+
signal_out_abs: Optional[RangeLike] = None,
178179
current_limits: RangeLike = RangeExpr.ALL,
179180
impedance: RangeLike = RangeExpr.ZERO):
180181
supply_range = VoltageLink._supply_voltage_range(neg, pos)
181182
if signal_out_bound is not None:
183+
assert signal_out_abs is None
182184
# signal limit bounds specified as (lower bound added to limit, upper bound added to limit)
183185
# typically (positive, negative)
184186
signal_out: RangeLike = (supply_range.lower() + signal_out_bound[0],
185187
supply_range.upper() + signal_out_bound[1])
188+
elif signal_out_abs is not None:
189+
assert signal_out_bound is None
190+
signal_out = signal_out_abs
186191
else: # generic default
187192
signal_out = supply_range
188193

edg/electronics_model/DigitalPorts.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ def __init__(self, voltage_out: RangeLike = RangeExpr.ZERO,
310310

311311
@staticmethod
312312
def low_from_supply(neg: Port[GroundLink], *, current_limits: RangeLike = RangeExpr.ALL) -> DigitalSource:
313+
"""Sink-only digital source, eg open-drain output"""
313314
return DigitalSource(
314315
voltage_out=neg.link().voltage,
315316
current_limits=current_limits,
@@ -320,6 +321,7 @@ def low_from_supply(neg: Port[GroundLink], *, current_limits: RangeLike = RangeE
320321

321322
@staticmethod
322323
def high_from_supply(pos: Port[VoltageLink], *, current_limits: RangeLike = RangeExpr.ALL) -> DigitalSource:
324+
"""Source-only digital source"""
323325
return DigitalSource(
324326
voltage_out=pos.link().voltage,
325327
current_limits=current_limits,

edg/electronics_model/footprint.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def gen_block_comp(block_name: str) -> str:
3535

3636
def gen_block_value(block: NetBlock, refdes_mode: RefdesMode) -> str:
3737
if refdes_mode == RefdesMode.PathnameAsValue:
38-
if block.refdes.startswith('TP'): # test points keep their value
38+
if 'TP' in block.refdes: # test points keep their value
3939
return f'(value "{block.value}")'
4040
else:
4141
pathname = '.'.join(block.path)

edg/jlcparts/JlcPartsBase.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def config_root_dir(root_dir: str):
9595
CapacitorsMultilayer_Ceramic_Capacitors_MLCC___SMDakaSMT.json.gz
9696
This setting is on a JlcPartsBase-wide basis."""
9797
assert JlcPartsBase._config_parts_root_dir is None, \
98-
f"attempted to reassign configure_root_dir, was {JlcPartsBase._config_parts_root_dir}, new {root_dir}"
98+
f"attempted to reassign config_root_dir, was {JlcPartsBase._config_parts_root_dir}, new {root_dir}"
9999
JlcPartsBase._config_parts_root_dir = root_dir
100100

101101
_JLC_PARTS_FILE_NAMES: ClassVar[List[str]] # set by subclass
@@ -165,7 +165,7 @@ def _parse_table(cls) -> PartsTable:
165165

166166
@classmethod
167167
def _row_sort_by(cls, row: PartsTableRow) -> Any:
168-
return [not row[cls.BASIC_PART_COL], cls._row_area(row), row[cls.COST_COL]]
168+
return [not row[cls.BASIC_PART_COL], cls._row_area(row), super()._row_sort_by(row), row[cls.COST_COL]]
169169

170170
def _row_generate(self, row: PartsTableRow) -> None:
171171
super()._row_generate(row)

0 commit comments

Comments
 (0)