main
ae007 Project page: AFS_Adom rev A (Rice Eclipse AFS clean-room AI redesign): highlight reel and narrated process video with… cbe460e 7d ago
"""AFS_Adom schematic generator: the complete netlist, five hierarchical sheets + root."""
import sys, os, math, json
sys.path.insert(0, '/tmp/claude-1001/-home-adom-project/8819fb5f-c2c6-4524-9528-99428cd6b88e/scratchpad/gen')
from kicad_gen import *

SCR = '/tmp/claude-1001/-home-adom-project/8819fb5f-c2c6-4524-9528-99428cd6b88e/scratchpad'
LIBS = SCR + '/libs'
OUT = SCR + '/AFS_Adom'
PROJECT = 'AFS_Adom'

lib = SymLib()
for name in ['Device', 'power', 'Connector', 'Connector_Generic', 'MCU_RaspberryPi', 'Memory_Flash', 'Regulator_Switching',
             'Power_Management', 'Transistor_FET', 'Power_Protection', 'Diode', 'Switch', 'Mechanical', '74xx', 'Sensor_Pressure']:
    lib.load(LIBS + '/' + name + '.kicad_sym', name)
lib.load(OUT + '/lib/AFS_Adom.kicad_sym', 'AFS_Adom')
lib.load(OUT + '/lib/Molecule.kicad_sym', 'Molecule')

# ---------------------------------------------------------------- parts (MPN + sourcing)
P = {
 'RP2350B':   dict(mfr='Raspberry Pi', mpn='RP2350B', mouser='358-SC1510', lcsc='C42415655', digikey='2648-SC1510-A4TR-ND', desc='MCU dual Cortex-M33/Hazard3 150 MHz, 48 GPIO, QFN-80'),
 'ICM45686':  dict(mfr='TDK InvenSense', mpn='ICM-45686', mouser='410-ICM-45686', lcsc='', digikey='1428-ICM-45686TR-ND', desc='6-axis IMU, LGA-14'),
 'KX134':     dict(mfr='Kionix/ROHM', mpn='KX134-1211', mouser='912-KX134-1211', lcsc='C2157908', digikey='846-KX134-1211TR-ND', desc='High-g accelerometer 64 g, LGA-12'),
 'MMC5983':   dict(mfr='MEMSIC', mpn='MMC5983MA', mouser='438-MMC5983MA', lcsc='C404329', digikey='1267-MMC5983MATR-ND', desc='3-axis magnetometer, LGA-16'),
 'MS5611':    dict(mfr='TE Connectivity', mpn='MS561101BA03-50', mouser='824-MS561101BA03-50', lcsc='', digikey='223-1622-2-ND', desc='Barometer MS5611-01BA03, LGA-8'),
 'TMP117':    dict(mfr='Texas Instruments', mpn='TMP117AIDRVR', mouser='595-TMP117AIDRVR', lcsc='C699536', digikey='296-51834-2-ND', desc='Temperature sensor 0.1 C, WSON-6'),
 'INA219':    dict(mfr='Texas Instruments', mpn='INA219AIDCNR', mouser='595-INA219AIDCNR', lcsc='C87469', digikey='296-23770-2-ND', desc='Current/voltage monitor, SOT-23-8'),
 'W25Q128':   dict(mfr='Winbond', mpn='W25Q128JVSIQ', mouser='454-W25Q128JVSIQ', lcsc='C97521', digikey='W25Q128JVSIQ-ND', desc='QSPI flash 128 Mbit, SOIC-8 208 mil'),
 'AP63205':   dict(mfr='Diodes Inc', mpn='AP63205WU-7', mouser='621-AP63205WU-7', lcsc='C2060170', digikey='AP63205WU-7DICT-ND', desc='Buck 5 V 2 A, TSOT-23-6'),
 'AP63203':   dict(mfr='Diodes Inc', mpn='AP63203WU-7', mouser='621-AP63203WU-7', lcsc='C2060169', digikey='AP63203WU-7DICT-ND', desc='Buck 3.3 V 2 A, TSOT-23-6'),
 'LTC4359':   dict(mfr='Analog Devices', mpn='LTC4359CMS8#PBF', mouser='584-LTC4359CMS8#PBF', lcsc='', digikey='505-LTC4359CMS8#PBF-ND', desc='Ideal diode controller, MSOP-8'),
 'DMN3404L':  dict(mfr='Diodes Inc', mpn='DMN3404L-7', mouser='621-DMN3404L-7', lcsc='C155257', digikey='DMN3404LDITR-ND', desc='N-MOSFET 30 V 5.8 A 65 mOhm, SOT-23'),
 'SMAJ12A':   dict(mfr='Littelfuse', mpn='SMAJ12A', mouser='652-SMAJ12A', lcsc='C113957', digikey='SMAJ12ALFTR-ND', desc='TVS 12 V standoff 400 W, SMA'),
 'SHUNT':     dict(mfr='Bourns', mpn='CRF2512-FZ-R005ELF', mouser='652-CRF2512FZR005ELF', lcsc='', digikey='', desc='Shunt 5 mOhm 1% 2 W, 2512'),
 'C220U':     dict(mfr='Panasonic', mpn='EEE-FK1C221P', mouser='667-EEE-FK1C221P', lcsc='', digikey='PCE3787TR-ND', desc='220 uF 16 V electrolytic, 8x10.2 mm'),
 'C10U25':    dict(mfr='Samsung', mpn='CL21A106KAYNNNE', mouser='187-CL21A106KAYNNNE', lcsc='C15850', digikey='1276-2891-2-ND', desc='10 uF 25 V X5R 0805'),
 'C22U':      dict(mfr='Samsung', mpn='CL21A226MQQNNNE', mouser='187-CL21A226MQQNNNE', lcsc='C5674', digikey='1276-1100-2-ND', desc='22 uF 6.3 V X5R 0805'),
 'C100N':     dict(mfr='Samsung', mpn='CL10B104KB8NNNC', mouser='187-CL10B104KB8NNNC', lcsc='C1591', digikey='1276-1000-2-ND', desc='100 nF 50 V X7R 0603'),
 'C4U7':      dict(mfr='Samsung', mpn='CL10A475KP8NNNC', mouser='187-CL10A475KP8NNNC', lcsc='C1705', digikey='1276-1044-2-ND', desc='4.7 uF 10 V X5R 0603'),
 'C1U':       dict(mfr='Samsung', mpn='CL10A105KA8NNNC', mouser='187-CL10A105KA8NNNC', lcsc='C5673', digikey='1276-1102-2-ND', desc='1 uF 25 V X5R 0603'),
 'C15P':      dict(mfr='Murata', mpn='GRM1885C1H150JA01D', mouser='81-GRM39C150J50', lcsc='C71651', digikey='490-1407-2-ND', desc='15 pF 50 V C0G 0603'),
 'C10U16':    dict(mfr='Samsung', mpn='CL21A106KOQNNNE', mouser='187-CL21A106KOQNNNE', lcsc='C1713', digikey='1276-1096-2-ND', desc='10 uF 16 V X5R 0805'),
 'C10U6':     dict(mfr='Samsung', mpn='CL10A106MQ8NNNC', mouser='187-CL10A106MQ8NNNC', lcsc='C1691', digikey='1276-1119-2-ND', desc='10 uF 6.3 V X5R 0603'),
 'L4U7':      dict(mfr='Bourns', mpn='SRN6045TA-4R7M', mouser='652-SRN6045TA-4R7M', lcsc='C2044594', digikey='SRN6045TA-4R7MTR-ND', desc='4.7 uH 4.5 A shielded, 6x6 mm'),
 'L3U3':      dict(mfr='Abracon', mpn='AOTA-B201610S3R3-101-T', mouser='815-AB201610S3R3101T', lcsc='C42411119', digikey='535-AOTA-B201610S3R3-101-TTR-ND', desc='3.3 uH 2.1 A polarity-marked, 0806'),
 'LEDG':      dict(mfr='Wurth', mpn='150060GS75000', mouser='710-150060GS75000', lcsc='C5252984', digikey='732-4971-2-ND', desc='LED green 0603'),
 'LEDR':      dict(mfr='Wurth', mpn='150060RS75000', mouser='710-150060RS75000', lcsc='C3030991', digikey='732-4978-2-ND', desc='LED red 0603'),
 'LEDB':      dict(mfr='Wurth', mpn='150060BS75000', mouser='710-150060BS75000', lcsc='C5197644', digikey='732-4966-2-ND', desc='LED blue 0603'),
 'LEDY':      dict(mfr='Wurth', mpn='150060YS75000', mouser='710-150060YS75000', lcsc='C6784997', digikey='732-4981-2-ND', desc='LED yellow 0603'),
 'SW':        dict(mfr='C&K', mpn='PTS645SM43SMTR92 LFS', mouser='611-PTS645SM43SMTR92', lcsc='', digikey='CKN9112TR-ND', desc='Tactile switch 6 mm SMD'),
 'USBC':      dict(mfr='GCT', mpn='USB4105-GF-A', mouser='640-USB4105-GF-A', lcsc='C3020560', digikey='2073-USB4105-GF-ATR-ND', desc='USB-C receptacle USB 2.0 16-pin'),
 'USBLC6':    dict(mfr='STMicroelectronics', mpn='USBLC6-2SC6', mouser='511-USBLC6-2SC6', lcsc='C2687116', digikey='497-5235-2-ND', desc='USB ESD array, SOT-23-6'),
 'XTAL':      dict(mfr='Abracon', mpn='ABM8-272-T3', mouser='815-ABM8-272-T3', lcsc='C20625731', digikey='535-ABM8-272-T3TR-ND', desc='Crystal 12 MHz 10 pF 3225'),
 'SD':        dict(mfr='Hirose', mpn='DM3AT-SF-PEJM5', mouser='798-DM3AT-SF-PEJM5', lcsc='C114218', digikey='HR1964TR-ND', desc='microSD push-push with detect'),
 'AHCT541':   dict(mfr='Texas Instruments', mpn='SN74AHCT541PWR', mouser='595-SN74AHCT541PWR', lcsc='C50989', digikey='296-1120-2-ND', desc='Octal buffer 5 V TTL-in, TSSOP-20'),
 'XT30':      dict(mfr='Amass', mpn='XT30PW-M', mouser='', lcsc='C431092', digikey='', desc='XT30 right-angle PCB male'),
 'PH4':       dict(mfr='JST', mpn='B4B-PH-K-S(LF)(SN)', mouser='306-B4BPHKSLFSNPP', lcsc='C131334', digikey='455-1706-ND', desc='JST PH 4-pin header'),
 'PH2':       dict(mfr='JST', mpn='B2B-PH-K-S(LF)(SN)', mouser='306-B2BPHKSLFSNPP', lcsc='C131337', digikey='455-1704-ND', desc='JST PH 2-pin header'),
 'HDR3':      dict(mfr='Samtec', mpn='TSW-103-07-G-S', mouser='200-TSW10307GS', lcsc='C3335811', digikey='612-TSW-103-07-G-S-ND', desc='Pin header 1x3 0.1 in'),
 'R10K':      dict(mfr='Yageo', mpn='RC0603FR-0710KL', mouser='603-RC0603FR-0710KL', lcsc='C98220', digikey='311-10.0KHRTR-ND', desc='10 kOhm 1% 0603'),
 'R1K':       dict(mfr='Yageo', mpn='RC0603FR-071KL', mouser='603-RC0603FR-071KL', lcsc='C22548', digikey='311-1.00KHRTR-ND', desc='1 kOhm 1% 0603'),
 'R27':       dict(mfr='Yageo', mpn='RC0603FR-0727RL', mouser='603-RC0603FR-0727RL', lcsc='C137753', digikey='311-27.0HRTR-ND', desc='27 Ohm 1% 0603'),
 'R5K1':      dict(mfr='Yageo', mpn='RC0603FR-075K1L', mouser='603-RC0603FR-075K1L', lcsc='C105580', digikey='311-5.10KHRTR-ND', desc='5.1 kOhm 1% 0603'),
 'R4K7':      dict(mfr='Yageo', mpn='RC0603FR-074K7L', mouser='603-RC0603FR-074K7L', lcsc='C99782', digikey='311-4.70KHRTR-ND', desc='4.7 kOhm 1% 0603'),
 'R33':       dict(mfr='Yageo', mpn='RC0603FR-0733RL', mouser='603-RC0603FR-0733RL', lcsc='', digikey='13-RC0603FR-0733RLTR-ND', desc='33 Ohm 1% 0603'),
 'R2K2':      dict(mfr='Yageo', mpn='RC0603FR-072K2L', mouser='603-RC0603FR-072K2L', lcsc='C114662', digikey='311-2.20KHRTR-ND', desc='2.2 kOhm 1% 0603'),
 'R10':       dict(mfr='Yageo', mpn='RC0603FR-0710RL', mouser='603-RC0603FR-0710RL', lcsc='C98211', digikey='311-10.0HRTR-ND', desc='10 Ohm 1% 0603'),
 'MPIN':      dict(mfr='Adom', mpn='MachinePinMediumShort', mouser='', lcsc='', digikey='', desc='Adom machine pin, medium short (1.6 mm pad / 1.2 mm drill), Adom stock'),
 'MCON':      dict(mfr='Adom', mpn='MachineContactMedium', mouser='', lcsc='', digikey='', desc='Adom machine contact, medium (1.3 mm pad / 0.78 mm drill), Adom stock'),
 'TP':        dict(mfr='', mpn='no part', mouser='', lcsc='', digikey='', desc='0.5 mm round exposed-copper test point (adom/testpoint-pad-0-5mm)'),
}

