ATMega328P sleep mode

I have seen several other posts and tutorials regarding putting an ATMEga328P to sleep and I have tried to follow those but my consumption is still quite high. My sketch is heavily based on a sparkfun tutorial I found but I can not replicate the results. My project requires the use of a small lipo battery with a solar cell to recharge it so I need to get the micro into the deepest sleep possible to conserve battery. I have moved the micro off the Arduino board and bread boarded it so I can accurately measure consumption. I loaded a boot loader from the Arduino tutorial which runs an 8MHz internal clock and I use a 32.768 external crystal for timer2. I only need it to wake up if timer2 overflows or if it gets an external interupt so I have tried to shut down everything else. My goal is to get this into the single digit uA range and at the moment I am only getting down to 1.2 mah. I thought that maybe my DMM was the issue so I bought a second one which reads the same. I have made sure to remove everything from the board including the programming leads. To check current I connect the battery GND directly to the bread board and I put my DMM inline with VCC. I posted my sketch below and hope that someone has some suggestions.

#include <avr/sleep.h> //Needed for sleep_mode
#include <avr/power.h> //Needed for powering down perihperals such as the ADC/TWI and Timers

#define TRUE 1
#define FALSE 0

int show_the_time = FALSE;

volatile long seconds = 0;

//The very important 32.686kHz interrupt handler
SIGNAL(TIMER2_OVF_vect){
seconds++;
}

//The interrupt occurs when you push the button
ISR(INT1_vect){
if(show_the_time == FALSE)show_the_time = TRUE;
}

//The interrupt occurs when you push the button
ISR(INT0_vect){
if(show_the_time == FALSE)show_the_time = TRUE;
}

void setup() {
//To reduce power, setup all pins as inputs with no pullups
for(int x = 1 ; x < 18 ; x++){
pinMode(x, INPUT);
digitalWrite(x, LOW);
}

//Power down various bits of hardware to lower power usage
set_sleep_mode(SLEEP_MODE_PWR_SAVE);
sleep_enable();

//Shut off ADC, TWI, SPI, Timer0, Timer1

ADCSRA &= ~(1<<ADEN); //Disable ADC
ACSR = (1<<ACD); //Disable the analog comparator
DIDR0 = 0x3F; //Disable digital input buffers on all ADC0-ADC5 pins
DIDR1 = (1<<AIN1D)|(1<<AIN0D); //Disable digital input buffer on AIN1/0

power_twi_disable();
power_spi_disable();
power_usart0_disable();
power_timer0_disable();
power_timer1_disable();
//power_timer2_disable(); //Needed for asynchronous 32kHz operation

//Setup TIMER2
TCCR2A = 0x00;
TCCR2B = (1<<CS22)|(1<<CS21)|(1<<CS20); //Set CLK/1024 or overflow interrupt every 8s
ASSR = (1<<AS2); //Enable asynchronous operation, 32kHz xtal needed
TIMSK2 = (1<<TOIE2); //Enable the timer 2 interrupt

//Setup external INT1 (pin3) interrupt
EICRA = (1<<ISC11)|(1<<ISC01)|(1<<ISC10)|(1<<ISC00); //Interrupt on rising edge
EIMSK = (1<<INT0)|(1<<INT1); //Enable INT interrupts
//need pull downs if active high, active low can enable internal pulllups
//digitalWrite(3, HIGH);
//digitalWrite(2, HIGH);

sei(); //Enable global interrupts
}

void loop() {
sleep_mode(); //Stop everything and go to sleep. Wake up if the Timer2 buffer overflows or if you hit the button

// 10000 ~= 5 hours
if(seconds > 3){
reset_5s();
}

if(show_the_time == TRUE) {
reset_5s();
}
}

void reset_5s(){

pinMode(A0, OUTPUT);
digitalWrite(A0, LOW); // needs to be LOW
fake_msdelay(1000);
digitalWrite(A0, HIGH); // needs to be HIGH
pinMode(A0, INPUT);

seconds = 0;
show_the_time = FALSE;
}

