Battery Management Systems: State-of-Charge Estimation with Coulomb Counting, OCV and Extended Kalman Filter (2026)

Battery Management Systems: State-of-Charge Estimation with Coulomb Counting, OCV and Extended Kalman Filter (2026)

State of Charge Estimation in a Battery Management System: Coulomb Counting, OCV and the Extended Kalman Filter

A battery has no fuel gauge. There is no float, no pressure dial, no sight glass. What a battery management system (BMS) actually receives is a handful of noisy voltages, one current reading and a few temperatures, and from those it must tell a vehicle, a grid inverter or a forklift controller how much energy is left. Get it wrong by five percentage points and a driver is stranded, a storage site derates for no reason, or a cell is pushed past its voltage window.

This is why state of charge estimation is the hardest everyday algorithm in a battery pack. Charge cannot be measured directly, the obvious method (counting current) drifts without bound, the other obvious method (reading voltage) is nearly useless on flat-curve chemistries, and every parameter you rely on changes with temperature and age.

This article walks through the three layers that production systems stack: coulomb counting, open-circuit-voltage (OCV) anchoring, and a model-based Extended Kalman Filter. You will leave with the drift math, a Thevenin equivalent-circuit model, the EKF equations with a worked step, a runnable Python simulation, and the failure modes that actually bite in the field.

What this covers: BMS functions, SOC definitions, coulomb-counting error growth, OCV curves and the LFP plateau, equivalent-circuit modelling, EKF design, state-of-health coupling, balancing, safety standards context, and cloud-side digital-twin estimation.

Context and Background

A battery management system is the embedded controller that keeps a pack inside its safe operating area and reports its condition. Its jobs fall into five groups: measure (cell voltages, pack current, temperatures), protect (over-voltage, under-voltage, over-current, over- and under-temperature, short circuit, with contactor control), balance (equalise cell charge so the weakest cell does not cap the pack), estimate (state of charge, state of health, power limits) and communicate (CAN, isolated serial or Ethernet to the host system and increasingly to the cloud).

Of these, estimation is the only function that is mathematically underdetermined. Measurement is an instrumentation problem and protection is threshold logic. Estimation asks the controller to infer a hidden internal state from indirect evidence, which is a textbook state-estimation problem. If you want the general theory of how filters keep a model synchronised with a physical asset, our piece on Kalman versus particle filters for digital twin state estimation covers the family tree; this article specialises it to a single cell.

Two definitions matter before any algorithm. State of charge (SOC) is the ratio of remaining extractable charge to the cell’s present full capacity, usually expressed as a fraction between 0 and 1. State of health (SOH) is a relative measure of ageing, most often capacity retention (present capacity divided by rated capacity) and sometimes internal resistance growth. Note the subtlety: SOC is relative to the current capacity, not the nameplate. A cell that has faded to 80% of its rated capacity and is fully charged is at 100% SOC, not 80%. Mixing the two is a classic source of reporting bugs between the BMS and the host.

The foundational reference for model-based SOC work is Gregory Plett’s three-part series on extended Kalman filtering for lithium-ion polymer battery packs, published in the Journal of Power Sources in 2004 (Part 1, background). Most commercial EKF-based estimators descend from that line of work, and the notation used below follows its conventions loosely. For a plain-language primer on the electrochemistry that makes these curves behave the way they do, see how lithium-ion batteries actually work, and for the architecture of the controller itself see our BMS architecture and SOC/SOH digital twin overview.

Why the problem is harder than it looks

Four properties conspire against a simple solution. First, the terminal voltage is not the open-circuit voltage: under load it sags by the ohmic drop plus slower diffusion and charge-transfer effects, which last for minutes after the current stops. Second, the OCV-SOC relationship is chemistry-dependent, and for lithium iron phosphate (LFP) it is almost flat across the middle of the range. Third, capacity and resistance both depend on temperature and age, so a model tuned at 25 degrees Celsius on a new cell is wrong on a cold, five-year-old one. Fourth, the current sensor has offset, gain error and finite resolution, and every error integrates.

The good news is that the three classical techniques fail in complementary ways. Current integration is accurate over seconds and wrong over days; voltage lookup is accurate at rest and wrong under load. A filter that fuses both with a model gets the strengths of each. That fusion is the whole idea.

Reference Architecture: Where State Estimation Sits in the Pack

