The EKF is not a better coulomb counter — it is a different class of estimator that fuses model predictions with measurements optimally under Gaussian noise assumptions. When the assumptions break, so does the filter.
- The three SOC estimation architectures (coulomb counting, OCV correction, EKF) are complementary, not competing — a production BMS fuses all three, with each handling the failure modes of the others.
- EKF performance depends critically on battery model quality and Q/R tuning; a well-structured EKF with poor parameterisation or miscalibrated matrices will underperform a simple CC+OCV architecture.
- LFP–s flat OCV plateau makes the Kalman gain collapse in mid-SOC (H ≈ 0), requiring dual OCV tables for hysteresis and modified filter tuning that effectively treats mid-plateau as pure coulomb counting.
- Q and R matrices must be characterised at operating temperatures, not just 25–C; in Indian fleet conditions (45–C operation), both matrices shift enough to meaningfully degrade estimation accuracy if not retuned.
Every production BMS uses some combination of three SOC estimation techniques: coulomb counting for dynamic tracking, OCV correction for static anchoring, and model-based filtering (typically Extended Kalman Filter) for optimal fusion of both with measurement noise handling. The question facing BMS engineers is not which method to choose — it is how to combine them correctly and how to tune the combination for specific operating conditions. In Indian commercial EVs, the most common failure mode is not choosing the wrong method but implementing the right method with wrong parameters or wrong model assumptions.
Architecture 1: Coulomb Counting Only
The simplest architecture:
Requires: one current sensor, one initial SOC estimate, one capacity value.
When it works: Fresh cell, accurate current sensor, frequent OCV anchoring, stable temperature.
When it fails: Extended operation without rest (taxi/delivery fleet), temperature variations without compensation, aging cells with untracked capacity fade.
The characteristic failure mode: progressive monotonic drift, either always positive or always negative depending on sensor bias direction. Easy to diagnose (SOC trends systematically high or low over time) but hard to correct without breaking the user experience (sudden step correction).
| Method | Pros | Cons | When to Use |
|---|---|---|---|
| Coulomb counting only | Simple, low compute, low memory | Drifts without anchoring, cannot recover from wrong initial SOC | Simple, low-cost BMS with frequent full charges |
| OCV lookup only | Accurate at rest, self-correcting | Requires rest state, very poor for LFP mid-SOC | Static SOH monitoring, not dynamic SOC |
| CC + periodic OCV correction | Good balance of accuracy and complexity | OCV correction quality depends on rest duration and LFP plateau problem | Most residential EV BMS |
| EKF with 1RC equivalent circuit model | Dynamic correction, handles transients, noise-aware | Requires model parameterisation, Jacobian computation | Commercial EV BMS, fleet applications |
| EKF with 2RC model | Better accuracy for complex dynamics | Higher compute, more parameters to tune | High-accuracy applications, ARAI certification-grade BMS |
LFP cells have different OCV–SOC relationships depending on whether the current SOC was approached from charging (higher OCV) or discharging (lower OCV). This hysteresis band is typically 5–15 mV. A BMS using a single averaged OCV table will have systematic errors of 3–8% depending on recent current direction — always reading low after a charge and always reading high after a discharge. The correct design uses two separate OCV tables (charge-approach and discharge-approach) and tracks recent current direction to select the appropriate table for OCV corrections.
Architecture 2: OCV Correction as Anchor
OCV correction works by comparing the battery's resting terminal voltage to a pre-characterised OCV-SOC lookup table. After sufficient rest (30 min to 4 hours depending on chemistry and previous current), the terminal voltage equals the open circuit voltage, and SOC can be read directly.
The rest detection problem: How does the BMS know the battery has rested sufficiently? Common criteria: |dV/dt| < 1 mV/minute for 5 consecutive samples. This works for NMC (large OCV slope). For LFP (flat plateau), dV/dt may fall below this threshold long before equilibrium is reached — leading to premature OCV reads with 5–10% error.
The hysteresis problem: LFP (and some NMC) cells have different OCV-SOC curves depending on whether the SOC was approached from above (recent discharge) or below (recent charge). This hysteresis band is 5–15 mV for LFP. A BMS that uses a single OCV table without hysteresis compensation will have systematically different errors after charging vs after discharging.
For LFP BMS design, the correct architecture uses two OCV tables: one for charge-approached SOC states and one for discharge-approached SOC states. The BMS must track the recent current direction to select the correct table. Implementations that use a single averaged OCV table for LFP will have 3–8% systematic error depending on usage pattern.
Architecture 3: Extended Kalman Filter
The EKF treats SOC estimation as a state estimation problem. The battery is modelled as a dynamic system:
where is coulombic efficiency (~0.99 for Li-ion), and is process noise.
where is series resistance, is the voltage across the first RC element, and is measurement noise.
The EKF prediction step computes an expected terminal voltage from the model. The correction step computes the Kalman gain:
where is error covariance, is the measurement Jacobian, and is measurement noise covariance. The state estimate update:
The measurement Jacobian H = ∂OCV/∂SOC is the slope of the OCV-SOC curve. For NMC, this slope is significant and well-defined across most of the SOC range — the Kalman gain is high and the filter corrects aggressively. For LFP in the plateau, H ≈ 0, the Kalman gain collapses to near zero, and the EKF behaves like pure coulomb counting — it cannot correct itself from voltage measurements alone in the flat region.
An EKF tuned for NMC and deployed on an LFP pack without retunning will exhibit extremely slow convergence in the 20–90% SOC range. The flat H (OCV slope) means the Kalman gain is near zero, so the filter essentially ignores voltage measurements and integrates current alone. This is not the EKF 'not working' — it is the EKF correctly recognising that voltage measurements contain no SOC information in the flat region. The solution is to use a dual-SOC approach: OCV anchoring at rest + EKF with modified Q/R tuning that prioritises current integration in mid-SOC LFP operation.
import numpy as np
from dataclasses import dataclass
@dataclass
class BatteryModel:
"""First-order RC equivalent circuit."""
R0_ohm: float = 0.005 # series resistance
R1_ohm: float = 0.003 # diffusion resistance
C1_F: float = 3000.0 # diffusion capacitance
Q_Ah: float = 60.0 # nominal capacity
def ocv_lookup(soc: float, ocv_table: list) -> float:
"""Linear interpolation from (soc, ocv_V) pairs."""
for i in range(len(ocv_table) - 1):
s0, v0 = ocv_table[i]
s1, v1 = ocv_table[i + 1]
if s0 <= soc <= s1:
return v0 + (v1 - v0) * (soc - s0) / (s1 - s0)
return ocv_table[-1][1]
def ekf_jacobian_H(soc: float, ocv_table: list) -> float:
"""Numerical Jacobian dOCV/dSOC at current SOC point."""
delta = 0.001
v_plus = ocv_lookup(min(1.0, soc + delta), ocv_table)
v_minus = ocv_lookup(max(0.0, soc - delta), ocv_table)
return (v_plus - v_minus) / (2 * delta)
def ekf_step(x, P, I_A, V_meas, dt_s, model: BatteryModel, ocv_table, Q_n, R_n):
"""
Single EKF predict + update.
State vector x = [SOC, V_RC] (V_RC = voltage across R1-C1)
"""
soc, v_rc = x
# --- Predict ---
soc_new = soc - (I_A * dt_s) / (model.Q_Ah * 3600.0)
alpha = np.exp(-dt_s / (model.R1_ohm * model.C1_F))
v_rc_new = alpha * v_rc + (1 - alpha) * model.R1_ohm * I_A
F = np.array([[1, 0], [0, alpha]]) # State transition Jacobian
x_pred = np.array([soc_new, v_rc_new])
P_pred = F @ P @ F.T + Q_n
# --- Update ---
V_pred = (ocv_lookup(soc_new, ocv_table)
- model.R0_ohm * I_A
- v_rc_new)
H = np.array([[ekf_jacobian_H(soc_new, ocv_table), -1.0]])
S = H @ P_pred @ H.T + R_n
K = P_pred @ H.T / S[0, 0] # Kalman gain (scalar measurement)
innov = V_meas - V_pred
x_upd = x_pred + K.flatten() * innov
P_upd = (np.eye(2) - np.outer(K.flatten(), H)) @ P_pred
return np.clip(x_upd, [0, -0.5], [1, 0.5]), P_updThe Kalman gain K = P–H^T / (H–P–H^T + R), where H = dOCV/dSOC is the slope of the OCV curve. In the LFP plateau (15–90% SOC), this slope is approximately zero — a 75% SOC range corresponds to less than 50 mV of OCV change. With H ≈ 0, the gain K ≈ 0 regardless of sensor quality or algorithm sophistication. The EKF effectively ignores voltage measurements and runs as pure coulomb counting in this region. This is not an EKF limitation — it is correct Bayesian behaviour: when measurements carry no information about the state, they should receive zero weight.
The 2RC Model: When One Time Constant Is Not Enough
The simplest equivalent circuit model uses one RC element to capture diffusion dynamics. This works well for slow dynamics (C/10 discharge) but misses the fast surface dynamics that dominate at 1C+ rates.
A 2RC model adds a second RC pair with a shorter time constant (τ₁ ≈ 1–10 seconds for surface diffusion; τ₂ ≈ 60–600 seconds for bulk diffusion). This captures the fast initial voltage drop and the slower relaxation separately, improving voltage prediction accuracy during aggressive charge/discharge.
Parameters required for a 2RC model at one temperature:
- OCV-SOC table: 50–100 points
- R₀(SOC): series resistance vs SOC
- R₁(SOC), C₁(SOC): fast RC pair
- R₂(SOC), C₂(SOC): slow RC pair
- η: coulombic efficiency (nearly 1 for Li-ion)
Parameters required across temperature range (say, -10°C to 55°C, 7 points):
- All of the above × 7 = 700+ parameter values
- All requiring HPPC (Hybrid Pulse Power Characterisation) test data from cell manufacturer or own measurements
This is not a trivial characterisation exercise. Many Indian BMS developers use a simplified model — single temperature, single SOC set-point — rather than a full parameterised table. The resulting EKF is accurate only near the characterisation conditions and degrades substantially outside them.
# SOC error budget -- root-sum-square of all error sources
import numpy as np
error_sources = {
"CC current sensor bias (0.3%)": 0.30, # % SOC
"CC current sensor noise (1-sigma)": 0.15,
"OCV model mismatch (NMC plateau)": 1.20,
"Temperature correction error": 0.50,
"Capacity fade (80% SoH assumed)": 2.50,
"Initialisation error": 0.80,
}
rss = np.sqrt(sum(v**2 for v in error_sources.values()))
print(f"Total SOC error (RSS): +/-{rss:.2f}%")
for src, val in error_sources.items():
print(f" {src:<45} {val:.2f}%")Tuning Q and R for Indian Conditions
The Q and R matrices are the primary tuning levers for EKF performance. Poorly tuned Q and R produce either sluggish correction (R too high) or noise amplification (Q too high).
Practical Q tuning approach:
- Measure current sensor noise power spectral density at 25°C and 45°C
- Compute expected SOC variance from sensor noise over one timestep: Q_SOC ≈ (σ_I × Δt / Q_rated)²
- Add a small model uncertainty term: Q_total = Q_SOC + Q_model (tuning parameter, typically 1e-7 to 1e-5)
Practical R tuning approach:
- Measure voltage sensor noise at 25°C and 45°C: σ_V typically 0.5–2 mV for BMS-grade ADCs
- R = σ_V² in V² (typically 1e-7 to 1e-6 V²)
A common mistake in Indian BMS development is tuning Q and R once at 25°C in the lab and using these values across the full operating range. At 45°C, current sensor noise is higher (thermal noise), voltage sensor ADC noise may change, and the battery's electrochemical dynamics change (faster kinetics, higher OCV slope in some regions). A production-quality BMS should use temperature-indexed Q and R tables, or at minimum characterise the sensitivity of estimation accuracy to Q/R mismatch across the temperature range.
/* SOC fusion state machine -- selects estimation mode based on conditions */
typedef enum {
SOC_MODE_COULOMB_ONLY = 0,
SOC_MODE_OCV_ANCHOR = 1,
SOC_MODE_EKF_ACTIVE = 2,
} SocEstimMode_t;
static SocEstimMode_t soc_mode = SOC_MODE_COULOMB_ONLY;
void soc_fusion_update(float I_A, float V_term, float T_degC, bool at_rest) {
switch (soc_mode) {
case SOC_MODE_COULOMB_ONLY:
cc_integrate(I_A, T_degC);
if (at_rest && dv_dt_stable())
soc_mode = SOC_MODE_OCV_ANCHOR;
else if (ekf_model_valid())
soc_mode = SOC_MODE_EKF_ACTIVE;
break;
case SOC_MODE_OCV_ANCHOR:
soc_apply_ocv_correction(V_term, T_degC);
soc_mode = SOC_MODE_COULOMB_ONLY; /* one-shot then back to CC */
break;
case SOC_MODE_EKF_ACTIVE:
ekf_predict(I_A, T_degC);
ekf_update(V_term, T_degC);
if (!ekf_model_valid())
soc_mode = SOC_MODE_COULOMB_ONLY;
break;
}
}Architecture Decision: What to Use When
Passenger EV (frequent full charges, known drive patterns) → CC + OCV works. Commercial fleet (irregular charging, partial cycles) → EKF with 2RC model required.
Obtain full OCV-SOC tables at -10°C, 10°C, 25°C, 40°C, 55°C. Characterise hysteresis for LFP. Run HPPC at each temperature for RC parameter extraction.
1RC model for 1C or lower discharge applications. 2RC model for fast charge/discharge or high-accuracy requirements. Add thermal model coupling if cell temperature gradients exceed 5°C.
Do not tune at 25°C only. At minimum, characterise at 25°C and 45°C and interpolate. Validate convergence speed from ±20% SOC error injection.
Add direction-tracking logic and dual OCV tables. Validate SOC accuracy across at least 20 consecutive partial cycles without full charge.
Mount cells in pack thermal environment, run 72-hour drive cycle with thermal control at 45°C ambient, measure SOC error at hourly OCV checkpoints. Pass criterion: ±3% for passenger EV, ±5% for commercial.
Key Takeaways
- The three SOC estimation architectures — coulomb counting, OCV correction, and EKF — are complementary methods that should be fused; pure CC drifts, pure OCV is only valid at rest, and EKF combines both optimally with noise handling.
- The EKF's performance depends critically on battery model quality and Q/R tuning — a well-structured EKF with poorly parameterised model or miscalibrated matrices will perform worse than a simple CC+OCV architecture.
- LFP's flat OCV plateau collapses the Kalman gain in mid-SOC, making EKF equivalent to pure CC in that range; correct LFP BMS design requires dual OCV tables for hysteresis and modified filter tuning.
- Q and R matrices must be characterised at operating temperatures, not just 25–C — in Indian fleet conditions at 45–C, both matrices shift enough to meaningfully degrade estimation accuracy if not retuned.
- The 2RC equivalent circuit model adds significant accuracy for 1C+ charge/discharge rates but requires 700+ parameter values across temperature and SOC; Indian BMS developers who skip full characterisation limit accuracy outside calibration conditions.
Part of the bms Series
Frequently Asked Questions
What is the fundamental limitation of pure OCV-based SOC estimation for LFP batteries?
Why does the EKF need a battery equivalent circuit model?
What are the Q and R matrices in the EKF and how do you tune them for Indian conditions?
What is the difference between EKF and UKF for SOC estimation?
Can machine learning replace the Kalman filter for SOC estimation?
References
- Plett, G.L. (2004) — Extended Kalman filtering for battery management systems of LiPB-based HEV battery packs, Journal of Power Sources
- He, H. et al. (2011) — State of charge estimation for Li-ion batteries using neural network modeling and unscented Kalman filter-based error cancellation, International Journal of Electrical Power & Energy Systems
- Waag, W. et al. (2014) — Critical review of the methods for monitoring of lithium-ion batteries in electric and hybrid vehicles, Journal of Power Sources
- Hu, X. et al. (2012) — A comparative study of equivalent circuit models for Li-ion batteries, Journal of Power Sources