FP = {
 'R': 'Resistor_SMD:R_0603_1608Metric', 'C': 'Capacitor_SMD:C_0603_1608Metric', 'C0805': 'Capacitor_SMD:C_0805_2012Metric',
 'LED': 'LED_SMD:LED_0603_1608Metric', 'SHUNT': 'Resistor_SMD:R_2512_6332Metric', 'CP': 'Capacitor_SMD:CP_Elec_8x10',
}


def fields(pid, extra=None):
    p = P[pid]
    f = {'MPN': p['mpn'], 'Manufacturer': p['mfr'], 'Mouser': p['mouser'], 'LCSC': p['lcsc'], 'DigiKey': p['digikey'],
         'Description': p['desc']}
    if extra:
        f.update(extra)
    return f


class Sheet:
    """A schematic sheet with a simple flowing auto-layout."""

    def __init__(self, name, filename, root, paper='A2', cols_width=560, top=20):
        self.sch = Schematic(PROJECT, filename, lib, root_uuid=root.uuid if root else None, paper=paper)
        self.sch.title = PROJECT + ' - ' + name
        self.name = name
        self.x, self.y = 25.4, top
        self.row_h = 0
        self.maxw = cols_width
        self.refs = {}

    def ref(self, prefix):
        self.refs[prefix] = REFS.get(prefix, 0) + 1
        REFS[prefix] = self.refs[prefix]
        return '%s%d' % (prefix, REFS[prefix])

    def place(self, libname, sym, ref, value, fp, nets, pid=None, extra=None, rot=0, w=None, h=None, at=None, stub=2.54, **kw):
        pins = lib.pins(sym)
        # pin extents (lib frame) after rotation
        xs = [rot_(p.x, p.y, rot)[0] for p in pins] or [0]
        ys = [rot_(p.x, p.y, rot)[1] for p in pins] or [0]
        pw = (max(xs) - min(xs)) + 2 * stub + 36
        ph = (max(ys) - min(ys)) + 2 * stub + 10
        pw = w or pw
        ph = h or ph
        if at is None:
            if self.x + pw > self.maxw:
                self.x = 25.4
                self.y += self.row_h + 6
                self.row_h = 0
            X = self.x - min(xs) + stub + 18
            Y = self.y + max(ys) + stub + 6
            self.x += pw
            self.row_h = max(self.row_h, ph)
            X, Y = round(X / 1.27) * 1.27, round(Y / 1.27) * 1.27
        else:
            X, Y = at
        # map names to numbers
        nmap = {}
        for k, v in nets.items():
            hit = [p.number for p in pins if p.number == k]
            if not hit:
                hit = [p.number for p in pins if p.name == k]
            if not hit:
                raise KeyError('%s: no pin %r' % (sym, k))
            for n in hit:
                nmap[n] = v
        missing = [p.number + ':' + p.name for p in pins if p.number not in nmap]
        if missing:
            raise KeyError('%s %s: unconnected pins %s' % (ref, sym, missing))
        f = fields(pid, extra) if pid else dict(extra or {})
        suid = self.sch.add_symbol(libname, sym, ref, value, (X, Y), rot, fp, nmap, fields=f, stub=stub, **kw)
        COMPS[ref] = dict(ref=ref, lib=libname, sym=sym, value=value, footprint=fp, pads=dict(nmap), uuid=suid,
                          sheet=self.sch.filename, sheet_uuid=self.sch.uuid, path=self.sch.path(), fields=f)
        return ref

    def text(self, s, at=None, size=1.5):
        if at is None:
            self.x = 25.4
            self.y += self.row_h + 6
            self.row_h = 0
            at = (self.x, self.y)
            self.y += 6 * (s.count('\n') + 1) * size / 1.5
        self.sch.add_text(s, at, size)

    def flags(self, nets):
        self.x = 25.4
        self.y += self.row_h + 8
        self.row_h = 12
        for n in nets:
            X = round(self.x / 1.27) * 1.27
            Y = round((self.y + 5.08) / 1.27) * 1.27
            self.sch.add_pwr_flag(n, (X, Y))
            self.x += 25.4