The estimator is a software block that consumes three sensor streams and produces SOC, SOH and power limits. It runs on the BMS microcontroller at a rate typically between 1 and 100 Hz, depending on the application, and its outputs feed both the host controller and the protection layer.

Battery management system signal flow showing voltage, current and temperature sensing into a state-of-charge estimator and protection logic

Figure 1: Signal flow in a battery management system. Sensing feeds the estimator, which publishes SOC, SOH and power limits; protection and balancing run on a separate, simpler path.

Figure 1 shows an important structural decision: protection logic does not wait for the estimator. Cell over-voltage and over-current trips are implemented on a faster, simpler path (often partly in the analog front end silicon itself), because a filter that has diverged must never be the thing standing between a cell and an unsafe condition. The estimator informs the user and the host; the protector guards the cell.

In short: a BMS estimates SOC by fusing integrated current, a voltage-to-charge lookup and a dynamic cell model, then corrects the result against measured terminal voltage. Production systems layer coulomb counting for short-term accuracy, OCV anchoring at rest to remove drift, and a Kalman filter to blend them under load.

Layer 1: coulomb counting

Coulomb counting is just bookkeeping. If the cell starts at SOC z(0) and current i(t) is positive on discharge, then

z(t) = z(0) − (1 / Q) · ∫ η · i(τ) dτ

where Q is the cell’s full capacity in ampere-seconds and η is the coulombic efficiency (close to 1 for lithium-ion, slightly below 1 on charge). In discrete time with a sample period Δt this becomes z[k+1] = z[k] − η · i[k] · Δt / Q. It costs one multiply-add per sample and is exact in the absence of measurement error. It also needs no model of the cell’s voltage behaviour, which is why every BMS has one.

The weakness is that it is an open-loop integrator. Any constant bias in the current reading becomes a ramp in the SOC estimate, and nothing in the loop ever pulls it back. A concrete illustration: assume a 5 Ah cell and a current sensor with a 50 mA offset (a plausible figure for an uncalibrated shunt chain, used here as an illustrative number, not a datasheet value). Over 24 hours that offset accumulates 1.2 Ah, or 24% of the cell’s capacity. Even at a tenth of that offset, the drift is 2.4 percentage points per day, more than enough to break a claim of “three percent accuracy” if the estimator never gets corrected.

Three further error sources compound the sensor bias. The initial SOC z(0) may simply be wrong after a power cycle or a cell replacement. The capacity Q drifts as the cell ages, so a fixed Q mis-scales every integral. And the sampling itself introduces error: if current changes faster than the sampling interval, rectangular integration misses charge. In practice, high-quality designs use a hardware charge-accumulating converter or sample at several hundred hertz and decimate.

Coulomb counting error sources and the OCV reset anchor that bounds drift

Figure 2: Sources of coulomb counting error (sensor offset, wrong initial SOC, uncorrected capacity fade) and how an OCV-based reset at rest bounds the drift.

Layer 2: OCV anchoring

If the cell has rested long enough, its terminal voltage equals its open-circuit voltage, and the OCV is a monotonic function of SOC for a given chemistry and temperature. A lookup table with interpolation turns the voltage into an SOC value that is independent of the integrated history. This is the anchor that stops drift: every time the pack rests, the estimator can compare its running value against the OCV-derived value and reset or blend.

The catch is “long enough”. After a current step, the diffusion processes inside the cell relax with time constants that range from seconds to many minutes, and for some chemistries and temperatures to hours. A practical BMS therefore uses the OCV reset conditionally: only after a rest period exceeding a calibrated threshold, only when the temperature is inside the validity range of the table, and only when the OCV curve is steep enough to be informative.

That last condition is where chemistry decides everything. NMC and NCA cells show an OCV that changes by several millivolts for each percentage point of SOC across much of the range (the illustrative curve used later in this article averages about 6 mV per point in the mid-range), so a 5 mV measurement error maps to roughly one percentage point of SOC. LFP is different, as the next section explains.

The LFP plateau problem

Lithium iron phosphate cells discharge along a remarkably flat voltage plateau, sitting near 3.2 to 3.3 V per cell across most of the usable SOC window, with steep changes only near the empty and full ends. That flatness is a benefit for power delivery and a curse for estimation: a voltage change of a few millivolts, well inside the noise of a cell-monitor analog-to-digital converter, can correspond to ten or twenty percentage points of SOC. An OCV lookup in the plateau is therefore ill-conditioned. A small voltage error is amplified into a large SOC error, which is the inverse of the slope.

