Sleepy Boron - SOC Not Updating


Thank you for engaging with me on this issue. After a couple days of collecting data, I am less sure that there is an issue.

Three factors may have combined to create the impression that there is an issue with Sleep and Power Management in deviceOS@1.5.0. The three factors are:

  • dramatically lower power consumption for the Boron LTE vs the Electron 3G
  • a redesign of my carrier board upon which much of my testing is done which also reduced power consumption
  • the finding that batteryMonitor.getSOC() and the newer System.batteryCharge() provide 5 digits of data but not 5 digit precision in their measurements.

When I take these considerations into account, I am not able to prove that there is an issue here and I do not want to waste Particle resources pursuing an edge case.

When I made the following changes, I was able to see changes in state of charge even for sleepy use cases.

 * Project Sleep-Battery-Test-2
 * Description: Another test for SOC
 * Author: Guohui modifications by Chip McClelland
 * Date: 4-10-20
 * In this application, the SoC is printed to Serial1 every second and once you type character on Serial1 terminal, 
 * device will enter sleep mode(STOP) for 3 seconds and then wakes up to keep printing the SoC.

#include "Particle.h"



FuelGauge batteryMonitor;
SystemPowerConfiguration conf;
SystemSleepConfiguration config;

char batteryString[32];

void setup() {
    // Comment out this section out and the problem goes away
    conf.powerSourceMaxCurrent(1500)                                     // default is 900mA this let's me charge faster
        .powerSourceMinVoltage(4208)                                     // This is the default value for the Boron
        .batteryChargeCurrent(1024)                                      // default is 2048mA (011000) = 512mA+1024mA+512mA)
        .batteryChargeVoltage(4112)                                      // default is 4.112V termination voltage
        .feature(SystemPowerFeature::USE_VIN_SETTINGS_WITH_USB_HOST) ;
    System.setPowerConfiguration(conf);                                  // These settings are for the DC power mode


    Particle.publish("Status","Starting Sleep / Wake Cycles",PRIVATE);

void loop() {
    snprintf(batteryString,sizeof(batteryString), "SoC: %4.2f%%", System.batteryCharge());

I changed the following:

  • I collected the battery charge level before connecting
  • I used the newer (and I assume equivalent) System.batteryCharge() API call
  • I found that SOC and vCell move in lock step so only one was needed.

Thank you for your help on this. I will continue to explore this issue but, until I can show a definitive use case - or someone else comes forward to confirm - we can mark this as closed.

Here is the data I collected over a 12 hour period - note how the charge level declines slowly and in ~0.15% increments

Thank you again for your help!



@chipmc @Rftop Can either of you confirm the low power consumption claims with the new 1.5.0 firmware on the Boron or Argon as accurate?

I haven’t had time to verify the improvements yet but would love to know what you guys are seeing as far as battery runtime improvements.

@chipmc, after ignoring the issue associated with running an Electron on battery until dead for too long, I’ve revised my several applications to put my Electrons to sleep for a couple hours when the state of charge falls below 50%. I eventually disconnect the battery when the state of charge falls below 40% (totally arbitrary numbers). I have updated all of the devices to 1.5.0 and wondered why I was never seeing a state of charge reading once it dropped below 50% (sleep introduced).

I’m also sending the state of charge to Ubidots along with the sensory data I’m collecting and issue an alert when the state of charge gets below 50%. Along with the alert that warns of the loss of incoming 5Vdc, this serves as a more urgent message to get to the device to restore incoming power.

The part of the code I’m using to read state of charge is:

//returns the battery state of charge as INT
int get_soc(String c) {
    return (int)(FuelGauge().getSoC());

The above worked fine until sleeping is introduced. Do I merely need to change the FuelGage().getSoC() call to System.batteryCharge() and I’m good or do I need to do something else in addition?


That is my current approach and it seems to be working. Also if possible, run this command before the device reconnects to Particle as the power demands of the cellular radio could impact the battery reading. Like you, I send my data to Ubidots and will be watching this issue to see make sure it is resolved. Please share your information going forward as well.

Thanks, Chip

I haven’t yet. It’s on my “To-Do” list as soon as I get some free time.

1 Like


Results are posted here.

Please take a look at the results and my approach.



@chipmc, based on your additional Sleep 2.0 testing (awesome work thank you), do you believe that SOC with sleep is behaving as intended or there could be an issue?


To be honest, I am not sure but I am having a hard time finding a clean way of proving there is an issue. I have a device on my desktop which I put an external load on and I am seeing updates to the SOC. I also have tow identical devices deployed in local parks with solar panels and they stay pegged at 100% charge. It could very well be that they are that much more efficient but I am worried that I am missing changes in their state of charge and that error could grow over time.

So, I am still testing and will post updates as I get them. I can say that moving from the Electron to the Boron and using the new system power management and sleep APIs has dramatically cut power consumption. So, I will likely keep moving forward with my Boron / deviceOS@1.5.0 deployments.

Thanks, Chip

@chipmc, I keep ending up in the same boat. Each time I think I found the smoking gun that soc is not working, something changes to make me think it is working. Check this out though with solar boron test over the weekend:

Bat. voltage via fuel.getVCell() as @Rftop suggested.

soc over exact time period using soc = System.batteryCharge();

In the soc the stair step is me removing the solar power and battery and connecting back up because I was suspicious that with that much Texas sun soc was “frozen”, and turns out it was misbehaving.

I am sleeping with: config.mode(SystemSleepMode::STOP).duration(300s).network(NETWORK_INTERFACE_CELLULAR);

It is like soc is responsive, but being dampen by something. Notice that bat. voltage behaved as you would expect over the time period. For this reason, I think that bat. voltage is a safe root level value to use.

Bottom line, right now I do not think soc can not be trusted and we need to get bat. voltage and map it using something like this, but this mapped value is lower then regular soc by ~28% when I test is (I could totally be using the wrong lower bound).

FuelGauge fuel;
bat_volt = fuel.getVCell();
soc_calc = map(bat_volt,3.600,4.112,0.000,100.000);

Oh well, testing continues but I wanted to share my latest.

1 Like

Its been a while since I read the datasheet for the Fuel Gauge, but the SOC is a calculated value based on many variables. The Fuel Gauge will gather data and eventually return a correct assumption about the charge condition of the Li-Po. The Fuel Gauge doesn’t have the luxury of taking a voltage measurement of a Li-Po in a Resting Condition.
The SOC “inaccuracies” for a Sleepy Boron might be simply that the Fuel Gauge is sleeping also (guessing) with the recent Updates in 1.5.0 final ?

My previous tests showed the Fuel Gauge to be very accurate, after it has a chance to sort out the operational parameters of the Li-Po. That didn’t include a Low Duty Cycle Sleep setup though.

@Backpacker87, You might not want to assume (MAP) a linear relationship with SOC and Voltage.
Pick any Voltage Chart from the Internet for a 1S Li-Po and use that in your Code Instead.
Here’s a sample. Dont be too worried about precision, since the ambient temperature will play a factor in the expressed voltage.

Take your measurement after the Boron’s current demand has settled down…or before you fire up the Cellular Modem. You could Turn OFF Charging temporarily also, for more accuracy.

Remember, it doesn’t matter what your charge termination voltage is, this chart shows the remaining chemical energy at any voltage. IE: a 3.7V “Nominal” 1,000 mAh Li-Po has ~600 mAh remaining at 3.85V resting (per the sample chart above).
The chart below says ~550 mAh for the same example:
All the charts and curves that you find will be slightly different.

Personally, I prefer tracking Voltage directly. I use SOC as more of a generalization.
IMO, If I have a Solar Recharged Device that spends a lot of time in the 3.8V range, then the Power Budget wasn’t correct .


I am actually seeing the opposite problem. I have two devices which refuse to charge - one is solar powered and one DC powered.

To re-enable charging, I had to physically access the device and do the following:

  • Power down the Boron
  • Remove both LiPo and DC-In power from the board
  • Add the DC-In Power and power back up the Boron
  • Attach the LiPO battery - at which time it immediately started charging

Here is battery and charge state looked like before and after I did this:

It seems to me that the new deviceOS@1.5.0 powerManagementConfiguration API can lock up leaving the device unable to report SOC and, occasionally, to accept a charge.

Here are the relevant snippets of code from my sketch:

In program header:

   SystemPowerConfiguration conf;                      // Initalize the PMIC class so you can call the Power Management functions below.

In the Setup() section:

    conf.powerSourceMaxCurrent(1500)                                 // default is 900mA this let's me charge faster
    .powerSourceMinVoltage(4208)                                     // This is the default value for the Boron
    .batteryChargeCurrent(1024)                                      // default is 2048mA (011000) = 512mA+1024mA+512mA)
    .batteryChargeVoltage(4112)                                      // default is 4.112V termination voltage
    .feature(SystemPowerFeature::USE_VIN_SETTINGS_WITH_USB_HOST) ;

Executed from the main loop() every hour as part of reporting process:

   void getBatteryContext() {
     const char* batteryContext[7] ={"Unknown","Not Charging","Charging","Charged","Discharging","Fault","Diconnected"};  // Battery conect information -
     snprintf(batteryContextStr, sizeof(batteryContextStr),"%s", batteryContext[System.batteryState()]);

This seems to be a rare event so I can live with it for a while if I can find a way to “reset” the PMIC using the new API or the old PMIC calls remotely. Would be a pain to have to physically travel to get these devices to charge again.

Any help or suggestions would be welcome.



1 Like

Hi there,

FYI, the FuelGauge is put into sleep state if you calling sleep API to make your Boron enter sleep. I think this is the major change in comparison to previous release before 1.5.0. Could you try reverting the change here: and see if the SoC can get updated as normal as you expected?

Best regards,


Hi all,
Regarding FuelGauge update I was facing similar issue, VCell read was always resulting the same value.
For me it seems that something is not ok when waking up Fuel Gauge.
So I went into MAX1704x datasheet and found:

The A/D calculates the average cell voltage for a period of 125ms after IC POR and then for a period of 500ms for every cycle afterwards. The VCELL register requires 500ms to update after exiting Sleep mode. The result is placed in the VCELL register at the end of each conversion period.

My code was already waiting more than 500ms after waking up before VCELL read so I went into conclusion that VCELL register is not being updated after system waking up at all.
So I went into different approach where I issue a POR to the Fuel Gauge and then read values.
Take a look at simple code snippet:

fuel.reset();  //reset the Fuel Gauge chip
delay(500);  // ToDo test 125ms from datasheet
batteryVCELL = fuel.getVCell(); //read VCELL into variable for later publish

And it solved the problem with Fuel Gauge after waking up from sleep mode.
So this shed some light on a problem and I went further with an idea that maybe just this gauge.wakeup() within Sleep procedures is not working because it was too early for an I2C interface or MAX chip (1ms). (didn’t analyse error handling)
And I did tests with:

//before there is a code for like 200-300ms after wake up from sleep.
fuel.wakeup();  //wake up the Fuel Gauge chip
delay(500);  // As datasheet says
batteryVCELL = fuel.getVCell();  //publish at later stages
batterySOC = fuel.getSoC();  //publish at later stages

This also solves the problem.

I could recommend for engineers to take a look at I2C traces with some external logic analyser close to waking up procedure. Eventually add public method to read Fuel Gauge register to determine its state.
Also taking a look into:

Sleep Mode
Holding both SDA and SCL logic-low forces the MAX17043/MAX17044 into Sleep mode. While in Sleep mode, all IC operations are halted and power drain of the IC is greatly reduced. After exiting Sleep mode, fuel-gauge operation continues from the point it was halted. SDA and SCL must be held low for at least 2.5s to guarantee transition into Sleep mode. Afterwards, a rising edge on either SDA or SCL immediately transitions the IC out of Sleep mode.Alternatively, Sleep mode can be entered by setting the SLEEP bit in the CONFIG register to logic 1 through I2C communication. If the SLEEP bit is set to logic 1, the only way to exit Sleep mode is to write SLEEP to logic 0 or power-on reset the IC.

It may seem depending on I2C line state in a NRF’s sleep mode it is not needed to power down Fuel Gauge by its config bits during system’s Sleep() procedure.



Hi @Wojciech, thanks for this great effort digging into the datasheet and making things clear. Our engineer also mentioned internally that by resetting the PMIC can also fix the issue. Would you mind having it a try?

Best regards

@Wojciech, that was one heck of a first post !
Thanks for digging so deep into the issue.

Looks like fuel.wakeup(); with delay(500) was the least intrusive fix.

@ielec, Wouldn’t fuel.wakeup() eventually becoming part of the wakeup procedure in DeviceOS be a lot cleaner than the user resetting the PMIC after each sleep ?

Hi all,

A PR for this issue is now ready on Github:
I would suggest you to read through the description for the PR to be aware of some cautions.


1 Like

Hi there

So if I wanted to sleep with 1.5.0 or later there would be no way to keep the FuelGauge alive?


Hi @delphius, any reason to keep the FuelGauge alive while MCU is sleeping in you application?

Kind regards

Hi @ielec

I opened a new discussion that you can find here.

In short, I’m running the Electron on a solar cell and while the battery SoC is high enough the Electron wakes up every minute to publish new data.

The delay that’s required on the new DeviceOS seems like an unneccessary overhead when I can simply leave the fuel gauge on. It worked perfectly on DeviceOS 1.4.4 but if I can’t get it to work on DeviceOS 1.5.0 and newer I’ll miss out on all the new features.

Any ideas on how to keep the fuel gauge on on DeviceOS 1.5.0 would be appreciated! :slightly_smiling_face:

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.