“배터리 몇 % 남았어요?”
간단한 질문 같은데 답하기가 어렵다.
SOC란
State of Charge. 충전 상태. 0%~100%.
SOC = 현재 잔량 / 최대 용량 × 100
문제는 “현재 잔량"을 직접 측정할 방법이 없다는 거다.
방법 1: OCV 기반
Open Circuit Voltage. 무부하 전압으로 SOC 추정.
배터리는 SOC에 따라 전압이 다르다. 테이블 만들어놓고 역으로 찾으면 된다.

// OCV → SOC 룩업 테이블
const uint16_t ocv_table[101] = {
// SOC 0% ~ 100%
2500, 2850, 2920, 2960, 2990, ... // mV
};
uint8_t OCV_LookupSOC(uint16_t voltage_mv) {
for (int soc = 100; soc >= 0; soc--) {
if (voltage_mv >= ocv_table[soc]) {
return soc;
}
}
return 0;
}
문제: 전류 흐르면 전압이 달라진다. 무부하 상태에서만 정확.
방법 2: 쿨롱 카운팅
전류 적분해서 용량 계산.
Q = ∫ I dt
SOC = SOC_init - Q / Capacity
전류 센서로 전류 측정하고, 시간으로 적분:
void SOC_CoulombCounting(void) {
static uint32_t last_time = 0;
uint32_t now = HAL_GetTick();
float dt = (now - last_time) / 1000.0f; // 초
last_time = now;
float current_a = g_bms.pack_current_ma / 1000.0f;
float dq_ah = current_a * dt / 3600.0f; // Ah
g_bms.soc_coulomb -= dq_ah / g_bms.capacity_ah * 100.0f;
// 범위 제한
if (g_bms.soc_coulomb > 100) g_bms.soc_coulomb = 100;
if (g_bms.soc_coulomb < 0) g_bms.soc_coulomb = 0;
}
문제: 오차가 누적된다. 전류 센서 오차, 샘플링 오차가 쌓임.
방법 3: 하이브리드
둘을 섞는다.
- 평소: 쿨롱 카운팅
- 휴지 시: OCV로 보정
void SOC_UpdateHybrid(void) {
// 쿨롱 카운팅
SOC_CoulombCounting();
// 휴지 상태면 OCV 보정
if (fabsf(g_bms.pack_current_ma) < 500) { // 0.5A 미만
g_bms.rest_time++;
if (g_bms.rest_time > 60) { // 1분 이상 휴지
uint8_t soc_ocv = OCV_LookupSOC(g_bms.avg_cell_mv);
// 가중 평균
g_bms.soc = g_bms.soc_coulomb * 0.8f + soc_ocv * 0.2f;
}
} else {
g_bms.rest_time = 0;
}
}
LFP 문제
LFP는 OCV 커브가 평평하다.

20%~80% 구간에서 전압이 거의 같다. OCV로 SOC 추정이 안 된다.
3.28V → SOC 30%? 50%? 70%?
LFP는 쿨롱 카운팅에 더 의존해야 한다.
완충/완방 리셋
누적 오차 해결책: 끝점에서 리셋.
void SOC_CheckEndpoints(void) {
// 완충 감지
if (g_bms.max_cell_mv > 3600 &&
fabsf(g_bms.pack_current_ma) < 200) {
g_bms.soc = 100;
g_bms.soc_coulomb = 100;
}
// 완방 감지
if (g_bms.min_cell_mv < 2600) {
g_bms.soc = 0;
g_bms.soc_coulomb = 0;
}
}
완충/완방 될 때마다 오차 리셋.
정리
- OCV: 무부하에서 정확, 부하 시 부정확
- 쿨롱 카운팅: 연속 추정, 오차 누적
- 하이브리드: 둘 조합
- LFP: OCV 안 먹힘, 쿨롱 카운팅 의존
- 끝점 리셋으로 오차 보정
다음은 데이터 로깅.