Replace legacy LAN design with Pico 2 W Clockport project
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Create a Pico 2 W placement revision from the imported Zorro LAN/IDE board."""
|
||||
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
|
||||
import pcbnew
|
||||
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
SOURCE = HERE / "ZORRO-LAN-IDE-CP-V2F.kicad_pcb"
|
||||
TARGET = HERE / "ZORRO-PICO2W-CP.kicad_pcb"
|
||||
PICO_LIBRARY = "/usr/share/kicad/demos/tiny_tapeout/tinytapeout-kicad-libs/footprints/MCU_RaspberryPi_and_Boards.pretty"
|
||||
SO_LIBRARY = "/usr/share/kicad/footprints/Package_SO.pretty"
|
||||
SOT_LIBRARY = "/usr/share/kicad/footprints/Package_TO_SOT_SMD.pretty"
|
||||
|
||||
|
||||
def mm(value):
|
||||
return pcbnew.FromMM(value)
|
||||
|
||||
|
||||
def point(x, y):
|
||||
return pcbnew.VECTOR2I(mm(x), mm(y))
|
||||
|
||||
|
||||
def add_footprint(board, library, name, reference, value, x, y, rotation=0):
|
||||
footprint = pcbnew.FootprintLoad(library, name)
|
||||
if footprint is None:
|
||||
raise RuntimeError(f"Cannot load {library}:{name}")
|
||||
footprint.SetReference(reference)
|
||||
footprint.SetValue(value)
|
||||
footprint.SetPosition(point(x, y))
|
||||
footprint.SetOrientationDegrees(rotation)
|
||||
board.Add(footprint)
|
||||
return footprint
|
||||
|
||||
|
||||
def set_net(footprint, pad_number, net):
|
||||
for pad in footprint.Pads():
|
||||
if pad.GetNumber() == str(pad_number):
|
||||
pad.SetNet(net)
|
||||
|
||||
|
||||
def add_text(board, text, x, y, size=1.2):
|
||||
label = pcbnew.PCB_TEXT(board)
|
||||
label.SetText(text)
|
||||
label.SetPosition(point(x, y))
|
||||
label.SetTextSize(pcbnew.VECTOR2I(mm(size), mm(size)))
|
||||
label.SetLayer(pcbnew.F_SilkS)
|
||||
board.Add(label)
|
||||
|
||||
|
||||
def add_net(board, nets, name):
|
||||
net = pcbnew.NETINFO_ITEM(board, name)
|
||||
board.Add(net)
|
||||
nets[name] = net
|
||||
return net
|
||||
|
||||
|
||||
def pad_position(footprint, number):
|
||||
"""Return the absolute pad location for a unique footprint pad."""
|
||||
for pad in footprint.Pads():
|
||||
if pad.GetNumber() == str(number):
|
||||
return pad.GetPosition()
|
||||
raise RuntimeError(f"Pad {number} not found on {footprint.GetReference()}")
|
||||
|
||||
|
||||
def add_route(board, net, coordinates, width=0.20, layer=pcbnew.F_Cu):
|
||||
"""Add a deliberate Manhattan copper route through the supplied points."""
|
||||
for start, end in zip(coordinates, coordinates[1:]):
|
||||
if start == end:
|
||||
continue
|
||||
track = pcbnew.PCB_TRACK(board)
|
||||
track.SetNet(net)
|
||||
track.SetLayer(layer)
|
||||
track.SetWidth(mm(width))
|
||||
track.SetStart(start)
|
||||
track.SetEnd(end)
|
||||
board.Add(track)
|
||||
|
||||
|
||||
shutil.copy2(SOURCE, TARGET)
|
||||
board = pcbnew.LoadBoard(str(TARGET))
|
||||
if board is None:
|
||||
raise RuntimeError("KiCad could not load the imported board")
|
||||
|
||||
# The converter placed CN1's footprint drawing on Edge.Cuts. Those three
|
||||
# segments duplicate part of the actual perimeter and make the outline appear
|
||||
# self-intersecting. Keep them as silkscreen artwork instead.
|
||||
for footprint in board.GetFootprints():
|
||||
if footprint.GetReference() == "CN1":
|
||||
for graphic in footprint.GraphicalItems():
|
||||
if graphic.GetLayer() == pcbnew.Edge_Cuts:
|
||||
graphic.SetLayer(pcbnew.F_SilkS)
|
||||
|
||||
board_nets = board.GetNetsByName()
|
||||
nets = {str(name): board_nets[name] for name in board_nets}
|
||||
for name in (
|
||||
*(f"PICO_D{index}" for index in range(8)),
|
||||
"PICO_A2", "PICO_A3", "PICO_A4", "PICO_A5", "PICO_CS", "PICO_WE",
|
||||
"PICO_RD", "PICO_RESET", "PICO_IRQ", "CP_DIR",
|
||||
):
|
||||
add_net(board, nets, name)
|
||||
|
||||
def source_pad(reference, number):
|
||||
for footprint in board.GetFootprints():
|
||||
if footprint.GetReference() == reference:
|
||||
return pad_position(footprint, number)
|
||||
raise RuntimeError(f"Source footprint {reference} not found")
|
||||
|
||||
|
||||
# The direct signal roots at the Zorro glue logic. These replace the removed
|
||||
# Clockport header endpoints; routing from them avoids any dead-end detour into
|
||||
# the former connector area.
|
||||
signal_sources = {
|
||||
"DQ0": source_pad("IC2", 118), "DQ1": source_pad("IC2", 117),
|
||||
"DQ2": source_pad("IC2", 120), "DQ3": source_pad("IC2", 119),
|
||||
"DQ4": source_pad("IC2", 124), "DQ5": source_pad("IC2", 121),
|
||||
"DQ6": source_pad("IC2", 126), "DQ7": source_pad("IC2", 125),
|
||||
"LAN_A3": source_pad("IC2", 5), "LAN_A4": source_pad("IC2", 9),
|
||||
"LAN_A5": source_pad("IC2", 7), "LAN_A6": source_pad("IC2", 11),
|
||||
"CP_CS": source_pad("IC2", 34), "CP_WE": source_pad("IC2", 70),
|
||||
"CP_RD": source_pad("IC2", 71), "IDE_RST": source_pad("IC5", 18),
|
||||
"CP_IRQ": source_pad("IC2", 31),
|
||||
}
|
||||
|
||||
# Pico 2 W uses the same 2x20 castellated pad geometry as Pico W. It is placed
|
||||
# horizontally in the former LAN/MagJack area, USB toward the right board edge.
|
||||
pico = add_footprint(board, PICO_LIBRARY, "RPi_Pico_SMD_TH", "PICO1", "Raspberry Pi Pico 2 W", 183.5, 102.0, 90)
|
||||
mapping = {
|
||||
1: "PICO_D0", 2: "PICO_D1", 4: "PICO_D2", 5: "PICO_D3", 6: "PICO_D4", 7: "PICO_D5", 9: "PICO_D6", 10: "PICO_D7",
|
||||
11: "PICO_A2", 12: "PICO_A3", 14: "PICO_A4", 15: "PICO_A5", 16: "PICO_CS",
|
||||
17: "PICO_WE", 19: "PICO_RD", 20: "PICO_RESET", 24: "PICO_IRQ", 36: "+3V3", 39: "+5V",
|
||||
}
|
||||
for pad, name in mapping.items():
|
||||
set_net(pico, pad, nets[name])
|
||||
for pad in (3, 8, 13, 18, 23, 28, 38):
|
||||
set_net(pico, pad, nets["GND"])
|
||||
|
||||
# Clockport routing. U1 translates the bidirectional data bus, U2 buffers the
|
||||
# address/control lines, U3 derives U1's direction, and Q3 drives !INT6 safely.
|
||||
u1 = add_footprint(board, SO_LIBRARY, "TSSOP-24_4.4x7.8mm_P0.65mm", "U1", "SN74LVC8T245 DATA BUFFER", 150.0, 94.0, 90)
|
||||
u2 = add_footprint(board, SO_LIBRARY, "TSSOP-24_4.4x7.8mm_P0.65mm", "U2", "SN74LVC8T245 CONTROL BUFFER", 150.0, 104.5, 90)
|
||||
u3 = add_footprint(board, SOT_LIBRARY, "SOT-23", "U3", "SN74LVC1G04", 153.0, 113.5, 0)
|
||||
q3 = add_footprint(board, SOT_LIBRARY, "SOT-23", "Q3", "2N7002 !INT6 OPEN DRAIN", 158.0, 116.5, 0)
|
||||
r11 = next(footprint for footprint in board.GetFootprints() if footprint.GetReference() == "R11")
|
||||
r11.SetPosition(point(162.5, 116.5))
|
||||
r11.SetOrientationDegrees(90)
|
||||
|
||||
for index, pad in enumerate(range(3, 11)):
|
||||
set_net(u1, pad, nets[f"DQ{index}"])
|
||||
for index, pad in enumerate(range(21, 13, -1)):
|
||||
set_net(u1, pad, nets[f"PICO_D{index}"])
|
||||
for pad in (1, 23, 24):
|
||||
set_net(u1, pad, nets["+3V3"])
|
||||
for pad in (11, 12, 13):
|
||||
set_net(u1, pad, nets["GND"])
|
||||
set_net(u1, 2, nets["CP_DIR"])
|
||||
set_net(u1, 22, nets["CP_CS"])
|
||||
|
||||
control_inputs = ("LAN_A3", "LAN_A4", "LAN_A5", "LAN_A6", "CP_CS", "CP_WE", "CP_RD", "IDE_RST")
|
||||
control_outputs = ("PICO_A2", "PICO_A3", "PICO_A4", "PICO_A5", "PICO_CS", "PICO_WE", "PICO_RD", "PICO_RESET")
|
||||
for pad, name in zip(range(3, 11), control_inputs):
|
||||
set_net(u2, pad, nets[name])
|
||||
for pad, name in zip(range(21, 13, -1), control_outputs):
|
||||
set_net(u2, pad, nets[name])
|
||||
for pad in (1, 2, 23, 24):
|
||||
set_net(u2, pad, nets["+3V3"])
|
||||
for pad in (11, 12, 13, 22):
|
||||
set_net(u2, pad, nets["GND"])
|
||||
|
||||
set_net(u3, 1, nets["CP_WE"])
|
||||
set_net(u3, 2, nets["GND"])
|
||||
set_net(u3, 3, nets["CP_DIR"])
|
||||
set_net(u3, 5, nets["+3V3"])
|
||||
set_net(q3, 1, nets["PICO_IRQ"])
|
||||
set_net(q3, 2, nets["GND"])
|
||||
set_net(q3, 3, nets["CP_IRQ"])
|
||||
|
||||
# Remove only the connector-side copper stubs. The retained portions keep the
|
||||
# original Zorro circuitry intact, while no selected signal returns to CN2.
|
||||
def remove_clockport_stubs():
|
||||
# These branches ended at CN2, IC1, R10, or R11 and otherwise leave visible,
|
||||
# electrically meaningless DQ stubs such as the old DQ1 run.
|
||||
routed_nets = set(signal_sources)
|
||||
for track in list(board.GetTracks()):
|
||||
# A3-A6 were repurposed as the four Clockport address lines. Every
|
||||
# other LAN-named net terminated at the omitted ENC controller.
|
||||
if track.GetNetname().startswith("LAN_") and track.GetNetname() not in {"LAN_A3", "LAN_A4", "LAN_A5", "LAN_A6"}:
|
||||
board.Remove(track)
|
||||
continue
|
||||
if track.GetNetname() in {"N$3", "LAN_A2", "LAN_INT", "LEDA", "LEDAC", "LEDB", "LEDBC"}:
|
||||
board.Remove(track)
|
||||
continue
|
||||
if track.GetNetname() not in routed_nets:
|
||||
continue
|
||||
start, end = track.GetStart(), track.GetEnd()
|
||||
if ((min(start.x, end.x) > mm(155) and max(start.y, end.y) < mm(110)) or
|
||||
(min(start.y, end.y) < mm(75) and max(start.x, end.x) > mm(150))):
|
||||
board.Remove(track)
|
||||
|
||||
|
||||
# Route all new logic on F.Cu so it electrically joins the SMD pads. Lanes are
|
||||
# separated to make the handoff readable.
|
||||
def route_from_source(net_name, destination, lane_y):
|
||||
source = signal_sources[net_name]
|
||||
add_route(board, nets[net_name], [source, point(source.x / 1e6, lane_y),
|
||||
point(destination.x / 1e6, lane_y), destination])
|
||||
|
||||
|
||||
# Clockport to the A sides of both translators.
|
||||
for index, name in enumerate(f"DQ{n}" for n in range(8)):
|
||||
route_from_source(name, pad_position(u1, 3 + index), 91.8 + index * 0.42)
|
||||
for index, name in enumerate(control_inputs):
|
||||
route_from_source(name, pad_position(u2, 3 + index), 104.0 + index * 0.42)
|
||||
route_from_source("CP_CS", pad_position(u1, 22), 90.8)
|
||||
route_from_source("CP_WE", pad_position(u3, 1), 112.0)
|
||||
route_from_source("CP_IRQ", pad_position(q3, 3), 114.0)
|
||||
|
||||
# Data translator to the Pico data GPIOs (GP0-GP7).
|
||||
pico_data_pads = (1, 2, 4, 5, 6, 7, 9, 10)
|
||||
for index, pico_pad in enumerate(pico_data_pads):
|
||||
start = pad_position(u1, 21 - index)
|
||||
end = pad_position(pico, pico_pad)
|
||||
lane_y = 89.0 + index * 0.45
|
||||
add_route(board, nets[f"PICO_D{index}"], [start, point(start.x / 1e6, lane_y),
|
||||
point(end.x / 1e6, lane_y), end])
|
||||
|
||||
# Control translator to GP8-GP15. The Pico pin numbers skip ground pads.
|
||||
pico_control_pads = (11, 12, 14, 15, 16, 17, 19, 20)
|
||||
for index, (name, pico_pad) in enumerate(zip(control_outputs, pico_control_pads)):
|
||||
start = pad_position(u2, 21 - index)
|
||||
end = pad_position(pico, pico_pad)
|
||||
lane_y = 97.5 + index * 0.42
|
||||
add_route(board, nets[name], [start, point(start.x / 1e6, lane_y),
|
||||
point(end.x / 1e6, lane_y), end])
|
||||
|
||||
# Local direction and interrupt paths.
|
||||
add_route(board, nets["CP_DIR"], [pad_position(u3, 3), point(156.0, 113.5),
|
||||
point(156.0, 90.0), pad_position(u1, 2)])
|
||||
add_route(board, nets["PICO_IRQ"], [pad_position(q3, 1), point(160.0, 115.55),
|
||||
point(160.0, 96.5), point(200.01, 96.5),
|
||||
pad_position(pico, 24)])
|
||||
add_route(board, nets["CP_IRQ"], [pad_position(q3, 3), pad_position(r11, 2)])
|
||||
|
||||
add_text(board, "ZORRO PICO 2 W CLOCKPORT", 183.0, 124.0, 1.4)
|
||||
add_text(board, "PICO USB -> BOARD EDGE", 196.0, 85.0, 0.9)
|
||||
add_text(board, "NO COPPER UNDER ANTENNA", 159.0, 85.0, 0.9)
|
||||
add_text(board, "LAN COMPONENTS OMITTED", 183.0, 129.0, 1.0)
|
||||
|
||||
# The full ENC624J600/MagJack circuit is not fitted in this revision. Existing
|
||||
# copper is intentionally retained until the follow-up routing pass so that the
|
||||
# imported board remains reviewable against the original.
|
||||
lan_references = {
|
||||
"CN2", "IC1", "X1", "Q1", "Q2", "C1", "C3", "C4", "C5", "C6", "C7", "C8", "C13", "C18",
|
||||
"C19", "C24", "C25", "C26", "C27", "C28", "C33", "R4", "R6", "R13",
|
||||
"R10", "R12", "R14", "R15", "R16", "R17", "R18", "R19", "LEDA", "LEDB", "LEDA0", "LEDB0", "SJ2", "SV1",
|
||||
}
|
||||
for footprint in list(board.GetFootprints()):
|
||||
if footprint.GetReference() in lan_references:
|
||||
board.Remove(footprint)
|
||||
|
||||
remove_clockport_stubs()
|
||||
|
||||
pcbnew.SaveBoard(str(TARGET), board)
|
||||
print(TARGET)
|
||||
Reference in New Issue
Block a user