178 lines
12 KiB
HTML
178 lines
12 KiB
HTML
|
|
<!DOCTYPE html>
|
|||
|
|
<html lang="en">
|
|||
|
|
<head>
|
|||
|
|
<meta charset="UTF-8">
|
|||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|||
|
|
<title>Reading Battery State-of-Health — Field Guide</title>
|
|||
|
|
<meta name="description" content="How an ASE tech judges a hybrid pack without trusting the dashboard: module voltage spread, internal-resistance rise, capacity fade — plus an interactive pack simulator.">
|
|||
|
|
<meta property="og:type" content="article">
|
|||
|
|
<meta property="og:title" content="Reading Battery State-of-Health — Field Guide">
|
|||
|
|
<meta property="og:description" content="Module voltage spread, IR rise, capacity fade. The numbers behind a pack health verdict.">
|
|||
|
|
<style>
|
|||
|
|
:root{
|
|||
|
|
--paper:#e8e2d3; --rule:#1a1a18; --ink:#16150f; --accent:#b5482c;
|
|||
|
|
--grn:#2e6b3a; --amber:#c88a1e;
|
|||
|
|
}
|
|||
|
|
*{box-sizing:border-box;margin:0;padding:0;}
|
|||
|
|
body{background:var(--paper);color:var(--ink);font-family:"Courier New",ui-monospace,Menlo,monospace;line-height:1.5;padding-bottom:3rem;}
|
|||
|
|
.grid{position:fixed;inset:0;z-index:-1;pointer-events:none;background-image:linear-gradient(rgba(22,21,15,.06) 1px,transparent 1px),linear-gradient(90deg,rgba(22,21,15,.06) 1px,transparent 1px);background-size:24px 24px;}
|
|||
|
|
header{border-bottom:4px solid var(--ink);padding:2rem 2rem 1rem;background:var(--paper);}
|
|||
|
|
.stamp{display:inline-block;border:2px solid var(--ink);padding:.2rem .6rem;font-size:.75rem;letter-spacing:.15em;text-transform:uppercase;color:var(--accent);margin-bottom:.8rem;}
|
|||
|
|
h1{font-size:clamp(1.4rem,3.5vw,2.2rem);line-height:1.1;text-transform:uppercase;max-width:22ch;}
|
|||
|
|
.sub{font-size:.85rem;color:#4a463a;margin-top:.4rem;max-width:70ch;}
|
|||
|
|
.bread{font-size:.72rem;margin-top:.2rem;color:#4a463a;}
|
|||
|
|
.bread a{color:var(--accent);}
|
|||
|
|
nav{display:flex;flex-wrap:wrap;gap:.5rem;border-bottom:2px solid var(--ink);padding:1rem 2rem;background:var(--paper);}
|
|||
|
|
nav a{border:2px solid var(--ink);padding:.3rem .6rem;text-decoration:none;color:var(--ink);background:#fff;font-size:.8rem;text-transform:uppercase;letter-spacing:.05em;}
|
|||
|
|
nav a:hover{background:var(--ink);color:var(--paper);}
|
|||
|
|
main{max-width:1000px;margin:0 auto;padding:2rem;}
|
|||
|
|
.lede{font-size:1.02rem;max-width:75ch;border-left:6px solid var(--accent);padding-left:1rem;margin-bottom:2rem;}
|
|||
|
|
h2{font-size:1.15rem;text-transform:uppercase;letter-spacing:.08em;border-bottom:2px solid var(--ink);padding-bottom:.3rem;margin:2.2rem 0 1rem;}
|
|||
|
|
h3{font-size:.9rem;text-transform:uppercase;letter-spacing:.08em;margin:1.2rem 0 .4rem;}
|
|||
|
|
p{max-width:72ch;margin-bottom:.8rem;font-size:.88rem;}
|
|||
|
|
.eq{background:#fff;border:2px solid var(--ink);padding:.8rem;margin:1rem 0;font-size:.9rem;overflow-x:auto;}
|
|||
|
|
table{width:100%;border-collapse:collapse;font-size:.8rem;background:#fff;margin:1rem 0;}
|
|||
|
|
th,td{border:2px solid var(--ink);padding:.5rem;text-align:left;}
|
|||
|
|
th{background:#d9d2bf;text-transform:uppercase;font-size:.72rem;letter-spacing:.05em;}
|
|||
|
|
.safety{border:3px solid var(--grn);padding:1rem;margin:1.5rem 0;background:#eef3ea;}
|
|||
|
|
.safety h3{color:var(--grn);text-transform:uppercase;font-size:.85rem;letter-spacing:.1em;margin-bottom:.4rem;}
|
|||
|
|
.flag{border:3px solid var(--amber);padding:1rem;margin:1.5rem 0;background:#faf3dd;}
|
|||
|
|
ul{list-style:none;font-size:.85rem;}
|
|||
|
|
ul li::before{content:"▸ ";margin-right:.2rem;}
|
|||
|
|
ul li{margin-bottom:.4rem;}
|
|||
|
|
/* SIMULATOR */
|
|||
|
|
#sim{border:3px solid var(--ink);background:#fff;padding:1.2rem;margin:1.5rem 0;}
|
|||
|
|
.ctrl{margin:.8rem 0;}
|
|||
|
|
.ctrl label{display:block;font-size:.78rem;text-transform:uppercase;letter-spacing:.06em;margin-bottom:.3rem;}
|
|||
|
|
.ctrl input[type=range]{width:100%;accent-color:var(--accent);}
|
|||
|
|
.ctrl .val{font-size:.9rem;font-weight:bold;color:var(--accent);}
|
|||
|
|
#readout{border:2px dashed var(--rule);background:#f5f1e5;padding:.8rem;margin:.8rem 0;font-size:.8rem;white-space:pre-wrap;}
|
|||
|
|
#verdict{font-weight:bold;font-size:.95rem;margin-top:.4rem;}
|
|||
|
|
.badge{display:inline-block;padding:.2rem .5rem;font-weight:bold;font-size:.75rem;border:2px solid var(--ink);}
|
|||
|
|
.footer{margin-top:3rem;border-top:4px solid var(--ink);padding-top:1rem;font-size:.72rem;color:#4a463a;}
|
|||
|
|
.footer a{color:var(--accent);}
|
|||
|
|
.src-note{font-size:.72rem;color:#59533f;margin-top:1.5rem;}
|
|||
|
|
</style>
|
|||
|
|
<script defer src="https://analytics.4ort.xyz/script.js" data-website-id="d3ed927c-888a-4a6c-ae5f-0b1c613ddf5b"></script>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<div class="grid"></div>
|
|||
|
|
<header>
|
|||
|
|
<div class="stamp">FIELD GUIDE · v2</div>
|
|||
|
|
<h1>Reading Battery State-of-Health</h1>
|
|||
|
|
<p class="sub">How I judge a hybrid pack without trusting the dashboard. Module voltage spread, internal-resistance rise, capacity fade — and the equipment that does the judging.</p>
|
|||
|
|
<p class="bread"><a href="index.html">← Adam Pollard · Shop</a></p>
|
|||
|
|
</header>
|
|||
|
|
<nav>
|
|||
|
|
<a href="#why">Why SOH</a>
|
|||
|
|
<a href="#numbers">The Numbers</a>
|
|||
|
|
<a href="#procedure">Procedure</a>
|
|||
|
|
<a href="#sim">Pack Simulator</a>
|
|||
|
|
<a href="#flags">What I Flag</a>
|
|||
|
|
</nav>
|
|||
|
|
<main>
|
|||
|
|
|
|||
|
|
<p class="lede">The dashboard tells you "battery health is good" and gives you a bar chart nobody reads. That's a marketing screen, not a diagnosis. To judge a pack you need three numbers from a live BMS scan or a bench test: module voltage spread, internal-resistance rise, and capacity fade against rated. This guide is the way I actually run that read, and the thresholds where I start writing "replace module" instead of "drive another year."</p>
|
|||
|
|
|
|||
|
|
<h2 id="why">Why SOH, and Why Not the Dashboard</h2>
|
|||
|
|
<p>State-of-health (SOH) is the catch-all term for how much usable life a pack has left versus its rated new condition. It's not one thing. Aging shows up as: lithium inventory lost to solid-electrolyte interphase growth (capacity fade), impedance climbing as conductive paths degrade (IR rise), and cells drifting apart from each other (imbalance). All three march together, but they don't mark time equally. A pack can lose 12% capacity and still read "good health" on the dash because it's balanced enough to deliver its now-shorter range. The dash graphs range, not degradation.</p>
|
|||
|
|
|
|||
|
|
<div class="safety">
|
|||
|
|
<h3>HV safety — read before you sweep a pack</h3>
|
|||
|
|
<ul>
|
|||
|
|
<li>Class-0 insulated gloves, rated and re-tested, before any orange connector.</li>
|
|||
|
|
<li>Disconnect and WAIT — capacitors hold lethal charge minutes after cutoff.</li>
|
|||
|
|
<li>Isolation test with a megohmmeter, not a continuity beep.</li>
|
|||
|
|
<li>One hand in pocket near the pack. Always.</li>
|
|||
|
|
</ul>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<h2 id="numbers">The Three Numbers That Matter</h2>
|
|||
|
|
<table>
|
|||
|
|
<tr><th>Metric</th><th>What it catches</th><th>Healthy</th><th>I get suspicious</th><th>I write it up</th></tr>
|
|||
|
|
<tr><td>Module voltage spread</td><td>Cell imbalance / weak cells</td><td>< 10 mV between modules at rest</td><td>25–50 mV spread</td><td>> 50 mV sustained</td></tr>
|
|||
|
|
<tr><td>Internal resistance rise</td><td>Impedance / degradation</td><td>< +10% over rated</td><td>+20–40%</td><td>> +40% on any module</td></tr>
|
|||
|
|
<tr><td>Capacity fade</td><td>Usable energy loss</td><td>> 90% of rated</td><td>80–90%</td><td>< 80% (or per fleet policy)</td></tr>
|
|||
|
|
</table>
|
|||
|
|
<p>Those are my working bands for a typical NiMH/Li-ion hybrid traction pack. Fleet policy and the vehicle's Warranty SOH threshold override them; a taxi that's down to 82% gets pulled a lot sooner than a weekend car at 78%. The shape of the fault matters as much as the number.</p>
|
|||
|
|
|
|||
|
|
<div class="eq">
|
|||
|
|
<b>IR rise (module):</b> ΔR% = (R_test − R_rated) / R_rated × 100<br>
|
|||
|
|
<b>Capacity fade:</b> C_fade% = C_measured / C_rated × 100<br>
|
|||
|
|
<b>Voltage spread:</b> V_max − V_min across all modules at 20% SOC rest.
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<h2 id="procedure">The Procedure, In Order</h2>
|
|||
|
|
<ol style="font-size:.88rem;margin:0 0 1rem 1.4rem;max-width:72ch;">
|
|||
|
|
<li><b>Park at known SOC.</b> I bring the pack to ~20% SOC and let it rest minimum one hour — voltages settle; a fresh-off-charge read lies.</li>
|
|||
|
|
<li><b>Full module voltage sweep.</b> Via the BMS scan tool or service port. Log every module, not just the pack average. I want the spread, not the mean.</li>
|
|||
|
|
<li><b>IR test.</b> Megohmmeter for isolation on the pack side. For per-module impedance I use the BMS's own cell-balancing current pulses — every module reports voltage under a known load, and I take the delta.</li>
|
|||
|
|
<li><b>Capacity check.</b> Controlled discharge at the nominal C-rate, logging amp-hours out. This is the slow one — it's a full discharge cycle, not a 10-minute tap.</li>
|
|||
|
|
<li><b>Read the pattern, not the bar.</b> One module 40 mV high with matching IR rise = single weak cell, replace that module. Uniform IR creep across all = whole pack age, plan for pack, not module.</li>
|
|||
|
|
</ol>
|
|||
|
|
|
|||
|
|
<div class="flag">
|
|||
|
|
<h3>Patterns I read in the scan</h3>
|
|||
|
|
<ul>
|
|||
|
|
<li><b>One high IR module</b> — that cell's the weak link; the pack is fine, swap the module.</li>
|
|||
|
|
<li><b>Uniform IR rise + uniform fade</b> — the pack is aging as a unit; schedule replacement.</li>
|
|||
|
|
<li><b>Spike on one cell at low SOC</b> — classic lithium plating / dendrite risk. That module comes out NOW, not at the next service.</li>
|
|||
|
|
<li><b>Spread that tightens under load, widens at rest</b> — a wiring/connector resistance, not a cell fault. Look before you condemn a battery.</li>
|
|||
|
|
</ul>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<h2 id="sim">Pack Simulator — You Drive the Read</h2>
|
|||
|
|
<p>Drag the three sliders and watch what I'd say about the hypothetical pack. This is the same arithmetic the BMS uses under the hood, rendered as a verdict instead of a bar.</p>
|
|||
|
|
<div id="sim">
|
|||
|
|
<div class="ctrl">
|
|||
|
|
<label>Module voltage spread <span class="val" id="spreadVal">10 mV</span></label>
|
|||
|
|
<input type="range" id="spread" min="5" max="120" value="10" step="1">
|
|||
|
|
</div>
|
|||
|
|
<div class="ctrl">
|
|||
|
|
<label>Internal-resistance rise <span class="val" id="irVal">+5%</span></label>
|
|||
|
|
<input type="range" id="ir" min="0" max="80" value="5" step="1">
|
|||
|
|
</div>
|
|||
|
|
<div class="ctrl">
|
|||
|
|
<label>Capacity fade <span class="val" id="capVal">96% of rated</span></label>
|
|||
|
|
<input type="range" id="cap" min="55" max="100" value="96" step="1">
|
|||
|
|
</div>
|
|||
|
|
<div id="readout">—</div>
|
|||
|
|
<div id="verdict"></div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<p class="src-note">Grounding: pack reads and thresholds above are from my field work on hybrid traction packs; chemistry baseline on the working anode/cathode assumptions cites <a href="https://www.wikidata.org/wiki/Q2822895" style="color:var(--accent);">lithium-ion battery (Wikidata)</a>. Slider verdicts follow the threshold bands in the table; a real diagnosis needs the full procedure, not the simulator.</p>
|
|||
|
|
|
|||
|
|
<footer class="footer">
|
|||
|
|
<p>Adam Pollard · ASE Master Technician · Sparks, Nevada</p>
|
|||
|
|
<p><a href="soh.json">Machine-readable data twin of this guide →</a> · <a href="index.html">Shop home →</a> · <a href="tool.html">Hybrid diagnostic tree →</a></p>
|
|||
|
|
</footer>
|
|||
|
|
</main>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
(function(){
|
|||
|
|
var spread=document.getElementById('spread'), ir=document.getElementById('ir'), cap=document.getElementById('cap');
|
|||
|
|
var spreadVal=document.getElementById('spreadVal'), irVal=document.getElementById('irVal'), capVal=document.getElementById('capVal');
|
|||
|
|
var ro=document.getElementById('readout'), vd=document.getElementById('verdict');
|
|||
|
|
function stringify(){
|
|||
|
|
var s=+spread.value, r=+ir.value, c=+cap.value;
|
|||
|
|
spreadVal.textContent=s+' mV'; irVal.textContent='+'+r+'%'; capVal.textContent=c+'% of rated';
|
|||
|
|
// spread verdict
|
|||
|
|
var sFlag = s<25 ? 'MODULE SPREAD: acceptable' : (s<=50 ? 'MODULE SPREAD: watch — single weak cell possible' : 'MODULE SPREAD: FAIL — one or more modules out of band');
|
|||
|
|
var rFlag = r<20 ? 'IR RISE: normal aging' : (r<=40 ? 'IR RISE: elevated — trace which modules' : 'IR RISE: FAIL — replace offending module(s)');
|
|||
|
|
var cFlag = c>90 ? 'CAPACITY: healthy' : (c>=80 ? 'CAPACITY: faded — plan replacement window' : 'CAPACITY: FAIL — below usable line');
|
|||
|
|
// composite verdict
|
|||
|
|
var f=[sFlag,rFlag,cFlag].filter(function(x){return x.indexOf('FAIL')>-1;});
|
|||
|
|
var w=[sFlag,rFlag,cFlag].filter(function(x){return x.indexOf('watch')>-1 || x.indexOf('elevated')>-1 || x.indexOf('faded')>-1;});
|
|||
|
|
var verdict;
|
|||
|
|
if(f.length>=1){ verdict='VERDICT: '+f.length+' failing metric(s). Don\u2019t drive-away this one. '+(f.join(' / ')); }
|
|||
|
|
else if(w.length>=1){ verdict='VERDICT: borderline. '+(w.join(' / '))+' — worth a full procedure, not a shrug.'; }
|
|||
|
|
else { verdict='VERDICT: pack healthy. Balance is tight, IR near rated, capacity holding. Send it.'; }
|
|||
|
|
ro.textContent='Scan readout:\n '+sFlag+'\n '+rFlag+'\n '+cFlag;
|
|||
|
|
vd.innerHTML=verdict;
|
|||
|
|
}
|
|||
|
|
[spread,ir,cap].forEach(function(el){el.addEventListener('input',stringify);});
|
|||
|
|
stringify();
|
|||
|
|
})();
|
|||
|
|
</script>
|
|||
|
|
</body>
|
|||
|
|
</html>
|