def rot_(x, y, r):
    a = math.radians(r)
    return (x * math.cos(a) - y * math.sin(a), x * math.sin(a) + y * math.cos(a))


REFS = {}
COMPS = {}
STOCK = {"RC0603FR-074K7L": "2613170 (Mouser)", "AOTA-B201610S3R3-101-T": "9385 (Mouser)", "SC2212": "140 (Mouser)", "SC2211": "45 (Mouser)", "AOTA-B201610S3R3101T": "0 (Mouser)", "RC0603FR-1327RL": "59895 (Mouser)", "RC0603FR-0727RL": "44783 (Mouser)", "DM3AT-SF-PEJM5": "74225 (Mouser)", "DM3AT-SF-PEJM5(41)": "16061 (Mouser)", "DM3AT-SF-PEJM5(40)": "6570 (Mouser)", "DM3AT-SF-PEJM5(11)": "3913 (Mouser)", "DM3AT-SF-PEJM5(15)": "0 (Mouser)", "RC0603FR-071KL": "16873680 (Mouser)", "B2B-PH-SM4-TB(LF)(SN)": "56975 (Mouser)", "B2B-PH-K-S-GW": "6153 (Mouser)", "B2B-PH-K-K(LF)(SN)": "984 (Mouser)", "B2B-PH-K-S(LF)(SN)": "811 (Mouser)", "B2B-PH-TW-S(LF)(SN)": "311 (Mouser)", "B2B-PH-K-S": "202586 (Mouser)", "B2B-PH-K-S-GW(PP)": "0 (Mouser)", "CSS2H-2512K-5L00F": "0 (Mouser)", "SMAJ12A": "4822 (Mouser)", "SMAJ12A-13-F": "10254 (Mouser)", "SMAJ12A-E3/61": "7628 (Mouser)", "SMAJ12A-E3/5A": "1909 (Mouser)", "B4B-PH-K-S(LF)(SN)": "73995 (Mouser)", "B4B-PH-K-S": "43249 (Mouser)", "INA219AIDCNR": "132 (Mouser)", "INA219AIDCNT": "91 (Mouser)", "INA219AIDCNR1G4": "0 (Mouser)", "INA219AIDCNRG4": "2675 (Mouser)", "AO3400A": "500000 (Mouser)", "AO3400A-EV": "0 (Mouser)", "W25Q128JVSIQ TR": "44 (Mouser)", "W25Q128JVSIQ": "0 (Mouser)", "USBLC6-2SC6": "217220 (Mouser)", "USBLC6-2SC6Y": "0 (Mouser)", "PTS645SM43SMTR92 LFS": "0 (Mouser)", "PTS645SM43SMTR92LFS": "10000 (Mouser)", "DK-45686": "17 (Mouser)", "EV_ICM-45686": "13 (Mouser)", "DK-45605": "6 (Mouser)", "ICM-45686": "0 (Mouser)", "MIKROE-6514": "0 (Mouser)", "USB4105-GF-A": "403592 (Mouser)", "USB4105-GF-A-120": "88215 (Mouser)", "USB4105-GF-A-060": "42083 (Mouser)", "USB4105-15-A-120": "0 (Mouser)", "RC0603FR-0710KL": "2009902 (Mouser)", "RC0603FR-1310KL": "816991 (Mouser)", "CL10A475KP8NNNC": "1311868 (Mouser)", "SN74AHCT541PWRG4": "2048 (Mouser)", "SN74AHCT541PWR": "0 (Mouser)", "SN74AHCT541PWRE4": "0 (Mouser)", "SN74AHCT541PWRG3": "994 (Mouser)", "CL21A106KOQNNNE": "1585760 (Mouser)", "SRN6045TA-4R7M": "0 (Mouser)", "150060BS75000": "242689 (Mouser)", "MS561101BA03-50": "18 (Mouser)", "MS561101BA03-00": "0 (Mouser)", "B4B-PH-K-S-GW": "0 (Mouser)", "B4B-PH-K-S-GU": "0 (Mouser)", "B4B-PH-K-S-GW(PP)": "0 (Mouser)", "B4B-PH-K-S-GU(P)": "0 (Mouser)", "RC0603FR-075K1L": "391040 (Mouser)", "RC0603FR-135K1L": "7752 (Mouser)", "150060YS75000": "202157 (Mouser)", "TSW-103-07-G-S": "268065 (Mouser)", "TSW-103-07-G-S-LL": "1549 (Mouser)", "MTSW-103-07-G-S-240": "459 (Mouser)", "TSW-103-07-G-S-003": "320 (Mouser)", "TSW-103-07-G-S-001": "24 (Mouser)", "HTSW-103-07-G-S": "12278 (Mouser)", "MTSW-103-07-G-S-220": "1472 (Mouser)", "MTSW-103-07-G-S-185": "1472 (Mouser)", "MTSW-103-07-G-S-200": "1470 (Mouser)", "CRF2512-FZ-R005ELF": "70658 (Mouser)", "CRH2512-FZ-R005ELF": "0 (Mouser)", "ABM8-272-T3": "10381 (Mouser)", "CL10A105KA8NNNC": "1546253 (Mouser)", "LTC4359CMS8#PBF": "5731 (Mouser)", "LTC4359CMS8#TRPBF": "5000 (Mouser)", "GRM1885C1H150JA01D": "0 (Mouser)", "TMP117AIDRVR": "36 (Mouser)", "TMP117AIDRVRG4": "0 (Mouser)", "CL10A106MQ8NNNC": "46368 (Mouser)", "CL10B104KB8NNNC": "0 (Mouser)", "KX134-1211": "0 (Mouser)", "KX134-1211-EVK-001": "0 (Mouser)", "17589": "14 (Mouser)", "KX134-1211-EVB110": "0 (Mouser)", "EEE-FK1C221P": "13265 (Mouser)", "150060GS75000": "588289 (Mouser)", "MMC5983MA": "34027 (Mouser)", "SEN-19921": "211 (Mouser)", "SEN-19895": "53 (Mouser)", "MMC5983MA-B": "21 (Mouser)", "DMN3404L-7": "142453 (Mouser)", "RC0603FR-0733RL": "457736 (Mouser)", "RC0603FR-072K2L": "1181614 (Mouser)", "CL21A106KAYNNNE": "625800 (Mouser)", "CL21A226MQQNNNE": "271772 (Mouser)", "SRN6045TA-3R3Y": "29886 (Mouser)", "SRN6045TA-6R8M": "18978 (Mouser)", "SRN6045TA-221M": "12169 (Mouser)", "SRN6045TA-8R2M": "1374 (Mouser)", "SRN6045TA-331K": "596 (Mouser)", "150060RS75000": "206994 (Mouser)", "SC1510(13)-A4": "3793 (Mouser)", "SC1510-A4": "3432 (Mouser)", "SC1510(13)": "3134 (Mouser)", "FIT0586": "6393 (Mouser)", "CXT3019 TR PBFREE": "353 (Mouser)", "SATAEXT30CM": "20 (Mouser)", "100045091": "0 (Mouser)", "AXT300124": "0 (Mouser)", "NLXT301ZPE.F4": "4863 (Mouser)", "NLXT300ZPE.F4": "685 (Mouser)"}
STOCK = {k: v for k, v in STOCK.items()}

