diff --git a/bom/helpers.zen b/bom/helpers.zen index ff07329..daa18cd 100644 --- a/bom/helpers.zen +++ b/bom/helpers.zen @@ -1,6 +1,7 @@ """BOM matching helper functions""" load("../utils.zen", "e96", "e24") +load("../units.zen", "ResistanceRange", "Voltage", "Power", "Capacitance") def prop(c, names): @@ -89,14 +90,15 @@ def iec60062_3digit(value): return sig_digits + str(mult) -def resistor_series(series, min_val, max_val, max_v, tolerance, e_series, encode, suffix): +def resistor_series(manufacturer, series, resistance_range, max_v, power, tolerance, e_series, encode, suffix): """Helper to create resistor series entry. Args: + manufacturer: Manufacturer name (e.g., "Panasonic Electronic Components") series: Series prefix (e.g., "ERJ-2RKF") - min_val: Min resistance (e.g., "10Ohm") - max_val: Max resistance (e.g., "1MOhm") + resistance_range: Resistance range (e.g., "10Ohm – 1MOhm") max_v: Max voltage (e.g., "50V") + power: Power rating (e.g., "0.1W") tolerance: Tolerance string (e.g., "1%") e_series: List of E-series (e.g., ["e96", "e24"]) encode: Encoding function (iec60062_3digit or iec60062_4digit) @@ -106,10 +108,11 @@ def resistor_series(series, min_val, max_val, max_v, tolerance, e_series, encode Resistor series dictionary """ return { + "manufacturer": manufacturer, "series": series, - "min": min_val, - "max": max_val, - "max_v": max_v, + "resistance_range": ResistanceRange(resistance_range), + "max_v": Voltage(max_v), + "power": Power(power), "tolerance": tolerance, "e_series": e_series, "encode": encode, @@ -117,6 +120,61 @@ def resistor_series(series, min_val, max_val, max_v, tolerance, e_series, encode } +def panasonic_erj(series, resistance_range, voltage, power, tolerance, e_series, suffix, encode): + """Shorthand for Panasonic ERJ resistor series. + + Args: + series: Series name (e.g., "ERJ-3EKF") + resistance_range: Resistance range (e.g., "10Ohm – 1MOhm") + voltage: Max voltage (e.g., "75V") + power: Power rating (e.g., "0.333W") + tolerance: Tolerance string (e.g., "1%") + e_series: List of E-series (e.g., ["e96", "e24"]) + suffix: MPN suffix (e.g., "V") + encode: Encoding function (iec60062_3digit or iec60062_4digit) + + Returns: + Resistor series dictionary + """ + return resistor_series( + "Panasonic Electronic Components", + series, + resistance_range, + voltage, + power, + tolerance, + e_series, + encode, + suffix, + ) + + +def discrete_resistor_series(manufacturer, series, max_v, power, tolerance, values_with_mpns): + """Helper to create discrete resistor series (non-E-series, exact values only). + + Args: + manufacturer: Manufacturer name (e.g., "Stackpole Electronics Inc") + series: Series name (e.g., "CSNL2512") + max_v: Max voltage (e.g., "170V") + power: Power rating (e.g., "2W") + tolerance: Tolerance string (e.g., "1%") + values_with_mpns: List of tuples [(resistance_str, mpn), ...] + e.g., [("0.5mOhm", "CSNL2512FTL500"), ("1mOhm", "CSNL2512FT1L00")] + + Returns: + Discrete resistor series dictionary + """ + return { + "type": "discrete", + "manufacturer": manufacturer, + "series": series, + "values": values_with_mpns, + "max_v": Voltage(max_v), + "power": Power(power), + "tolerance": tolerance, + } + + def murata_cap(dielectric, voltage, cap, base_mpn, suffixes, manufacturer="Murata Electronics"): """Helper to create Murata capacitor entry with auto-generated packaging variants. @@ -135,8 +193,8 @@ def murata_cap(dielectric, voltage, cap, base_mpn, suffixes, manufacturer="Murat return { "dielectric": dielectric, - "voltage": voltage, - "cap": cap, + "voltage": Voltage(voltage), + "cap": Capacitance(cap), "mpns": mpns, "manufacturer": manufacturer, } diff --git a/bom/match_generics.zen b/bom/match_generics.zen index ca4a84c..1591516 100644 --- a/bom/match_generics.zen +++ b/bom/match_generics.zen @@ -3,7 +3,7 @@ Automatically assigns house MPNs to resistors and capacitors. """ -load("../units.zen", "Voltage", "Resistance", "Capacitance") +load("../units.zen", "Voltage", "Resistance", "ResistanceRange", "Capacitance", "Power") load( "helpers.zen", "prop", @@ -11,7 +11,8 @@ load( "find_e_series_match", "iec60062_3digit", "iec60062_4digit", - "resistor_series", + "panasonic_erj", + "discrete_resistor_series", "murata_cap", ) @@ -31,16 +32,50 @@ _DIELECTRIC_RANK = { # Resistor series by package SERIES_BY_PKG = { "0402": [ - resistor_series("ERJ-H2CF", "1Ohm", "10Ohm", "50V", "1%", ["e96", "e24"], iec60062_4digit, "X"), - resistor_series("ERJ-2RKF", "10Ohm", "1MOhm", "50V", "1%", ["e96", "e24"], iec60062_4digit, "X"), - resistor_series("ERJ-U02J", "1MOhm", "10MOhm", "50V", "5%", ["e24"], iec60062_3digit, "X"), - resistor_series("ERJ-PA2F", "1Ohm", "1MOhm", "50V", "1%", ["e96", "e24"], iec60062_4digit, "X"), + panasonic_erj("ERJ-H2CF", "1Ohm – 10Ohm", "50V", "0.1W", "1%", ["e96", "e24"], "X", iec60062_4digit), + panasonic_erj("ERJ-2RKF", "10Ohm – 1MOhm", "50V", "0.1W", "1%", ["e96", "e24"], "X", iec60062_4digit), + panasonic_erj("ERJ-U02J", "1MOhm – 10MOhm", "50V", "0.1W", "5%", ["e24"], "X", iec60062_3digit), + panasonic_erj("ERJ-PA2F", "1Ohm – 1MOhm", "50V", "0.1W", "1%", ["e96", "e24"], "X", iec60062_4digit), ], "0603": [ - resistor_series("ERJ-H3QF", "1Ohm", "10Ohm", "75V", "1%", ["e96", "e24"], iec60062_4digit, "V"), - resistor_series("ERJ-3EKF", "10Ohm", "1MOhm", "75V", "1%", ["e96", "e24"], iec60062_4digit, "V"), - resistor_series("ERJ-U03J", "1MOhm", "10MOhm", "75V", "5%", ["e24"], iec60062_3digit, "V"), - resistor_series("ERJ-PA3F", "1Ohm", "1MOhm", "75V", "1%", ["e96", "e24"], iec60062_4digit, "V"), + panasonic_erj("ERJ-H3QF", "1Ohm – 10Ohm", "75V", "0.333W", "1%", ["e96", "e24"], "V", iec60062_4digit), + panasonic_erj("ERJ-3EKF", "10Ohm – 1MOhm", "75V", "0.333W", "1%", ["e96", "e24"], "V", iec60062_4digit), + panasonic_erj("ERJ-U03J", "1MOhm – 10MOhm", "75V", "0.333W", "5%", ["e24"], "V", iec60062_3digit), + panasonic_erj("ERJ-PA3F", "1Ohm – 1MOhm", "75V", "0.333W", "1%", ["e96", "e24"], "V", iec60062_4digit), + ], + "2512": [ + # Stackpole CSNL series: Current sense resistors + discrete_resistor_series( + "Stackpole Electronics Inc", + "CSNL2512", + "170V", + "2W", + "1%", + [ + ("1mOhm", "CSNL2512FT1L00"), + ("2mOhm", "CSNL2512FT2L00"), + ("3mOhm", "CSNL2512FT3L00"), + ("4mOhm", "CSNL2512FT4L00"), + ("5mOhm", "CSNL2512FT5L00"), + ("10mOhm", "CSNL2512FT10L0"), + ], + ), + # YAGEO PA_E series: AEC-Q200 current sense resistors + discrete_resistor_series( + "YAGEO", + "PA_E", + "170V", + "2W", + "1%", + [ + ("1mOhm", "PA2512FKF7W0R001E"), + ("2mOhm", "PA2512FKF7W0R002E"), + ("3mOhm", "PA2512FKF7W0R003E"), + ("4mOhm", "PA2512FKF7W0R004E"), + ("5mOhm", "PA2512FKF7W0R005E"), + ("10mOhm", "PA2512FKF7W0R01E"), + ], + ), ], } @@ -104,34 +139,56 @@ def assign_house_resistor(c, series_by_pkg): resistance = Resistance(prop(c, ["resistance", "Resistance"])) pkg = prop(c, ["package", "Package"]) v_req = prop(c, ["Voltage", "voltage"]) - voltage_constraint = Voltage(v_req).value if v_req else None + voltage_constraint = Voltage(v_req) if v_req else None + p_req = prop(c, ["Power", "power"]) + power_constraint = Power(p_req) if p_req else None # Skip 0 ohm resistors - they can't be matched to E-series - if resistance.value <= 0: + if resistance.value <= 0.0: return series_list = series_by_pkg.get(pkg, []) matches = [] + manufacturer = None + for spec in series_list: - spec_min = Resistance(spec["min"]).value - spec_max = Resistance(spec["max"]).value - spec_max_v = Voltage(spec["max_v"]).value + spec_type = spec.get("type", "e_series") + spec_max_v = spec["max_v"] + spec_power = spec["power"] spec_tol = float(spec["tolerance"].replace("%", "")) / 100.0 if voltage_constraint and voltage_constraint > spec_max_v: continue - matched = find_e_series_match(resistance, spec_tol, spec["e_series"]) - if not matched or matched.value < spec_min or matched.value > spec_max: + if power_constraint and power_constraint > spec_power: continue - encoder = spec.get("encode", iec60062_4digit) - code = encoder(matched.value) - mpn = spec["series"] + code + spec.get("suffix", "X") - matches.append(mpn) + if spec_type == "discrete": + # Discrete series: exact value match with tolerance + r_with_tol = resistance.with_tolerance(spec_tol) if resistance.tolerance <= 0 else resistance + + for val_str, mpn in spec["values"]: + val_r = Resistance(val_str).with_tolerance(spec_tol) + if val_r.within(r_with_tol): + matches.append(mpn) + if manufacturer == None: + manufacturer = spec["manufacturer"] + else: + # E-series: standard matching logic + spec_range = spec["resistance_range"] + matched = find_e_series_match(resistance, spec_tol, spec["e_series"]) + if not matched or matched.value < spec_range.min or matched.value > spec_range.max: + continue + + encoder = spec.get("encode", iec60062_4digit) + code = encoder(matched.value) + mpn = spec["series"] + code + spec.get("suffix", "X") + matches.append(mpn) + if manufacturer == None: + manufacturer = spec["manufacturer"] if matches: - set_primary_and_alts(c, matches[0], "Panasonic Electronic Components", matches[1:]) + set_primary_and_alts(c, matches[0], manufacturer, matches[1:]) c.matcher = "assign_house_resistor" return @@ -156,7 +213,7 @@ def assign_house_capacitor(c, house_caps_by_pkg): pkg = prop(c, ["package", "Package"]) req_diel = prop(c, ["dielectric", "Dielectric"]) v_req = prop(c, ["voltage", "Voltage"]) - req_voltage = Voltage(v_req).value if v_req else None + req_voltage = Voltage(v_req) if v_req else None if cap.tolerance <= 0: cap = cap.with_tolerance(0.2) @@ -165,8 +222,8 @@ def assign_house_capacitor(c, house_caps_by_pkg): matches = [] for p in parts: - p_cap = Capacitance(p["cap"]) - p_voltage = Voltage(p["voltage"]).value + p_cap = p["cap"] + p_voltage = p["voltage"] if req_diel: h_rank = _DIELECTRIC_RANK.get(p["dielectric"], 99) @@ -180,9 +237,9 @@ def assign_house_capacitor(c, house_caps_by_pkg): if not p_cap.within(cap): continue - err = abs(p_cap.value - cap.value) / cap.value + err = p_cap.diff(cap) / cap diel_rank = _DIELECTRIC_RANK.get(p["dielectric"], 99) - score = (err, p_cap.tolerance, -p_voltage, diel_rank) + score = (err, p_cap.tolerance, p_voltage * -1, diel_rank) matches.append((score, p)) diff --git a/generics/Resistor.zen b/generics/Resistor.zen index 7dc034f..95b77b2 100644 --- a/generics/Resistor.zen +++ b/generics/Resistor.zen @@ -1,5 +1,5 @@ load("../config.zen", "config_unit") -load("../units.zen", "Resistance", "Voltage") +load("../units.zen", "Resistance", "Voltage", "Power") load("../utils.zen", "format_value") # ----------------------------------------------------------------------------- @@ -22,6 +22,7 @@ mpn = config("mpn", str, optional=True) manufacturer = config("manufacturer", str, optional=True) mount = config("mount", Mount, default=Mount("SMD"), optional=True) voltage = config_unit("voltage", Voltage, optional=True) +power = config_unit("power", Power, optional=True) use_us_symbol = config("use_us_symbol", bool, default=False, optional=True) do_not_populate = config("do_not_populate", bool, default=False) @@ -34,12 +35,15 @@ if exclude_from_bom: # Properties – combined and normalized properties = { - "value": format_value(value, voltage), + "value": format_value(value, voltage, power), "package": package, "resistance": value, "voltage": voltage, } +if power: + properties["power"] = power + # ----------------------------------------------------------------------------- # IO ports # -----------------------------------------------------------------------------