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/design.py
"""Deterministic electrical intent and placement. Coordinates are mm, top view, +Y up."""
import json,pathlib
R=pathlib.Path(__file__).resolve().parents[1]; pins=json.loads((R/'lib/cm5-pinout.json').read_text()); parts=[]
M=json.loads((R/'lib/manufacturing.json').read_text())
def part(ref,value,fp,x,y,net,labels=None,rotation=0,layer='top',section='Support',manufacturer='',lcsc='',purpose=''):
parts.append(dict(ref=ref,value=value,mpn=value,footprint=fp,x=x,y=y,rotation=rotation,layer=layer,nets={str(k):v for k,v in net.items()},labels={str(k):v for k,v in (labels or net).items()},section=section,manufacturer=manufacturer,lcsc=lcsc,purpose=purpose))
used={n:'GND' for n,s in pins.items() if s=='GND'}
used.update({str(n):'CM5_5V' for n in [77,79,81,83,85,87]})
used.update({'78':'CM5_3V3','84':'CM5_3V3','86':'CM5_3V3','94':'USB_CC1','96':'USB_CC2','151':'HDMI_CEC','153':'HDMI_HPD','199':'HDMI_SDA','200':'HDMI_SCL'})
for n,s in pins.items():
if s.startswith('HDMI0_') and ('TX' in s or 'CLK' in s):used[n]=s.replace('HDMI0_','HDMI_')
for i in (1,2):
off=100*(i-1)
part('J'+str(i),'10164227-1001A1RLF','cm5-'+str(i),-27 if i==1 else 7,-2.5,{str(int(n)-off):v for n,v in used.items() if off<int(n)<=off+100},{str(n-off):pins[str(n)] for n in range(off+1,off+101)},section='CM5_Power' if i==1 else 'CM5_HDMI',manufacturer='Amphenol',lcsc='C6782225',purpose='CM5 socket: module pins '+str(off+1)+'-'+str(off+100))
usb_labels={str(n):f'{"A" if n<=12 else "B"}{n if n<=12 else n-12}' for n in [1,4,5,6,7,8,9,12,13,16,17,18,19,20,21,24]};usb_labels['25']='SHIELD'
for alias in ['13','16','21','24']:del usb_labels[alias]
part('J3','USB4105-GF-A','usb-c',29.5,17,{1:'GND',12:'GND',25:'GND',4:'CM5_5V',9:'CM5_5V',5:'USB_CC1',17:'USB_CC2'},usb_labels,rotation=90,section='USB_C_Power',manufacturer='GCT',lcsc='C3020560',purpose='5 A rated USB-C power receptacle; data and SBU NC')
hdmi={1:'HDMI_TX2_P',2:'GND',3:'HDMI_TX2_N',4:'HDMI_TX1_P',5:'GND',6:'HDMI_TX1_N',7:'HDMI_TX0_P',8:'GND',9:'HDMI_TX0_N',10:'HDMI_CLK_P',11:'GND',12:'HDMI_CLK_N',13:'HDMI_CEC',15:'HDMI_SCL',16:'HDMI_SDA',17:'GND',18:'HDMI_5V',19:'HDMI_HPD',20:'GND',21:'GND',22:'GND',23:'GND'}
part('J4','690-019-298-412','hdmi',22.45,-11,hdmi,dict(hdmi,**{'14':'UTILITY_NC'}),rotation=90,section='HDMI_Output',manufacturer='EDAC',lcsc='C5291558',purpose='Full-size HDMI Type-A; port HDMI0')
part('U1','RT9742SNGV','rt9742',16,0,{1:'CM5_5V',2:'HDMI_5V',3:'GND'}, {1:'VIN',2:'VOUT',3:'GND'},section='Power_Protection',manufacturer='Richtek',lcsc='C3235509',purpose='HDMI 5 V 0.5 A current-limited reverse-blocking switch from CM5IO reference')
for i,lanes,y in [(2,['TX2_P','TX2_N','TX1_P','TX1_N'],-7.25),(3,['TX0_P','TX0_N','CLK_P','CLK_N'],-10.25),(4,['CEC','SCL','SDA','HPD'],-16.5)]:
net={3:'GND',8:'GND'}
for a,c,l in zip([1,2,4,5],[10,9,7,6],lanes):net[a]=net[c]='HDMI_'+l
labels={n:('ESD_'+net[n] if n in [1,2,4,5] else ('GND' if n in [3,8] else 'NC_THROUGH_'+net[n])) for n in range(1,11)}
part('U'+str(i),'TPD4E05U06DQAR','esd',17 if i==4 else 18,y,net,labels,section='HDMI_ESD',manufacturer='Texas Instruments',lcsc='C138714',purpose='0.5 pF ESD protection; NC lands externally routed through, no internal feed-through')
part('D1','SMAJ5.0A-13-F','sma',20,23,{1:'CM5_5V',2:'GND'},{1:'K',2:'A'},section='Power_Protection',manufacturer='Diodes Incorporated',lcsc='C87074',purpose='VBUS transient clamp; NOT sustained overvoltage protection')
for ref,val,fp,x,y,net,layer in [('C1','10uF','0805',22,17,'CM5_5V','top'),('C2','47uF','1206',-25,-9,'CM5_5V','bottom'),('C3','100nF','0402',-22,-12,'CM5_5V','bottom'),('C4','1uF','0805',12,3,'CM5_5V','top'),('C5','10uF','0805',15,-3,'HDMI_5V','top'),('C6','100nF','0402',-24,-6,'CM5_3V3','bottom')]:
mp={'100nF':('Samsung','CL05B104KO5NNNC','C1525'),'10uF':('Samsung','CL21A106KAYNNNE','C15850'),'1uF':('Samsung','CL21B105KBFNNNE','C28323'),'47uF':('Murata','GRM31CR61A476KE15L','C426659')}
m,mpn,l=mp[val]
part(ref,val,fp,x,y,{1:net,2:'GND'},layer=layer,section='Decoupling',manufacturer=m,lcsc=l,purpose=mpn+'; '+('10 V X5R bulk; validate DC-bias capacitance' if ref=='C2' else 'Local decoupling'))
parts[-1]['mpn']=mpn
# Physical USB aliases sharing the same copper land are consolidated by the renderer.
design=dict(name='CM5 Minimal Carrier V1',revision=M['revision'],width=64,height=60,thickness=1.6,moduleCenter=[-10,0],moduleSize=[40,55],moduleVariant='eMMC, non-wireless CM5 (pre-programmed)',parts=parts,nets=sorted({v for p in parts for v in p['nets'].values()}),holes=[[-26.5,24],[-26.5,-24],[6.5,24],[6.5,-24]],status='Engineering prototype: release requires impedance, assembly and bench validation')
(R/'lib/design.json').write_text(json.dumps(design,indent=2)+'\n')