Top‎ > ‎Atmel AVR stuff‎ > ‎

Low Power: Trying to reduce power consumption as much as possible.

My next project's going to involve running on small batteries, AA or smaller so I thought I'd do some
power consumption investigative work.  I'll use an ATTINY84a (that a suffix is important) again.

I'll just run in infinite loop on the tiny, see how much current it consumes and try to lower that.

First, I started with pretty much the worst case scenario:

                      Gnd        Vcc
                       |          |
                       +--/\/\/\--+  Floating pin(s) simulator.
                            ^
                            |
                            |
                            |
                       +----+----+
          Gnd          |    |    |         C    I
           |           |    |    |         L    S
       +---+------+    |    |    |         K    O
       |   |      |    |    |    |    |    |    |
       |   | +----|----|----|----|----|----|----|----+
       |   | |    G    P    P    P    P    P    P    |
       |   | |    N    A    A    A    A    A    A    |
       |   | |    D    0    1    2    3    4    5    |
       |   | |                                       |
     10- .1- |             ATTINY[248]4              |
     uF- uf- |                                       |
       |   | |    V    P    P    P    P    P    P    |
       |   | |    C    B    B    B    B    A    A    |
       |   | |    C    0    1    3    2    7    6    |
       |   | +----|----|----|----|----|----|----|----+
       |   |      |    |xtal|    |    |    |    |
       +---+------+    +-[]-+    R              M
           |           |    |    E              O
           |         20-  20-    S              S
          Vcc        pF-  pF-    E              I
                       |    |    T
                      Gnd  Gnd


Vcc is 4.81 volts (taken from a USB hub), run through an ammeter.  The crystal used is 20Mhz and the tiny is
configured to run at that speed.  The code is just that:

#include <avr/io.h>

int main(void) {
  CLKPR=(1<<CLKPCE); CLKPR=0;   // Full Speed!
  while(1);
}


As per default, when the µC resets all pins are in input mode.  In that circuit PA0, PA1 and PA2 are tied to
the wiper of a potentiometer to simulate floating.  The GPIO pins on those chips are so incredibly high
impedance that they'll often drift between GND and Vcc.  When they're are the exact wrong level between
the two, the input is sort of both off and on at the same time at that induces extra current.

So I adjusted the pot while looking at the ammeter and adjusted it for maximum current use.  Result:

Vcc:      4.81 volts
Current:  8.45 milli-amps.
Lifetime: 213.0 hours, 8.8 days

The "lifetime" is calculated for basic alkaline AA batteries; 1800 mAh.

So let's try to reduce consumption.

Let's remove the simulated floating inputs by disconnecting the wiper
and configure all the inputs with pull-ups:

                      Gnd        Vcc
                       |          |
                       +--/\/\/\--+  Adjusted for max. current usage.
                            ^
                            |
                            X
#include <avr/io.h>

int main(void) {
  DDRA=0;
  DDRB=0;
  PORTA=0xff;
  PORTB=0xff;
  while(1);
}

Vcc:      4.81 volts
Current:  6.65 milli-amps.
Lifetime: 270.6 hours, 11.2 days


We've reduced the current by almost %25 that was lost in those floating inputs.  No a bad start.

Let's remove that potentiometer completely:

Vcc:      4.80 volts
Current:  6.47 milli-amps.
Lifetime: 278.2 hours, 11.5 days


Proof that the pot itself wasn't responsible for most of that 2mA.

Next we're going to turn off the power of most of the µC's features one by one via the
Power Reduction Register (PRR: page 37 of the datasheet).

On my first PRR test I get a surprise:

  Add:

  PRR|=(1<<PRTIM0);             // Turn timer0 off.

Vcc:      4.80 volts
Current:  6.52 milli-amps.
Lifetime: 276.0 hours, 11.5 days


Turning off Timer zero increased power consumption by 50 µA.  Hmmm!  Not what I
expected at all.  Darn it.

  Change code to turn off timer1 only:

  PRR|=(1<<PRTIM1);             // Turn timer1 off.

Vcc:      4.80 volts
Current:  6.41 milli-amps.
Lifetime: 280.8 hours, 11.7 days


Hey!  That's better.  Reduced by 0.24 mA.  Let's try both timers:

  Both off:

  PRR|=(1<<PRTIM0);             // Turn timer0 off.
  PRR|=(1<<PRTIM1);             // Turn timer1 off.

