Battery power safeguard for Electron (and Boron)

I have several Electron-based (and Boron-based) devices in the field and they’ve been there for long periods of time. Occasionally, I’ll lose one (apparently fried) due to either incoming power issues or possibly the fact that I’ll let them run on battery until the battery is dead if they lose 5vdc on VIN. This happens usually due to a GFCI tripping.

It’s about time I added some safeguard to prevent the run-until-dead-battery scenario and lacking the ability to find a definitive best practice, I’ve tweaked some code from “Electron Maintain Capacity” program that I found.

BTW, the application is monitoring HVAC equipment parameters and pushes data to Ubidots. We are alerted when incoming power is lost and should(!) be sending someone to resolve the issue before the battery dies. However, life happens and sometimes we don’t get that chance to avoid a dead battery.

What I’m thinking of doing and would appreciate any suggestions on is the following code. I haven’t fit this in the my applications (I have several) yet. I’m hoping to get feedback prior to starting that effort in case I’m way off base. Yes, I’m trying to primarily show just the changes I’m planning on making. Thanks!

SYSTEM_THREAD(ENABLED);
SYSTEM_MODE(MANUAL);    // prevent load of modem occurring automatically

//we'll sleep for 24 hours at a time once the battery gets to the lowest desired state
#define SLEEP_DURATION 86400

PMIC pmic;
int gVIN;

const float LOW_BATT_CAPACITY = 20.0; // 20.0 is lowest it should be set at

/*
 * @param capacity The value to compare current battery to.
 * @return If battery is lower than `capacity`, return `true`.
 */
bool battery_lower_than(float capacity)
{
    return (FuelGauge().getSoC() < capacity) ? 1 : 0;
}

/*
 * checks to see if there's incoming power on VIN
 * @return 'true' if there's incoming power or 'false' if there is not
 */
bool no_incoming_power() {
    return (pmic.isPowerGood() ? 1 : 0);
}

//
void reset_battery_capacity() {
    FuelGauge().quickStart();
    // must delay at least 175ms after quickstart, before calling
    // getSoC(), or reading will not have updated yet.
    delay(200);
}

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


void qualify_battery_and_hibernate() {
    //if we're well below the lowest desired battery capacity, then eternal sleep to protect the device
    if (no_incoming_power() && battery_lower_than(LOW_BATT_CAPACITY-10.0)) {
        System.sleep(SLEEP_MODE_DEEP, 0);
    //if we're at the desired lowest, then let's start sleeping for extended periods
    if (no_incoming_power() && battery_lower_than(LOW_BATT_CAPACITY)) {
        System.sleep(SLEEP_MODE_DEEP, SLEEP_DURATION, SLEEP_DISABLE_WKP_PIN);
    }
}

//....and then in setup()
void setup() {
    //....some other code
    reset_battery_capacity();
    qualify_battery_and_hibernate();
    
    //if we have incoming power and/or sufficient battery capacity
    Particle.connect();
    //....balance of setup code

}

Unless you are using SOFT_POWER_OFF mode or manually turning off the fuel gauge, you should not quickStart() it at startup. This actually causes it to be less accurate because it throws out the previously acquired data.

If you are going to go into permanent sleep until the power is restored, you should disable the BATFET instead of using SLEEP_MODE_DEEP. This will completely turn off the device using even less power, but when power is restored it will boot normally. Make sure you turn the BATFET back on in setup().

1 Like

@rickkas7, Thanks for your feedback. Where do I find documentation on BATFET? I did a quick search in Reference and couldn’t find anything.

[update] OK. Found something under Misc Operation Control Reg, but I’m not sure how to enable and disable. Do I simply call disableBATFET(); in my SoC testing code and in setup() call enableBATFET(); ?

[another update] Nevermind…found it. Thanks!

So for final confirmation, here’s what I believe is considered a reasonable approach:

SYSTEM_THREAD(ENABLED);
SYSTEM_MODE(MANUAL);    // prevent load of modem occurring automatically

//we'll sleep for 24 hours at a time once the battery gets to the lowest desired state
#define SLEEP_DURATION 86400

PMIC pmic;
int gVIN;

const float LOW_BATT_CAPACITY = 20.0; // 20.0 is lowest it should be set at

/*
 * @param capacity The value to compare current battery to.
 * @return If battery is lower than `capacity`, return `true`.
 */
bool battery_lower_than(float capacity)
{
    return (FuelGauge().getSoC() < capacity) ? 1 : 0;
}

/*
 * checks to see if there's incoming power on VIN
 * @return 'true' if there's incoming power or 'false' if there is not
 */
bool incoming_power() {
    return (pmic.isPowerGood() ? 1 : 0);
}


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


void qualify_battery_and_hibernate() {
    //if we're well below the lowest desired battery capacity with no incoming power, then shutdown to protect the device
    if (!incoming_power() && battery_lower_than(LOW_BATT_CAPACITY-10.0)) {
        pmic.disableBATFET();
    //if we're at the desired lowest, then let's start sleeping for extended periods
    if (!incoming_power() && battery_lower_than(LOW_BATT_CAPACITY)) {
        System.sleep(SLEEP_MODE_DEEP, SLEEP_DURATION, SLEEP_DISABLE_WKP_PIN);
    }
}


//....and then in setup()
void setup() {
    //some of my other code
    pmic.enableBATFET();
    qualify_battery_and_hibernate();
    
    //if we get here we have incoming power and/or sufficient battery capacity
    Particle.connect();
    //....balance of my code

}


//...and in my main "loop" which runs once a minute
    qualify_battery_and_hibernate();

@ctmorrison thanks for sharing!

if (!incoming_power() && battery_lower_than(LOW_BATT_CAPACITY)) {
System.sleep(SLEEP_MODE_DEEP, SLEEP_DURATION, SLEEP_DISABLE_WKP_PIN);
}

In your decision to SLEEP_DISABLE_WKP_PIN does this remove the ability to wake from deep sleep on a VUSB state change? This could be valuable to detect a restored power situation rather than waiting the entire sleep duration to get back online/charge battery. Here is a reference to the functionality I am referencing: Boron Solar Charging with 1.5.0-rc1

We’ll have to hope for someone with more knowledge than I have to answer that. I’d love to have a solution that would bring the device back online if VIN power was restored and still protect the device in the meantime. I’m now just trying to be a bit prudent and eliminate the risk of the device failure due to running the battery until dead.

[updated] Upon further reflection on the code, I believe what I’ve created above would allow the device to come back online. However, it does not address your issue.