BlockWerk Documentation
🔋

Battery SOC EKF

Advanced

The Battery SOC EKF block estimates the state of charge of a battery cell from two signals

a battery management system actually has: the current and the terminal voltage. It runs an

extended Kalman filter on a one- or two-RC equivalent circuit, so it corrects the drift of plain

coulomb counting with the voltage, and the voltage's polarisation lag with the model. It

can also learn the offset of the current sensor — the error that makes coulomb counting

drift — and it reports its own uncertainty, so a BMS can show SOC ± a margin.

The open-circuit voltage curve is a table, evaluated either exactly like the LookupTable

block (linear) or as a monotone cubic without slope jumps (smooth). The filter

re-linearises that curve at its current estimate every sample, which is what keeps it

unbiased where a linear Kalman filter linearised at one point is not.

Simulate in BlockWerk

Interactive sandbox directly in your browser — zero installation, WASM-powered.

Simulate in BlockWerk →

Mathematical Model

x=[SOC, V1, V2, b],I=Im−b (true current; Im measured, b sensor offset)SOC˙=−η I3600 Q,V˙j=−VjRjCj+ICj (j=1,2),b˙=0V=OCV(SOC)−V1−V2−R0I,H=[d OCVd SOC, −1, −1, R0]Discretised exactly over Ts with Im held; Qd from the noise densities by Van LoanIterated update: xi+1=x^−+Ki (z−h(xi)−Hi(x^−−xi)),Ki=P−Hi⊤/(HiP−Hi⊤+R)x = [\text{SOC},\ V_1,\ V_2,\ b],\qquad I = I_m - b\ \text{(true current; } I_m \text{ measured, } b \text{ sensor offset)} \dot{\text{SOC}} = -\frac{\eta\, I}{3600\,Q},\qquad \dot{V}_j = -\frac{V_j}{R_j C_j} + \frac{I}{C_j}\ (j = 1, 2),\qquad \dot{b} = 0 V = \text{OCV}(\text{SOC}) - V_1 - V_2 - R_0 I,\qquad H = \left[\tfrac{d\,\text{OCV}}{d\,\text{SOC}},\ -1,\ -1,\ R_0\right] \text{Discretised exactly over } T_s \text{ with } I_m \text{ held; } Q_d \text{ from the noise densities by Van Loan} \text{Iterated update: } x_{i+1} = \hat{x}^- + K_i\,(z - h(x_i) - H_i(\hat{x}^- - x_i)),\quad K_i = P^- H_i^\top / (H_i P^- H_i^\top + R)

Where:

  • I_m is the measured current in A, positive when discharging, and V the terminal voltage in V
  • Q is the capacity in Ah, η the charge efficiency while charging (1 when discharging)
  • The discretisation — noise included — is recomputed from the parameters, so the filter

follows a changed c1 or sampleTime and does not change character when Ts does

  • H is the local OCV slope at the predicted SOC; prediction and correction are those of

the Kalman Filter block with this H

  • A branch with R = 0 is absent, and with pBias = qBias = 0 the offset stays exactly 0:

with the defaults (R2 = 0, no offset) this is the classic two-state EKF

  • One iteration is the classic EKF; more relinearise at the corrected estimate (IEKF)

Inputs & Outputs

Direction ID Label Type Status
→ In in1 I number Required
→ In in2 V number Required
→ In in3 Reset number Optional
→ In in4 Dropout number Optional
← Out soc_hat SOC number Output
← Out soc_sigma σ SOC number Output
← Out v1_hat V₁ number Output
← Out v2_hat V₂ number Output
← Out bias_hat I bias number Output
← Out voc_hat OCV number Output
← Out jacobian dOCV/dSOC number Output
← Out k_gain_0 K_SOC number Output
← Out innovation Innov. number Output
← Out innovation_cov S number Output
← Out nis NIS number Output

Parameters

Parameters Label Type Default Description
sampleTime Sample Time number 1 Filter period in seconds
socInit Initial SOC Estimate number 0.5 Fraction 0…1
v1Init Initial V1 Estimate number 0
pSoc Initial SOC Variance number 0.01 How unsure the initial SOC is; 0.01 is σ = 10 % SOC
pV1 Initial RC Voltage Variance number 0.000001 Per RC branch; small, since a rested cell has V1 ≈ V2 ≈ 0
pBias Initial Bias Variance number 0 How unsure the current sensor's offset is; 0 leaves the offset out of the model, 0.01 is σ = 0.1 A
qSoc Process Noise SOC number 1e-8 Noise density per second, so the filter does not change with the sample time
qV1 Process Noise RC Voltage number 0.000001 Noise density per second, per RC branch
qBias Process Noise Bias number 0 How fast the sensor offset may drift, as a density per second; 0 for a constant offset
rMeas Measurement Noise number 0.0001 Variance of the voltage measurement in V²
r0 R0 (Ω) number 0.05 Ohmic resistance
r1 R1 (Ω) number 0.02 Polarisation resistance of the fast branch
c1 C1 (F) number 1000 Polarisation capacitance of the fast branch
r2 R2 (Ω) number 0 Resistance of a second, slow RC branch; 0 leaves the branch out
c2 C2 (F) number 30000 Capacitance of the second RC branch
capacity Capacity (Ah) number 3.2
chargeEfficiency Charge Efficiency number 1 Coulombic efficiency while charging (current < 0); Li-ion cells are typically 0.99–0.999
ocvSoc OCV Table SOC text 0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1 SOC breakpoints, ascending
ocvVoltage OCV Table Voltage text 3.00 3.50 3.65 3.72 3.78 3.84 3.90 3.97 4.05 4.14 4.20 Open-circuit voltage at each breakpoint in V
ocvInterpolation OCV Interpolation select linear Linear matches a LookupTable exactly; Smooth (monotone cubic) has no slope jumps at the breakpoints, which steadies the filter's gain
iterations Update Iterations number 1 1 is the classic EKF; 3 relinearises at the corrected estimate (iterated EKF), which helps where the OCV curve bends sharply

Usage Examples

Finding the SOC of a Resting Cell

A rested cell at 3.72 V (30 % SOC), read by a noisy voltage sensor (σ = 50 mV) at 10 Hz.

The filter starts at 60 % and stays within 2 % of the true 30 % from 1.5 s on; its

soc_sigma output shrinks from 5.8 % to 0.8 % as the measurements accumulate.

Remarks & Best Practices

  • SOC σ: SOC ± 2σ is a 95 % band if the model is right; NIS checks that — its

time average is 1 for a consistent filter, clearly above 1 when it is overconfident.

  • Valid input: when the voltage reading drops out the filter coulomb-counts only, and

σ grows until readings return.

  • Outside the table the curve is extended along its end slope, so the slope the filter

uses never drops to zero there — a flat curve would make SOC unobservable.

  • A table whose SOC points are not ascending, or whose two rows differ in length, falls

back to the default table.

  • Convergence depends on where the filter starts. From 50 % to a true 15 % with the linear

curve the plain EKF overshoots into the steep tail on its first correction, and the

covariance it spent on that jump keeps later corrections small: about a minute, where

most starting errors take a few seconds. Set iterations to 3 to remove that.

  • Verified against an independent NumPy/SciPy implementation to 1e-9, for both curves and

with the offset state, a dropout, charging, a second RC branch and the iterated update

(scripts/kalman-reference.py), and by a Monte-Carlo NIS consistency test.

  • Not modelled: temperature, hysteresis, capacity fade.
  • Reset (> 0) restarts from socInit, v1Init and the initial variances.

Related Components