project
Adom Custom Electronic Load
Public Made by Adomby adom
Two-layer digitally controlled electronic-load prototype. Compact R11 is under active electrical, cooling and probe-access review; not a manufacturing release.
← Commit history
Add tested package cooling sensitivity budget with explicit unresolved assembly assumptions
6 files changed
+266
docs/package-cooling-review.mdadded+17@@ -0,0 +1,17 @@+# MOSFET package cooling review++++This is a separate series-resistance sensitivity calculation, not a Fields thermal pass. It uses 5 W entirely through the case, 40 C ambient, the unchanged 55 C trip, and an illustrative 3 C operating margin. Three numerical unit and energy-balance tests pass. No PCB geometry or gate acceptance was changed.++The 40 x 40 x 20 mm ATS-CPX040040020-115-C1-R0 sink has manufacturer ducted scenarios of 2.2, 1.7 and 1.3 K/W at 200, 400 and 700 LFM. Those velocities are not measured or established with our fan. A top-mounted axial fan does not automatically reproduce that test setup. [ATS sheet](https://www.qats.com/DataSheet/ATS-CPX040040020-115-C1-R0).++Using the generic CAD contact area of 129.96 mm2, the K1300 typical area-normalized interface becomes approximately 0.99 K/W. Wakefield 173-7's 0.33 K/W figure applies to its one-square-inch TO-3 test, not this TO-220; simple area scaling gives approximately 1.64 K/W. These calculations are screening estimates, not guaranteed package performance. [Wakefield catalog, page 4](https://wakefieldthermal.com/content/data_sheets/Thermal%20Interface%20Materials.pdf).++An illustrative 0.635 mm aluminum-nitride plate at 170 W/mK, plus two 122 grease films using the published area-normalized figure, gives approximately 0.23 K/W before spreading. Adding an assumed 0.3 K/W spreading term and the 400 LFM sink scenario gives about 51.1 C at the case. That is a promising scenario, not an accepted assembly. The AlN supplier page conflicts between 25 mm length in its title and 20 mm in its table; hole geometry, clamp load, exact contact area, grease application and installed airflow remain unresolved. [AlN source](https://wakefieldthermal.com/wakefield-thermal-25x14x0-635-to-220-aluminum-nitride-pad-178-25-220/).++The PCB already has Q1's 3.5 mm mounting hole at (127.46, 65.34). The manufacturer's TO-220 hole range is 3.53 to 4.08 mm. This permits investigating an insulated through-fastener, but does not select one. The generic body height is not a fixed manufacturing tolerance: the manufacturer's 4.32 to 4.83 mm package-thickness range requires a compliant clamp/support stack. The sink and fan mass must not be supported by the transistor leads. Do not drill or tap the commercial sink without verified base thickness, hardware engagement and revised CAD.++Remaining work: exact insulating/hardware drawings, support and clamp design, fan duct/operating-point analysis, same-board current/thermal evidence, and physical electrical-isolation, stall, sensor and temperature tests. The finish gate has not passed.++Reproduce: `python3 tools/package_cooling_budget.py`, `python3 -m unittest discover -s tests -p test_package_cooling_budget.py`, then `python3 -s tools/plot_package_cooling_budget.py`. The full assumptions and scenarios are in `mechanical/package-cooling-budget.json`.
mechanical/package-cooling-budget.jsonadded+135@@ -0,0 +1,135 @@+{+ "status": "sensitivity-only-unqualified",+ "acceptance": false,+ "boardSha256": "8d6cd24149f7de061f125bf45a26b50ca5b70a6b01c62b96dc3cf92187a531f1",+ "powerW": 5,+ "ambientC": 40,+ "tripC": 55,+ "illustrativeOperatingMarginC": 3,+ "contactAreaMm2": 129.961768906,+ "rJunctionCaseKPerW": 0.42,+ "assumedAdditionalSpreadingKPerW": 0.3,+ "assumptions": [+ "All 5 W follows the package case path; no benefit credited to PCB cooling.",+ "Nominal contact area comes from generic CAD, not a guaranteed contact drawing.",+ "Interface figures are typical/test-area conversions, not production maxima.",+ "Additional 0.3 K/W spreading is a sensitivity assumption, not a solved bound.",+ "3 C operating margin is an engineering screening choice, not sensor-error qualification.",+ "ATS ducted velocities are scenarios, not achieved flow with the Sunon fan."+ ],+ "sources": {+ "sink": "https://www.qats.com/DataSheet/ATS-CPX040040020-115-C1-R0",+ "wakefieldTIM": "https://wakefieldthermal.com/content/data_sheets/Thermal%20Interface%20Materials.pdf",+ "aln": "https://wakefieldthermal.com/wakefield-thermal-25x14x0-635-to-220-aluminum-nitride-pad-178-25-220/",+ "k1300": "https://datasheets.tdx.henkel.com/BERGQUIST-SIL-PAD-TSP-K1300-en_GL.pdf"+ },+ "unresolved": [+ "Exact contact geometry and insulating pad hole drawing",+ "AlN page conflicts: title 25 mm length, table 20 mm",+ "Fastener, shoulder insulation, clamp load and package support",+ "Heatsink attachment and fan duct geometry",+ "Installed pressure/flow and spreading resistance",+ "Physical stall, thermal-trip, insulation and mechanical tests"+ ],+ "scenarios": [+ {+ "interface": "K1300 typical area-normalized",+ "ductedLfm": 200,+ "rInterfaceKPerW": 0.9928458275550829,+ "rSinkKPerW": 2.2,+ "sinkC": 51.0,+ "caseC": 57.46422913777541,+ "junctionC": 59.56422913777541,+ "tripMarginC": -2.464229137775412,+ "meetsIllustrative3CDeratingMargin": false+ },+ {+ "interface": "K1300 typical area-normalized",+ "ductedLfm": 400,+ "rInterfaceKPerW": 0.9928458275550829,+ "rSinkKPerW": 1.7,+ "sinkC": 48.5,+ "caseC": 54.96422913777541,+ "junctionC": 57.06422913777541,+ "tripMarginC": 0.03577086222458803,+ "meetsIllustrative3CDeratingMargin": false+ },+ {+ "interface": "K1300 typical area-normalized",+ "ductedLfm": 700,+ "rInterfaceKPerW": 0.9928458275550829,+ "rSinkKPerW": 1.3,+ "sinkC": 46.5,+ "caseC": 52.96422913777541,+ "junctionC": 55.06422913777541,+ "tripMarginC": 2.035770862224588,+ "meetsIllustrative3CDeratingMargin": false+ },+ {+ "interface": "173-7 one-square-inch test scaled",+ "ductedLfm": 200,+ "rInterfaceKPerW": 1.6381956154658868,+ "rSinkKPerW": 2.2,+ "sinkC": 51.0,+ "caseC": 60.69097807732943,+ "junctionC": 62.790978077329434,+ "tripMarginC": -5.690978077329433,+ "meetsIllustrative3CDeratingMargin": false+ },+ {+ "interface": "173-7 one-square-inch test scaled",+ "ductedLfm": 400,+ "rInterfaceKPerW": 1.6381956154658868,+ "rSinkKPerW": 1.7,+ "sinkC": 48.5,+ "caseC": 58.19097807732943,+ "junctionC": 60.290978077329434,+ "tripMarginC": -3.190978077329433,+ "meetsIllustrative3CDeratingMargin": false+ },+ {+ "interface": "173-7 one-square-inch test scaled",+ "ductedLfm": 700,+ "rInterfaceKPerW": 1.6381956154658868,+ "rSinkKPerW": 1.3,+ "sinkC": 46.5,+ "caseC": 56.19097807732943,+ "junctionC": 58.290978077329434,+ "tripMarginC": -1.190978077329433,+ "meetsIllustrative3CDeratingMargin": false+ },+ {+ "interface": "AlN + two 122 grease films, illustrative",+ "ductedLfm": 200,+ "rInterfaceKPerW": 0.227310649634311,+ "rSinkKPerW": 2.2,+ "sinkC": 51.0,+ "caseC": 53.63655324817155,+ "junctionC": 55.73655324817155,+ "tripMarginC": 1.363446751828448,+ "meetsIllustrative3CDeratingMargin": false+ },+ {+ "interface": "AlN + two 122 grease films, illustrative",+ "ductedLfm": 400,+ "rInterfaceKPerW": 0.227310649634311,+ "rSinkKPerW": 1.7,+ "sinkC": 48.5,+ "caseC": 51.13655324817155,+ "junctionC": 53.23655324817155,+ "tripMarginC": 3.863446751828448,+ "meetsIllustrative3CDeratingMargin": true+ },+ {+ "interface": "AlN + two 122 grease films, illustrative",+ "ductedLfm": 700,+ "rInterfaceKPerW": 0.227310649634311,+ "rSinkKPerW": 1.3,+ "sinkC": 46.5,+ "caseC": 49.13655324817155,+ "junctionC": 51.23655324817155,+ "tripMarginC": 5.863446751828448,+ "meetsIllustrative3CDeratingMargin": true+ }+ ]+}
mechanical/package-cooling-budget.pngadded⋯ 1 unchanged line ⋯
tests/test_package_cooling_budget.pyadded+24@@ -0,0 +1,24 @@+import sys+import unittest+from pathlib import Path+sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'tools'))+from package_cooling_budget import area_resistance, slab_resistance, temperatures++class CoolingBudgetTests(unittest.TestCase):+ def test_units(self):+ self.assertAlmostEqual(area_resistance(.2, 100), 1.29032)+ self.assertAlmostEqual(slab_resistance(1, 100, 100), .1)++ def test_series_energy_balance(self):+ t = temperatures(5, 40, .42, .5, .3, 1.7)+ self.assertAlmostEqual(t['sinkC'], 48.5)+ self.assertAlmostEqual(t['caseC'], 52.5)+ self.assertAlmostEqual(t['junctionC'], 54.6)+ self.assertEqual(temperatures(0, 40, .42, .5, .3, 1.7)['junctionC'], 40)++ def test_invalid_inputs(self):+ with self.assertRaises(ValueError): area_resistance(.2, 0)+ with self.assertRaises(ValueError): slab_resistance(1, 0, 100)+ with self.assertRaises(ValueError): temperatures(float('nan'), 40, .42, .5, .3, 1.7)++if __name__ == '__main__': unittest.main()
tools/package_cooling_budget.pyadded+75@@ -0,0 +1,75 @@+"""Independent package-path sensitivity screen. Never substitutes for AIFlow/Fields evidence."""+import hashlib+import json+import math+from pathlib import Path++IN2_TO_MM2 = 25.4 ** 2++def area_resistance(value_c_in2_w, area_mm2):+ if not all(math.isfinite(v) for v in (value_c_in2_w, area_mm2)) or area_mm2 <= 0 or value_c_in2_w < 0:+ raise ValueError('Positive finite contact area required')+ return value_c_in2_w * IN2_TO_MM2 / area_mm2++def slab_resistance(thickness_mm, conductivity_w_mk, area_mm2):+ if not all(math.isfinite(v) for v in (thickness_mm, conductivity_w_mk, area_mm2)) or min(thickness_mm, conductivity_w_mk, area_mm2) <= 0:+ raise ValueError('Positive thickness, conductivity and area required')+ return thickness_mm * 1000 / (conductivity_w_mk * area_mm2)++def temperatures(power_w, ambient_c, r_jc, r_interface, r_spreading, r_sink):+ values = [power_w, ambient_c, r_jc, r_interface, r_spreading, r_sink]+ if not all(math.isfinite(v) for v in values) or min(power_w, r_jc, r_interface, r_spreading, r_sink) < 0:+ raise ValueError('Finite inputs and nonnegative power/resistances required')+ sink = ambient_c + power_w * r_sink+ case = sink + power_w * (r_spreading + r_interface)+ return {'sinkC': sink, 'caseC': case, 'junctionC': case + power_w * r_jc}++def main():+ root = Path(__file__).resolve().parents[1]+ area = 129.961768906 # generic KiCad nominal face, not qualified IXYS contact area+ interfaces = {+ 'K1300 typical area-normalized': area_resistance(.2, area),+ '173-7 one-square-inch test scaled': area_resistance(.33, area),+ 'AlN + two 122 grease films, illustrative': slab_resistance(.635, 170, area) + 2 * area_resistance(.02, area),+ }+ scenarios = []+ for label, interface in interfaces.items():+ for flow, sink in [(200, 2.2), (400, 1.7), (700, 1.3)]:+ t = temperatures(5, 40, .42, interface, .3, sink)+ scenarios.append({'interface': label, 'ductedLfm': flow,+ 'rInterfaceKPerW': interface, 'rSinkKPerW': sink,+ **t, 'tripMarginC': 55 - t['caseC'],+ 'meetsIllustrative3CDeratingMargin': t['caseC'] <= 52})+ report = {+ 'status': 'sensitivity-only-unqualified', 'acceptance': False,+ 'boardSha256': hashlib.sha256((root/'kicad/adom-custom-electronic-load.kicad_pcb').read_bytes()).hexdigest(),+ 'powerW': 5, 'ambientC': 40, 'tripC': 55, 'illustrativeOperatingMarginC': 3,+ 'contactAreaMm2': area, 'rJunctionCaseKPerW': .42,+ 'assumedAdditionalSpreadingKPerW': .3,+ 'assumptions': [+ 'All 5 W follows the package case path; no benefit credited to PCB cooling.',+ 'Nominal contact area comes from generic CAD, not a guaranteed contact drawing.',+ 'Interface figures are typical/test-area conversions, not production maxima.',+ 'Additional 0.3 K/W spreading is a sensitivity assumption, not a solved bound.',+ '3 C operating margin is an engineering screening choice, not sensor-error qualification.',+ 'ATS ducted velocities are scenarios, not achieved flow with the Sunon fan.',+ ],+ 'sources': {+ 'sink': 'https://www.qats.com/DataSheet/ATS-CPX040040020-115-C1-R0',+ 'wakefieldTIM': 'https://wakefieldthermal.com/content/data_sheets/Thermal%20Interface%20Materials.pdf',+ 'aln': 'https://wakefieldthermal.com/wakefield-thermal-25x14x0-635-to-220-aluminum-nitride-pad-178-25-220/',+ 'k1300': 'https://datasheets.tdx.henkel.com/BERGQUIST-SIL-PAD-TSP-K1300-en_GL.pdf',+ },+ 'unresolved': ['Exact contact geometry and insulating pad hole drawing',+ 'AlN page conflicts: title 25 mm length, table 20 mm',+ 'Fastener, shoulder insulation, clamp load and package support',+ 'Heatsink attachment and fan duct geometry',+ 'Installed pressure/flow and spreading resistance',+ 'Physical stall, thermal-trip, insulation and mechanical tests'],+ 'scenarios': scenarios,+ }+ (root/'mechanical/package-cooling-budget.json').write_text(json.dumps(report, indent=2)+'\n')+ print(json.dumps({'status':report['status'], 'acceptance':False, 'scenarios':scenarios},indent=2))++if __name__ == '__main__':+ main()
tools/plot_package_cooling_budget.pyadded+15@@ -0,0 +1,15 @@+from pathlib import Path+import json+import matplotlib+matplotlib.use('Agg')+import matplotlib.pyplot as plt+p=Path(__file__).resolve().parents[1]+d=json.loads((p/'mechanical/package-cooling-budget.json').read_text())+plt.style.use('dark_background');fig,ax=plt.subplots(figsize=(12,6.5),layout='constrained')+labels={'K1300 typical area-normalized':'K1300, typical area scaling','173-7 one-square-inch test scaled':'173-7, corrected area scaling','AlN + two 122 grease films, illustrative':'AlN + grease, unqualified candidate'}+for key,label in labels.items():+ r=[s for s in d['scenarios'] if s['interface']==key];ax.plot([v['ductedLfm'] for v in r],[v['caseC']for v in r],marker='o',linewidth=2,label=label)+ax.axhline(55,color='#ff7777',linestyle='--',label='55 C trip, unchanged');ax.axhline(52,color='#bbbbbb',linestyle=':',label='Illustrative 3 C operating margin')+ax.set(xlabel='Heatsink datasheet ducted velocity (LFM), NOT installed fan flow',ylabel='Calculated MOSFET case temperature (C)',title='Electronic load: package cooling sensitivity, 5 W / 40 C ambient')+ax.grid(alpha=.18);ax.legend(fontsize=9);fig.text(.01,.005,'Unqualified sensitivity study. Generic contact area; assumed spreading; actual mounting, pressure and airflow unresolved.',fontsize=9)+fig.savefig(p/'mechanical/package-cooling-budget.png',dpi=160)