Diode-based pull-up/pull-down calculator

by eddyb

HTML

<h1>Diode-based pull-up/pull-down calculator</h1>

<form>

<table><tr><td colspan="2"><h2>Standard circuit</h2></td></tr>
  <tr><td>Preset: </td><td><select name="preset">
    <option value="V=3.3,R_pu=1.5,R_pd=15,V_hi_min=2.8,V_lo_max=0.3">USB D-/D+</option>
    <option value="V=3.3,R_pu=36,R_pd=5.1,V_hi_min=1.04,V_lo_max=0.075">USB-C CC</option>
  </select></td></tr>
  <tr><td>V<sub>cc</sub> = </td><td><input size="4" name="V"> V</td></tr>
  <tr><td>R<sub>pull-up</sub> = </td><td><input size="4" name="R_pu"> kΩ</td></tr>
  <tr><td>R<sub>pull-down</sub> = </td><td><input size="4" name="R_pd"> kΩ</td></tr>
  <tr><td colspan="2"><h2/></td></tr>
  <tr><td>V<sub>idle</sub> = </td><td><input size="4" name="V_idle" readonly> V</td></tr>
  <tr><td>I<sub>idle</sub> = </td><td><input size="4" name="I_idle" readonly> mA</td></tr>
  <tr><td colspan="2"><h2/></td></tr>
  <tr><td colspan="2"><b>Drivers</b></td></tr>
  <tr><td>V<sub>logic high</sub> ≥ </td><td><input size="4" name="V_hi_min"> V</td></tr>
  <tr><td>V<sub>logic low</sub> ≤ </td><td><input size="4" name="V_lo_max"> V</td></tr>
  <tr><td colspan="2"><h2/></td></tr>
  <tr><td>Z<sub>drive high</sub> ≤ </td><td><input size="4" name="Z_hi_max" readonly> kΩ</td></tr>
  <tr><td>Z<sub>drive low</sub> ≤ </td><td><input size="4" name="Z_lo_max" readonly> kΩ</td></tr>
</table>

<table><tr><td colspan="2"><h2>Diode-based circuit</h2></td></tr>
  <tr><td>V<sub>pull-up diode drop</sub> = </td><td><input size="4" name="V_d_pu" value="0.220"> V</td></tr>
  <tr><td>R<sub>pull-up</sub> = </td><td><input size="4" name="R_d_pu" value="1"> kΩ</td></tr>
  <tr><td>R<sub>pull-down</sub> = </td><td><input size="4" name="R_d_pd" value="1"> kΩ</td></tr>
  <tr><td>V<sub>pull-down diode drop</sub> = </td><td><input size="4" name="V_d_pd" value="0.220"> V</td></tr>
  <tr><td colspan="2"><h2/></td></tr>
  <tr><td colspan="2"><b>Match V<sub>idle</sub> exactly</b> <button type="button" name="use_match_V_idle">Use</button></td></tr>
 ...

CSS

table {
  display: inline-block;
  border: 1px solid grey;
  vertical-align: top;
}

tr > td > h2 {
  margin: 0.5em;
  border-bottom: 1px solid grey;
}

tr > td:first-child, input {
  text-align: right;
}

tr > td:first-child[colspan] {
  text-align: center;
}

JavaScript

function voltage_divider(pus, pds) {
  let V = Math.max(...pus.map(pu => pu.V));

  let R_pu = 1/pus.map(pu => 1/pu.R).reduce((a, b) => a + b);
  let R_pd = 1/pds.map(pd => 1/pd.R).reduce((a, b) => a + b);

	// Figure out the equivalent extra currents, that would
  // pass through resistors, if no diodes were present.
  let I_pu_bypass = pus.map(pu => (V - pu.V) / pu.R).reduce((a, b) => a + b);
  let I_pd_bypass = pds.map(pd => pd.V / pd.R).reduce((a, b) => a + b);

  return (V / R_pu - I_pu_bypass + I_pd_bypass) * (1/(1/R_pd + 1/R_pu));
}

function compute({V, R_pu, R_pd, V_hi_min, V_lo_max, V_d_pu, R_d_pu, R_d_pd, V_d_pd}) {
	let pus = [{V, R: R_pu}, {V: V - V_d_pu, R: R_d_pu, suffix: '_d_pu'}];
  let pds = [{V: 0, R: R_pd}, {V: V_d_pu, R: R_d_pd, suffix: '_d_pd'}];
  let o = {};
  for(let pu of pus)
  	for(let pd of pds) {
    	let V_idle = voltage_divider([pu], [pd]);
      let suffix = (pu.suffix || '') + (pd.suffix || '') + '_idle';
      o['V' + suffix] = V_idle;
      o['I' + suffix] = (V_idle - pd.V) / pd.R;
    }

  // FIXME: deduplicate these.
  o.R_d_pu_match_V_idle = R_pd * ((V - V_d_pu - o.V_idle) / o.V_idle);
  o.R_d_pd_match_V_idle = R_pu * ((o.V_idle - V_d_pd) / (V - o.V_idle));

  o.R_d_pu_match_V_idle_hi = R_pd * ((V - V_d_pu - V_hi_min) / V_hi_min);
  o.R_d_pd_match_V_idle_hi = R_pu * ((V_hi_min - V_d_pd) / (V - V_hi_min));

  o.R_d_pu_match_V_idle_lo = R_pd * ((V - V_d_pu - V_lo_max) / V_lo_max);
  o.R_d_pd_match_V_idle_lo = R_pu * ((V_lo_max - V_d_pd) / (V - V_lo_max));
  
  // NOTE: commented out formulas are for PU+PD+Z_{hi,lo}, as oposed to PD+Z_lo and PU+Z_hi.
  o.Z_hi_max = R_pd * ((V - V_hi_min) / V_hi_min); // (1 / (1 / (R_pd * ((V - V_hi_min) / V_hi_min)) - 1 / R_pu));
  o.Z_lo_max = R_pu * (V_lo_max / (V - V_lo_max)); // (1 / (1 / (R_pu * (V_lo_max / (V - V_lo_max))) - 1 / R_pd));

	for(let side of ['hi', 'lo']) {
  	let Z_drive = o['Z_' + side + '_max'];

    for(let pu of pus) {
    	let drive_pus = [pu];
    	if(side == 'hi')
   ...