//This is a not-so-accurate delay routine
//Calling fake_msdelay(100) will delay for about 100ms
//Assumes 8MHz clock
void fake_msdelay(int x){
for( ; x > 0 ; x--)
fake_usdelay(1000);
}

//This is a not-so-accurate delay routine
//Calling fake_usdelay(100) will delay for about 100us
//Assumes 8MHz clock
void fake_usdelay(int x){
for( ; x > 0 ; x--) {
asm("nop\n\t");
asm("nop\n\t");
asm("nop\n\t");
asm("nop\n\t");
asm("nop\n\t");
asm("nop\n\t");
asm("nop\n\t");
}
}

so much kinder to wrap it in [ code ] ... [ / code ] :slight_smile:

#include <avr/sleep.h> //Needed for sleep_mode
#include <avr/power.h> //Needed for powering down perihperals such as the ADC/TWI and Timers

#define TRUE 1
#define FALSE 0

int show_the_time = FALSE;

volatile long seconds = 0;

//The very important 32.686kHz interrupt handler
SIGNAL(TIMER2_OVF_vect){
  seconds++;
}

//The interrupt occurs when you push the button
ISR(INT1_vect){
  if(show_the_time == FALSE)show_the_time = TRUE;
}

//The interrupt occurs when you push the button
ISR(INT0_vect){
  if(show_the_time == FALSE)show_the_time = TRUE;
}

void setup() {                
  //To reduce power, setup all pins as inputs with no pullups
  for(int x = 1 ; x < 18 ; x++){
    pinMode(x, INPUT);
    digitalWrite(x, LOW);
  }
 
  //Power down various bits of hardware to lower power usage  
  set_sleep_mode(SLEEP_MODE_PWR_SAVE);
  sleep_enable();

  //Shut off ADC, TWI, SPI, Timer0, Timer1

  ADCSRA &= ~(1<<ADEN); //Disable ADC
  ACSR = (1<<ACD); //Disable the analog comparator
  DIDR0 = 0x3F; //Disable digital input buffers on all ADC0-ADC5 pins
  DIDR1 = (1<<AIN1D)|(1<<AIN0D); //Disable digital input buffer on AIN1/0
  
  power_twi_disable();
  power_spi_disable();
  power_usart0_disable();
  power_timer0_disable();
  power_timer1_disable();
  //power_timer2_disable(); //Needed for asynchronous 32kHz operation

  //Setup TIMER2
  TCCR2A = 0x00;
  TCCR2B = (1<<CS22)|(1<<CS21)|(1<<CS20); //Set CLK/1024 or overflow interrupt every 8s
  ASSR = (1<<AS2); //Enable asynchronous operation, 32kHz xtal needed
  TIMSK2 = (1<<TOIE2); //Enable the timer 2 interrupt

  //Setup external INT1 (pin3) interrupt
  EICRA = (1<<ISC11)|(1<<ISC01)|(1<<ISC10)|(1<<ISC00); //Interrupt on rising edge
  EIMSK = (1<<INT0)|(1<<INT1); //Enable INT interrupts
  //need pull downs if active high, active low can enable internal pulllups
  //digitalWrite(3, HIGH); 
  //digitalWrite(2, HIGH); 

  sei(); //Enable global interrupts
}

void loop() {
  sleep_mode(); //Stop everything and go to sleep. Wake up if the Timer2 buffer overflows or if you hit the button
  
  // 10000 ~= 5 hours
  if(seconds > 3){
    reset_5s();
  }
  
  if(show_the_time == TRUE) {
    reset_5s();
  }
}

void reset_5s(){
  
  pinMode(A0, OUTPUT); 
  digitalWrite(A0, LOW); // needs to be LOW
  fake_msdelay(1000);
  digitalWrite(A0, HIGH); // needs to be HIGH
  pinMode(A0, INPUT); 
  
  seconds = 0;
  show_the_time = FALSE;
}

//This is a not-so-accurate delay routine
//Calling fake_msdelay(100) will delay for about 100ms
//Assumes 8MHz clock
void fake_msdelay(int x){
  for( ; x > 0 ; x--)
    fake_usdelay(1000);
}

