쿨롱 카운팅은 시간 지나면 오차가 누적된다. 전류 센서 오차, 샘플링 오차.

칼만 필터로 OCV 측정값이랑 융합하면 정확도가 올라간다.


칼만 필터 기초

예측 → 보정 반복.

예측: 이전 상태 + 입력으로 다음 상태 예측
보정: 측정값으로 예측 보정

SOC 추정에 적용:

  • 예측: 쿨롱 카운팅 (전류 적분)
  • 보정: OCV 측정

단순화된 구현

EKF(Extended Kalman Filter)가 정석이지만 복잡하다.

단순화 버전:

typedef struct {
    float soc;          // 상태 (SOC)
    float P;            // 오차 공분산
    float Q;            // 프로세스 노이즈
    float R;            // 측정 노이즈
} KalmanFilter_t;

KalmanFilter_t kf = {
    .soc = 50.0f,
    .P = 1.0f,
    .Q = 0.001f,   // 쿨롱 카운팅 신뢰도
    .R = 10.0f     // OCV 측정 신뢰도
};

예측 단계

쿨롱 카운팅으로 예측:

void KF_Predict(float current_a, float dt) {
    // 상태 예측: SOC -= I * dt / Capacity
    float dsoc = current_a * dt / (g_bms.capacity_ah * 3600) * 100;
    kf.soc -= dsoc;
    
    // 오차 공분산 증가
    kf.P += kf.Q;
    
    // 범위 제한
    if (kf.soc > 100) kf.soc = 100;
    if (kf.soc < 0) kf.soc = 0;
}

보정 단계

OCV 측정값으로 보정:

void KF_Update(float measured_soc) {
    // 칼만 게인
    float K = kf.P / (kf.P + kf.R);
    
    // 상태 보정
    kf.soc += K * (measured_soc - kf.soc);
    
    // 오차 공분산 감소
    kf.P *= (1 - K);
}

전체 흐름

void SOC_KalmanUpdate(void) {
    float current_a = g_bms.pack_current_ma / 1000.0f;
    float dt = 0.1f;  // 100ms
    
    // 예측 (항상)
    KF_Predict(current_a, dt);
    
    // 보정 (휴지 시에만)
    if (fabsf(current_a) < 0.5f && g_bms.rest_time > 60) {
        float soc_ocv = OCV_LookupSOC(g_bms.avg_cell_mv);
        KF_Update(soc_ocv);
    }
    
    g_bms.soc = (uint8_t)kf.soc;
}

파라미터 튜닝

Q와 R 비율이 중요하다.

Q 크면: 쿨롱 카운팅 안 믿음, OCV에 빨리 수렴
R 크면: OCV 안 믿음, 쿨롱 카운팅 위주

LFP처럼 OCV가 평평하면 R을 크게:

// NMC
kf.Q = 0.001f;
kf.R = 5.0f;

// LFP  
kf.Q = 0.0001f;  
kf.R = 50.0f;    // OCV 신뢰도 낮춤

테스트 결과

칼만 필터 결과

단순 쿨롱 카운팅: 오차 5~10%
칼만 필터 적용: 오차 2~3%

특히 장시간 사용 후 오차 누적이 줄어든다.


정리

  • 예측: 쿨롱 카운팅
  • 보정: OCV 측정 (휴지 시)
  • Q/R 비율로 각 측정 신뢰도 조절
  • LFP는 OCV 신뢰도 낮게

구현은 단순한데 효과는 좋다.


다음은 프리차지 회로.

#23 - 프리차지