Richard's Blog

Richard's Blog


Anything stupid

Tags


SoC/OCV Calculation

C source for calculating the state of charge based on the measured open circuit voltage of a liion cell (DLG INR18650-320). Applicable for other cells too!

SoC/OCV Calculation Source is finished and working. It is already in use in a on-going project. I mark is as incomplete, as it needs a current based normalization.

Based on the DLG INR18650-320 cell data I wrote myself a small script which I use for SoC calculation.
Feel free to use this snippet for implementing into your own application.

Currently no current/voltage normalization is happening, so the system will give you somewhat lower SoCs based on your current draw. This should not be an issue as you will switch off earlier and won't deep discharge the battery.
/* Copyright © 2024 Richard Zink <richard@familie-zink.org>
 * This work is free. You can redistribute it and/or modify it under the
 * terms of the Do What The Fuck You Want To Public License, Version 2,
 * as published by Sam Hocevar:
 *
 *          DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
 *                Version 2, December 2004
 *
 * Copyright (C) 2004 Sam Hocevar
 * 14 rue de Plaisance, 75014 Paris, France
 * Everyone is permitted to copy and distribute verbatim or modified
 * copies of this license document, and changing it is allowed as long
 * as the name is changed.
 *
 *       DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
 * TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 *
 * 0. You just DO WHAT THE FUCK YOU WANT TO.
 */

#define AND   &&
#define OR    ||
#define NOT   !
#define bAND  &
#define bOR   |
#define bSHL  <<
#define bSHR  >>

#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>

/* Cell data
 ***************
 * As the complete cell data is dependent on temperature,
 * we are storing the data in look up tables.
 * Steps are noted in the commments.
 */

const uint16_t SOC_OCV_DLG_3200mAh[21][8] = {      /* Step: 100 µV e.g: 27499 = 2.7499V*/
 /*-30C   -20C   -10C     0C    10C    20C    30C    40C */
 {27496, 27499, 27505, 27499, 27499, 27499, 27499, 27499}, /*   0% */
 {29961, 32402, 33253, 33099, 33478, 33232, 33247, 33211}, /*   5% */
 {31155, 33463, 34050, 33872, 33979, 33840, 33857, 33866}, /*  10% */
 {31860, 33986, 34634, 34557, 34631, 34364, 34367, 34358}, /*  15% */
 {32405, 34391, 35045, 35054, 35173, 35013, 35013, 34989}, /*  20% */
 {32891, 34722, 35336, 35392, 35505, 35451, 35451, 35437}, /*  25% */
 {33321, 35038, 35605, 35656, 35760, 35742, 35780, 35801}, /*  30% */
 {33697, 35336, 35872, 35922, 36014, 35991, 36041, 36065}, /*  35% */
 {34068, 35662, 36151, 36177, 36272, 36245, 36290, 36322}, /*  40% */
 {34418, 36106, 36506, 36500, 36586, 36554, 36598, 36625}, /*  45% */
 {34717, 36561, 36945, 36909, 36995, 36936, 36986, 37025}, /*  50% */
 {35102, 37022, 37398, 37395, 37414, 37460, 37517, 37552}, /*  55% */
 {35306, 37472, 37860, 37964, 38127, 38118, 38189, 38222}, /*  60% */
 {35549, 37863, 38311, 38453, 38589, 38595, 38648, 38672}, /*  65% */
 {35728, 38219, 38743, 38888, 38998, 39001, 39040, 39066}, /*  70% */
 {35866, 38678, 39232, 39336, 39442, 39425, 39478, 39499}, /*  75% */
 {36056, 39149, 39774, 39869, 39967, 39958, 40005, 40035}, /*  80% */
 {36675, 39487, 40195, 40352, 40447, 40456, 40497, 40515}, /*  85% */
 {37324, 39768, 40423, 40583, 40654, 40696, 40731, 40752}, /*  90% */
 {39125, 39955, 40595, 40788, 40871, 40906, 40942, 40965}, /*  95% */
 {40171, 41448, 41437, 41564, 41638, 41632, 41697, 41721}  /* 100% */
 };

const uint32_t Ri_DLG_3200mAh[8]=            /* Step: 1 µOhm */
/*  -30C    -20C    -10C      0C    10C    20C    30C    40C */
{1140569, 827424, 223999, 107429, 70863, 50282, 43432, 43424};

const uint32_t Capacity_DLG_3200mAh[8]=                 /* Step: 1 µAh */
/* -30C     -20C     -10C       0C      10C      20C      30C      40C */
{707373, 2619204, 2740442, 2892967, 2900489, 2973395, 2988637, 2996308};

