If you have ever bench-tested a battery pack or evaluated an electric vehicle prototype, you have probably run into this problem: the dashboard displays 45% State of Charge (SoC), but three minutes of hard acceleration later, the battery management system trips an undervoltage alarm and the system shuts down.

The root cause almost always traces back to naive current integration, commonly called Coulomb counting. While current integration is simple to compute, deploying it on physical battery cells exposes major electrochemical flaws.

Below is an analysis of why current integration fails in hardware, how a second-order (2RC) Thevenin equivalent circuit fixes the model, and how to write and tune an Extended Kalman Filter (EKF) in MATLAB and Simulink.


Why Coulomb counting fails in real battery packs

Coulomb counting calculates remaining capacity by integrating current over time:

SoC(t) = SoC(0) - (1 / Qn) ∫ [η · I(τ)] dτ

Where Qn is nominal capacity in Ampere-seconds, I(τ) is load current (positive during discharge), and η is Coulombic efficiency.

In an ideal simulation without noise, this integration works. On an actual vehicle circuit board, three issues break it:

  • Current sensor bias drift: Low-cost shunt or Hall-effect sensors always carry a slight DC offset. A bias of just 25 mA over four hours of driving accumulates into a 100 mAh estimation error. Over repeated discharge cycles without a full-charge reset, this open-loop integrator drifts 10% to 25% away from true capacity.
  • The LiFePO4 flat voltage plateau: Lithium iron phosphate cells have an open-circuit voltage curve that stays almost completely flat between 20% and 80% SoC. A tiny 1 mV measurement error inside that zone results in a 10% to 15% error in estimated capacity. Taking static open-circuit voltage snapshots during brief pauses cannot reliably reset the counter.
  • Capacity fade and temperature dependency: True capacity (Qn) is not constant. It shrinks at lower ambient temperatures and degrades as the cell ages. Because standard Coulomb counting treats Qn as a static number in memory, the math drifts further as the pack cycles.

Open-loop integration cannot correct itself. To solve this, you need a closed-loop observer that models internal electrochemical dynamics and balances voltage readings against current integration. That is what an Extended Kalman Filter does.


The 2RC Thevenin equivalent circuit model

Before applying a Kalman filter, you must model cell impedance. A simple internal resistance ignores dynamic polarization, while an electrochemical PDE model is too computationally heavy for an automotive microcontroller. A 2RC Thevenin circuit hits the sweet spot:

  • Voc(SoC): The open-circuit voltage, which changes non-linearly with battery charge.
  • R0: Ohmic resistance across the electrolyte, current collectors, and terminals. This explains immediate voltage drops during sudden acceleration or regenerative braking.
  • R1 || C1: Fast polarization pair modeling charge-transfer resistance and double-layer capacitance. Time constant: milliseconds to seconds.
  • R2 || C2: Slow polarization pair modeling diffusion impedance inside the active materials. Time constant: tens of seconds to minutes.

The terminal voltage (Vt) measured at the cell terminals is expressed as:

Vt = Voc(SoC) - I · R0 - V1 - V2

Where V1 and V2 represent the transient voltages across the two RC branches.


State-space representation for EKF

The state vector defines the internal conditions of the cell:

x = [ SoC ; V1 ; V2 ]

Converted to discrete time with sampling interval Ts, the linear state transitions are:

SoC(k+1) = SoC(k) - (η · Ts / Qn) · I(k) + w1(k)
V1(k+1)  = exp(-Ts / (R1C1)) · V1(k) + R1(1 - exp(-Ts / (R1C1))) · I(k) + w2(k)
V2(k+1)  = exp(-Ts / (R2C2)) · V2(k) + R2(1 - exp(-Ts / (R2C2))) · I(k) + w3(k)

The non-linearity exists in the output equation because open-circuit voltage Voc(SoC) is a non-linear curve:

y(k) = Vt(k) = Voc(SoC(k)) - I(k) · R0 - V1(k) - V2(k) + v(k)

Here, w(k) ~ N(0, Q) is process noise (sensor inaccuracies, parameter shifts) and v(k) ~ N(0, R) is measurement noise from the ADC voltage input.

Because Voc is non-linear, the filter linearizes the output matrix at every execution step using the partial derivative of open-circuit voltage with respect to SoC:

Ck = [ dVoc / dSoC |x̂k- , -1 , -1 ]


Step-by-step EKF recursion cycle

At every sample interval Ts, the filter executes two sequential phases:

Phase 1: Time update (Prediction)

  1. Predict states using the system model and measured current:
    x̂k- = A · x̂k-1 + B · Ik-1
  2. Predict error covariance:
    Pk- = A · Pk-1 · AT + Q

Phase 2: Measurement update (Correction)

  1. Compute the Jacobian row vector Ck from the local slope of the OCV curve.
  2. Calculate the Kalman Gain:
    Kk = Pk- CkT [ Ck Pk- CkT + R ]-1
  3. Correct the state estimate using the voltage residual (measured terminal voltage minus predicted terminal voltage):
    x̂k = x̂k- + Kk · [ Vt,k - V̂t,k ]
  4. Update error covariance for the next cycle:
    Pk = (I - Kk Ck) · Pk-

If your current sensor has a DC offset, the state prediction starts to drift. But the moment the predicted voltage diverges from the physical ADC voltage measurement, the innovation residual forces the state estimate back on track.


Tuning the Q and R covariance matrices

Tuning the noise covariance matrices determines how effectively the filter balances measurement confidence against model confidence:

  • If R is set too small: The filter over-trusts the voltage readings. Any transient voltage drop caused by sudden current pulses will cause erratic oscillations in your estimated SoC.
  • If R is set too large: The filter ignores voltage corrections and behaves like basic Coulomb counting, drifting with sensor errors.
  • Practical starting values: Set R = σv2, where σv is the standard deviation of your ADC voltage noise under load. Set Q(1,1) small (around 1e-7) to reflect slow capacity depletion, and set Q(2,2) and Q(3,3) slightly higher (around 1e-5) to give the polarization states flexibility to adapt.

Final engineering summary

Coulomb counting is an open-loop integrator, not an estimation algorithm. Any persistent sensor bias or thermal variation will accumulate error indefinitely. For high-reliability battery packs, combining a 2RC equivalent circuit model with an Extended Kalman Filter provides bounded estimation error, automated correction from poor starting values, and resilience against sensor noise.