# ====================================================================== ROOT
root = Sheet('root', 'AFS_Adom.kicad_sch', None, paper='A3')
R = root.sch

# ====================================================================== POWER
pw = Sheet('power', 'power.kicad_sch', R)
pw.text('POWER: 2S LiPo in through XT30, 5 mOhm shunt (INA219 measures the full pack current), TVS, bulk caps.\n'
        'Ideal-diode OR (2x LTC4359 + DMN3404L-7) joins VBAT and VBUS into VSYS; AP63205 (5 V) and AP63203 (3.3 V) bucks run from VSYS.\n'
        'Values from: INA219 SBOS448 s8.4.1 (10 Ohm input R), AP632xx datasheet Tables 2/3 (L, CIN 10 uF, COUT 2x22 uF, CBST 100 nF), LTC4359 Pin Functions (SHDN->IN).')
pw.place('Connector_Generic', 'Conn_01x02', 'J1', 'XT30 BATT', 'Connector_AMASS:AMASS_XT30PW-M_1x02_P2.50mm_Horizontal',
         {'1': 'VBAT_IN', '2': 'GND'}, 'XT30', {'Description': 'Battery input XT30, pin 1 = +, pin 2 = -'})
pw.place('Diode', 'SMAJ12A', 'D1', 'SMAJ12A', 'Diode_SMD:D_SMA', {'1': 'VBAT_IN', '2': 'GND'}, 'SMAJ12A', rot=90)
pw.place('Device', 'R', 'R1', '5m 2W', FP['SHUNT'], {'1': 'VBAT_IN', '2': 'VBAT'}, 'SHUNT', rot=90)
pw.place('Device', 'R', 'R2', '10', FP['R'], {'1': 'VBAT_IN', '2': 'ISNS_P'}, 'R10')
pw.place('Device', 'R', 'R3', '10', FP['R'], {'1': 'VBAT', '2': 'ISNS_N'}, 'R10')
pw.place('Device', 'C', 'C1', '100n', FP['C'], {'1': 'ISNS_P', '2': 'ISNS_N'}, 'C100N')
pw.place('AFS_Adom', 'INA219AIDCNR', 'U2', 'INA219AIDCNR', 'Package_TO_SOT_SMD:SOT-23-8',
         {'IN+': 'ISNS_P', 'IN-': 'ISNS_N', 'GND': 'GND', 'VS': '+3V3', 'SCL': 'I2C0_SCL', 'SDA': 'I2C0_SDA', 'A0': 'GND', 'A1': 'GND'}, 'INA219')
pw.place('Device', 'C', 'C2', '100n', FP['C'], {'1': '+3V3', '2': 'GND'}, 'C100N')
pw.place('Device', 'C_Polarized', 'C3', '220u 16V', FP['CP'], {'1': 'VBAT', '2': 'GND'}, 'C220U')
pw.place('Device', 'C_Polarized', 'C4', '220u 16V', FP['CP'], {'1': 'VBAT', '2': 'GND'}, 'C220U')
# ideal diodes
pw.place('Power_Management', 'LTC4359-MS8', 'U3', 'LTC4359CMS8', 'Package_SO:MSOP-8_3x3mm_P0.65mm',
         {'IN': 'VBAT', '~{SHDN}': 'VBAT', 'SRC': 'VBAT', 'GATE': 'GATE_BAT', 'OUT': 'VSYS', 'Vss': 'GND', '3': 'NC', '7': 'NC'}, 'LTC4359')
pw.place('Transistor_FET', 'DMN3404L', 'Q1', 'DMN3404L-7', 'Package_TO_SOT_SMD:SOT-23', {'G': 'GATE_BAT', 'S': 'VBAT', 'D': 'VSYS'}, 'DMN3404L')
pw.place('Power_Management', 'LTC4359-MS8', 'U4', 'LTC4359CMS8', 'Package_SO:MSOP-8_3x3mm_P0.65mm',
         {'IN': 'VBUS', '~{SHDN}': 'VBUS', 'SRC': 'VBUS', 'GATE': 'GATE_USB', 'OUT': 'VSYS', 'Vss': 'GND', '3': 'NC', '7': 'NC'}, 'LTC4359')
pw.place('Transistor_FET', 'DMN3404L', 'Q2', 'DMN3404L-7', 'Package_TO_SOT_SMD:SOT-23', {'G': 'GATE_USB', 'S': 'VBUS', 'D': 'VSYS'}, 'DMN3404L')
pw.place('Device', 'C', 'C5', '10u 25V', FP['C0805'], {'1': 'VSYS', '2': 'GND'}, 'C10U25')
# 5 V buck
pw.place('Regulator_Switching', 'AP63205WU', 'U5', 'AP63205WU', 'Package_TO_SOT_SMD:TSOT-23-6',
         {'IN': 'VSYS', 'EN': 'VSYS', 'FB': '+5V', 'GND': 'GND', 'SW': 'SW_5V', 'BST': 'BST_5V'}, 'AP63205')
pw.place('Device', 'C', 'C6', '10u 25V', FP['C0805'], {'1': 'VSYS', '2': 'GND'}, 'C10U25')
pw.place('Device', 'C', 'C7', '100n', FP['C'], {'1': 'BST_5V', '2': 'SW_5V'}, 'C100N')
pw.place('Device', 'L', 'L1', '4.7u', 'Inductor_SMD:L_Bourns_SRN6045TA', {'1': 'SW_5V', '2': '+5V'}, 'L4U7')
pw.place('Device', 'C', 'C8', '22u', FP['C0805'], {'1': '+5V', '2': 'GND'}, 'C22U')
pw.place('Device', 'C', 'C9', '22u', FP['C0805'], {'1': '+5V', '2': 'GND'}, 'C22U')
# 3.3 V buck
pw.place('Regulator_Switching', 'AP63203WU', 'U6', 'AP63203WU', 'Package_TO_SOT_SMD:TSOT-23-6',
         {'IN': 'VSYS', 'EN': 'VSYS', 'FB': '+3V3', 'GND': 'GND', 'SW': 'SW_3V3', 'BST': 'BST_3V3'}, 'AP63203')
pw.place('Device', 'C', 'C10', '10u 25V', FP['C0805'], {'1': 'VSYS', '2': 'GND'}, 'C10U25')
pw.place('Device', 'C', 'C11', '100n', FP['C'], {'1': 'BST_3V3', '2': 'SW_3V3'}, 'C100N')
pw.place('Device', 'L', 'L2', '4.7u', 'Inductor_SMD:L_Bourns_SRN6045TA', {'1': 'SW_3V3', '2': '+3V3'}, 'L4U7')
pw.place('Device', 'C', 'C12', '22u', FP['C0805'], {'1': '+3V3', '2': 'GND'}, 'C22U')
pw.place('Device', 'C', 'C13', '22u', FP['C0805'], {'1': '+3V3', '2': 'GND'}, 'C22U')
# rail LEDs
pw.place('Device', 'R', 'R4', '2.2k', FP['R'], {'1': 'VBAT', '2': 'LED_VBAT'}, 'R2K2')
pw.place('Device', 'LED', 'D2', 'RED VBAT', FP['LED'], {'A': 'LED_VBAT', 'K': 'GND'}, 'LEDR')
pw.place('Device', 'R', 'R5', '1k', FP['R'], {'1': 'VBUS', '2': 'LED_VBUS'}, 'R1K')
pw.place('Device', 'LED', 'D3', 'YEL VBUS', FP['LED'], {'A': 'LED_VBUS', 'K': 'GND'}, 'LEDY')
pw.place('Device', 'R', 'R6', '1k', FP['R'], {'1': '+5V', '2': 'LED_5V'}, 'R1K')
pw.place('Device', 'LED', 'D4', 'GRN 5V', FP['LED'], {'A': 'LED_5V', 'K': 'GND'}, 'LEDG')
pw.place('Device', 'R', 'R7', '1k', FP['R'], {'1': '+3V3', '2': 'LED_3V3'}, 'R1K')
pw.place('Device', 'LED', 'D5', 'GRN 3V3', FP['LED'], {'A': 'LED_3V3', 'K': 'GND'}, 'LEDG')
pw.text('NOTE (not from a datasheet): L1/L2 = 4.7 uH for both rails. AP63203 Table 2 lists 3.9 uH; no 3.9 uH exists in the SRN6045TA family,\n'
        '4.7 uH is inside the datasheet 2.2-10 uH inductor guidance. 220 uF bulk x2 on VBAT is a judgment value for servo start-up surge.\n'
        'Shunt sized for 4 A sustained / 8 A peak: 5 mOhm -> 40 mV at 8 A, INA219 PGA/2 (80 mV FSR = 16 A), 2 mA LSB.')
