Sipping power... sleep modes and Swift for Arduino

One feature I have been meaning to add to S4A for a while now is sleep.

It might not sound like an important feature but it could radically change the way projects are made.

A critical distinguishing feature of S4A is that it creates true native programs for the atmega328p microcontroller, meaning you don't need to have an Arduino UNO, plugged into your laptop or desktop via USB for these programs (although of course you can).

This sets it aside from many other attempts to simplify Arduino programming in the past, that effectively run programs on a full PC/Mac then send serial commands to the board to activate it.

The next step... how do you make a project that lives on batteries?

Well, right out of the box you can do that, at least for a while. See my Hallowe'en project from last year on this blog that I made for my daughter. But that lasted a couple of days on batteries before it faded. Anything with lots of lights and motors is going to need a lot of battery, of course. But many of the more interesting projects only need to wake up sometimes and spend a lot of time waiting for input.

In that case, the best approach is to power down the CPU to sleep mode, then either wait for a fixed period of time to the next wake up (for example to sample temperature regularly) or wait until an external input wakes up the project.

In this test case, I went the latter route.

Here's a photo of the circuit:

This is a simple circuit. Mostly it's just a standard breadboard arduino, with a 16MHz crystal, two capacitors, a pullup resistor on the reset pin and all power lines as standard. We have power to the board from off screen to the right, which is here going through a multimeter to get accurate current readings. And on the left we have basically just a push button that connects to 'pin 2', keeping it low usually and raising it high when the button is depressed. Finally we have 'pin 4' connected to an LED and resistor combo for the output of the circuit.

Here's the code:

import AVR
typealias IntegerLiteralType = Pin

let led = 4
let switchPin = 2

pinMode(pin: led, mode: OUTPUT)
pinMode(pin: switchPin, mode: INPUT)


while(true) {
    digitalWrite(pin: led, value: HIGH)
    delay(milliseconds: 4000)
    digitalWrite(pin: led, value: LOW)
    delay(milliseconds: 100)

    sleepCpuUntilPin2Trigger(edgeType: RISING_EDGE)
    
    delay(milliseconds: 4000)

    digitalWrite(pin: led, value: HIGH)
    delay(milliseconds: 4000)
    digitalWrite(pin: led, value: LOW)
    delay(milliseconds: 100)
    
    lowPowerSleepCpuUntilPin2Trigger(edgeType: RISING_EDGE)
    
    delay(milliseconds: 4000)
}

What is this doing? Well, we want to see how sleep affects (reduces) power consumption. So firstly we have the beginning of a normal looking blink program. Only we are holding the LED on for 4 seconds so we can stand a chance of reading accurately with the ammeter (multimeter) or bench PSU built in current measurement. We expect to see higher current when the LED is on of course. But what does the 'arduino' (actually just an atmega328p but S4A doesn't mind) consume in normal running? Past experiments would suggest about 16 mA at 16MHz and 5v.

So I'm basically doing a rotating set of levels:

- microcontroller ON, and LED ON for 4 seconds
- go to sleep mode until the button is pressed (indefinite)...
- when button is pressed wake up the MCU but do not yet light the LED... wait 4 seconds
- relight the LED for 4 seconds, continuing the cycle

You can see this all in a video i made earlier of testing the same circuit with my bench PSU, which is easier but less accurate than my multimeter:

You can see the full cycle and see how low the power consumption goes during sleep. It reads as 2mA but in reality it's much lower, that's just the lowest the PSU can read.

Now here are some photos from my multimeter:

First with the MCU running and the LED illuminated...

About 63mA consumed.

Now after 4 seconds it goes to sleep...

zero? OK... maybe we need to increase the accuracy of the ammeter...

Switching to the 20mA setting...
1mA... ok that's a big power saving, but is it accurate?

Let's switch to the 2000uA setting (2mA) to see what we are really using...

wow

So according to my multimeter, the circuit is now only consuming around 19uA of power... 19uA!!

So for a typical AA battery (or a set of 3 to get 5v), it would last about... 20,000 to 80,000 hours... even on cheap zinc carbon batteries! With a lifetime of about three to ten years, you're not going to have to spend your life changing batteries...

Obviously in reality this circuit isn't doing anything at all, so you're going to have to wake up at least sometimes to do useful work, but suddenly idle time pretty much is the same as disconnecting the battery, except it's just one line of code...

sleepCpuUntilPin2Trigger(edgeType: RISING_EDGE)

Also, if you have a periodic wake, the timers need to keep running and that means a higher power use. The S4A AVR library uses Timer1 as a high resolution time, meaning the lowest power sleep mode is 'idle' mode. In my measurements here I'm seeing about 8mA power consumption.



Footnote: you'll see lowPowerSleepCpuUntilPin2Trigger(edgeType: RISING_EDGE) in the code, what is that? The AVR library allows you to turn down the power even more by disabling brown out detection (BOD), this is a specialised feature that most will not need but if you really want to turn down the power, you can use this.

Comments

Popular posts from this blog

Halloween LED lights on a plastic trick or treat cauldron

All about bootloaders

code signing, entitlements, bundles, sandboxes, hardened runtime, notarisation, app store security