//This is a not-so-accurate delay routine
//Calling fake_usdelay(100) will delay for about 100us
//Assumes 8MHz clock
void fake_usdelay(int x){
  for( ; x > 0 ; x--) {
    __asm__("nop\n\t"); 
    __asm__("nop\n\t"); 
    __asm__("nop\n\t"); 
    __asm__("nop\n\t"); 
    __asm__("nop\n\t"); 
    __asm__("nop\n\t"); 
    __asm__("nop\n\t"); 
  }
}

Sorry about that :blush:, I will make sure to do that in the future.

Here's a basic sketch that perhaps you can use parts of.

//ATmega328P sleep demo
//
//Wire the usual Arduino LED from PB5 (DIP pin 19, Arduino pin 13) to ground, through
//an appropriate current-limiting resistor.
//Wire a tactile button switch from INT0/PD2 (DIP pin 4, Arduino pin 2) to ground.
//
//The sketch will blink the LED three times, then go to sleep.
//Press the button to generate an interrupt to wake the MCU.
//Best on a breadboard.  Note that voltage regulators and other circuitry on a real Arduino
//will continue to draw current and so the current consumed by just the MCU will be
//difficult to measure.
//While sleeping, I measure < 0.5uA supply current.
//
//Jack Christensen 11Sep2011
//
//Scotchware License: If we meet some day, and you think this is worth it, you can buy me a scotch.

#include <avr/sleep.h>

byte intCounter, adcsra, mcucr1, mcucr2;

void setup(void)
{
    for (byte i=0; i<20; i++) {
        pinMode(i, INPUT);        //make all pins input pins
        digitalWrite(i, HIGH);    //with pullup resistors to minimize power consumption
    }
    pinMode(13, OUTPUT);          //except LED pin
    Serial.begin(115200);
    EICRA = 0x00;                 //configure INT0 to trigger on low level
}

void loop(void)
{
    Serial.print("interrupts: ");
    Serial.println(intCounter, DEC);
    for (byte i=0; i<3; i++) {
        digitalWrite(13, LOW);
        delay(1000);
        digitalWrite(13, HIGH);
        delay(1000);
    }
    digitalWrite(13, LOW);
    sleep_enable();
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    EIMSK |= _BV(INT0);            //enable INT0
    adcsra = ADCSRA;               //save the ADC Control and Status Register A
    ADCSRA = 0;                    //disable ADC
    cli();
    mcucr1 = MCUCR | _BV(BODS) | _BV(BODSE);  //turn off the brown-out detector
    mcucr2 = mcucr1 & ~_BV(BODSE);
    MCUCR = mcucr1;
    MCUCR = mcucr2;
    sei();                         //ensure interrupts enabled so we can wake up again
    sleep_cpu();                   //go to sleep
    sleep_disable();               //wake up here
    ADCSRA = adcsra;               //restore ADCSRA
    digitalWrite(13, LOW);
    pinMode(13, OUTPUT);           //so we can blink the LED some more
}

ISR(INT0_vect)
{
    intCounter++;
    EIMSK &= ~_BV(INT0);           //one interrupt to wake up only
}

That sketch works MUCH better than what I was using. I am reading 98uA which is a big step forward for me. I will work off of this and keep you posted on my progress. Thanks a lot for posting!

Cool, but at the same time 98uA is a lot higher than what I was reading. I just had an ATmega328P on a breadboard, with only the LED and switch hooked to it. (In addition to the essentials that is: a crystal, bypass caps, reset switch). Might there be something else drawing current?

yeah, I know I am doing something wrong but I cant quite figure it out yet. My breadboard is bare except for the 328P, capacitors, 32.768 crystal and a 10k resistor on the reset pin. I remove the programmer completely and then power it with a lipo battery. What boot loader are you using? I have an ISP programmer, do I need to use that to change any fuse settings?

What's the 32.768kHz crystal for, are you using the Low-Freq Xtal Osc as the system clock?

I have the 10K pullup on the reset pin too, that should not pose a problem. I'm using the standard Uno Optiboot bootloader. Actually I'm using westfw's beta, but should be same difference for this purpose. Standard Uno fuses too:

uno.bootloader.low_fuses=0xff
uno.bootloader.high_fuses=0xde
uno.bootloader.extended_fuses=0x05

