Boron LTE-M Cat1 - High current consumption in ULTRA_LOW_POWER mode

I’m using the Boron LTE-M Cat1 variant with an external sim card (deviceOS 2.2.0). Following the firmware code of AN028 and AN029, I’m putting the Boron in ULTRA_LOW_POWER mode. Upon wake-up from a timer alarm, I do a reset to the device.

The problem I’m facing is that the current consumption is too high in sleep mode. I’m measuring around 97 mA. For me that probably means that a high consumer is on, such as the modem. However, as you can see in the code below, I’m disconnecting from the network and turning-off the modem before going to sleep.

Nothing is connected to the Boron, except an external power supply at 3.3V to the Li+ pin.

Any suggestions of what to try or any ideas about why is it happening?

 * Project DHI_WRM_PoCDemo_VegetationCamera
 * Description:
 * Author: NIAG
 * Date: 27-10-2021

#include <Particle.h>

// Don't wait for cloud connection to run the code

// This is the maximum amount of time to wait for the cloud to be connected in
// milliseconds. This should be at least 5 minutes. If you set this limit shorter,
// on Gen 2 devices the modem may not get power cycled which may help with reconnection.
const std::chrono::milliseconds connectMaxTime = 6min;

// This is the minimum amount of time to stay connected to the cloud. You can set this
// to zero and the device will sleep as fast as possible, however you may not get 
// firmware updates and device diagnostics won't go out all of the time. Setting this
// to 10 seconds is typically a good value to use for getting updates.
const std::chrono::milliseconds cloudMinTime = 10s;

// How long to sleep
const std::chrono::seconds sleepTimeNormal = 1min;//24h;
// How long to sleep (short duration, after error)
const std::chrono::seconds sleepTimeShort = 5min;

// Maximum amount of time to wait for a user firmware download in milliseconds
// before giving up and just going back to sleep
const std::chrono::milliseconds firmwareUpdateMaxTime = 5min;

// How often to publish device diagnostics (vitals). If you set this 
// equal to the sleep period they'll be sent on every connection, or you can set
// it higher to save data. For example, if you set it to 24 * 60 * 60 it would
// only publish once per day. 
const std::chrono::seconds diagnosticPublishTime = 24h;

// These are the states in the finite state machine, handled in loop()
enum State {
State state = STATE_SLEEP;
unsigned long stateTime;
bool firmwareUpdateInProgress = false;
long lastFirmwareUpdateCheck = 0;
long lastDiagnosticsPublish = 0;

SystemSleepConfiguration sleepConfigNormal;
SystemSleepConfiguration sleepConfigShort;

enum SleepDuration {
SleepDuration sleepDuration = SLEEP_DURATION_NORMAL;

// setup() runs once, when the device is first turned on.
void setup() {
   * Setup system sleep configuration
   * Wake-up source is set to RTC


  // It's only necessary to turn cellular on and connect to the cloud. Stepping up
  // one layer at a time with Cellular.connect() and wait for Cellular.ready() can
  // be done but there's little advantage to doing so.
  stateTime = millis();

// loop() runs over and over again, as quickly as it can execute.
void loop() {

  switch(state) {
          // Wait for the connection to the Particle cloud to complete
          if (Particle.connected()) {
//    "connected to the cloud in %lu ms", millis() - stateTime);
              // state = STATE_PUBLISH;
              // connect to the MQTT server (unique id by
              mqttClient.connect("sparkclient_" + String(,"wrm","WRM_mosquitto_2021");   
              state = STATE_CAPTURE_PHOTO;
              stateTime = millis(); 
          if (millis() - stateTime >= connectMaxTime.count()) {
              // Took too long to connect, go to sleep with shorter sleep period.
//    "failed to connect, going to sleep");
              sleepDuration = SLEEP_DURATION_SHORT;
              state = STATE_SLEEP;
          // capturePhoto();
          state = STATE_PUBLISH;
      case STATE_PUBLISH:
          // Read camera fifo in burst mode and send everything to the cloud
            // Error: Either too big or empty buffer

          //Clear the capture done flag

          //if (millis() - stateTime < cloudMinTime.count()) {
          //"waiting %lu ms before sleeping", cloudMinTime.count() - (millis() - stateTime));
          //    state = STATE_PRE_SLEEP;
          //else {
          //    state = STATE_SLEEP;
          state = STATE_WAIT_DISCONNECTED;

              state = STATE_SLEEP;
      case STATE_SLEEP:

          // TODO: put camera to low-power mode
          // Go to sleep
          if (sleepDuration == SLEEP_DURATION_SHORT)
//  "going to sleep for %ld seconds", (long) sleepTimeShort.count());
          else if (sleepDuration == SLEEP_DURATION_NORMAL)
//  "going to sleep for %ld seconds", (long) sleepTimeNormal.count());

//"woke from sleep");

          // On wake-up, device will be reset

@niagFT ,

Looks like a cool project and I wish you luck with it.

Current consumption of 97mA is a lot and it makes me think that perhaps your cellular modem is not being powered down.

In looking at your states, it seems that you connect (STATE_WAIT_CONNECTED), then capture a photo, then go to STATE_WAIT_DISCONNECTED. But, in that state, if you are still Particle.connected(), it seems you would skip turning off the cellular modem. I may be missing something but, I would have expected a Particle.disconnect() before that state.

Alos, you may want to consider a waitFor(Cellular.isOff, 30000) to prevent the possibility of execution hanging if the cellular modem stays on.

Hope this helps,



Thank you for your answer. You are of course correct, but unfortunately I’m doing that already. In my effort to delete commented/not relevant code for the post, I also skipped one State completely (the STATE_PUBLISH).

As you can see (I’ve just corrected the code), what I’m doing there, is indeed Particle.disconnect() and then go to the state where I wait until I’m disconnected… don’t you think that’s correct now?

And I agree with you, this ~100mA seems like the modem is still on.

Does that have anything to do with the older OS I’m using? I didn’t see though any relevant entry in the change logs from 2.2.0 to the latest.

Can it be related to the fact that I’m using a 3rd-party sim? I can see it is powered by the same rail (different pin though) as the esim.

Edit: Oh yes, and thanks for the tip for adding a timeout for the cellular off. That’s definitely a good idea.

@niagFT ,

Ok, wondered if you had taken care of that Particle.disconnect() elsewhere. Always a challenge to decide what code to put into the post…

It is possible that you may not have disconnected from Particle yet. Perhaps you could try belt and suspenders:

bool disconnectFromParticle()                                     // Ensures we disconnect cleanly from Particle
  return true;

Just to make sure you are disconnected before moving to the next state.


For completeness, I must say that the reason for this high current consumption was that the GPIOs are by default floating after reset. Which by itself is totally surprising, I would expect that they are set to a known state. But anyway, after setting them to e.g. output and LOW or input with pull-down, the current consumption is at expected levels.

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