When is Electron Powered By Battery

This is good to know @ScruffR - I didn’t realize that the battery would be used that often or in that manner. So to actually detect when the external power goes down, I really need to measure that on one of the I/O pins to get a the real status of external power.

Have any of you had any success using the PMIC.readInputSourceRegister() when i use it it always seems to output the same value, 51 regardless if the battery is hooked up or not. I have a system that incorporates a switch to disconnect the battery from the electron. I need to be able to tell if the battery is connected when the electron is connected to an external power supply so that if it is not then i can display a message for the user to connect the battery, otherwise it will not charge.
For this purpose I do not think that the readInputSourceRegister() would work.
I wanted to try to see if some of the other codes from the PMIC would work for this purpose, such as getChargingStat() but whenever I try those I get an undefined reference error. I was wondering if you have had any experience with some of the other commands from the PMIC as the documentation on it is not very detailed.

@SailorDude Show the code your using to try to read that register please so other can see any errors with it.

Here is a sample of some of the code that i am trying to use, just including the parts that are causing trouble and have to deal with the PMIC commands:

PMIC pmic;   

void setup() {
    pmic.begin();                   // initiializes the PMIC (power management chip) so that system status informaiton and 
    Serial.begin(9600);             // initializes serial communicaiton via USB at BAUD of 9600, used for debugging 
}

void loop() {
    Serial.print("Get Charging Stat: ");
    Serial.println(pmic.getChargingStat());     // this function does NOT work
    Serial.print("Get Vbus Stat: ");
    Serial.println(pmic.getVbusStat());           // this function does NOT work
    Serial.print("Get DPM Stat: ");
    Serial.println(pmic.getDPMStat());           // this function does NOT work
    Serial.print("Is Power Good?: ");
    Serial.println(pmic.isPowerGood());          // this function does work
    Serial.print("Get Vsys Stat: ");
    Serial.println(pmic.getVsysStat());          // this function does NOT work
    Serial.print("System Status: ");
    Serial.println(pmic.getSystemStatus());          // this function does work
    Serial.print("Input Source Register info: ");
    Serial.println(pmic.readInputSourceRegister());          // this function does work
    delay(2000);
}

And these are the error messages that i get. I don’t understand why some of the functions from PMIC are working but the others are not. Do you have to call some of these functions in a different manner and if so does anyone know how to do that? I appreciate the help.

undefined reference to `PMIC::getChargingStat()'
undefined reference to `PMIC::getVbusStat()'
undefined reference to `PMIC::getDPMStat()'
undefined reference to `PMIC::getVsysStat()'

Those functions aren’t implemented. It’s a bug. But you can easily work around it because those are just elements of the pmic.getSystemStatus() value.

But it won’t actually help, for reasons I’m about to post…

1 Like

This is a deceptively difficult thing to detect. I’m not sure the method here is the best way to do it, but this is the first method I’ve found that actually produces the correct result, so there is that.

I believe the problem is that when there’s no battery, the PMIC doesn’t go into some easy-to-read no battery state. It rapidly switches between fast charging and charge complete states, continuously.

it’s possible to detect this, and this sample code breaks out three things that are sort of useful to be able to detect:

  • hasPower (USB Host, USB Charger, or VIN)
  • hasBattery
  • isCharging (in either pre-charge or fast charge mode, basically red light continuously on)

I tested as many cases as I could think of, but this should be treated as a proof-of-concept.

#include "Particle.h"

/**
 * Simple class to monitor for has power (USB or VIN), has a battery, and is charging
 *
 * Just instantiate one of these as a global variable and call loop() out of loop. You
 * should call it on every loop.
 */
class PowerCheck {
public:
	const unsigned long FAST_CHECK_INTERVAL_MS = 50;
	const unsigned long SLOW_CHECK_INTERVAL_MS = 2000;

	PowerCheck();
	virtual ~PowerCheck();

