An active Atmega328 consumes over 10mA of current at around 5V. To reduce power consumption various techniques can be used. Using all the techniques below I was able to reduce power consumption to less than 0.001 mA (my multimeter reads 0.000mA).
Turn off ADC
Set ADEN bit to 1 in register ADCSRA:
ADCSRA &= ~(_BV(ADEN));
Turn off Analog Comparator
Set ACD bit to 1 in ACSR
ACSR &= ~(_BV(ACD));
Turn off the Watchdog Timer
Straightforward but first remember to unset the flags which are set on Reset.
#include <avr/wdt.h> MCUSR = 0; wdt_disable();
Turn off power to peripherals
Set bits in the PRR (Power Reduction Register)
PRR = 0b11001111;
Bit | Label | Purpose |
---|---|---|
7 | PRTWI | Shut down TWI by writing 1. |
6 | PRTIM2 | Shut down Timer/Counter2 in synch mode (AS2 = 0). |
5 | PRTIM0 | Shut down Timer/Counter0 by writing 1. |
4 | Reserved (set to 0). | |
3 | PRTIM1 | Shut down Timer/Counter1. |
2 | PRSPI | Shut down SPI by writing 1. |
1 | PRUSART0 | Shut down USART by writing 1. |
0 | PRADC | Shut down ADC. ADC must be disabled before shut down. |
Sleep whenever possible
Setup the chip in a particular mode. Can be done in setup.
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
Sleep Mode | Meaning |
---|---|
SLEEP_MODE_IDLE | Turn off CPU clock |
SLEEP_MODE_ADC | ADC Noise Reduction. Turn of I/O clock in addition. |
SLEEP_MODE_PWR_DOWN | Power Down. All clocks off. Cannot wake up from timer |
SLEEP_MODE_PWR_SAVE | Allows Timer2 to be running. Power Down + Timer. |
#include <avr/sleep.h> set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Setup Power mode cli(); // Disable interrupts // Do whatever if needed to confirm we need to go to sleep sleep_enable(); // Enable sleep (needed to prevent us going to sleep by accident) sleep_bod_disable(); // Optionally disable the BOD sei(); // Enable interrupts just before going to sleep sleep_cpu(); // Go to sleep sleep_disable(); // When woken up - disable sleep to be on the safe side
Disable BOD when sleeping
This should have been really simple but it’s unnecessarily complex (it seems to me) requiring first setting and then unsetting a bit. The below function may be used (as in example above).
#define sleep_bod_disable() \ { \ uint8_t tempreg; \ __asm__ __volatile__("in %[tempreg], %[mcucr]" "\n\t" \ "ori %[tempreg], %[bods_bodse]" "\n\t" \ "out %[mcucr], %[tempreg]" "\n\t" \ "andi %[tempreg], %[not_bodse]" "\n\t" \ "out %[mcucr], %[tempreg]" \ : [tempreg] "=&d" (tempreg) \ : [mcucr] "I" _SFR_IO_ADDR(MCUCR), \ [bods_bodse] "i" (_BV(BODS) | _BV(BODSE)), \ [not_bodse] "i" (~_BV(BODSE))); \ }