Initial Ti375 GNSS IMU board design and documentation
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Install deterministic 0.8-mm-pitch dogbone escapes for connected Ti375 balls."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
import pcbnew
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_INPUT = ROOT / "hardware" / "board.kicad_pcb"
|
||||
DEFAULT_OUTPUT = ROOT / "hardware" / "board_prerouted.kicad_pcb"
|
||||
POWER_NETS = {
|
||||
"GND", "VCC0V95", "VCC1V8", "VCC3V3", "VCC3V0_DIG",
|
||||
}
|
||||
|
||||
|
||||
def mm(value: float) -> int:
|
||||
return pcbnew.FromMM(value)
|
||||
|
||||
|
||||
def add_segment(board: pcbnew.BOARD, pad: pcbnew.PAD,
|
||||
end: pcbnew.VECTOR2I, width_mm: float) -> None:
|
||||
track = pcbnew.PCB_TRACK(board)
|
||||
track.SetStart(pad.GetPosition())
|
||||
track.SetEnd(end)
|
||||
track.SetWidth(mm(width_mm))
|
||||
track.SetLayer(pcbnew.F_Cu)
|
||||
track.SetNet(pad.GetNet())
|
||||
track.SetLocked(True)
|
||||
board.Add(track)
|
||||
|
||||
|
||||
def add_via(board: pcbnew.BOARD, pad: pcbnew.PAD,
|
||||
position: pcbnew.VECTOR2I) -> None:
|
||||
via = pcbnew.PCB_VIA(board)
|
||||
via.SetPosition(position)
|
||||
via.SetWidth(mm(0.45))
|
||||
via.SetDrill(mm(0.225))
|
||||
via.SetLayerPair(pcbnew.F_Cu, pcbnew.B_Cu)
|
||||
via.SetNet(pad.GetNet())
|
||||
via.SetLocked(True)
|
||||
board.Add(via)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("input", nargs="?", type=Path, default=DEFAULT_INPUT)
|
||||
parser.add_argument("output", nargs="?", type=Path, default=DEFAULT_OUTPUT)
|
||||
args = parser.parse_args()
|
||||
|
||||
board = pcbnew.LoadBoard(str(args.input))
|
||||
fpga = board.FindFootprintByReference("U1")
|
||||
if fpga is None:
|
||||
raise RuntimeError("U1 Ti375 footprint is missing")
|
||||
centre = fpga.GetPosition()
|
||||
occupied: set[tuple[int, int]] = set()
|
||||
counts = {"power": 0, "signal": 0}
|
||||
|
||||
for pad in fpga.Pads():
|
||||
if not pad.GetNetname():
|
||||
continue
|
||||
position = pad.GetPosition()
|
||||
# Move each ball toward its package quadrant. Centre-row/column balls
|
||||
# use the positive direction, leaving a full 0.8-mm via grid without
|
||||
# duplicated interstitial locations.
|
||||
dx = 0.4 if position.x >= centre.x else -0.4
|
||||
dy = 0.4 if position.y >= centre.y else -0.4
|
||||
via_position = pcbnew.VECTOR2I(position.x + mm(dx), position.y + mm(dy))
|
||||
key = (via_position.x, via_position.y)
|
||||
if key in occupied:
|
||||
raise RuntimeError(f"Duplicate dogbone via at {key} for U1.{pad.GetNumber()}")
|
||||
occupied.add(key)
|
||||
is_power = pad.GetNetname() in POWER_NETS
|
||||
add_segment(board, pad, via_position, 0.15 if is_power else 0.10)
|
||||
add_via(board, pad, via_position)
|
||||
counts["power" if is_power else "signal"] += 1
|
||||
|
||||
if not pcbnew.ZONE_FILLER(board).Fill(board.Zones()):
|
||||
raise RuntimeError("Zone fill failed after BGA fanout")
|
||||
pcbnew.SaveBoard(str(args.output), board)
|
||||
for suffix in (".kicad_pro", ".kicad_dru"):
|
||||
source = args.input.with_suffix(suffix)
|
||||
if source.exists():
|
||||
shutil.copyfile(source, args.output.with_suffix(suffix))
|
||||
print(f"Installed {counts['power']} power/GND and {counts['signal']} signal dogbones")
|
||||
print(f"Saved {args.output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user