1

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.

Clifford
  • 88,407
  • 13
  • 85
  • 165
Jdelmore
  • 11
  • 3
  • 2
    Welcome to SO. I would assume your question is not about programming but about the electrical background. Maybe the site next door [Electrocal Engineering](https://electronics.stackexchange.com/) is more useful for that purpose. – Gerhardh Jun 01 '23 at 16:35
  • @Jdelmore , A few comments describing the units in `static const uint8_t lifepo4[NUM_BATT_READINGS][2]` would add clarity to code. It appears `lifepo4[][0]` are _decivolts_ and `lifepo4[][1]` is percentage. – chux - Reinstate Monica Jun 01 '23 at 18:40
  • Your battery has a capacity (mWh) and load implies energy (mW) being used up over time (h) could you track that instead of non-load voltage? If you don't like your lut fit a curve. – Allan Wind Jun 01 '23 at 19:22
  • https://minds.wisconsin.edu/bitstream/handle/1793/7897/three.pdf;sequence=8 – Allan Wind Jun 01 '23 at 19:31
  • https://physics.stackexchange.com/questions/130533/name-of-battery-voltage-when-load-connected-disconnected might be a good start – Allan Wind Jun 01 '23 at 19:35
  • 1
    Generally, you cannot measure a battery's voltage to obtain the remaining charge. It heavily depends on the individual cells, its age, the current load, temperature, and so on. We are spoiled by the indicators of our mobiles and notebooks. But these devices measure voltage _and current_ during many charge and discharge cycles and _estimate_ the current percentage. Anyway, this is not a software problem, but an electrical issue. So please ask on SE/EE as suggested for serious approaches. – the busybee Jun 02 '23 at 05:55
  • Your data looks like hogwash. You need a discharge curve with voltage over time. Start with battery _cell_ datasheets and take it from there. And if there's a BMS you need to take that in account too. Designing battery chargers is mostly chemistry and electrical engineering, then a little bit of programming on top of that. – Lundin Jun 02 '23 at 06:32

1 Answers1

0

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;
}
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • 1
    I could be entirely wrong but I think the issue is op has a non-load lut so 13.1V correspond to 40% remaining battery life. But under load the voltage might be 12V (making it up) so how do you map between those two? – Allan Wind Jun 01 '23 at 19:27
  • 1
    @AllanWind Batteries are the dark art of electrical engineering. To map between those two, perhaps [Prof. Snapes](https://harrypotter.fandom.com/wiki/Severus_Snape) knows? Here, code is simply employing a better look-up-table. – chux - Reinstate Monica Jun 01 '23 at 20:18
  • Thanks for the input. The MCU is 16bit, hence the explicit callout for long (100L). I do agree with removing the extra if condition. Personally I am not a fan of macros and your version is a bit wordy and harder to follow. It is unfortunate no one had any real insight into a solution other than to nitpick semantics like commenting. Again, I do appreciate your time! – Jdelmore Jun 09 '23 at 21:47
  • @Jdelmore, Even if the "MCU is 16bit", the `L` is not needed as the largest product is 144*100 (or even 255*100), well within the 16-bit `int` range. The macros prevent naked [magic numbers](https://en.wikipedia.org/wiki/Magic_number_(programming)). For a person reviewing and maybe later needing to update the code, the macro encapsulation better self-documents than the original code. – chux - Reinstate Monica Jun 09 '23 at 22:05