SOC는 “지금 몇 % 남았나"고, SOH는 “원래 대비 얼마나 건강한가"다.

새 배터리 100%, 쓰다 보면 80%, 70%로 떨어진다.


SOH란

State of Health. 건강 상태.

SOH = 현재 최대 용량 / 정격 용량 × 100

새 배터리 10Ah → 3년 후 8Ah면 SOH 80%.


측정 방법

1. 용량 기반

완충→완방 용량 측정:

void SOH_UpdateCapacity(void) {
    static float discharge_ah = 0;
    static bool measuring = false;
    
    // 완충 상태에서 시작
    if (g_bms.soc >= 99 && !measuring) {
        measuring = true;
        discharge_ah = 0;
    }
    
    // 방전량 적산
    if (measuring && g_bms.pack_current_ma < 0) {
        float dt = 0.1f;  // 100ms
        discharge_ah += fabsf(g_bms.pack_current_ma / 1000.0f) * dt / 3600.0f;
    }
    
    // 완방 도달
    if (measuring && g_bms.soc <= 1) {
        measuring = false;
        
        g_bms.measured_capacity_ah = discharge_ah;
        g_bms.soh_capacity = (discharge_ah / g_bms.rated_capacity_ah) * 100;
    }
}

문제: 완충→완방 사이클이 필요. 일상 사용에서 잘 안 일어남.

2. 내부 저항 기반

배터리 노화하면 내부 저항 증가.

void SOH_UpdateResistance(void) {
    // 부하 변화 시 저항 측정
    // R = ΔV / ΔI
    
    static int16_t prev_current = 0;
    static uint16_t prev_voltage = 0;
    
    int16_t di = g_bms.pack_current_ma - prev_current;
    int16_t dv = g_bms.pack_voltage_mv - prev_voltage;
    
    prev_current = g_bms.pack_current_ma;
    prev_voltage = g_bms.pack_voltage_mv;
    
    // 전류 변화가 충분히 클 때만
    if (abs(di) > 5000) {  // 5A 이상 변화
        float r_mohm = (float)abs(dv) / (abs(di) / 1000.0f);  // mΩ
        
        // 이동 평균
        g_bms.internal_resistance = g_bms.internal_resistance * 0.9f + r_mohm * 0.1f;
        
        // 저항 기반 SOH (신품 대비)
        g_bms.soh_resistance = (g_bms.new_resistance / g_bms.internal_resistance) * 100;
        if (g_bms.soh_resistance > 100) g_bms.soh_resistance = 100;
    }
}

복합 SOH

용량과 저항 둘 다 고려:

void SOH_Update(void) {
    // 둘 다 있으면 가중 평균
    if (g_bms.soh_capacity > 0 && g_bms.soh_resistance > 0) {
        g_bms.soh = g_bms.soh_capacity * 0.7f + g_bms.soh_resistance * 0.3f;
    }
    // 하나만 있으면 그거 사용
    else if (g_bms.soh_capacity > 0) {
        g_bms.soh = g_bms.soh_capacity;
    }
    else if (g_bms.soh_resistance > 0) {
        g_bms.soh = g_bms.soh_resistance;
    }
}

사이클 카운트

충방전 사이클 수도 노화 지표:

void SOH_CountCycle(void) {
    static uint8_t prev_soc = 0;
    static float partial_cycle = 0;
    
    int delta = prev_soc - g_bms.soc;
    prev_soc = g_bms.soc;
    
    if (delta > 0) {  // 방전
        partial_cycle += delta / 100.0f;
    }
    
    // 1 사이클 누적되면 카운트
    if (partial_cycle >= 1.0f) {
        partial_cycle -= 1.0f;
        g_bms.cycle_count++;
        
        // Flash에 저장
        Flash_WriteU32(FLASH_CYCLE_ADDR, g_bms.cycle_count);
    }
}

SOH 표시

사이클:        500회
측정 용량:     9.2Ah / 10Ah (92%)
내부 저항:     25mΩ / 20mΩ (80%)
종합 SOH:      88%

정리

  • 용량 SOH: 완충→완방 용량 측정
  • 저항 SOH: 부하 변화 시 저항 측정
  • 사이클 수: 누적 방전량
  • 복합 SOH: 가중 평균

SOH 80% 이하면 배터리 교체 권장.


다음은 칼만 필터로 SOC 정확도 높이기.

#22 - 칼만 필터