LFP also exhibits pronounced hysteresis: the OCV measured after charging differs from the OCV measured after discharging at the same SOC, by a margin that is large relative to the plateau slope. Plett and later authors model it explicitly with a hysteresis state. The consequence is that LFP packs lean harder on coulomb counting and rely on opportunistic recalibration at the steep ends, typically when the pack reaches a full-charge condition (a voltage threshold plus a tapering current) or approaches empty. If you operate an LFP storage fleet, expect SOC to be “sticky” in the middle of the range and to snap when the pack is topped off.

Deeper Analysis: Modelling the Cell and Building the Filter

Voltage under load is what the estimator actually observes, so we need a model that explains it. The workhorse for real-time use is the first-order Thevenin equivalent circuit: an ideal voltage source representing the OCV, in series with a resistor R0 (the instantaneous ohmic drop) and one parallel resistor-capacitor pair R1 and C1 (the slower polarisation dynamics).

The Thevenin equivalent-circuit model

With discharge current i[k] taken as positive, the terminal voltage is

v[k] = OCV(z[k]) − v1[k] − R0 · i[k]

and the polarisation voltage across the RC pair evolves as

v1[k+1] = a · v1[k] + R1 · (1 − a) · i[k], where a = exp(−Δt / (R1 · C1)).

The time constant R1·C1 sets how long the cell “remembers” a current step. With the illustrative values used in the code below (R1 = 10 mΩ and C1 = 2,000 F, so a 20-second time constant), a current step settles in roughly a minute. Real cells need more than one RC pair to capture slow diffusion; second-order models are common, and fractional-order or physics-based reduced models appear where accuracy justifies the compute.

The parameters R0, R1, C1 and the OCV curve are not constants. They vary with temperature (resistance rises sharply in the cold), with SOC (especially near the ends), with current direction and with age. Production implementations store them as two-dimensional lookup tables indexed by SOC and temperature, often with separate charge and discharge sets. Identifying them is a laboratory exercise: pulse tests such as hybrid pulse power characterisation, or electrochemical impedance spectroscopy, are fitted to extract the values per temperature.

Extended Kalman Filter loop for state of charge estimation: predict with coulomb counting, compare predicted and measured voltage, correct with Kalman gain

Figure 3: The EKF loop. The predict step is coulomb counting plus RC relaxation; the update step compares predicted terminal voltage with the measurement and corrects the state through the Kalman gain.

The Extended Kalman Filter, step by step

A Kalman filter maintains a state estimate and an uncertainty about it, and each cycle it predicts forward using a model and corrects with a measurement, weighting the two by their relative uncertainties. The standard filter assumes linear dynamics. The battery measurement equation is nonlinear because of the OCV curve, so we linearise it around the current estimate: that is the “extended” in EKF.

Choose the state vector x = [z, v1]ᵀ (SOC and polarisation voltage). The process model is

x[k+1] = A · x[k] + B · i[k] + w[k], with A = diag(1, a) and B = [−η·Δt/Q, R1·(1 − a)]ᵀ.

The measurement model is

y[k] = OCV(z[k]) − v1[k] − R0 · i[k] + e[k].

Process noise w has covariance Qw and measurement noise e has variance Re. The filter performs five operations each cycle:

  1. Predict the state: x⁻ = A · x + B · i.
  2. Predict the covariance: P⁻ = A · P · Aᵀ + Qw.
  3. Linearise the measurement: H = [dOCV/dz, −1], evaluated at x⁻.
  4. Compute the gain: S = H · P⁻ · Hᵀ + Re, then K = P⁻ · Hᵀ / S.
  5. Correct: x = x⁻ + K · (v_measured − ŷ), then P = (I − K · H) · P⁻.

Look at step 3. The Jacobian entry dOCV/dz is exactly the slope of the OCV curve, so the filter automatically becomes more trusting of voltage where the curve is steep and less trusting where it is flat. On an LFP plateau, dOCV/dz is tiny, the gain on the SOC component collapses, and the filter behaves like a coulomb counter. At the steep ends, the gain rises and the filter pulls the SOC back to the voltage. That is the formal reason the EKF is the right generalisation of the “count current, but reset at the ends” heuristics.