pw.flags(['GND', '+3V3', '+5V', 'VBUS', 'VBAT_IN', 'VBAT', 'VSYS'])

# ====================================================================== MCU
mc = Sheet('mcu', 'mcu.kicad_sch', R)
mc.text('MCU: RP2350B, decoupling per Hardware design with RP2350 s2.2.1 (100 nF per power pin); core regulator per RP2350 datasheet s6.3.8\n'
        '(CIN 4.7 uF, COUT 4.7 uF + second 4.7 uF on DVDD, L 3.3 uH AOTA-B201610S3R3-101-T polarity-marked, VREG_AVDD RC 33 Ohm / 4.7 uF);\n'
        'crystal ABM8-272-T3 with 15 pF loads and 1 kOhm series (HW guide s4); flash W25Q128JVS with 10k CS pull-up, BOOTSEL via 1k (HW guide s3.1); USB 27 Ohm series (s5).')
rp_nets = {
 'GPIO0': 'UART0_TX', 'GPIO1': 'UART0_RX', 'GPIO2': 'SD_SCK', 'GPIO3': 'SD_MOSI', 'GPIO4': 'SD_MISO', 'GPIO5': 'SD_CS', 'GPIO6': 'SD_CD',
 'GPIO7': 'NC', 'GPIO8': 'I2C0_SDA', 'GPIO9': 'I2C0_SCL', 'GPIO10': 'SPI1_SCK', 'GPIO11': 'SPI1_MOSI', 'GPIO12': 'SPI1_MISO',
 'GPIO13': 'IMU_CS', 'GPIO14': 'ACC_CS', 'GPIO15': 'MAG_CS', 'GPIO16': 'BARO_CS', 'GPIO17': 'SPIX_CS',
 'GPIO18': 'IMU_INT1', 'GPIO19': 'IMU_INT2', 'GPIO20': 'ACC_INT1', 'GPIO21': 'ACC_INT2', 'GPIO22': 'MAG_INT', 'GPIO23': 'TMP_ALERT',
 'GPIO24': 'NC', 'GPIO25': 'USER_LED', 'GPIO26': 'I2C1_SDA', 'GPIO27': 'I2C1_SCL',
 'GPIO28': 'GPIO28', 'GPIO29': 'GPIO29', 'GPIO30': 'GPIO30', 'GPIO31': 'GPIO31',
 'GPIO39': 'SERVO1', 'GPIO38': 'SERVO2', 'GPIO37': 'SERVO3', 'GPIO36': 'SERVO4', 'GPIO35': 'SERVO5', 'GPIO34': 'SERVO6', 'GPIO33': 'SERVO7', 'GPIO32': 'SERVO8',
 'GPIO40/ADC0': 'NC', 'GPIO41/ADC1': 'NC', 'GPIO42/ADC2': 'NC', 'GPIO43/ADC3': 'NC', 'GPIO44/ADC4': 'NC', 'GPIO45/ADC5': 'NC', 'GPIO46/ADC6': 'NC', 'GPIO47/ADC7': 'NC',
 'IOVDD': '+3V3', 'DVDD': 'DVDD', 'QSPI_IOVDD': '+3V3', 'USB_OTP_VDD': '+3V3', 'ADC_AVDD': '+3V3',
 'VREG_AVDD': 'VREG_AVDD', 'VREG_PGND': 'GND', 'VREG_LX': 'VREG_LX', 'VREG_VIN': '+3V3', 'VREG_FB': 'DVDD',
 'USB_DM': 'USB_DM', 'USB_DP': 'USB_DP', 'QSPI_SD3': 'QSPI_SD3', 'QSPI_SCLK': 'QSPI_SCLK', 'QSPI_SD0': 'QSPI_SD0', 'QSPI_SD2': 'QSPI_SD2',
 'QSPI_SD1': 'QSPI_SD1', '~{QSPI_SS}': 'QSPI_SS', 'GND': 'GND', 'XIN': 'XIN', 'XOUT': 'XOUT', 'SWCLK': 'SWCLK', 'SWDIO': 'SWDIO', 'RUN': 'RUN',
}
mc.place('MCU_RaspberryPi', 'RP2350B', 'U1', 'RP2350B', 'Package_DFN_QFN:QFN-80-1EP_10x10mm_P0.4mm_EP3.4x3.4mm', rp_nets, 'RP2350B', w=120, h=190)
# decoupling: IOVDD x8, QSPI_IOVDD, USB_OTP_VDD, ADC_AVDD
cn = 14
for i in range(8):
    mc.place('Device', 'C', 'C%d' % cn, '100n', FP['C'], {'1': '+3V3', '2': 'GND'}, 'C100N', {'Description': 'IOVDD decoupling'}); cn += 1
for d in ('QSPI_IOVDD', 'USB_OTP_VDD', 'ADC_AVDD'):
    mc.place('Device', 'C', 'C%d' % cn, '100n', FP['C'], {'1': '+3V3', '2': 'GND'}, 'C100N', {'Description': d + ' decoupling'}); cn += 1
for i in range(3):
    mc.place('Device', 'C', 'C%d' % cn, '100n', FP['C'], {'1': 'DVDD', '2': 'GND'}, 'C100N', {'Description': 'DVDD decoupling'}); cn += 1
mc.place('Device', 'C', 'C%d' % cn, '10u', FP['C0805'], {'1': '+3V3', '2': 'GND'}, 'C10U16', {'Description': '3V3 bulk at MCU'}); cn += 1
# core regulator
mc.place('Device', 'C', 'C%d' % cn, '4.7u', FP['C'], {'1': '+3V3', '2': 'GND'}, 'C4U7', {'Description': 'VREG CIN (RP2350 DS 6.3.8.2)'}); cn += 1
mc.place('Device', 'L', 'L3', '3.3u', 'AFS_Adom:L_0806_2016Metric_AOTA-B201610S', {'1': 'VREG_LX', '2': 'DVDD'}, 'L3U3', {'Description': 'VREG LX inductor, polarity dot per RP2350 DS Fig 26/28'})
mc.place('Device', 'C', 'C%d' % cn, '4.7u', FP['C'], {'1': 'DVDD', '2': 'GND'}, 'C4U7', {'Description': 'VREG COUT (RP2350 DS 6.3.8.2)'}); cn += 1
mc.place('Device', 'C', 'C%d' % cn, '4.7u', FP['C'], {'1': 'DVDD', '2': 'GND'}, 'C4U7', {'Description': 'second 4.7 uF on DVDD (RP2350 DS 6.3.8.1)'}); cn += 1
mc.place('Device', 'R', 'R8', '33', FP['R'], {'1': '+3V3', '2': 'VREG_AVDD'}, 'R33', {'Description': 'VREG_AVDD RC filter'})
mc.place('Device', 'C', 'C%d' % cn, '4.7u', FP['C'], {'1': 'VREG_AVDD', '2': 'GND'}, 'C4U7', {'Description': 'VREG_AVDD filter cap CFILT'}); cn += 1
# crystal
mc.place('Device', 'Crystal_GND24', 'Y1', '12MHz ABM8-272-T3', 'Crystal:Crystal_SMD_3225-4Pin_3.2x2.5mm', {'1': 'XIN', '3': 'XOUT_R', '2': 'GND', '4': 'GND'}, 'XTAL')
mc.place('Device', 'C', 'C%d' % cn, '15p', FP['C'], {'1': 'XIN', '2': 'GND'}, 'C15P'); cn += 1
mc.place('Device', 'C', 'C%d' % cn, '15p', FP['C'], {'1': 'XOUT_R', '2': 'GND'}, 'C15P'); cn += 1
mc.place('Device', 'R', 'R9', '1k', FP['R'], {'1': 'XOUT', '2': 'XOUT_R'}, 'R1K', {'Description': 'crystal series damping (HW guide s4)'})
# flash
mc.place('Memory_Flash', 'W25Q128JVS', 'U7', 'W25Q128JVSIQ', 'Package_SO:SOIC-8_5.3x5.3mm_P1.27mm',
         {'~{CS}': 'QSPI_SS', 'DO/IO_{1}': 'QSPI_SD1', '~{WP}/IO_{2}': 'QSPI_SD2', 'GND': 'GND', 'DI/IO_{0}': 'QSPI_SD0', 'CLK': 'QSPI_SCLK',
          '~{HOLD}/~{RESET}/IO_{3}': 'QSPI_SD3', 'VCC': '+3V3'}, 'W25Q128')