///////////////////////////////////////////////////////////////////////////////
uint16_t SOC_Calculation (  uint16_t U_min, 
                            uint16_t U_max, 
                            uint16_t T_min, 
                            uint16_t T_max, 
                            uint8_t Bat_Type)
{
    uint16_t OCV[21][8];
    uint16_t U_tmp[21];
    uint16_t SoC=0;
    uint8_t col1, col2;
    uint16_t T_avg, U_avg;

    if (0 == Bat_Type)
    {
        memcpy(OCV, SOC_OCV_DLG_3200mAh, sizeof OCV);
    }
    else if(1 == Bat_Type)
    {
//        memcpy(OCV, SOC_OCV_other_cell, sizeof OCV);
    }
    else
    {
        SoC = 0xFFFE;
        return SoC;
    }

    T_avg = (T_min + T_max)/2;
    U_avg = (U_min + U_max)/2;

    col1 = (T_avg-24315)/1000;
    col2 = col1 + 1;

    //prepare temporary dataset for given temperature
    for (uint8_t i = 0; i<21; i++)
    {
        U_tmp[i] = OCV[i][col1] 
                   + ((OCV[i][col2] - OCV[i][col1]) 
                   * ((T_avg-(col1*1000+24315)))/1000.0);
    }

    for (uint8_t i = 0; i<21; i++)
    {
        if ((U_avg > U_tmp[i]) AND (U_avg <= U_tmp[i+1]))
        {
            SoC = i * 500 
                  + (500 * (U_avg*100 - U_tmp[i]*100) 
                  / (U_tmp[i+1] - U_tmp[i]) / 100.0);
            break;
        }
        else if (U_avg > U_tmp[20])
        {
            SoC = 10000;
            break;
        }
        else if (U_avg < U_tmp[0])
        {
            SoC = 0;
            break;
        }
        else
        {
            SoC = 0xFFFF;
        }
    }
    return SoC;
}

///////////////////////////////////////////////////////////////////////////////
uint32_t Ri_Calculation (   uint16_t T_min, 
                            uint16_t T_max,
                            uint8_t Bat_Type)
{
    uint32_t Ri_Tab[8];
    uint32_t Ri=0;
    uint8_t col1, col2;
    uint16_t T_avg;

    if (0 == Bat_Type)
    {
        memcpy(Ri_Tab, Ri_DLG_3200mAh, sizeof Ri_Tab);
    }
    else if(1 == Bat_Type)
    {
//        memcpy(Ri_Tab, Ri_other_cell, sizeof Ri_Tab);        
    }
    else
    {
        Ri = 0xFFFFFFFE;
        return Ri;
    }
    
    T_avg = (T_min + T_max)/2;
    col1 = (T_avg-24315)/1000;
    col2 = col1 + 1;
    
    if ((T_avg > 24315) AND (T_avg <= 31315))
        {
            Ri = Ri_Tab[col2] 
                 + ((Ri_Tab[col1]-Ri_Tab[col2]) 
                 * ((T_avg-(col1*1000+24315))*0.001));
        }
    else
        {
            Ri = 0xFFFFFFFF;
        }
    return Ri;
}

///////////////////////////////////////////////////////////////////////////////
uint32_t Capacity_Calculation (   uint16_t T_min, 
                                  uint16_t T_max,
                                  uint8_t Bat_Type)
{
    uint32_t Capacity_Tab[8];
    uint16_t U_tmp[21];
    uint32_t Capacity=0;
    uint8_t col1, col2;
    uint16_t T_avg, U_avg;

    if (0 == Bat_Type)
    {
        memcpy(Capacity_Tab, Capacity_DLG_3200mAh, sizeof Capacity_Tab);
    }
    else if(1 == Bat_Type)
    {
//        memcpy(Capacity_Tab, Capacity__other_cell, sizeof Capacity_Tab);
    }
    else
    {
        Capacity = 0xFFFFFFFE;
        return Capacity;
    }
    
    T_avg = (T_min + T_max)/2;
    col1 = (T_avg-24315)/1000;
    col2 = col1 + 1;
    
    if ((T_avg > 24315) AND (T_avg <= 31315))
        {
            Capacity = Capacity_Tab[col1] 
                       + ((Capacity_Tab[col2]-Capacity_Tab[col1]) 
                       * ((T_avg-(col1*1000+24315))*0.001));
        }
    else
        {
            Capacity = 0xFFFFFFFF;
        }    
    return Capacity;
}

///////////////////////////////////////////////////////////////////////////////
int main (void)
{
    uint16_t Umin = 37865;
    uint16_t Umax = 37913;
    uint16_t Tmin = 25415; 
    uint16_t Tmax = 25915;
    uint16_t Bat_Type = 0;
    printf("SoC %9.2f\n", 
            (SOC_Calculation(Umin, Umax, Tmin, Tmax, Bat_Type)*0.01));
            
    printf("Ri mOhm %9.2f\n", 
            (Ri_Calculation(Tmin, Tmax, Bat_Type)*0.001));
            
    printf("Capacity mAh %9.2f\n", 
            (Capacity_Calculation(Tmin, Tmax, Bat_Type)*0.001));
            
    printf("Remaining capacity mAh %9.2f\n", 
            (Capacity_Calculation(Tmin, Tmax, Bat_Type)*0.001)
            *(SOC_Calculation(Umin, Umax, Tmin, Tmax, Bat_Type)*0.0001));
}
View Comments