	/**
	 * Call out of loop(), perferably on every loop
	 */
	void loop();

	/**
	 * Returns true if the Electron has power, either a USB host (computer), USB charger, or VIN power.
	 */
	bool getHasPower() const { return hasPower; }

	/**
	 * Returns true if the Electron has a battery. This is only updated every 2 seconds.
	 */
	bool getHasBattery() const { return hasBattery; }

	/**
	 * Returns true if the Electron is currently charging (red light on)
	 */
	bool getIsCharging() const { return isCharging; }

private:
	unsigned long lastFastCheck = 0;
	unsigned long lastSlowCheck = 0;
	PMIC pmic;
	int checkCount = 0;
	int changeCount = 0;
	byte lastChrgStat = 0;
	bool hasPower = false;
	bool hasBattery = true;
	bool isCharging = false;
};

PowerCheck::PowerCheck() {
}

PowerCheck::~PowerCheck() {
}

void PowerCheck::loop() {
	if (millis() - lastFastCheck >= FAST_CHECK_INTERVAL_MS) {
		// For reasons explained in greater detail we need to check the system status register frequently.
		// But not too frequently, because it does use some system resources to communicate over I2C.
		// An interval of 50 milliseconds, or 20 times per second, seems about right.
		// Setting the interval to 100 milliseconds causes it to miss the condition looked for below sometimes.

		lastFastCheck = millis();

		byte systemStatus = pmic.getSystemStatus();
		if ((systemStatus & 0x04) != 0) {
			// Bit 2 (mask 0x4) == PG_STAT. If non-zero, power is good
			// This means we're powered off USB or VIN, so we don't know for sure if there's a battery

			// Having power and no battery is probably not what you'd expect:
			// if you're connected to a computer, it just alternates between these two states very quickly:
			// 0x64 Computer connected (VBUS=USB Host, Fast charging, Power Good)
			// 0x74 Computer connected (VBUS=USB Host, Charge Termination Done, Power Good)
			// (It works similarly for a USB charger, except it's 0x24 and 0x34).

			// Bit 5 CHRG_STAT[1] R
			// Bit 4 CHRG_STAT[0] R
			// 00 – Not Charging, 01 – Pre-charge (<VBATLOWV), 10 – Fast Charging, 11 – Charge Termination Done
			byte chrgStat = (systemStatus >> 4) & 0x3;

			if (chrgStat != lastChrgStat) {
				// Here's where we check to see if the charging status changes. When there's no battery present
				// it changes a lot between fast charging and charge termination done, but since we're polling
				// here instead of using interrupts, we have to poll frequently enough to see states.
				// (The BQ24195 power manager controller is connected to BATT_INT_PC13, but accessing that
				// interrupt from user code is a pain, so I just poll here.)
				changeCount++;
				lastChrgStat = chrgStat;
			}

			// We have power (USB or VIN)
			hasPower = true;

			// Other Common System Status Values:
			// 0x00 No USB or VIN power connected (running off battery)
			// 0x04 Power Good only - seems to happen in transition when it's deciding whether to charge or not
			// 0x34 USB Charger connected (Charge Termination Done, Power Good)
			// 0x74 Computer connected (VBUS=USB Host, Charge Termination Done, Power Good)
			//
			// Battery that needs charging:
			// 0x64 Computer connected (VBUS=USB Host, Fast charging, Power Good)
			//
			// Powered by VIN
			// 0x34 (Charge Termination Done, Power Good)
			// It does not seem to be possible to tell the difference between VIN and a USB charger
		}
		else {
			// Power good is false, so that means we must be running off battery
			hasPower = false;
		}

		checkCount++;
	}
	if (millis() - lastSlowCheck > SLOW_CHECK_INTERVAL_MS) {
		lastSlowCheck = millis();

		// Normally we get a checkCount of 36 or so, and a changeCount of 18 when there's no battery
		hasBattery = ! ((checkCount > 10) && (changeCount > (checkCount / 4)));

		// The battery is charging if in pre-charge or fast charge mode
		isCharging = hasBattery && (lastChrgStat == 1 || lastChrgStat == 2);

		checkCount = 0;
		changeCount = 0;
	}

}