mc.place('Device', 'C', 'C%d' % cn, '100n', FP['C'], {'1': '+3V3', '2': 'GND'}, 'C100N', {'Description': 'flash decoupling'}); cn += 1
mc.place('Device', 'R', 'R10', '10k', FP['R'], {'1': '+3V3', '2': 'QSPI_SS'}, 'R10K', {'Description': 'flash CS pull-up (HW guide R1)'})
mc.place('Device', 'R', 'R11', '1k', FP['R'], {'1': 'QSPI_SS', '2': 'BOOTSEL_BTN'}, 'R1K', {'Description': 'BOOTSEL series (HW guide R6)'})
mc.place('Switch', 'SW_Push', 'SW1', 'BOOTSEL', 'Button_Switch_SMD:SW_SPST_PTS645Sx43SMTR92', {'1': 'BOOTSEL_BTN', '2': 'GND'}, 'SW')
mc.place('Device', 'R', 'R12', '10k', FP['R'], {'1': '+3V3', '2': 'RUN'}, 'R10K', {'Description': 'RUN pull-up'})
mc.place('Switch', 'SW_Push', 'SW2', 'RESET', 'Button_Switch_SMD:SW_SPST_PTS645Sx43SMTR92', {'1': 'RUN', '2': 'GND'}, 'SW')
# USB series
mc.place('Device', 'R', 'R13', '27', FP['R'], {'1': 'USB_DP', '2': 'USB_DP_C'}, 'R27', {'Description': 'USB series, close to RP2350 (HW guide)'})
mc.place('Device', 'R', 'R14', '27', FP['R'], {'1': 'USB_DM', '2': 'USB_DM_C'}, 'R27', {'Description': 'USB series, close to RP2350 (HW guide)'})
# user LED
mc.place('Device', 'R', 'R15', '1k', FP['R'], {'1': 'USER_LED', '2': 'LED_USER'}, 'R1K')
mc.place('Device', 'LED', 'D6', 'BLU USER', FP['LED'], {'A': 'LED_USER', 'K': 'GND'}, 'LEDB')
mc.flags(['DVDD', 'VREG_AVDD'])
C_NEXT = cn

# ====================================================================== SENSORS
se = Sheet('sensors', 'sensors.kicad_sch', R)
se.text('SENSORS: shared SPI1 (SCK/MOSI/MISO), one CS per device, each interrupt on its own GPIO. All run from +3V3 (every part accepts 1.71/2.8-3.6 V).\n'
        'ICM-45686 DS-000489 s4.1: 0.1 uF on VDD and VDDIO, RESV pins NC. KX134-1211 Table 4: 0.1 uF VDD/IO_VDD, TRIG=GND. MMC5983MA: 10 uF on CAP (SET/RESET), 0.1 uF VDD/VDDIO.\n'
        'MS5611: PS=GND selects SPI, pins 4+5 both CSB. TMP117 Table 5-1: ADD0=GND -> 0x48, ALERT open-drain with 10k pull-up. I2C0 pull-ups 4.7k (judgment, 400 kHz bus).')
cn = C_NEXT
se.place('AFS_Adom', 'ICM-45686', 'U8', 'ICM-45686', 'Package_LGA:Bosch_LGA-14_3x2.5mm_P0.5mm',
         {'AP_CS': 'IMU_CS', 'AP_SCLK': 'SPI1_SCK', 'AP_SDI': 'SPI1_MOSI', 'AP_SDO/AD0': 'SPI1_MISO', 'INT1': 'IMU_INT1', 'INT2/FSYNC/CLKIN': 'IMU_INT2',
          '2': 'NC', '3': 'NC', '7': 'NC', '10': 'NC', '11': 'NC', 'VDD': '+3V3', 'VDDIO': '+3V3', 'GND': 'GND'}, 'ICM45686')
se.place('Device', 'C', 'C%d' % cn, '100n', FP['C'], {'1': '+3V3', '2': 'GND'}, 'C100N', {'Description': 'ICM-45686 VDD'}); cn += 1
se.place('Device', 'C', 'C%d' % cn, '100n', FP['C'], {'1': '+3V3', '2': 'GND'}, 'C100N', {'Description': 'ICM-45686 VDDIO'}); cn += 1
se.place('AFS_Adom', 'KX134-1211', 'U9', 'KX134-1211', 'Package_LGA:Kionix_LGA-12_2x2mm_P0.5mm_LayoutBorder2x4y',
         {'nCS': 'ACC_CS', 'SCLK/SCL': 'SPI1_SCK', 'SDI/SDA': 'SPI1_MOSI', 'SDO/ADDR': 'SPI1_MISO', 'INT1': 'ACC_INT1', 'INT2': 'ACC_INT2',
          'TRIG': 'GND', '11': 'NC', 'VDD': '+3V3', 'IO_VDD': '+3V3', 'GND': 'GND'}, 'KX134')
se.place('Device', 'C', 'C%d' % cn, '100n', FP['C'], {'1': '+3V3', '2': 'GND'}, 'C100N', {'Description': 'KX134 VDD'}); cn += 1
se.place('Device', 'C', 'C%d' % cn, '100n', FP['C'], {'1': '+3V3', '2': 'GND'}, 'C100N', {'Description': 'KX134 IO_VDD'}); cn += 1
se.place('AFS_Adom', 'MMC5983MA', 'U10', 'MMC5983MA', 'Package_LGA:LGA-16_3x3mm_P0.5mm',
         {'SPI_CS': 'MAG_CS', 'SCL/SPI_SCK': 'SPI1_SCK', 'SDA/SPI_SDI': 'SPI1_MOSI', 'SPI_SDO': 'SPI1_MISO', 'INT': 'MAG_INT',
          '3': 'NC', '6': 'NC', '7': 'NC', '8': 'NC', '12': 'NC', '14': 'NC', 'VDD': '+3V3', 'VDDIO': '+3V3', 'CAP': 'MAG_CAP', 'GND': 'GND'}, 'MMC5983')
se.place('Device', 'C', 'C%d' % cn, '10u', FP['C'], {'1': 'MAG_CAP', '2': 'GND'}, 'C10U6', {'Description': 'MMC5983MA SET/RESET cap, low ESR, close to CAP pin'}); cn += 1
se.place('Device', 'C', 'C%d' % cn, '100n', FP['C'], {'1': '+3V3', '2': 'GND'}, 'C100N', {'Description': 'MMC5983MA VDD'}); cn += 1
se.place('Device', 'C', 'C%d' % cn, '100n', FP['C'], {'1': '+3V3', '2': 'GND'}, 'C100N', {'Description': 'MMC5983MA VDDIO'}); cn += 1
se.place('Sensor_Pressure', 'MS5611-01BA', 'U11', 'MS5611-01BA03', 'Package_LGA:LGA-8_3x5mm_P1.25mm',
         {'VDD': '+3V3', 'PS': 'GND', 'GND': 'GND', '4': 'BARO_CS', '5': 'BARO_CS', 'SDO': 'SPI1_MISO', 'SDI/SDA': 'SPI1_MOSI', 'SCLK': 'SPI1_SCK'}, 'MS5611')
se.place('Device', 'C', 'C%d' % cn, '100n', FP['C'], {'1': '+3V3', '2': 'GND'}, 'C100N', {'Description': 'MS5611 VDD'}); cn += 1
se.place('AFS_Adom', 'TMP117AIDRVR', 'U12', 'TMP117AIDRVR', 'Package_SON:WSON-6-1EP_2x2mm_P0.65mm_EP1x1.6mm',
         {'SCL': 'I2C0_SCL', 'SDA': 'I2C0_SDA', 'ADD0': 'GND', 'ALERT': 'TMP_ALERT', 'V+': '+3V3', 'GND': 'GND', 'EP': 'GND'}, 'TMP117')
se.place('Device', 'C', 'C%d' % cn, '100n', FP['C'], {'1': '+3V3', '2': 'GND'}, 'C100N', {'Description': 'TMP117 V+'}); cn += 1
se.place('Device', 'R', 'R16', '10k', FP['R'], {'1': '+3V3', '2': 'TMP_ALERT'}, 'R10K', {'Description': 'ALERT open-drain pull-up'})
se.place('Device', 'R', 'R17', '4.7k', FP['R'], {'1': '+3V3', '2': 'I2C0_SDA'}, 'R4K7', {'Description': 'I2C0 pull-up'})
se.place('Device', 'R', 'R18', '4.7k', FP['R'], {'1': '+3V3', '2': 'I2C0_SCL'}, 'R4K7', {'Description': 'I2C0 pull-up'})
C_NEXT = cn

# ====================================================================== SERVOS
sv = Sheet('servos', 'servos.kicad_sch', R)
sv.text('SERVOS: 8 PWM outputs GPIO39..32 = SERVO1..8 (PWM slices 11B..8A, RP2350 DS Table 678; order chosen so the pin escape order matches the buffer inputs) buffered by a 74AHCT541 at 5 V (TTL-level inputs accept 3.3 V CMOS).\n'
        'Headers are 1x3 0.1 in: pin 1 signal (5 V), pin 2 VBAT (raw pack rail after the shunt), pin 3 GND. OE1/OE2 tied low (always enabled).')
sv.place('74xx', '74AHCT541', 'U13', 'SN74AHCT541PWR', 'Package_SO:TSSOP-20_4.4x6.5mm_P0.65mm',
         {'G1': 'GND', 'G2': 'GND', 'GND': 'GND', 'VCC': '+5V',
          'A0': 'SERVO1', 'A1': 'SERVO2', 'A2': 'SERVO3', 'A3': 'SERVO4', 'A4': 'SERVO5', 'A5': 'SERVO6', 'A6': 'SERVO7', 'A7': 'SERVO8',
          'Y0': 'SERVO1_5V', 'Y1': 'SERVO2_5V', 'Y2': 'SERVO3_5V', 'Y3': 'SERVO4_5V', 'Y4': 'SERVO5_5V', 'Y5': 'SERVO6_5V', 'Y6': 'SERVO7_5V', 'Y7': 'SERVO8_5V'}, 'AHCT541', w=90)
cn = C_NEXT
sv.place('Device', 'C', 'C%d' % cn, '100n', FP['C'], {'1': '+5V', '2': 'GND'}, 'C100N', {'Description': '74AHCT541 VCC'}); cn += 1
for i in range(1, 9):
    sv.place('Connector_Generic', 'Conn_01x03', 'J%d' % (i + 1), 'SERVO%d' % i, 'Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_Vertical',
             {'1': 'SERVO%d_5V' % i, '2': 'VBAT', '3': 'GND'}, 'HDR3', {'Description': 'Servo %d: S / VBAT / GND' % i})
C_NEXT = cn

# ====================================================================== CONNECTORS
co = Sheet('connectors', 'connectors.kicad_sch', R)
co.text('CONNECTORS: USB-C (USB 2.0, Rd 5.1k on CC1/CC2 as a UFP, USBLC6-2SC6 ESD at the connector), microSD in SPI mode (Hirose DM3AT push-push,\n'
        '10k pull-ups on CS/DI/DO and unused DAT1/DAT2 per SD spec, card detect switch to GPIO6 with 10k pull-up), JST-PH 2.0 mm expansion/debug headers.')
co.place('Connector', 'USB_C_Receptacle_USB2.0_16P', 'J10', 'USB-C', 'Connector_USB:USB_C_Receptacle_GCT_USB4105-xx-A_16P_TopMnt_Horizontal',
         {'A1': 'GND', 'A12': 'GND', 'B1': 'GND', 'B12': 'GND', 'A4': 'VBUS', 'A9': 'VBUS', 'B4': 'VBUS', 'B9': 'VBUS',
          'A5': 'CC1', 'B5': 'CC2', 'A6': 'USB_DP_C', 'B6': 'USB_DP_C', 'A7': 'USB_DM_C', 'B7': 'USB_DM_C', 'A8': 'NC', 'B8': 'NC', 'SH': 'GND'}, 'USBC', w=70)
co.place('Device', 'R', 'R19', '5.1k', FP['R'], {'1': 'CC1', '2': 'GND'}, 'R5K1')
co.place('Device', 'R', 'R20', '5.1k', FP['R'], {'1': 'CC2', '2': 'GND'}, 'R5K1')
co.place('Power_Protection', 'USBLC6-2SC6', 'U14', 'USBLC6-2SC6', 'Package_TO_SOT_SMD:SOT-23-6',
         {'1': 'USB_DP_C', '6': 'USB_DP_C', '3': 'USB_DM_C', '4': 'USB_DM_C', '2': 'GND', '5': 'VBUS'}, 'USBLC6')
cn = C_NEXT
co.place('Device', 'C', 'C%d' % cn, '10u', FP['C0805'], {'1': 'VBUS', '2': 'GND'}, 'C10U16', {'Description': 'VBUS bulk at connector'}); cn += 1
co.place('Connector', 'Micro_SD_Card_Det_Hirose_DM3AT', 'J11', 'microSD', 'Connector_Card:microSD_HC_Hirose_DM3AT-SF-PEJM5',
         {'DAT2': 'SD_DAT2', 'DAT3/CD': 'SD_CS', 'CMD': 'SD_MOSI', 'VDD': '+3V3', 'CLK': 'SD_SCK', 'VSS': 'GND', 'DAT0': 'SD_MISO', 'DAT1': 'SD_DAT1',
          'DET_A': 'SD_CD', 'DET_B': 'GND', 'SHIELD': 'GND'}, 'SD', w=70)
co.place('Device', 'C', 'C%d' % cn, '10u', FP['C0805'], {'1': '+3V3', '2': 'GND'}, 'C10U16', {'Description': 'SD VDD bulk'}); cn += 1
co.place('Device', 'C', 'C%d' % cn, '100n', FP['C'], {'1': '+3V3', '2': 'GND'}, 'C100N', {'Description': 'SD VDD'}); cn += 1
rn = 21
for net in ('SD_CS', 'SD_MOSI', 'SD_MISO', 'SD_DAT1', 'SD_DAT2', 'SD_CD'):
    co.place('Device', 'R', 'R%d' % rn, '10k', FP['R'], {'1': '+3V3', '2': net}, 'R10K', {'Description': net + ' pull-up'}); rn += 1
co.place('Device', 'R', 'R%d' % rn, '4.7k', FP['R'], {'1': '+3V3', '2': 'I2C1_SDA'}, 'R4K7', {'Description': 'I2C1 pull-up'}); rn += 1
co.place('Device', 'R', 'R%d' % rn, '4.7k', FP['R'], {'1': '+3V3', '2': 'I2C1_SCL'}, 'R4K7', {'Description': 'I2C1 pull-up'}); rn += 1
PH4 = 'Connector_JST:JST_PH_B4B-PH-K_1x04_P2.00mm_Vertical'
PH2 = 'Connector_JST:JST_PH_B2B-PH-K_1x02_P2.00mm_Vertical'
co.place('Connector_Generic', 'Conn_01x04', 'J12', 'SWD', PH4, {'1': 'SWCLK', '2': 'SWDIO', '3': '+3V3', '4': 'GND'}, 'PH4', {'Description': 'SWD: SWCLK, SWDIO, 3V3, GND'})
co.place('Connector_Generic', 'Conn_01x04', 'J13', 'UART0', PH4, {'1': 'UART0_TX', '2': 'UART0_RX', '3': '+3V3', '4': 'GND'}, 'PH4', {'Description': 'UART0: TX, RX, 3V3, GND'})
co.place('Connector_Generic', 'Conn_01x04', 'J14', 'I2C0', PH4, {'1': 'I2C0_SCL', '2': 'I2C0_SDA', '3': '+3V3', '4': 'GND'}, 'PH4', {'Description': 'I2C0: SCL, SDA, 3V3, GND'})
co.place('Connector_Generic', 'Conn_01x04', 'J15', 'I2C1', PH4, {'1': 'I2C1_SCL', '2': 'I2C1_SDA', '3': '+3V3', '4': 'GND'}, 'PH4', {'Description': 'I2C1: SCL, SDA, 3V3, GND'})
co.place('Connector_Generic', 'Conn_01x04', 'J16', 'SPI', PH4, {'1': 'SPI1_MISO', '2': 'SPIX_CS', '3': 'SPI1_SCK', '4': 'SPI1_MOSI'}, 'PH4', {'Description': 'spare SPI on sensor bus: MISO, CS, SCK, MOSI'})
co.place('Connector_Generic', 'Conn_01x04', 'J17', 'GPIO', PH4, {'1': 'GPIO28', '2': 'GPIO29', '3': 'GPIO30', '4': 'GPIO31'}, 'PH4', {'Description': 'spare GPIO28-31'})
co.place('Connector_Generic', 'Conn_01x02', 'J18', '3V3 OUT A', PH2, {'1': '+3V3', '2': 'GND'}, 'PH2')
co.place('Connector_Generic', 'Conn_01x02', 'J19', '3V3 OUT B', PH2, {'1': '+3V3', '2': 'GND'}, 'PH2')
co.place('Connector_Generic', 'Conn_01x02', 'J20', '5V OUT A', PH2, {'1': '+5V', '2': 'GND'}, 'PH2')
co.place('Connector_Generic', 'Conn_01x02', 'J21', '5V OUT B', PH2, {'1': '+5V', '2': 'GND'}, 'PH2')
for i in range(1, 5):
    co.place('Mechanical', 'MountingHole', 'H%d' % i, 'M3', 'MountingHole:MountingHole_3.2mm_M3', {}, None, {'Description': 'M3 mounting hole'}, w=30, h=14)

# ====================================================================== MOLECULE (machine pins, contacts, test points)
mo = Sheet('molecule', 'molecule.kicad_sch', R)
mo.text('ADOM MOLECULE INTERFACE: MP1..MP4 MachinePinMediumShort at the corners of an 88 x 64 mm span (GND), MachineContactMedium contacts on the 2 mm grid\n'
        'from MP1 (front-left): left column MC1..MC4 = battery and servo rail; right column MC5..MC7 = rails, MC13 = RUN, MC14 = one servo channel;\nbottom edge beside the UART/SWD headers: MC9/MC10 = UART0, MC11/MC12 = SWD, MC8 = GND (probe tether ground for the USB/flash area).\n'
        'Test points TP1.. (0.5 mm pads) are placed by the test plan (aiflow/tests.json). Positions in mm from MP1, y up, are in MOLECULE_FIXED below (pcb.py reads them).')
for i in range(1, 5):
    mo.place('Molecule', 'MachinePinMediumShort', 'MP%d' % i, 'MachinePinMediumShort', 'Molecule:MachinePinMediumShort', {'1': 'GND'}, 'MPIN', w=34, h=16)
MOLECULE_CONTACTS = [('MC1', 'VBAT_IN'), ('MC2', 'GND'), ('MC3', 'VBAT'), ('MC4', 'GND'),
                     ('MC5', '+5V'), ('MC6', 'GND'), ('MC7', '+3V3'), ('MC8', 'GND'), ('MC9', 'UART0_TX'), ('MC10', 'UART0_RX'),
                     ('MC11', 'SWCLK'), ('MC12', 'SWDIO'), ('MC13', 'RUN'), ('MC14', 'SERVO1_5V')]
for ref, net in MOLECULE_CONTACTS:
    mo.place('Molecule', 'MachineContactMedium', ref, net, 'Molecule:MachineContactMedium', {'1': net}, 'MCON', {'Description': 'Control Panel contact: ' + net}, w=34, h=16)
TESTPOINTS = json.load(open(OUT + '/aiflow/testpoints.json')) if os.path.exists(OUT + '/aiflow/testpoints.json') else []
for ref, net in TESTPOINTS:
    mo.place('AFS_Adom', 'TestPoint_0.5mm', ref, net, 'AFS_Adom:TestPoint_Pad_D0.5mm', {'1': net}, 'TP', {'Description': 'probe spot: ' + net}, w=30, h=14)

# ====================================================================== ROOT sheet symbols + notes
sheets = [('power', pw), ('mcu', mc), ('sensors', se), ('servos', sv), ('connectors', co), ('molecule', mo)]
R.add_text('AFS_Adom - Active Fin Stabilization flight computer (rev A, 2026-10-15)\n'
           'RP2350B + ICM-45686 / KX134-1211 / MMC5983MA / MS5611 on SPI1, TMP117 + INA219 on I2C0, 8 servo outputs at 5 V, 2S LiPo via XT30.\n'
           'Sheets: power, mcu, sensors, servos, connectors. Nets cross sheets as global labels. See DESIGN_NOTES.md.', (20, 20), 2)
x = 20
for i, (name, sh) in enumerate(sheets):
    R.add_sheet(name, sh.sch.filename, (x, 50), (50, 30), sh.sch.uuid, i + 2)
    x += 65

root.sch.write(OUT + '/AFS_Adom.kicad_sch', '1')
for i, (name, sh) in enumerate(sheets):
    sh.sch.write(OUT + '/' + sh.sch.filename)
json.dump(dict(root_uuid=root.sch.uuid, comps=COMPS), open(OUT + '/netlist.json', 'w'), indent=1)
# aiflow netlist.json: components + nets map
nets = {}
for ref, c in COMPS.items():
    for pin, net in c['pads'].items():
        if net != 'NC':
            nets.setdefault(net, []).append(ref + '.' + pin)
af = dict(design='AFS_Adom', components=[dict(ref=r, sym=c['lib'] + ':' + c['sym'], value=c['value'], fp=c['footprint'], mpn=c['fields'].get('MPN', ''),
          mfr=c['fields'].get('Manufacturer', ''), src=('Mouser ' + c['fields']['Mouser']) if c['fields'].get('Mouser') else (('LCSC ' + c['fields']['LCSC']) if c['fields'].get('LCSC') else ''),
          block=c['sheet'].replace('.kicad_sch', '')) for r, c in COMPS.items()], nets=nets,
          molecule=dict(pin='MachinePinMediumShort', grid_mm=2, pin_span_mm=[88, 64], board_mm=[92, 68], edge_margin_mm=2, origin='centre of MP1 (FL) pin', corner_pins_net='GND'))
json.dump(af, open(OUT + '/aiflow/netlist.json', 'w'), indent=1)
import csv
with open(OUT + '/design/bom.csv', 'w', newline='') as f:
    w = csv.writer(f)
    w.writerow(['ref', 'value', 'function', 'mpn', 'manufacturer', 'package', 'source', 'vendor_pn', 'stock_checked_2026-10-15', 'note'])
    for r, c in COMPS.items():
        if r.startswith('H'):
            continue   # plain mounting holes are not electrical parts
        fl = c['fields']
        src = 'Mouser' if fl.get('Mouser') else ('Adom stock' if fl.get('Manufacturer') == 'Adom' else ('LCSC' if fl.get('LCSC') else 'no part'))
        pn = fl.get('Mouser') or fl.get('LCSC') or ''
        note = 'user-fixed XT30; no Mouser PCB-mount XT30 exists, LCSC ' + fl.get('LCSC', '') if r == 'J1' else ('second source LCSC ' + fl['LCSC'] if fl.get('LCSC') else '')
        st = STOCK.get(fl.get('MPN', '').upper(), None)
        if st is None: st = 'n/a' if fl.get('MPN') not in ('no part',) and fl.get('Manufacturer') != 'Adom' else ('stocked (Adom)' if fl.get('Manufacturer') == 'Adom' else 'no part')
        w.writerow([r, c['value'], fl.get('Description', ''), fl.get('MPN', 'no part'), fl.get('Manufacturer', ''), c['footprint'].split(':')[1], src, pn, st, note])
print('written sheets;', len(COMPS), 'components')