Reading battery voltage?

  • Is it possible to read battery voltage yet?

    API Overview says "We plan to provide you with access to the current battery output voltage" but has this been implemented yet?

  • @malaire We have talked a lot about this issue in the initial design. We have a power circuit chip, that monitors the system voltage level and active sleep mode on low power.

    Unfortunately it is not as precise as we had expected, and it does not provide a read voltage function. What you can do is to use its SystemVoltageThreshold level to get an interrupt, when the battery power is below a certain level. This is what we currently use for detecting low battery. However, I would not recommend this approach.

    I will need to talk to @lh about this issue, you might be able to use the ADC in conjunction with a static voltage reference.

  • Thanks. Also, is the 3.3V max value for mbed::AnalogIn, so I would need a voltage divider if I were to use a hack like soldering a wire from battery to e.g. J_RING1.

  • You can connect the 3V3 to J_RING1, however it should be possible to get the voltage level without any soldering needed.

    I have put lasse (the hardware guy) to the task :-) So we will need to get back to you with a method.

  • @malaire Okay, we have a method now - no soldering needed.

    I have written a C++ class with one static method, that samples the battery voltage. This is done by letting the ADC sample an internal fixed voltage reference, and then calculate the actual input voltage to the ADC from that.

    It only works when the USB is not connected - else you measure the USB voltage.

    Gist with code:

    (I plan to add this class to Mono Framework soon)

    and here is how to use it:

    #include "psoc_battery_voltage.h"
    uint16_t mv = mono::power::BatteryVoltage::mVoltage();

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

    Happy hacking :-)

  • @stoffera said in Reading battery voltage?:

    Gist with code:

    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}
    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 =
      static bool isStarted = false;
      if (!isStarted) {
          isStarted = true;
      // Disconnect AMUXBUSL ; Connect AG5 / CMP2 to AG5 / vref to CMP2
      // wait for voltage level to settle
      uint32_t count_log = 6;
      uint32_t sum = 0;
      for (uint32_t n = 0; n < (uint32_t)1 << count_log; n++) {
        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();
      uint32_t avg = sum >> count_log;
      // Disconnect CMP2 from vref and AG5 / AG5 from ADC ; Connect ADC to AMUXBUSL
      // 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.


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


    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).


  • 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 (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.


  • @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