// Global variables
PowerCheck powerCheck;
unsigned long lastCheck = 0;
char lastStatus[256];

void setup() {
	Serial.begin(9600);
}

void loop() {
	powerCheck.loop();

	if (millis() - lastCheck > 2000) {
		lastCheck = millis();

		char buffer[256];
		snprintf(buffer, sizeof(buffer), "hasPower=%d hasBattery=%d isCharging=%d",
				powerCheck.getHasPower(), powerCheck.getHasBattery(), powerCheck.getIsCharging());

		if (strcmp(lastStatus, buffer) != 0) {
			strcpy(lastStatus, buffer);
			Particle.publish("battery", buffer, PRIVATE);
		}
	}
}

5 Likes

Thanks for putting that together. It seems like it should work for my purposes, and hope that it solves some problems that other people have as well.
One comment though, it seems like the PowerCheck() function needs to be running continuously to be able to get the readings from the system status register frequently enough. Do you think that this check could be implemented with an IntervalTimer? Breaking out of whatever loop it is running and then checking the system status ever loop and check the system status every 50ms.
I have not tested this yet, but i think that it might work and be less disruptive to the overall functionality of the code.

I wouldn’t use powerCheck.loop() from a timer because it calls I2C and I2C is not reentrant. At least I don’t think I2C is. I’ll write the interrupt-based version of this code which is a much more efficient way to do this, anyway. I probably won’t get a chance until next week, after the 4th of July holiday in the US, however.

This version is much better. It uses the BAT_INT_PC13 interrupt to detect the rapid switching between fast charge and charge done when there is no battery. It’s much more efficient and doesn’t require being called from loop. You just initialize it once and use the getHasBattery() method to find out if there is one.

#include "Particle.h"

/**
 * Simple class to monitor for has power (USB or VIN), has a battery, and is charging
 *
 * Just instantiate one of these as a global variable and call setup() out of setup()
 * to initialize it. Then use the getHasPower(), getHasBattery() and getIsCharging()
 * methods as desired.
 */
class PowerCheck {
public:

	PowerCheck();
	virtual ~PowerCheck();

	/**
	 * You must call this out of setup() to initialize the interrupt handler!
	 */
	void setup();

	/**
	 * Returns true if the Electron has power, either a USB host (computer), USB charger, or VIN power.
	 *
	 * Not interrupt or timer safe; call only from the main loop as it uses I2C to query the PMIC.
	 */
	bool getHasPower();

	/**
	 * Returns true if the Electron has a battery.
	 */
	bool getHasBattery();

	/**
	 * Returns true if the Electron is currently charging (red light on)
	 *
	 * Not interrupt or timer safe; call only from the main loop as it uses I2C to query the PMIC.
	 */
	bool getIsCharging();

private:
	void interruptHandler();

	PMIC pmic;
	volatile bool hasBattery = true;
	volatile unsigned long lastChange = 0;
};

PowerCheck::PowerCheck() {
}

PowerCheck::~PowerCheck() {
}

void PowerCheck::setup() {
	// This can't be part of the constructor because it's initialized too early.
	// Call this from setup() instead.

	// BATT_INT_PC13
	attachInterrupt(LOW_BAT_UC, &PowerCheck::interruptHandler, this, FALLING);
}

bool PowerCheck::getHasPower() {
	// Bit 2 (mask 0x4) == PG_STAT. If non-zero, power is good
	// This means we're powered off USB or VIN, so we don't know for sure if there's a battery
	byte systemStatus = pmic.getSystemStatus();
	return ((systemStatus & 0x04) != 0);
}

/**
 * Returns true if the Electron has a battery.
 */