Vcc:      4.80 volts
Current:  6.22 milli-amps.
Lifetime: 289.3 hours, 12.0 days


Ah!  Well, OK.  I guess the lesson is not to turn of timer zero by itself.

Next is the ADC:

  ADD:

  PRR|=(1<<PRADC);              // Turn ADC off.

Vcc:      4.80 volts
Current:  5.92 milli-amps.
Lifetime: 304.0 hours, 12.6 days


0.3 mA for the ADC.  Alright.

  Add:

  PRR|=PRUSI;                   // Turn USI off.

Vcc:      4.80 volts
Current:  5.85 milli-amps.
Lifetime: 307.6 hours, 12.8 days


The USI (Universal Serial Interface) apparently drained 0.07 mA.

Since we're not going to use the ADC let's turn off the Analog comparator:

  Add:

  ACSR|=(1<<ACD);               // Turn off Analog comparator.

Vcc:      4.80 volts
Current:  6.02 milli-amps.
Lifetime: 299.0 hours, 12.4 days


Another surprise!  Turning off the comparator actually increased current consumption,
and that's exactly the reverse of what I expected from the datasheet.  I had a hunch at
that time and kept the line but commented for later use.

Let's see what code we have so far:

#include <avr/io.h>

int main(void) {
  CLKPR=(1<<CLKPCE); CLKPR=0;   // Full Speed!
  DDRA=0;                       // All inputs.
  DDRB=0;                       // All inputs.
  PORTA=0xff;                   // All pull-ups to prevent floating.
  PORTB=0xff;                   // All pull-ups to prevent floating.
  PRR|=(1<<PRTIM0);             // Turn timer0 off.
  PRR|=(1<<PRTIM1);             // Turn timer1 off.
  PRR|=(1<<PRADC);              // Turn ADC off.
  PRR|=PRUSI;                   // Turn USI off.
  // ACSR|=(1<<ACD);               // Turn off Analog comparator.
  while(1);                     // Loop forever.
}

Time to reduce speed.  20Mhz is a lot.  Let's try with just the internal
8Mhz oscillator.

    $ avrdude -B 4 -c usbtiny -P usb -p attiny84 -U lfuse:w:0xe2:m
     ...
    $ showlfuse
    FUSELB Bit Description            Default            Cur  LFUSE=0xE2  226
    ------ --- ---------------------- -----------------  ----
    CKDIV8  7  Divide clock by 8      0 (programmed)      1
    CKOUT   6  Clock Output Enable    1 (unprogrammed)    1
    SUT1    5  Select Start-up time   1 (unprogrammed)    1
    SUT0    4  Select Start-up time   0 (programmed)      0
    CKSEL3  3  Select Clock source    0 (programmed)      0
    CKSEL2  2  Select Clock source    0 (programmed)      0
    CKSEL1  1  Select Clock source    1 (unprogrammed)    1
    CKSEL0  1  Select Clock source    0 (programmed)      0
    ---------------------------------------------------------
    SUT:   From Power down: 6CK, Reset delay: 14CK+64ms, Slow Rising power
    CKSEL: Calibrated 8 Mhz Oscillator


Vcc:      4.80 volts
Current:  2.99 milli-amps.
Lifetime: 602.0 hours, 25.0 days


Not surprisingly running at a lower speed decreases current consumption a lot.

Let's try 1Mhz.

   Change:

  CLKPR=(1<<CLKPCE); CLKPR=3;   // Clock/8.

Vcc:      4.80 volts
Current:  824  micro-amps
Lifetime: 2184.4 hours, 91.0 days


Great!  Now we've got a micro-controller that will run for about 3 months on AA batteries.

Do we need that much speed?  Why stop there?

  Change:

  CLKPR=(1<<CLKPCE); CLKPR=8;   // Clock/256.

Vcc:      4.80 volts
Current:  241  micro-amps.
Lifetime: 7468.8 hours, 311.2 days.


There's many many useful things I can do with a micro running at 31.25Khz.  And 10 months on
AA batteries isn't bad.

Why use the internal oscillator at all since we're that low speed?  There's a low-power 128Khz
oscillator that's running anyway, so if we use it, the calibrated one is turned off:

Change:

  CLKPR=(1<<CLKPCE); CLKPR=0;   // Full speed...

    $ avrdude -B 250 -c usbtiny -P usb -p attiny84 -U lfuse:w:0xC4:m
     ...
    $ showlfuse
    FUSELB Bit Description            Default            Cur  LFUSE=0x44  68
    ------ --- ---------------------- -----------------  ----
    CKDIV8  7  Divide clock by 8      0 (programmed)      1
    CKOUT   6  Clock Output Enable    1 (unprogrammed)    1
    SUT1    5  Select Start-up time   1 (unprogrammed)    0
    SUT0    4  Select Start-up time   0 (programmed)      0
    CKSEL3  3  Select Clock source    0 (programmed)      0
    CKSEL2  2  Select Clock source    0 (programmed)      1
    CKSEL1  1  Select Clock source    1 (unprogrammed)    0
    CKSEL0  1  Select Clock source    0 (programmed)      0
    ---------------------------------------------------------
    SUT:   From Power down: 6CK, Reset delay: 14CK, Recommended with BOD
    CKSEL: Internal 128 kHz Oscillator

Vcc:      4.80 volts
Current:  151  micro-amps.
Lifetime: 11920.5 hours, 496.6 days, 1.3 years.


Look at that!  I've broken the year barrier with a running speed of (around) 128Khz!

What the heck, I don't need that much speed!

  Change:

  CLKPR=(1<<CLKPCE); CLKPR=3;   // Low-power 128Khz clock / 8 = 16Khz

Vcc:      4.80 volts
Current:  93   micro-amps.
Lifetime: 19354.8 hours, 806.4 days, 2.2 years


Better and better.

Time to try to turn off that analog comparator again:

  Change:

  ACSR|=(1<<ACD);               // Turn off Analog comparator.

Vcc:      4.80 volts
Current:  14.3 micro-amps.
Lifetime: 125874.1 hours, 5244.7 days, 14.3 years.


Well, that made a huge difference.  Looks like turning off the comparator
is only a win at low frequencies.  No idea why.

Does the low-power internal 128Khz oscillator use more power than
the low-frequency crystal oscillator?  Hmmm...  Let's put in a 32768 Hz
watch crystal and a single load cap.

  Change:

  CLKPR=(1<<CLKPCE); CLKPR=1;   // 32768/2 = 16Khz

    $ avrdude -B 250 -c usbtiny -P usb -p attiny84 -U lfuse:w:0xD6:m

    $ showlfuse
    FUSELB Bit Description            Default            Cur  LFUSE=0xD6  214
    ------ --- ---------------------- -----------------  ----
    CKDIV8  7  Divide clock by 8      0 (programmed)      1
    CKOUT   6  Clock Output Enable    1 (unprogrammed)    1
    SUT1    5  Select Start-up time   1 (unprogrammed)    0
    SUT0    4  Select Start-up time   0 (programmed)      1
    CKSEL3  3  Select Clock source    0 (programmed)      0
    CKSEL2  2  Select Clock source    0 (programmed)      1
    CKSEL1  1  Select Clock source    1 (unprogrammed)    1
    CKSEL0  1  Select Clock source    0 (programmed)      0
    ---------------------------------------------------------
    SUT:   From Power down: 6CK, Reset delay: 14CK+4ms, Fast rising power
    CKSEL: Low Frequency Crystal Oscillator

Vcc:      4.80 volts
Current:  19.5 micro-amps.
Lifetime: 92307.6 hours, 3846.1 days, 10.5 years


So, no, the internal low-power oscillator uses less power than using
a low-frequency crystal, but this solution gives use a more stable clock.

Let's go back to the low-power internal 128Khz oscillator and try lowering Vcc:

  Change:

  $ avrdude -B 250 -c usbtiny -P usb -p attiny84 -U lfuse:w:0xC4:m

  CLKPR=(1<<CLKPCE); CLKPR=4;   // 128Khz / 16 = 8Khz.

Vcc:      3.39 volts
Current:   7.2 micro-amps.
Lifetime: 246575.3 hours, 10273.9 days, 28.1 years


Hopefully the AA batteries won't leak during that 25+ years period.

At that point I started thinking about what kind of useful projects I could build that would
still be useful at such slow speeds.

Obviously you can't do numerical computations...  Or can you?

<DevilishGrin></DevilishGrin>
Comments