HINT: How to gain stability and save battery power!


#1

This draft is intended for the average hacker, who may not have the benefit of a Computer Science degree behind them … and is written by one of similar ilk. Comments and criticism welcomed!

“Now, I’m no expert. But …” – Someone famous, for those very words!

BluzDK is Special

Battery life is everything

BluzDK differs from Particle’s Photon, SparkCore etc in an important way. It does not have built-in multitasking abilities – and in fact if it did, it would not be so light on our batteries – perhaps its primary goal!

The upshot of this is that we need to take care that the setup() or loop() functions never block – certainly not for more than a very short while. If we do block, BluzDK wouldn’t be able to process cloud events, such as OTA updates, and would consume a LOT more power!

A, “blocking” function is simply one that hogs CPU clocks by not returning to its caller as swiftly as possible. Something like …

while(true) { /* do stuff forever */ }

The above example will block forever! Even blocking for a small amount of time can delay cloud events being processed, so a delay(30000) would mean no Particle.function calls can happen for 30 seconds.

For example, the following pseudo code could easily prevent BluzDK from being able to process cloud events for too long, meaning you can’t OTA update the board or check Particle.variables or any other cloud functionality while this is happening:

void main() {
    while (somethingTrueForTooLongTime) { 
        /* sit in this loop for a too long a time, just waiting for something */
    }
}

But wait!

Isn’t waiting an unknown and often lengthy time for something just exactly what we almost always want to do? Yup!

So HOW then?

One could invent any manner of cleverness to deal with such a problem. Luckily though, some clever people long ago came up with a programming pattern, called a Finite State Machine or FSM. By all means Google that to learn the juicy details. Suffice to say for now, it’s a beautifully simple, easy to read and understand pattern and is possibly the most uses programming pattern of all time.

Here, I present a simple (standard C – not C++) FSM style example of how we can wait a potentially long time for something without ever staying in loop() for more than a few microseconds. (Feel free to Google up on some fancy C++ class oriented versions, if it suits you._

void setup() {

}

#define TIME_TO_WAIT 5000 /* milliseconds */

typedef enum {
    WAIT_ONLINE,
    ALIVE,
    PUBLISH,
    SET_TIMER,
    WAIT
} FSM_state_t;

void loop() {

    static FSM_state_t myState = WAIT_ONLINE;
    static uint32_t saveTime;

    switch (myState) {

        case WAIT_ONLINE: // stay in this state  until we're connected to the big old cloud in the sky
            if (Particle.connected()) myState = ALIVE;
            break;

        case ALIVE: // we're alive! shout about it
            Particle.publish("Came online at", String(millis()) );
            myState = SET_TIMER; // next time through loop(), we'll set up our custom timer
            break;

        case PUBLISH: // it's been TIME_TO_WAIT milliseconds -- make some noise!
            if ( !Particle.connected() ) { // first though ... did we get disconnected somehow?
                myState = WAIT_ONLINE;
            } else {
                Particle.publish("PING!! at ", String(millis()) );
                myState = SET_TIMER;
            }
            break;

        case SET_TIMER: // record the current time for the WAIT state to reference back to
            saveTime = millis();
            myState = WAIT;
            break;

        case WAIT: // stay in this state until TIME_TO_WAIT milliseconds have gone by since the SET_TIMER state
            if ( millis() > (saveTime + TIME_TO_WAIT) ) {
                
                // Time's up! But we'll drop out of loop() for and get it done next time through
                // This allows all the background network stuff the best chance of keeping up with business
                myState = PUBLISH; 
            }
            break;
        
        default:
            myState = WAIT_ONLINE;
    }          

    // We can save MUCH more battery drain this way too! ...
    System.sleep(SLEEP_MODE_CPU);

}

Notice that each time loop() is called by the BluzDK system, only two or three instructions get executed (in this example) …even though the state machine as a whole is achieving quite a bit more – including not one but two potentially long waits.

Want to have virtual multitasking but could never find an efficient way to structure the code? Just use two or more state machines – each with its own states.

void loop() {
   cycle_power-monitor_state_machine();
   cycle_user_keypad_state_machine();
   cycle_camera_tracking_state_machine();
}

Just so long as each state machine takes care of itself and always drops through to return to loop() as soon as possible, then your system could appear to be three distinctly separate devices in one. Multitasking for the poor – whilst maintaining a degree of code readability, for next year, when you come back to make changes. :wink:

Gruvin


#2

Thanks! I definitely love the effort to explain how to make bluz more efficient, perhaps we can morph this into a tutorial in the docs.

I would like to clarify one very important point though, based on this:

Blocking of loop() could not actually cause a radio link loss. So there are two connections most people care about:

  1. The link connection, that is, the physical BLE radio connection that exists between bluz and the gateway
  2. The cloud connection, the link between bluz and the Particle cloud that rides on top of the -BLE connection.

The BLE connection is completely interrupt driven in the nrf51, and those interrupts run at the highest possible priority. Blocking the loop() CANNOT cause the BLE link loss, the interrupts simply run overtop of any user code.

The cloud connection is driven through the main context, that is, the same process that the user loop() function is called. Think of the main loop of the entire application as this:

int main() {
while(1) {

loop()

ProcessCloudEvents()

}
}

As you can imagine, blocking forever in the loop() function means the ProcessCloudEvents() will never get called. This means variable and function calls cannot be processed, OTA updates cannot take place, and any subscribed-for events would seemingly never arrive (even though they actually did and they are sitting in a buffer, they just don’t get processed).

So blocking the loop() function will definitely cause issues with the cloud connection, but never the BLE connection.

There is one more item. Blocking in the loop() function KILLS battery life. Having a while(1) { } loop will mean the CPU is running forever and that will cause bluz to draw as much current as possible. This is also not good.

@gruvin I ha slightly edited your post to reflect the above changes, just the effects of blocking. Otherwise I love the model and think building a tutorial around this would help people keep bluz in the lowest state possible.


#3

Great stuff @eric! :slight_smile:

It’s public domain, far as I’m concerned. Morph away at will and without fear of prejudice. :stuck_out_tongue:

As a mostly self-taught programmer, from back before there was any formal training accessible to me (1979ish and TRS-80s) and even later when I was considering University around 1989 – where the desktop computer had existed for a little but no one took them seriously still and the Babbage Machine was about as good as it got … it took me YEARS to finally stumble on the simple state machine pattern.

Of course, I had got the same jobs done using if/then/else etc. But I had no real pattern to guide me.Thus, every project would start out by having to solve more or less the same problem all over again. I just didn’t realise that was what was happening, per se.

I really should have gone back to varsity in the 90’s or something. But by then, I was too busy trying to get rich with a high paying job and getting married and stuff. Such is life. Luckily, around 1995, someone at CERN took a weekend out to invent the World Wide Web, before getting back to their real work. The rest is history in the making.

P.S: I took the liberty to try and edit one of your posts. D’oh!! hehe


#4

Quick question…if I’m trying to maximize battery life on a DK in a battery shield, is it detrimental to use particle.variable() in order to see the state of “important” variables?


#5

This shouldn’t have any kind of impact on battery life. If you poll extremely frequently, this will of course hurt battery life as you will be sending/receiving lots of data. But simply having the variable won’t impact anything