How to more accurately calculate a LiFePO4 12.8V battery percentage?

135 Views Asked by At

Grafana Data

static const uint8_t lifepo4[NUM_BATT_READINGS][2] = {
  {144, 100}, {136, 100}, {134,  99}, {133,  90},
  {132,  70}, {131,  40}, {130,  30}, {129,  20},
  {128,  17}, {125,  14}, {120,   9}, {100,   0}
};

int16_t measurements_get_batt_perc(int16_t mv)
{
  int16_t pct = 0;
  int32_t x1, x2, vD, pD;
  int32_t theta;
  if (mv >= lifepo4[0][0]*100)
      pct = lifepo4[0][1]; // cap value at 100%
  else
  {
    for (int i=0; i<NUM_BATT_READINGS-1; i++)
    {
      // put voltages into mV instead of 100s of mV
      x1 = (int32_t)(lifepo4[i][0])*100L;   // Higher value
      x2 = (int32_t)(lifepo4[i+1][0])*100L; // Lower value
      if (mv <= x1 && mv > x2)
      {
        vD = x1-x2;
        pD = (int32_t)(lifepo4[i][1] - lifepo4[i+1][1]);
        if (pD != 0)
          theta = pD*10000/vD;
        else
          theta = 0;
        pct = lifepo4[i][1] - (x1-mv)*theta/10000;
        break;
      }
    }
  }

  return pct;
}

The code is calculating battery percentage based in a LUT from this table: LiFePO4 Voltage Chart https://www.mobile-solarpower.com/diy-lifepo4-solar-battery1.html

Does anyone have an idea or a better approach to help calculate battery percentage? The voltage is changing with respect to load. Is there a known formula for calculating battery percentage with a varying load etc? Or what is the best way to fuse the integral of current with voltage?

The battery percentage follows the chart provided however it does not take into account the current load and therefore is not displaying an accurate estimate.

1

There are 1 best solutions below

4
chux - Reinstate Monica On

Does anyone have an idea or a better approach to help calculate battery percentage?

OP is on the right track.

Some ideas:

  • if (mv <= x1 && mv > x2) only one test needed with a monotonic_function.

  • Lack of in-code documentation left unclear the meaning of the lifepo4.

  • Scaling by 100L may incur 64-bit math. Only 32-bit needed.

  • Consider rounding.

  • Use better names.

  • Let lifepo4[] drive BATT_READING_N rather than the other way around.

// Untested code!

#include <stdint.h>

// lifepo4[][0] decivolts
// lifepo4[][1] percentage
static const uint8_t lifepo4[][2] = { //
    {144, 100}, {136, 100}, {134, 99}, {133, 90},
    {132, 70}, {131, 40}, {130, 30}, {129, 20},
    {128, 17}, {125, 14}, {120, 9}, {100, 0}};

#define DECIVOLTS2MILLIVOLTS(v)  ((v)*100)
#define BATT_READING_N (sizeof lifepo4/sizeof lifepo4[0])
#define BATT_READING_MIN (DECIVOLTS2MILLIVOLTS(lifepo4[BATT_READING_N-1][0]))
#define BATT_READING_MAX (DECIVOLTS2MILLIVOLTS(lifepo4[0][0]))

int16_t measurements_get_batt_perc(int16_t voltage /* mV */) {
  if (voltage >= BATT_READING_MAX) {
    return 100;
  }

  for (unsigned i = 1; i < BATT_READING_N; i++) {
    int voltage_lo = DECIVOLTS2MILLIVOLTS(lifepo4[i][0]);
    if (voltage >= voltage_lo) {
      int voltage_hi = DECIVOLTS2MILLIVOLTS(lifepo4[i - 1][0]);
      int_least32_t percent_hi = lifepo4[i - 1][1];
      int_least32_t percent_lo = lifepo4[i][1];
      // int percent = (percent_hi - percent_lo)/(voltage_hi - voltage_lo)*(voltage - voltage_lo) + percent_lo;
      int_least32_t numerator = (percent_hi - percent_lo) * voltage
          + voltage_hi * percent_lo - voltage_lo * percent_hi;
      int_least32_t denominator = (int_least32_t) (voltage_hi - voltage_lo);
      // To form a rounded result:
      numerator += denominator / 2;
      return (int16_t) (numerator / denominator);
    }
  }
  return 0;
}