The brownout detect consumes about 20uA++. The default bootloader enables them.
You can either choose to hardware disable them or do it on the fly using software.

But, somewhere else is sucking that extra 70uA++.

This is great, with your suggestions I got it down to 0.2uA! I was orginally using the internal 8MHz bootloader found in the Arduino tutorial, I changed that to the standard UNO bootloader and replaced the crytal with a 16MHz which did the trick. No matter what I did with the other bootloader I could not get consumption below 1mah. I am using the 32.768 khz crystal for a timer which I am hoping to get to run for as much as a year or more before it overflows and wakes up.

What are you going to do and how important is reasonable exact timing for your project?

I have 2 applications for this and neither of them require any kind of precision timing. The first application is for a GSM modem I am using the gets hung periodiclly and it is in a remote location so it cant be manually reset. The modem runs every few hours to transmit some data so I am using this as an external watchdog. So I will set the Mega328 to look for an interupt from the modem and if it does not get one within 24 hours it indicates that it got hung and it will reboot it.

The second application is basically being used as a simple timer which will wake up in say 18 months. It will take a pin high which triggers some events and then its work is done. Timing is not critical at all and if it fires a few days early or late it doesn't matter. I know most people will say to just use a timer but I think this will work better for my overall needs.

Just wanted to add a note that I found a problem in Jack Christensen's code above. If the button is held down while powering down, the power-up interrupt will occur before the device goes to sleep, turning it's own interrupt off and leaving it in an inwakeable state.

All I've done is move the EIMSK |= _BV(INT0); after the cli(); command. Modified code below:

//ATmega328P sleep demo
//
//Wire the usual Arduino LED from PB5 (DIP pin 19, Arduino pin 13) to ground, through
//an appropriate current-limiting resistor.
//Wire a tactile button switch from INT0/PD2 (DIP pin 4, Arduino pin 2) to ground.
//
//The sketch will blink the LED three times, then go to sleep.
//Press the button to generate an interrupt to wake the MCU.
//Best on a breadboard.  Note that voltage regulators and other circuitry on a real Arduino
//will continue to draw current and so the current consumed by just the MCU will be
//difficult to measure.
//While sleeping, I measure < 0.5uA supply current.
//
//Jack Christensen 11Sep2011
//
//Scotchware License: If we meet some day, and you think this is worth it, you can buy me a scotch.

#include <avr/sleep.h>

byte intCounter, adcsra, mcucr1, mcucr2;

void setup(void)
{
    for (byte i=0; i<20; i++) {
        pinMode(i, INPUT);        //make all pins input pins
        digitalWrite(i, HIGH);    //with pullup resistors to minimize power consumption
    }
    pinMode(13, OUTPUT);          //except LED pin
    Serial.begin(115200);
    EICRA = 0x00;                 //configure INT0 to trigger on low level
}

void loop(void)
{
    Serial.print("interrupts: ");
    Serial.println(intCounter, DEC);
    for (byte i=0; i<3; i++) {
        digitalWrite(13, LOW);
        delay(1000);
        digitalWrite(13, HIGH);
        delay(1000);
    }
    digitalWrite(13, LOW);
    sleep_enable();
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    adcsra = ADCSRA;               //save the ADC Control and Status Register A
    ADCSRA = 0;                    //disable ADC
    cli();
    EIMSK |= _BV(INT0);            //enable INT0
    mcucr1 = MCUCR | _BV(BODS) | _BV(BODSE);  //turn off the brown-out detector
    mcucr2 = mcucr1 & ~_BV(BODSE);
    MCUCR = mcucr1;
    MCUCR = mcucr2;
    sei();                         //ensure interrupts enabled so we can wake up again
    sleep_cpu();                   //go to sleep
    sleep_disable();               //wake up here
    ADCSRA = adcsra;               //restore ADCSRA
    digitalWrite(13, LOW);
    pinMode(13, OUTPUT);           //so we can blink the LED some more
}

ISR(INT0_vect)
{
    intCounter++;
    EIMSK &= ~_BV(INT0);           //one interrupt to wake up only
}