- Cell voltage, Gibbs energy and energy content
- Peukert exponent and runtime at high current
- State of charge by Coulomb counting
- OCV-SOC curve of a Li-ion cell
- Impedance spectrum of a Randles circuit
- PEM electrolyzer: voltage breakdown at 2 A/cm2
- Diffusion coefficient from a Cottrell plot
- Electrons transferred in the oxygen reduction (Koutecky-Levich)
- Finite-space Warburg impedance of an insertion electrode
- Incoming inspection and matching of 18650 cells
Cell voltage, Gibbs energy and energy content
A Daniell cell runs at 37 degC with 0.50 M Zn2+ and 0.010 M Cu2+. The script applies the Nernst equation E = E0 - RT/(nF).ln Q, gives dG = -nFE and the energy that 10 g of zinc can deliver in Wh.
#1. Daniell cell at 37 degC: Zn | Zn2+ 0.50 M || Cu2+ 0.010 M | Cu, E0 = 1.100 V, n = 2; -> "[t]" 310.15; -> "[e]" (math calc "1.100 - _R*[t]/(2*_F)*ln(0.50/0.010)"); #2. Gibbs energy and maximum electrical work per mol of Zn; -> "[dg]" (unit calc (concat "-2 * _F * " [e] " V") "kJ/mol"); #3. Wh delivered by 10 g of Zn, M = 65.38 g/mol; -> "[wh]" (unit calc (concat "10 g / (65.38 g/mol) * 2 * _F * " [e] " V") "Wh"); concat "E = " (math round [e] 4) " V, dG = " (math round [dg] 1) " kJ/mol, energy from 10 g Zn = " (math round [wh] 2) " Wh"
E = 1.0477 V, dG = -202.2 kJ/mol, energy from 10 g Zn = 8.59 Wh
Peukert exponent and runtime at high current
A 100 Ah (C20) lead-acid battery was discharged at 5, 10, 20 and 40 A. The script fits Peukert's law t = a.I^-k as a power law, then predicts the runtime and the usable capacity at 30 A.
#1. Lead-acid battery: runtimes measured at constant currents; -> "[i]" "[5, 10, 20, 40]"; -> "[h]" "[20.0, 8.9, 3.95, 1.75]"; #2. Peukert law t = a.I^-k, power fit; json load "rx_ch_pk" (stat fit [i] [h] "power"); -> "[a]" (json select "rx_ch_pk" /a); -> "[k]" (math calc (concat "-1*" (json select "rx_ch_pk" /b))); #3. Runtime and usable capacity at 30 A; -> "[t30]" (math calc "[a]*30^(-[k])"); concat "Peukert k = " (math round [k] 3) ", runtime at 30 A = " (math round [t30] 2) " h, usable capacity " (math round (math calc "30*[t30]") 1) " Ah instead of 100 Ah, r2 = " (math round (json select "rx_ch_pk" /r2) 5)
Peukert k = 1.172, runtime at 30 A = 2.45 h, usable capacity 73.6 Ah instead of 100 Ah, r2 = 1
State of charge by Coulomb counting
A 3.0 Ah Li-ion cell starting at 95 % SOC is discharged with a current logged every 15 minutes. The script integrates the current (zero-order hold) to get the charge removed, deduces the SOC and reads the expected open circuit voltage from the OCV-SOC table with a monotone interpolation.
#1. Discharge of a 3.0 Ah Li-ion cell, current logged every 15 min, in A; -> "[t]" "[0, 0.25, 0.5, 0.75, 1.0, 1.25, 1.5]"; -> "[i]" "[1.5, 1.5, 2.8, 2.8, 0.6, 0.6, 1.2]"; #2. Coulomb counting: charge removed = integral of i dt, then SOC; -> "[q]" (num interpolate_integral [t] [i] 0 1.5 "previous"); -> "[soc]" (math calc "100*(0.95 - [q]/3.0)"); #3. Open circuit voltage from the OCV - SOC table, monotone interpolation; -> "[ocv]" (num interpolate "[0, 10, 20, 40, 60, 80, 100]" "[3.00, 3.45, 3.55, 3.65, 3.78, 3.95, 4.18]" [soc] "pchip"); concat "Charge removed " (math round [q] 3) " Ah, SOC " (math round [soc] 1) " %, expected OCV " (math round [ocv] 3) " V"
Charge removed 2.45 Ah, SOC 13.3 %, expected OCV 3.495 V
- Invert the OCV table to correct the SOC from a rest voltage.
- Compute the energy with the logged voltage and
num interpolate_integralon V.i.
OCV-SOC curve of a Li-ion cell
The open circuit voltage measured at rest against the state of charge is modelled by a degree-5 polynomial, as in a battery management system. The script evaluates the OCV and its slope at 55 % SOC and draws the model curve.
#1. Open circuit voltage of a Li-ion cell measured at rest, SOC 0 to 1; -> "[pts]" "[[0, 3.00], [0.1, 3.45], [0.2, 3.58], [0.3, 3.64], [0.4, 3.68], [0.5, 3.72], [0.6, 3.78], [0.7, 3.86], [0.8, 3.95], [0.9, 4.05], [1.0, 4.18]]"; #2. Polynomial OCV model of degree 5; -> "[c]" (pa polynomial_curve_fit_get_coeff [pts] 5); #3. OCV at 55 % SOC and slope dOCV/dSOC there, for SOC estimation; -> "[v55]" (pa polynomial_curve_fit_eval [c] 0.55); -> "[s55]" (num poly_eval (num poly_derive [c]) 0.55); #4. Model curve every 2 % SOC; -> "[curve]" (pa polynomial_curve_fit_eval_incr [c] 0 1 0.02); plot xy (matrix col [curve] 1) (matrix col [curve] 2)
The chart is shown in the Plot tab of the editor.
- Invert the model with
num solveto read the SOC from a measured voltage. - Compare with a monotone interpolation:
plot interpolatewith mode pchip.
Impedance spectrum of a Randles circuit
Compute the impedance of a Randles equivalent circuit (Rs, Rct parallel to Cdl, Warburg diffusion) from 100 kHz to 10 mHz: Nyquist coordinates, Bode phase, the top of the semicircle at 1/(2.pi.Rct.Cdl), and the exchange current density from Rct.
#1. Randles circuit: Rs 10 Ohm, Rct 100 Ohm, Cdl 20 uF, Warburg coefficient 50 Ohm.s^-1/2, electrode 1 cm2; -> "[Rs]" 10; -> "[Rct]" 100; -> "[Cdl]" 20e-6; -> "[sig]" 50; -> "[fpk]" (math calc "1/(2*pi*[Rct]*[Cdl])"); -> "[out]" ""; json load "rx_tu1_f" (concat "[100000, 1000, " [fpk] ", 1, 0.01]"); for (-> "[i]" 0) (< [i] 5) (++ "[i]") { -> "[w]" (math calc (concat "2*pi*" (json select "rx_tu1_f" (concat "/[" [i] "]")))); #2. Warburg Zw = sigma.1-j over sqrt w, Faradaic branch Rct + Zw in parallel with Cdl; -> "[Zw]" (complex div (concat [sig] "-" [sig] "i") (math sqrt [w])); -> "[Yc]" (complex mul (concat [Cdl] "i") [w]); -> "[Z]" (complex add [Rs] (complex div 1 (complex add [Yc] (complex div 1 (complex add [Rct] [Zw]))))); #3. Nyquist coordinates and Bode phase; -> "[p]" (complex to_polar [Z]); -> "[out]" (concat [out] "f = " (math round_sig (json select "rx_tu1_f" (concat "/[" [i] "]")) 4) " Hz: Z' = " (math round (complex re [Z]) 1) ", -Z'' = " (math round (- 0 (complex im [Z])) 1) ", phase " (math round (math rad_to_deg (json load select "rx_tu1_p" [p] "/theta")) 1) " deg; "); }; #4. Exchange current density from Rct, n = 1, 25 degC; concat [out] "i0 = RT/nFRct = " (math round (math calc (concat (const get "R") "*298.15/(" (const get "F") "*[Rct])*1000")) 3) " mA/cm2"
f = 100000 Hz: Z' = 10, -Z'' = 0.1, phase -0.5 deg; f = 1000 Hz: Z' = 10.6, -Z'' = 7.9, phase -36.6 deg; f = 79.58 Hz: Z' = 58.9, -Z'' = 51.1, phase -40.9 deg; f = 1.0 Hz: Z' = 129.3, -Z'' = 21.7, phase -9.5 deg; f = 0.01 Hz: Z' = 309.3, -Z'' = 199.5, phase -32.8 deg; i0 = RT/nFRct = 0.257 mA/cm2
- Replace Cdl by a constant phase element Q(jw)^n with
complex pow - Fit Rct and Cdl to measured data with
stat fit_modelon the real and imaginary parts
PEM electrolyzer: voltage breakdown at 2 A/cm2
Split the cell voltage of a PEM electrolyzer at 80 degC into reversible voltage, OER and HER activation overpotentials (Butler-Volmer inverted with asinh) and ohmic loss, then give the HHV efficiency and the specific energy per kg of hydrogen.
#1. PEM electrolyzer at 80 degC and 2 A/cm2: exchange current densities in A/cm2, ohmic ASR in ohm.cm2; -> "[t]" 353.15; -> "[i]" 2.0; -> "[i0a]" 0.00001; -> "[i0c]" 0.3; -> "[asr]" 0.12; -> "[aa]" 1.0; -> "[ac]" 0.5; #2. Reversible voltage at T, then overpotentials from i = 2.i0.sinh of alpha.F.eta/RT, inverted by asinh; -> "[erev]" (math calc "1.229-0.0009*([t]-298.15)"); -> "[eta_a]" (math calc (concat "_R*[t]/([aa]*_F)*" (math asinh (math calc "[i]/(2*[i0a])")))); -> "[eta_c]" (math calc (concat "_R*[t]/([ac]*_F)*" (math asinh (math calc "[i]/(2*[i0c])")))); #3. Error of the pure Tafel approximation on the cathode; -> "[tafel_c]" (math calc "_R*[t]/([ac]*_F)*ln([i]/[i0c])"); #4. Cell voltage and efficiency on the higher heating value 1.481 V; -> "[u]" (math calc "[erev]+[eta_a]+[eta_c]+[asr]*[i]"); concat "Erev = " (math round [erev] 3) " V, eta OER = " (math round [eta_a] 3) " V, eta HER = " (math round [eta_c] 3) " V, Tafel error HER = " (math round (math calc "1000*([tafel_c]-[eta_c])") 2) " mV, Ucell = " (math round [u] 3) " V, HHV efficiency = " (math round (math calc "100*1.481/[u]") 1) " %, energy = " (math round (math calc "[u]*2*_F/0.002016/3600000") 1) " kWh/kg H2"
Erev = 1.18 V, eta OER = 0.371 V, eta HER = 0.117 V, Tafel error HER = -1.33 mV, Ucell = 1.908 V, HHV efficiency = 77.6 %, energy = 50.7 kWh/kg H2
- Sweep the current with
num tableto draw the polarization curve. - Add the mass transport loss with a limiting current term.
Diffusion coefficient from a Cottrell plot
After a potential step on a Pt disk in ferrocyanide, regress the current against t^-1/2 through the origin (Cottrell) and deduce the diffusion coefficient.
#1. Chronoamperometry of 5 mM ferrocyanide on a 3 mm Pt disk, current in uA, times 0.5 to 5 s; -> "[i]" "[72.97, 51.77, 42.11, 36.44, 32.43, 29.76, 27.88, 25.93, 24.6, 23.16]"; -> "[x]" (matrix transpose (concat "[" (matrix col (num table "1/sqrt(t)" "t" 0.5 5 0.5) 2) "]")); #2. Cottrell plot i vs t^-1/2 forced through the origin; pa rm load_from_json "rx_p4c_cot" [x] [i]; pa rm set_no_intercept "rx_p4c_cot" true; -> "[k]" (matrix get (concat "[" (pa rm estimate_regression_parameters "rx_p4c_cot") "]") 1 1); -> "[r2]" (pa rm calculate_r_squared "rx_p4c_cot"); #3. Cottrell: k = n.F.A.C.sqrt of D/pi, A in cm2, C in mol/cm3; -> "[a]" (math calc "pi*0.15^2"); -> "[d]" (math calc "pi*([k]*1e-6/(1*_F*[a]*5e-6))^2"); pa rm close "rx_p4c_cot"; concat "Cottrell slope = " (math round [k] 2) " uA.s^1/2, R2 = " (math round [r2] 5) ", A = " (math round [a] 4) " cm2, D = " (math round (math calc "[d]*1e6") 2) "e-6 cm2/s"
Cottrell slope = 51.66 uA.s^1/2, R2 = 0.99998, A = 0.0707 cm2, D = 7.21e-6 cm2/s
- Keep the intercept with
pa rmto detect a capacitive or background current. - Compare with D from the Randles-Sevcik slope of a CV scan-rate study.
Electrons transferred in the oxygen reduction (Koutecky-Levich)
From rotating disk currents at several rotation rates, fit the Koutecky-Levich law to get the kinetic current and the Levich constant B, then the number of electrons per O2 (4 = direct path, 2 = peroxide).
#1. Oxygen reduction on a Pt/C rotating disk in O2-saturated 0.1 M KOH, j in mA/cm2 at 0.6 V vs RHE; -> "[rpm]" "[400, 625, 900, 1225, 1600, 2025, 2500]"; -> "[j]" "[2.067, 2.425, 2.718, 3.038, 3.288, 3.519, 3.687]"; #2. Koutecky-Levich: 1/j = 1/jk + 1/B.w^1/2, w in rad/s; json load "rx_p4c_kl" (stat fit_model "1/(1/jk + 1/(B*sqrt(2*pi*x/60)))" "x" [rpm] [j] "{\"jk\": 5, \"B\": 0.5}"); -> "[jk]" (json select "rx_p4c_kl" /parameters/jk); -> "[b]" (json select "rx_p4c_kl" /parameters/B); #3. Levich constant for one electron: 0.62.F.D^2/3.nu^-1/6.C, D and nu in cm2/s, C in mol/cm3; -> "[d23]" (math power (math cbrt 0.000019) 2); -> "[nu16]" (math nroot 0.01 -6); -> "[b1]" (math calc "0.62*_F*[d23]*[nu16]*1.2e-6*1000"); #4. Number of electrons exchanged per O2; concat "jk = " (math round [jk] 2) " mA/cm2, B = " (math round [b] 4) " mA.cm-2.s^1/2, n = " (math round (math calc "[b]/[b1]") 2) " electrons per O2"
jk = 7.88 mA/cm2, B = 0.4329 mA.cm-2.s^1/2, n = 3.93 electrons per O2
- Repeat at several potentials: jk vs E gives the Tafel slope of the ORR.
- Confirm the peroxide yield with a ring-disk electrode collection efficiency.
Finite-space Warburg impedance of an insertion electrode
Compute the reflective finite Warburg impedance R_D.coth(s)/s of a graphite particle at 0.1 Hz and 1 mHz, check the low-frequency limits (R_D/3 and capacitive behaviour) and get the chemical diffusion coefficient of lithium.
#1. Graphite particle in a Li-ion anode: diffusion resistance R_D in ohm and time constant tau = L^2/D in s; -> "[rd]" 8.5; -> "[tau]" 40; #2. Reflective finite Warburg: Z = R_D.coth of s / s, with s = sqrt of j.w.tau, at 0.1 Hz; -> "[s]" (complex sqrt (concat (math calc "2*pi*0.1*[tau]") "i")); -> "[z]" (complex div (complex mul [rd] (complex cosh [s])) (complex mul [s] (complex sinh [s]))); #3. Same at 1 mHz: Re Z tends to R_D/3 and the line becomes a capacitance C = tau/R_D; -> "[s2]" (complex sqrt (concat (math calc "2*pi*0.001*[tau]") "i")); -> "[z2]" (complex div (complex mul [rd] (complex cosh [s2])) (complex mul [s2] (complex sinh [s2]))); #4. Chemical diffusion coefficient with a diffusion length of 2 um; concat "Z at 0.1 Hz = " (math round (complex abs [z]) 3) " ohm, phase " (math round (math calc (concat (complex arg [z]) "*180/pi")) 1) " deg; at 1 mHz Re Z = " (math round (complex re [z2]) 3) " ohm (R_D/3 = " (math round (math calc "[rd]/3") 3) "), phase " (math round (math calc (concat (complex arg [z2]) "*180/pi")) 1) " deg; C_lim = " (math round (math calc "[tau]/[rd]") 2) " F; D = " (math calc "(2e-4)^2/[tau]") " cm2/s"
Z at 0.1 Hz = 1.697 ohm, phase -45.1 deg; at 1 mHz Re Z = 2.832 ohm (R_D/3 = 2.833), phase -85.2 deg; C_lim = 4.71 F; D = 1.0E-9 cm2/s
- Use
complex tanhfor the transmissive Warburg (blocking vs. absorbing boundary). - Add Rct and a CPE in series to build a full Randles-Warburg spectrum.
Incoming inspection and matching of 18650 cells
Test whether a lot of cells meets the nominal capacity with the supplier sigma (z-test), find the modal internal resistance class used for matching, and compute the resistance of a 4P group.
#1. Incoming inspection of 16 cells 18650: capacity in Ah and AC internal resistance in mohm; -> "[cap]" "[2.981, 2.995, 2.972, 2.988, 3.004, 2.979, 2.968, 2.990, 2.985, 2.976, 2.999, 2.983, 2.970, 2.992, 2.987, 2.980]"; -> "[res]" "[22, 23, 22, 21, 22, 24, 23, 22, 22, 21, 23, 22, 25, 22, 23, 22]"; #2. Is the lot below the 3.000 Ah nominal? z-test with the supplier sigma of 0.015 Ah; json load "rx_p4c_zt" (stat ztest [cap] 3.000 0.015); #3. Resistance classes: the modal class is used to build the 4P groups; -> "[mode]" (matrix get (concat "[" (stat mode [res]) "]") 1 1); -> "[share]" (json load select "rx_p4c_fr" (stat frequency [res]) (concat "/[" (math calc "[mode]-21") "]/frequency")); #4. Resistance of a 4P group: cells in parallel, harmonic mean / 4; -> "[rp]" (math calc (concat (stat harm_mean "[22, 22, 22, 21]") "/4")); concat "mean capacity = " (math round (stat mean [cap]) 4) " Ah, z = " (math round (json select "rx_p4c_zt" /z) 2) ", p = " (math round (json select "rx_p4c_zt" /p_value) 6) "; modal resistance = " [mode] " mohm for " (math round (math calc (concat "100*" [share])) 1) " % of the cells; 4P group = " (math round [rp] 3) " mohm"
mean capacity = 2.9843 Ah, z = -4.18, p = 2.9E-5; modal resistance = 22 mohm for 50 % of the cells; 4P group = 5.435 mohm
- Reject the outliers with
stat zscorebefore building the pack. - Use
stat kruskalto compare the resistance of three lots.
