4、卡尔曼滤波法(KF):线性KF原理、状态方程与观测方程、噪声协方差调参

各位工程师朋友,咱们继续聊SOC估算。前面讲了安时积分、开路电压法,还有各种查表法。说实话,这些方法单独用,都有点「瘸腿」。安时积分漂移,开路电压法又不能实时用。那有没有办法把两者结合起来,取长补短?

有,就是卡尔曼滤波。我当年第一次接触这玩意儿,是在一个BMS预研项目上。当时被一堆矩阵和协方差搞得头大,但真正上手调参之后,才发现这东西是真香。说白了,卡尔曼滤波就是一个「最优估计器」,它能帮你从一堆带噪声的测量值里,把真实状态给「猜」出来。

4.1 线性KF的核心思想

咱们先别急着上公式。卡尔曼滤波的核心逻辑,其实就两步:预测更新

  • 预测:根据上一时刻的状态,猜一下当前时刻的状态。比如,上一秒SOC是50%,电流是10A,那这一秒SOC大概是多少?嗯,用安时积分算一下,大概49.9%。
  • 更新:用当前的测量值(比如电压)来修正这个预测值。如果电压测出来偏低,说明实际SOC可能比预测的还要低一点,那就把预测值往下调一调。

你想想看,这个过程像不像我们平时做决策?先凭经验猜一个,再根据实际情况修正。卡尔曼滤波就是把这个过程数学化了,而且它还能告诉你,你猜得有多准——这就是协方差矩阵的作用。

核心要点:卡尔曼滤波不是「黑盒魔法」,它本质上是一个加权平均的过程。权重由「预测的不确定性」和「测量的不确定性」共同决定。谁更靠谱,就多信谁一点。

4.2 状态方程与观测方程

要把卡尔曼滤波用起来,首先得把电池模型写成数学形式。这里有两个方程,是KF的基石。

4.2.1 状态方程(描述系统怎么变)

状态方程描述的是:系统的内部状态(比如SOC)是如何随时间演变的。对于电池来说,最核心的状态就是SOC。咱们用离散时间形式来写:

x(k) = A * x(k-1) + B * u(k-1) + w(k-1)

这里:

  • x(k) 是k时刻的状态向量。对于SOC估算,通常 x = [SOC; V_p],V_p是极化电压。我习惯把SOC和极化电压放在一起估,效果比单估SOC好很多。
  • A 是状态转移矩阵。它描述了状态如何从上一时刻演变到当前时刻。对于SOC,就是安时积分的关系:SOC(k) = SOC(k-1) - (η*Δt/C_n)*I(k-1)。对于极化电压,就是一阶RC的零输入响应。
  • B 是控制输入矩阵。输入通常是电流 u = I。它描述了电流如何影响状态变化。
  • w(k-1) 是过程噪声。它代表模型的不确定性。比如,安时积分不准、库仑效率有误差,都算在这里。

举个例子,一个简单的一阶RC模型,状态方程长这样:

| SOC(k)   |   | 1   0   |   | SOC(k-1) |   | -η*Δt/C_n |            | w1(k-1) |
|          | = |         | * |          | + |            | * I(k-1) + |         |
| V_p(k)   |   | 0  e^(-Δt/τ) |   | V_p(k-1) |   | R_p*(1-e^(-Δt/τ)) |            | w2(k-1) |

嗯,看着有点复杂,但其实就是把安时积分和RC电路的响应写成了矩阵形式。我在项目中,一般会把Δt取为1秒,这样计算量适中,精度也够。

4.2.2 观测方程(描述测量值怎么来)

观测方程描述的是:我们能测到的量(比如端电压)和内部状态之间是什么关系。

z(k) = H * x(k) + v(k)

这里:

  • z(k) 是k时刻的测量值,通常是端电压 V_t
  • H 是观测矩阵。它把状态映射到测量值。对于电池,端电压 = 开路电压(OCV) - 欧姆压降 - 极化电压。所以 V_t(k) = OCV(SOC(k)) - R_0*I(k) - V_p(k)
  • v(k) 是测量噪声。比如电压传感器的噪声、ADC量化误差等。

注意:OCV和SOC的关系是非线性的!但在线性KF里,我们把它近似为线性关系。怎么近似?通常是在当前SOC点做泰勒展开,取一阶项。这就是后面要讲的扩展卡尔曼滤波(EKF)的雏形。对于线性KF,我们假设OCV-SOC曲线是线性的,或者工作点变化不大。

4.3 线性KF的五步黄金流程

好了,方程有了,接下来就是卡尔曼滤波的迭代过程。一共五步,我称之为「黄金五步曲」:

  1. 预测状态x_pred = A * x_est + B * u
  2. 预测协方差P_pred = A * P_est * A^T + Q
  3. 计算卡尔曼增益K = P_pred * H^T * (H * P_pred * H^T + R)^(-1)
  4. 更新状态估计x_est = x_pred + K * (z - H * x_pred)
  5. 更新协方差估计P_est = (I - K * H) * P_pred

看着公式多,其实逻辑很简单。第1、2步是「预测」,第3、4、5步是「更新」。其中第3步的卡尔曼增益K,就是那个「加权系数」。它决定了你是更相信模型预测,还是更相信传感器测量。

