Reading battery voltage?



  • @stoffera said in Reading battery voltage?:

    Gist with code: https://gist.github.com/stoffera/9ce4704c3cb2044b7a017fcced95ab74

    Be aware that the measure is quite noisy - so you will need to do some filtering.

    I'm now experimenting with different filtering methods. I'm currently trying initial delay of 50 µs, then 10 µs delay between samples, and taking average of 64 samples. Result seems quite stable and I can even watch value go down few mV at a time. (This measurement takes a bit under 1200 µs total.)

    Here is current code as single method:

    // BASED ON psoc_battery_voltage.{cpp,h}
    // https://gist.github.com/stoffera/9ce4704c3cb2044b7a017fcced95ab74
    uint16_t AppController::readBatteryVoltage() {
      static const uint32_t RawAdcMax = 0xFFF;
      static const uint32_t ReferenceVoltage = 0x400;
      static const uint32_t CorrectionFactor = 1588;
      static const uint32_t CorrectDenominator = 1000;
      static const uint32_t CorrectionOffset = 440;
    
      static const uint32_t CorrectionScale =
        RawAdcMax*CorrectionFactor/CorrectDenominator*ReferenceVoltage;
    
      static bool isStarted = false;
      if (!isStarted) {
          ADC_SAR_1_Start();
          isStarted = true;
      }
    
      // Disconnect AMUXBUSL ; Connect AG5 / CMP2 to AG5 / vref to CMP2
      CY_SET_REG8(CYDEV_ANAIF_RT_SAR0_SW3, CY_GET_REG8(CYDEV_ANAIF_RT_SAR0_SW3) & ~0x01);
      CY_SET_REG8(CYDEV_ANAIF_RT_SAR0_SW0, CY_GET_REG8(CYDEV_ANAIF_RT_SAR0_SW0) | 0x20);
      CY_SET_REG8(CYDEV_ANAIF_RT_CMP2_SW4, CY_GET_REG8(CYDEV_ANAIF_RT_CMP2_SW4) | 0x20);
      CY_SET_REG8(CYDEV_ANAIF_RT_CMP2_SW3, CY_GET_REG8(CYDEV_ANAIF_RT_CMP2_SW3) | 0x20);
    
      // wait for voltage level to settle
      wait_us(50);
    
      uint32_t count_log = 6;
      uint32_t sum = 0;
      for (uint32_t n = 0; n < (uint32_t)1 << count_log; n++) {
        ADC_SAR_1_StartConvert();
        ADC_SAR_1_IsEndConversion(ADC_SAR_1_WAIT_FOR_RESULT);
        uint16_t reading = ADC_SAR_1_GetResult16();
        
        // After reset value seems to stay at 0 for some time.
        // In that case don't try to calculate average, just return 0.
        if (reading == 0) return 0;    
        
        sum += ADC_SAR_1_GetResult16();
        wait_us(10);
      }
      uint32_t avg = sum >> count_log;
    
      // Disconnect CMP2 from vref and AG5 / AG5 from ADC ; Connect ADC to AMUXBUSL
      CY_SET_REG8(CYDEV_ANAIF_RT_CMP2_SW4, CY_GET_REG8(CYDEV_ANAIF_RT_CMP2_SW4) & ~0x20);
      CY_SET_REG8(CYDEV_ANAIF_RT_CMP2_SW3, CY_GET_REG8(CYDEV_ANAIF_RT_CMP2_SW3) & ~0x20);
      CY_SET_REG8(CYDEV_ANAIF_RT_SAR0_SW0, CY_GET_REG8(CYDEV_ANAIF_RT_SAR0_SW0) & ~0x20);
      CY_SET_REG8(CYDEV_ANAIF_RT_SAR0_SW3, CY_GET_REG8(CYDEV_ANAIF_RT_SAR0_SW3) | 0x01);
    
      // scale from 12 bit ADC to mV
      return CorrectionScale / avg + CorrectionOffset;
    }
    

    EDIT: And I just noticed a bug - the "return 0" special case I added doesn't restore state properly.



  • I created small battery-logger app for Mono, which shows voltage on screen and also logs to SD card -- my first app :)

    After my Mono is recharged, I'll get complete log from full battery until Mono shuts down because of low battery, and then try to determine how to calculate battery-percentage.



  • Great app, I just tried it :-)

    I found out I used a too short delay after setting up the analog routing. I used 20 µs and you wisely changed that to 50 µs. However, my 20µs delay introduced a measurement error, that I compensated for by subtracting an error function. (The magic numbers I use to calculate the voltage from the ADC count output.)

    I will supply some new coefficients next week, when I have time to make new calculations.

    Background

    To calculate the voltage from ADC output, without any error correction function, the formula is:

    ADC_MAX * VREF / ADC_OUTPUT = mVOLTAGE
    

    The raw Adc_max is 4096, Vref is 2048. This result will contain a linear error, that must be subtracted.



  • When calculating percentage of battery remaining, linear error doesn't really matter as long as conversion from mV to percents is calibrated with same error.

    I just got first graph from full battery to Mono shutting off (which took about 3.5 hours). This seems to be quite easy to approximate with few linear parts. At least initially I'm happy with about 10% accuracy and without any temperature compensation (but even that might also be doable since Mono has thermometer - but it would require more data and more complex calculations).

    0_1490951407596_battery-voltage.png



  • I just added initial version of battery percentage calculation to my battery-logger app.

    I decided to make Mono-side of code really simple, so I created a script log-analyzer.pl (source) which calculates mV limits from given logs, which can then be used to easily determine battery percentage.

    For now the app is showing battery percentage at 1% resolution while testing this, but actual accuracy is lower. The script can also generate data with other resolutions, and I think something like 5% might work better generally.



  • With three logs so far, discharge graph is nearly equal between logs, except at around 16% to 23% percent remaining, where there are significant variations.

    The 'Combined' line here is what my script calculates, and battery percentage is then determined based on that.

    0_1491167385280_three-logs.png



  • @malaire, this is great :-) ! We should put this into the framework!

    May I suggest a class that lives in the namespace mono::power and that can return the remaining percentage of battery charge.

    Maybe just extend the BatteryPower class I sent you some days ago. Tell me what you think.



  • @stoffera said in Reading battery voltage?:

    @malaire, this is great :-) ! We should put this into the framework!

    May I suggest a class that lives in the namespace mono::power and that can return the remaining percentage of battery charge.

    Maybe just extend the BatteryPower class I sent you some days ago. Tell me what you think.

    Sounds good. I'll test this few days to see if there are any problems, and I'll also add few changes:

    • return <0 when percentage can't be calculated (e.g. -1 when charging, -2 when mV is zero)
    • include internal battery_percent_mV table, but allow overriding it with custom table
    • manually "fix" the graph between 16% to 23% so that it's a straight line there


  • I just added initial framework-version to GitHub (commit).

    I decided to reverse the lookup-table for percentage calculation and always use 1% resolution to make code simpler. If user wants less resolution it's easy to just calculate e.g. ReadPercentage() / 10 * 10 or even supply a custom lookup-table.

    Also I think it would be better to not use special return values in CalculatePercentage and just return value from 0 to 100.


Log in to reply