molecule
AM/FM Receiver Molecule
Public Made by Adomby adom
USB-powered AM/FM receiver on an Adom Molecule: discrete BJT and op-amp superhet (10.7 MHz and 455 kHz IFs), STM32G0B1 tuning, self-calibration and USB audio over one USB-C cable. 96 x 96 mm pin grid, 4 layers, KiCad 10.
main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
"""Pick a JLCPCB (LCSC) part for every passive BOM group, with value/package/dielectric checks."""
import json, os, re, sys
from collections import OrderedDict
from design import DESIGN
import jlc
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'build', 'jlc_passives.json')
# capacitors that must be C0G/NP0 even above 10 nF (none) / may be X7R even below (coupling / decoupling)
C0G_MAX = 10e-9 # every capacitor <= 10 nF is C0G/NP0 unless listed below
X7R_OK_SMALL = {'10n'} # 10 nF parts are coupling/bypass: X7R fine
def val_to_float(v, unit):
m = re.match(r'^([\d.]+)([pnumkM]?)$', v)
mult = {'p': 1e-12, 'n': 1e-9, 'u': 1e-6, 'm': 1e-3, 'k': 1e3, 'M': 1e6, '': 1.0}
return float(m.group(1)) * mult[m.group(2)]
def jlc_value_text(v, unit):
"""Design value ('4.7k', '100n') -> JLC attribute text ('4.7kΩ', '100nF')."""
if unit == 'Ω':
return v + 'Ω'
if unit == 'F':
return v.replace('u', 'u') + 'F'
if unit == 'H':
return v + 'H'
return v
def attr(p, *names):
for n in names:
if n in p['attrs']:
return p['attrs'][n]
return None
def voltage(p):
v = attr(p, 'Voltage Rating', 'Rated Voltage')
m = re.search(r'([\d.]+)\s*V', v or p['describe'])
return float(m.group(1)) if m else 0.0
def rank(p):
return (p['type'] == 'base', p['preferred'], p['stock'])
def pick_resistor(value, pkg='0603'):
want = jlc_value_text(value, 'Ω')
cands = []
for q in ('%s %s' % (want, pkg), '%s %s resistor 1%%' % (want, pkg)):
for p in jlc.search(q, size=50):
if p['package'] != pkg or 'Resistor' not in (p['category'] or ''):
continue
if attr(p, 'Resistance') != want or p['stock'] < 500:
continue
if '±1%' not in p['describe']:
continue
cands.append(p)
return sorted({c['code']: c for c in cands}.values(), key=rank, reverse=True)
def pick_capacitor(value, pkg, need_c0g, vmin):
want = jlc_value_text(value, 'F')
cands = []
for q in ('%s %s' % (want, pkg), '%s %s %s' % (want, pkg, 'C0G' if need_c0g else 'X7R')):
for p in jlc.search(q, size=50):
if p['package'] != pkg or 'Capacitor' not in (p['category'] or ''):
continue
if attr(p, 'Capacitance') != want or p['stock'] < 500:
continue
d = p['describe']
diel = 'C0G' in d or 'NP0' in d or 'NPO' in d
if need_c0g and not diel:
continue
if not need_c0g and not (diel or 'X7R' in d or 'X5R' in d):
continue
if voltage(p) < vmin:
continue
cands.append(p)
return sorted({c['code']: c for c in cands}.values(), key=rank, reverse=True)
def pick_inductor(value, pkg, wirewound):
want = jlc_value_text(value, 'H')
cands = []
for q in ('%s %s' % (want, pkg), '%s %s inductor' % (want, pkg)):
for p in jlc.search(q, size=50):
if p['package'] != pkg or 'nductor' not in (p['category'] or '') + p['describe']:
continue
if attr(p, 'Inductance') != want and (' ' + want + ' ') not in (' ' + p['describe'] + ' '):
continue
if p['stock'] < 100:
continue
if wirewound and not ('LQW' in (p['mpn'] or '') or 'ire' in p['describe'] or 'CS' in (p['mpn'] or '')):
continue
cands.append(p)
return sorted({c['code']: c for c in cands}.values(), key=rank, reverse=True)
def groups():
g = OrderedDict()
for p in DESIGN.parts:
if not p.in_bom:
continue
g.setdefault((p.lib_id, p.value, p.footprint), []).append(p)
return g
def main():
res = OrderedDict()
for (lib, value, fp), parts in groups().items():
refs = [p.ref for p in parts]
pkg = re.search(r'_(\d{4})_', fp)
pkg = pkg.group(1) if pkg else None
notes = ' '.join(p.description for p in parts)
if lib == 'Device:R':
c = pick_resistor(value, pkg)
elif lib == 'Device:C':
f = val_to_float(value, 'F')
need_c0g = f <= C0G_MAX and value not in X7R_OK_SMALL
vmin = 16 if f < 4e-6 else 10
c = pick_capacitor(value, pkg, need_c0g, vmin)
elif lib == 'Device:L':
c = pick_inductor(value, pkg, wirewound=pkg == '0603')
else:
continue
key = '%s|%s|%s' % (lib, value, fp)
res[key] = dict(refs=refs, candidates=c[:4])
best = c[0] if c else None
print('%-8s %-6s %-5s x%-2d -> %s' % (lib.split(':')[1], value, pkg, len(refs),
jlc.fmt(best) if best else 'NONE FOUND'))
json.dump(res, open(OUT, 'w'), indent=1)
if __name__ == '__main__':
main()