bool PowerCheck::getHasBattery() {
	if (millis() - lastChange < 100) {
		// When there is no battery, the charge status goes rapidly between fast charge and
		// charge done, about 30 times per second.

		// Normally this case means we have no battery, but return hasBattery instead to take
		// care of the case that the state changed because the battery just became charged
		// or the charger was plugged in or unplugged, etc.
		return hasBattery;
	}
	else {
		// It's been more than a 100 ms. since the charge status changed, assume that there is
		// a battery
		return true;
	}
}


/**
 * Returns true if the Electron is currently charging (red light on)
 */
bool PowerCheck::getIsCharging() {
	if (getHasBattery()) {
		byte systemStatus = pmic.getSystemStatus();

		// Bit 5 CHRG_STAT[1] R
		// Bit 4 CHRG_STAT[0] R
		// 00 – Not Charging, 01 – Pre-charge (<VBATLOWV), 10 – Fast Charging, 11 – Charge Termination Done
		byte chrgStat = (systemStatus >> 4) & 0x3;

		// Return true if battery is charging if in pre-charge or fast charge mode
		return (chrgStat == 1 || chrgStat == 2);
	}
	else {
		// Does not have a battery, can't be charging.
		// Don't just return the charge status because it's rapidly switching
		// between charging and done when there is no battery.
		return false;
	}
}

void PowerCheck::interruptHandler() {
	if (millis() - lastChange < 100) {
		// We very recently had a change; assume there is no battey and we're rapidly switching
		// between fast charge and charge done
		hasBattery = false;
	}
	else {
		// Note: It's quite possible that hasBattery will be false when there is a battery; the logic
		// in getHasBattery() takes this into account by checking lastChange as well.
		hasBattery = true;
	}
	lastChange = millis();
}



// Global variables
PowerCheck powerCheck;
unsigned long lastCheck = 0;
char lastStatus[256];

void setup() {
	Serial.begin(9600);
	powerCheck.setup();
}

void loop() {

	if (millis() - lastCheck > 2000) {
		lastCheck = millis();

		char buffer[256];
		snprintf(buffer, sizeof(buffer), "hasPower=%d hasBattery=%d isCharging=%d",
				powerCheck.getHasPower(), powerCheck.getHasBattery(), powerCheck.getIsCharging());

		if (strcmp(lastStatus, buffer) != 0) {
			strcpy(lastStatus, buffer);
			Particle.publish("battery", buffer, PRIVATE);
		}
	}
}

14 Likes

Thank you! @rickkas7 - it works perfectly :slight_smile:

LOW_BAT_UC isn’t found

Has it been deprecated, or did I miss a #include ?

For me this builds.
Have you got an Electron selected as target?
What system version are you targeting on that Electron?

1 Like

Gah.
My USB hub disconnected and it dropped back to photon and I didn't spot it, cheers for the pointer.

Happy days.

2 Likes

Hello. I am an absolute newbie, and I have been searching for an answer to this very problem. I was beginning to think I was going to have to start from scratch. Thank you, all of you, especially @rickkas7 for generously sharing your work. It works perfectly! Now all I have to learn about is web hooks, and I just might get my boat’s powerfail alarm notifications!!

Hi @rickkas7 I’m attempting as a complete noob to understand this language. I need to add a delay in notification so that a power loss of a couple of seconds down’t trigger the Publish. Will a simple Wait() somewhere do it?

Do you know if it is possible to determine if power is coming from VIN or if it is coming from USB individually?

It's possible to tell if you're connected to a USB host (a computer, for example).

I'm pretty sure it's not possible to tell the difference between being powered by a USB charger (not a host) and VIN.

2 Likes

@rickkas7,

Is there a version of PowerCheck I can use with the Boron? The version I have now will only compile for the Electron.

Awesome work BTW!

Chip

The battery interrupt code above is built into device OS now, and it's much better to get the is battery powered state from device diagnostics. This code can be used instead of the code above: