pixalynx/cm5-minimal-carrier
A 4-layer Raspberry Pi CM5 carrier board routes USB‑C 5V power through an RT9742 load switch with TVS protection and capacitive filtering, while providing ESD-protected HDMI output, CM5 connectors, test points, and ground/power planes.
- Version
- 0.1.1
- License
- unset
- Stars
- 0
scripts/review-minima.py
"""Extract comparison facts from the pinned upstream reference, without running its code."""
import collections, hashlib, json, pathlib, subprocess, tempfile, xml.etree.ElementTree as E
import pcbnew as k
R=pathlib.Path(__file__).resolve().parents[1]
repo=R/'references/external/CM5_MINIMA_REV2'
expected='e9dbac2df2d8a7606cb91eae80b1b3b740250910'
commit=subprocess.check_output(['git','-C',str(repo),'rev-parse','HEAD'],text=True).strip()
assert commit==expected, 'Review the comparison again before accepting a different upstream revision'
cli='/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli'
with tempfile.TemporaryDirectory() as temp:
net=pathlib.Path(temp)/'reference.xml'
subprocess.run([cli,'sch','export','netlist','--format','kicadxml','-o',str(net),str(repo/'CM5_MINIMA_2.kicad_sch')],check=True,capture_output=True)
root=E.parse(net).getroot()
parts={p.get('ref'):{'value':p.findtext('value'),'footprint':p.findtext('footprint')} for p in root.find('components')}
nets={p.get('name'):{(x.get('ref'),x.get('pin')) for x in p.findall('node')} for p in root.find('nets')}
def same(*nodes):return next((name for name,pins in nets.items() if set(nodes)<=pins),None)
local=json.loads((R/'lib/design.json').read_text());lp={p['ref']:p for p in local['parts']}
mapping=[(170,1),(172,3),(176,4),(178,6),(182,7),(184,9),(188,10),(190,12),(151,13),(153,19),(199,16),(200,15)]
hdmi=[]
for cm,connector in mapping:
upstream=same(('Module302',str(cm)),('J501',str(connector)))
ours=lp['J2']['nets'][str(cm-100)]
assert upstream and ours==lp['J4']['nets'][str(connector)]
hdmi.append({'cm5Pin':cm,'hdmiPin':connector,'referenceNet':upstream,'ourNet':ours,'match':True})
cc=[{'modulePin':94,'usbPin':'A5'},{'modulePin':96,'usbPin':'B5'}]
for row in cc:
row['referenceNet']=same(('Module301',str(row['modulePin'])),('J101',row['usbPin']))
assert row['referenceNet']
power=same(*[('Module301',str(n)) for n in [77,79,81,83,85,87]],('J101','A4'),('J101','A9'),('J101','B4'),('J101','B9'))
assert power
b=k.LoadBoard(str(repo/'CM5_MINIMA_2.kicad_pcb'))
refpoints=[{'ref':f.GetReference(),'label':f.GetValue(),'layer':f.GetLayerName()} for f in b.GetFootprints() if f.GetReference().startswith('TP')]
sources=['CM5_MINIMA_2.kicad_sch','CM5.kicad_sch','HDMI.kicad_sch','USB.kicad_sch','CM5_MINIMA_2.kicad_pcb','CM5_MINIMA_2.kicad_pro','LICENSE']
report={'repository':'https://github.com/piecol/CM5_MINIMA_REV2','commit':commit,'reviewed':'2026-09-18',
'license':'CERN-OHL-S-2.0; retained with the unmodified reference checkout',
'scope':'Connectivity and manufacturing comparison; official Raspberry Pi sources remain the primary design reference.',
'hdmiSignalMappings':hdmi,'ccDirectConnections':cc,'directMainPowerNet':power,
'hdmiPowerSwitch':parts['U405'],'hdmiEsd':parts['D401'],'inputUsbEsd':parts['D101'],
'inputUsbEsdProtects':'USB D+/D-, not CC1/CC2; this carrier intentionally omits USB data',
'referenceLayerCount':b.GetCopperLayerCount(),
'referenceViaDrillsMm':dict(collections.Counter(str(round(k.ToMM(t.GetDrillValue()),4)) for t in b.GetTracks() if isinstance(t,k.PCB_VIA))),
'referenceProbePads':sorted(refpoints,key=lambda p:p['ref']),
'knownIssue':{'url':'https://github.com/piecol/CM5_MINIMA_REV2/issues/2','stateObserved':'open',
'summary':'Reported failure to restart with HDMI attached under certain supply/cable combinations; root cause is not established.'},
'sha256':{name:hashlib.sha256((repo/name).read_bytes()).hexdigest() for name in sources}}
(R/'references/minima-comparison.json').write_text(json.dumps(report,indent=2)+'\n')
print('Reference comparison: 12 HDMI mappings, 2 direct CC connections and all six 5 V inputs agree; 6-layer reference has 6 probe points.')