我的调参小技巧:刚开始调KF时,别急着上真实数据。先用仿真数据,把Q和R调到一个合理的范围。我一般先固定R(根据传感器手册),然后调Q。Q越大,说明模型越不可靠,KF会更依赖测量值,响应快但噪声大;Q越小,KF会更平滑,但响应慢。找到一个平衡点,就是好参数。

4.4 噪声协方差调参:Q和R的艺术

说到调参,这是卡尔曼滤波里最「玄学」的部分。Q是过程噪声协方差,R是测量噪声协方差。这两个矩阵,直接决定了滤波器的性能。

4.4.1 Q矩阵(过程噪声协方差)

Q矩阵代表你对模型的信任程度。Q越大,说明你觉得模型误差大,滤波器会更积极地用测量值来修正。Q越小,说明你觉得模型很准,滤波器会更平滑。

对于SOC估算,Q矩阵通常是对角阵:

Q = | q_SOC   0    |
    |   0    q_Vp  |

其中q_SOCq_Vp分别代表SOC和极化电压的过程噪声方差。我一般这样设初值:

  • q_SOC:取一个很小的值,比如1e-6到1e-5。因为SOC变化很慢,模型相对准确。
  • q_Vp:取稍大一点,比如1e-4到1e-3。因为极化电压模型误差较大,尤其是温度变化时。

4.4.2 R矩阵(测量噪声协方差)

R矩阵代表你对传感器的信任程度。R越大,说明你觉得测量噪声大,滤波器会更依赖模型预测。R越小,说明传感器很准,滤波器会更相信测量值。

对于电压测量,R通常是一个标量(因为只有一个测量值):

R = [r_voltage]

这个值可以从传感器数据手册里查。比如,一个12位ADC,参考电压3.3V,量化误差大约是0.8mV。再加上一些电路噪声,我一般取r_voltage = (5mV)^2 = 2.5e-5

避坑指南:我曾经在一个项目中,把Q设得太小,R设得太大。结果滤波器几乎不更新,完全依赖安时积分,SOC越跑越偏。后来我把Q调大了一个数量级,滤波器立刻「活」过来了。记住:Q和R的比值,比它们的绝对值更重要。这个比值决定了滤波器的带宽。

4.5 一个完整的线性KF代码示例

光说不练假把式。下面是一个简单的线性KF实现,用于SOC估算。假设OCV-SOC是线性的(简化情况)。

import numpy as np

class LinearKF_SOC:
    def __init__(self, dt, C_n, eta, R_0, R_p, tau):
        # 初始化状态和协方差
        self.x = np.array([[0.5], [0.0]])  # [SOC; V_p]
        self.P = np.eye(2) * 0.1           # 初始协方差
        
        # 系统参数
        self.dt = dt
        self.C_n = C_n
        self.eta = eta
        self.R_0 = R_0
        self.R_p = R_p
        self.tau = tau
        
        # 状态转移矩阵 A
        self.A = np.array([[1, 0],
                           [0, np.exp(-dt/tau)]])
        
        # 控制输入矩阵 B
        self.B = np.array([[-eta*dt/C_n],
                           [R_p*(1-np.exp(-dt/tau))]])
        
        # 观测矩阵 H (假设OCV-SOC线性: OCV = a*SOC + b)
        self.a = 3.0  # 斜率, 单位V
        self.b = 2.5  # 截距, 单位V
        self.H = np.array([[self.a, -1]])  # V_t = a*SOC - V_p - R_0*I
        
        # 噪声协方差
        self.Q = np.diag([1e-5, 1e-4])  # 过程噪声
        self.R = np.array([[2.5e-5]])    # 测量噪声
        
    def predict(self, I):
        """预测步骤"""
        self.x = self.A @ self.x + self.B * I
        self.P = self.A @ self.P @ self.A.T + self.Q
        
    def update(self, V_t, I):
        """更新步骤"""
        # 计算卡尔曼增益
        S = self.H @ self.P @ self.H.T + self.R
        K = self.P @ self.H.T @ np.linalg.inv(S)
        
        # 更新状态
        z_pred = self.H @ self.x - self.R_0 * I  # 预测电压
        innovation = V_t - z_pred
        self.x = self.x + K * innovation
        
        # 更新协方差
        self.P = (np.eye(2) - K @ self.H) @ self.P
        
        return self.x[0, 0]  # 返回SOC估计值

这段代码虽然简单,但五脏俱全。实际项目中,你还需要处理OCV-SOC的非线性、温度补偿、以及异常值检测。不过,掌握了这个框架,剩下的就是往里面加细节了。

4.6 小结与经验之谈

卡尔曼滤波法,说白了就是一套「预测-修正」的数学框架。它把安时积分的长期稳定性和电压测量的短期准确性结合了起来。我做了这么多年BMS,可以负责任地说:只要模型建得不太离谱,KF的SOC估算精度能轻松做到3%以内

最后分享一个经验:调参时,别追求一步到位。先用仿真数据跑通流程,再上真实数据微调。我曾经花了一周时间调Q和R,最后发现是初始协方差P设得太离谱。嗯,有时候问题不在KF本身,而在你对电池模型的理解上。

下一节,咱们聊聊非线性情况下的扩展卡尔曼滤波(EKF)。那才是真正考验功力的地方。