Files
Zorro-LAN-IDE/Pico2W-Clockport-Version/kicad/create_pico2w_layout.py
T

267 lines
11 KiB
Python

#!/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)