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