A worked step with numbers

Take the illustrative cell used in the simulation: Q = 5 Ah, R0 = 15 mΩ, mid-range OCV slope of 0.8 V per unit SOC near SOC 0.5 (the illustrative curve rises from 3.72 V at SOC 0.5 to 3.80 V at 0.6). Suppose the prior estimate is SOC 0.50 with variance 0.0025 (a 5-point standard deviation) and v1 = 0.05 V, the cell is discharging at 5 A, and the measured terminal voltage is 3.60 V.

The predicted voltage is OCV(0.50) − v1 − R0·i = 3.72 − 0.05 − 0.075 = 3.595 V. The innovation is 3.60 − 3.595 = +5 mV. Ignoring cross-covariances for clarity, the SOC variance dominates S: S ≈ 0.8² · 0.0025 + Re = 0.0016 + 0.000036 (for Re = 6 mV squared), about 0.00164. The gain on SOC is K ≈ 0.8 · 0.0025 / 0.00164 ≈ 1.22, so the SOC correction is about 1.22 · 0.005 = 0.0061, roughly 0.6 percentage points upward. A 5 mV surprise nudged the SOC by half a point, and the covariance shrinks accordingly so the next correction is smaller.

Two things deserve emphasis. The correction magnitude scales with the prior uncertainty: after a power cycle, when P is large, the filter converges quickly. And the correction is bounded by the noise parameters you chose, which is why tuning matters more than the algebra.

Choosing the noise covariances

Qw encodes how much you distrust the model; Re encodes how much you distrust the voltage reading. A common starting point is Re set from the cell monitor’s measured noise (a few millivolts) and Qw on the SOC channel set very small, because SOC process noise is really just current-sensor error divided by capacity. Raise Qw and the filter leans on voltage and tracks faster but jitters; lower it and it is smooth but slow to recover from a wrong initial state. A 2024 study on systematic EKF tuning for cells with hysteresis, published in Scientific Reports, reports that covariance choices change accuracy materially and proposes a structured procedure rather than trial and error (Scientific Reports, 2024). Treat tuning as a calibration campaign with data from your own cell, temperature range and load profile.

Walk-through: A Runnable EKF Simulation

The following script is self-contained (NumPy only). It simulates a truth cell with a pulsed discharge, corrupts the measurements with a 50 mA current offset and 3 mV voltage noise, and then runs both a plain coulomb counter and the EKF from a deliberately wrong starting SOC of 0.70 (truth starts at 0.90). All parameters are illustrative, not taken from a datasheet.

import numpy as np
rng = np.random.default_rng(7)

Q_AS = 5.0 * 3600.0                  # 5 Ah in ampere-seconds
R0, R1, C1 = 0.015, 0.010, 2000.0    # ohm, ohm, farad (illustrative)
dt = 1.0
a = np.exp(-dt / (R1 * C1))

soc_pts = np.array([0, .05, .1, .2, .3, .4, .5, .6, .7, .8, .9, 1.0])
ocv_pts = np.array([3.00, 3.25, 3.40, 3.52, 3.60, 3.66,
                    3.72, 3.80, 3.90, 4.00, 4.10, 4.20])
ocv = lambda z: np.interp(z, soc_pts, ocv_pts)
def docv(z, h=1e-3):
    lo, hi = max(z - h, 0.0), min(z + h, 1.0)
    return (ocv(hi) - ocv(lo)) / (hi - lo)

# ---- truth: 1C pulses with rests, lighter load in second half
N = 6000
i_true = np.where((np.arange(N) // 300) % 2 == 0, 5.0, 0.0)
i_true[3000:] *= 0.6
z = np.zeros(N); v1 = np.zeros(N); v = np.zeros(N); z[0] = 0.9
for k in range(N - 1):
    z[k+1]  = z[k] - dt * i_true[k] / Q_AS
    v1[k+1] = a * v1[k] + R1 * (1 - a) * i_true[k]
for k in range(N):
    v[k] = ocv(z[k]) - v1[k] - R0 * i_true[k]

i_meas = i_true + 0.05 + rng.normal(0, 0.02, N)   # 50 mA bias
v_meas = v + rng.normal(0, 0.003, N)              # 3 mV noise

# ---- coulomb counting from wrong initial SOC
z_cc = np.zeros(N); z_cc[0] = 0.70
for k in range(N - 1):
    z_cc[k+1] = z_cc[k] - dt * i_meas[k] / Q_AS

# ---- EKF, state x = [SOC, V1]
x = np.array([0.70, 0.0])
P = np.diag([0.05**2, 0.01**2])
Qw = np.diag([1e-9, 1e-7]); Re = (2 * 0.003) ** 2
A = np.array([[1, 0], [0, a]])
B = np.array([-dt / Q_AS, R1 * (1 - a)])
z_ekf = np.zeros(N)
for k in range(N):
    if k > 0:                                   # predict
        x = A @ x + B * i_meas[k-1]
        P = A @ P @ A.T + Qw
    H = np.array([docv(x[0]), -1.0])            # linearise
    y_hat = ocv(x[0]) - x[1] - R0 * i_meas[k]
    S = H @ P @ H + Re
    K = P @ H / S                               # gain
    x = x + K * (v_meas[k] - y_hat)             # correct
    x[0] = np.clip(x[0], 0, 1)
    P = (np.eye(2) - np.outer(K, H)) @ P
    z_ekf[k] = x[0]

for name, est in (("coulomb", z_cc), ("EKF", z_ekf)):
    e = est - z
    print(name, "RMSE %.4f" % np.sqrt(np.mean(e**2)),
          "final error %.3f" % e[-1])

When I ran this exact script with seed 7, the coulomb counter finished with a final SOC error of about −0.217 (an RMSE near 0.21), and the EKF finished with a final error of about 0.001 (RMSE near 0.0008). Your numbers will vary slightly with the random seed and library version, and, more importantly, these are simulation outputs on a model that matches the filter’s own model by construction. That is the best case. Real cells deviate from any model, which is why the EKF’s real-world accuracy is always worse than the simulation suggests.

Where does the coulomb counter’s 0.217 error come from? Most of it is the 0.20 initial offset, which a counter can never remove. The remaining 0.017 is the 50 mA bias integrated over 6,000 seconds (0.05 A · 6,000 s ÷ 18,000 A·s = 0.0167). In a short run the bias is dwarfed by the wrong start; over a day it would dominate, as the earlier arithmetic showed. In this run the EKF pulled the estimate to within two percentage points of truth on its very first update, because the initial covariance was large and the OCV slope in this model is informative, and it also absorbs the bias because the voltage keeps correcting the integrator.

Edge and cloud split: the BMS runs the EKF in real time while a cloud digital twin refits capacity and resistance and pushes signed parameter updates

Figure 4: Edge and cloud division of labour. The BMS runs state estimation and protection locally; the cloud twin refits slow parameters from fleet telemetry and returns signed updates.

What the simulation does not show

The filter above assumes known R0, R1, C1 and capacity, a known OCV curve, no hysteresis, constant temperature and a Gaussian noise floor. Break any of those and accuracy degrades. A mis-specified capacity Q acts like a gain error on the process model; a wrong R0 biases the predicted voltage by R0 · i, which under high current is directly converted into an SOC error through the inverse OCV slope. For a 15 mΩ cell at 5 A, a 20% error in R0 is a 15 mV bias, equivalent to about 2.5 percentage points of SOC at a 6 mV-per-point slope. That arithmetic is the quickest way to understand why resistance tracking across temperature and age is not optional.

Joint and dual estimation

The standard remedy is to estimate parameters alongside states. In joint estimation the state vector is augmented with slowly varying parameters (capacity, R0), and the same EKF estimates all of them. In dual estimation a second, slower filter estimates parameters while the fast filter estimates states, which keeps each filter small and numerically better conditioned. Dual designs are popular because capacity is nearly unobservable on short horizons: it only becomes observable when a meaningful charge throughput has passed. A common compromise is to update capacity only after a large SOC swing (for example, a deep discharge segment or a full charge) by comparing the integrated charge against the change in the OCV-derived SOC.

Why not just use a neural network?

Learned estimators have attracted a great deal of research attention, and they can outperform a poorly tuned EKF on a fixed dataset. In production they are harder to defend. They need training data covering every temperature, ageing state and load pattern the fleet will see; their failure behaviour outside that envelope is difficult to bound; and a safety case under functional-safety standards wants analysable behaviour. Hybrid designs, with an EKF as the backbone and a learned correction for the model residual, are a more common path than replacement. That is an opinion about current engineering practice rather than a measured result.

State of Health Estimation

SOC answers “how full is it now”. State of health answers “how much has the cell aged”. The two are coupled because SOC is defined against present capacity, so a stale capacity estimate corrupts SOC directly.

Capacity-based and resistance-based SOH

Capacity-based SOH is present capacity divided by the rated (or beginning-of-life) capacity. It is the number a warranty or a second-life buyer cares about. It is estimated from charge throughput over a known SOC interval: if the OCV-derived SOC moved from 20% to 80% while the coulomb counter integrated 3.0 Ah on a nominally 5 Ah cell, then the implied capacity is 3.0 / 0.6 = 5.0 Ah. The same ratio with 2.7 Ah implies 4.5 Ah, or 90% SOH. Because both numerator and denominator carry errors, the estimate is noisy per event and is smoothed across many events, typically with a recursive least squares or a slow filter.

Resistance-based SOH tracks the growth of R0 (and sometimes R1) relative to its beginning-of-life value. Resistance matters for power capability: a pack whose resistance has doubled can deliver less peak power even if its capacity is intact, which is the figure that dominates fast-charge and regenerative braking limits. Resistance is estimated from the voltage response to current steps and is strongly temperature dependent, so it must be normalised to a reference temperature before it is trended.

Why SOH belongs in a fleet context

A single pack rarely sees enough varied conditions to identify its own ageing quickly, and the signals are slow. A fleet of identical packs, by contrast, provides a population: you can compare each unit against the cohort, spot outliers early and fit ageing models with real statistics. This is where the cloud side earns its place, which the digital-twin section below develops. For a deeper look at the failure physics that ageing precedes, see our explainer on lithium-ion battery thermal runaway.

Balancing, Protection and Where Estimation Errors Hurt

Estimation does not live alone. Cell balancing, power limiting and protection all depend on it, directly or through shared sensors.

Passive versus active balancing

Cells in a series string drift apart in charge because of manufacturing spread, temperature gradients and self-discharge differences. The pack’s usable energy is capped by the weakest cell on discharge and by the strongest on charge. Passive balancing bleeds charge from the highest cells through resistors, converting it to heat. It is cheap, simple and sufficient for many packs, but it wastes energy and is slow, with bleed currents typically in the tens to low hundreds of milliamps. Active balancing moves charge between cells or between a cell and the pack using capacitors, inductors or transformers, which saves energy and can be much faster, at the cost of components, complexity and more failure modes.

The link to estimation is that balancing decisions need per-cell SOC, not per-pack SOC. Many designs balance by voltage near the top of charge, where the curve is steep and voltage is a good proxy. LFP packs, again, complicate this, because the plateau hides imbalance until the end of charge, so balancing is concentrated in a short window near full. Balancing time is then a design constraint: if the available window is short and the bleed current small, imbalance accumulates faster than it can be removed.

Power limits and the estimator

The host rarely wants SOC alone; it wants the power the pack can deliver or accept right now without violating voltage, current or temperature limits. A simple, widely used approach computes the maximum discharge current such that the predicted terminal voltage stays above the minimum cell voltage over a prediction horizon, using the same equivalent-circuit model. This is a constrained prediction problem, and organisations that need more look-ahead apply the same ideas as model predictive control with constraints and horizons. The reason to mention it here is that an SOC error leaks into the power limit through the OCV: an optimistic SOC overstates the open-circuit voltage, so the predicted headroom is too large and the pack can be asked for more than it can deliver.

Safety standards context

BMS design sits inside a safety framework. For road vehicles, ISO 26262 is the functional-safety standard; its 2018 second edition spans twelve parts and defines Automotive Safety Integrity Levels from A to D. Reporting indicates that work on a third edition has been registered, but I have not verified its scope or timeline, so treat any claim about it as unconfirmed. For industrial and stationary lithium batteries, IEC 62619 specifies safety requirements for secondary lithium cells and batteries for industrial applications; the current edition is the 2022 Edition 2.0 (IEC webstore listing). Clause-level content, including any functional-safety requirements placed on the battery management function, should be checked against your licensed copy of the standard, and I am not reproducing clause numbers here for that reason. A typical outcome for SOC is that it is classed as an informational output, while over-voltage, under-voltage and over-current protection are the safety-relevant functions with diagnostic coverage requirements. That classification is an engineering judgement made per system in a hazard analysis, not something the standards decide for you.

Cloud-Side Estimation and the Battery Digital Twin

A BMS microcontroller has a few hundred kilobytes of memory and a tight cycle budget. It cannot afford large lookup tables per temperature and age, a full electrochemical model or fleet statistics. A cloud digital twin can, and it has an additional advantage: it sees the whole population.

What belongs at the edge and what belongs in the cloud

Real-time SOC, protection and balancing stay on the BMS, because latency and availability requirements forbid depending on a network link. The cloud twin takes the slow work: refitting capacity and resistance from historical charge and discharge segments, estimating SOH with a model that uses full-resolution history, detecting anomalies such as a cell whose resistance grows faster than its cohort, and scheduling firmware or parameter updates. Figure 4 shows the exchange. Telemetry travels from the pack through a gateway, typically over CAN to the gateway and MQTT or HTTPS upstream, in batches rather than streaming every sample.

What goes upstream matters. Sending SOC alone is nearly useless to the twin; it needs raw or lightly decimated voltage, current and temperature plus the filter’s innovation (the difference between measured and predicted voltage). A persistently biased innovation is the earliest sign that the on-board model no longer matches the cell, and it is a better trigger for re-identification than a calendar timer.

Closing the loop with signed parameter updates

When the twin refits the parameters, it returns updated tables to the pack. This is a firmware-adjacent operation on a safety-relevant controller, so it needs authentication, integrity and rollback. Signed parameter bundles, versioning, bounds checking on the device and a limit on how far one update may move any parameter are baseline controls. For the constrained-device transport and security patterns, see our article on CoAP, OSCORE, EDHOC and DTLS 1.3 for constrained-device security. An unauthenticated parameter channel would let an attacker change the capacity or resistance the filter believes, which turns an estimation error into a safety problem. The same on-device sanity checks that protect against a bad update also protect against a corrupt flash page.

The broader pattern, where a physical asset and a model are kept in sync by a filter and the model is used for monitoring and prediction, is the digital-twin synchronisation problem. A battery is one of its cleanest examples because the state is low-dimensional, the sensors are cheap and the payoff for accuracy is large and measurable.

Trade-offs, Gotchas, and What Goes Wrong

Current sensor offset and gain error. The integrator is only as good as the sensor. Offset drifts with temperature, and a shunt amplifier chain adds its own error. Mitigations: zero-current calibration whenever the contactors are open, redundancy between a shunt and a Hall sensor, and treating long-term disagreement between integrated charge and OCV resets as a diagnostic signal rather than silently correcting it.

Temperature dependence. Resistance rises steeply in the cold, capacity available at a given rate falls, and the OCV curve shifts slightly. A model with fixed parameters is wrong at the edges of the operating range, precisely where drivers notice. Schedule parameters on temperature, and remember the cell’s internal temperature during a high-current pulse can differ from the surface NTC reading.

Hysteresis. Charging and discharging trace different OCV curves, especially in LFP and in silicon-containing anodes. Ignoring it makes the OCV reset biased by direction. Model it with a state, or restrict resets to the steep ends where the effect is small relative to the slope.

Insufficient rest for OCV. Using an OCV reset before the cell has relaxed injects error exactly when the filter trusts it most. Use calibrated rest thresholds that scale with temperature and SOC, and prefer the EKF’s continuous correction over hard resets where possible.

Filter divergence. An EKF can diverge if the linearisation is poor (very steep OCV regions at low SOC), if covariances are badly tuned or if a model parameter is wrong by a lot. Defences: clamp SOC to its physical range, monitor the innovation sequence and its normalised magnitude, and fall back to coulomb counting plus a flag when the innovation stays out of bounds for too long.

Anti-pattern: reporting a single number. A SOC value without an uncertainty invites over-trust. Publishing the filter’s own standard deviation (from the covariance) lets the host decide how much margin to hold. It also makes field data far more useful for debugging.

Anti-pattern: tuning on the simulator. A model-matched simulation always flatters the filter. Validate on real dynamic profiles at several temperatures, including starting from a wrong SOC, and include aged cells.

Trade-off: compute versus accuracy. A two-state EKF is cheap enough for a microcontroller; adding second RC pairs, hysteresis, temperature dynamics and parameter estimation raises cost and tuning burden. Choose the smallest model whose residual error is inside your accuracy budget.

Practical Recommendations

Start from the chemistry. If your cells are NMC or NCA, an OCV-anchored EKF with a first-order Thevenin model is a sound baseline, and most of your effort goes to resistance-versus-temperature tables. If they are LFP, accept that voltage carries little information in the plateau: invest in current sensor quality and calibration, plan deliberate recalibration at full charge, and model hysteresis.

Build the estimator in layers so every layer can be tested and can fail safely. Coulomb counting is the always-available backbone, OCV resets are the bounded-drift anchor, and the EKF is the blend that handles load. Keep protection independent of all three. Publish uncertainty, not just a point value, and log the innovation so the cloud twin can see when the on-board model has gone stale.

Then treat validation as a first-class deliverable. Run dynamic drive or duty-cycle profiles at several temperatures and ageing states, start from deliberately wrong initial SOC values, inject sensor offsets, and measure worst-case rather than average error.

Checklist:

  • Define SOC against present capacity, and document how SOH enters the calculation.
  • Characterise R0, R1, C1 and the OCV curve by temperature, with separate charge and discharge sets where hysteresis matters.
  • Calibrate current-sensor offset at zero current and cross-check shunt against Hall where redundancy exists.
  • Tune Qw and Re from measured noise, then validate on real dynamic data, not the simulator.
  • Monitor normalised innovation and define a fallback mode when it stays out of bounds.
  • Use conditional OCV resets with rest-time and temperature gates.
  • Keep protection logic independent of the estimator.
  • Sign, version and bounds-check every parameter update from the cloud.
  • Record the safety classification of each output in the hazard analysis.

Frequently Asked Questions

What is the difference between SOC and SOH?

State of charge is how much charge remains relative to the cell’s present full capacity, so it moves up and down every cycle. State of health describes ageing and changes slowly over months and years, usually as capacity retention or resistance growth. The two interact: if capacity fades and the estimator keeps the old value, the SOC calculation is scaled wrongly. That is why capacity updates and SOC estimation are designed together rather than as independent features.

Why does coulomb counting drift?

Coulomb counting integrates a measured current, so any constant measurement error accumulates as a ramp. A 50 mA offset on a 5 Ah cell adds up to 1.2 Ah, or 24% of capacity, in a day (an illustrative figure). Drift also comes from a wrong starting SOC and from capacity that has changed with age. Because the integrator has no feedback from the cell’s voltage, nothing corrects the error unless an OCV reset or a filter is added.

Why is SOC hard to estimate for LFP batteries?

LFP cells have an almost flat voltage plateau across most of the SOC range, so a few millivolts of noise can correspond to many percentage points of SOC. They also show pronounced hysteresis between charge and discharge. As a result, voltage-based corrections are weak in the middle of the range and LFP systems rely more on accurate current integration, with recalibration near full or empty where the curve steepens.

Why use an Extended Kalman Filter instead of a plain lookup?

A lookup of OCV only works at rest, but a vehicle or grid battery is almost always loaded. The EKF uses an equivalent-circuit model to predict terminal voltage under load, compares it with the measurement and corrects SOC by a gain that depends on the OCV slope and the estimated uncertainty. It therefore blends current integration and voltage information continuously, and it can report an uncertainty alongside the estimate.

How accurate can SOC estimation be in practice?

It depends on chemistry, sensor quality, model fidelity and operating conditions, so a single number would be misleading. Simulations on matched models reach well under one percentage point, as in the script above, but real packs see temperature swings, ageing and hysteresis that widen errors. Treat quoted accuracy as meaningful only when the test profile, temperature range and ageing state are stated, and prefer worst-case figures to averages.

Should the estimator run in the cloud or on the BMS?

Run real-time estimation and protection on the BMS, because they must work without connectivity and with predictable latency. Use the cloud for slow, data-hungry tasks: refitting capacity and resistance, fleet comparison, SOH trending and anomaly detection. Return refined parameters through an authenticated, signed update channel with bounds checking on the device so a bad update cannot push the filter outside safe limits.

Further Reading

By Riju — about

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *