1

I'm using sleep mode to turn off my device after the routine has finished executing and want to use interrupts to wake it up every 33 milliseconds. Basically, the idea is that if the routine completes in less than 33 milliseconds the processor can shutdown and save power for whatever remaining time, which will hopefully translate to reduced power consumption. I've currenlty managed to turn off my device with the sleepNow() function you see below, but the interrupt I have programmed in doesn't seem to work. The program simply goes to sleep and never wakes up. What am I doing wrong?

#include <avr/interrupt.h>
#include <avr/power.h>
#include <avr/sleep.h>

const uint16_t PERIOD = 2150;

void setup()
{
  noInterrupts();
  TCCR1A = 0;            // undo the timer config done...
  TCCR1B = 0;            // ...by the Arduino core library
  TCNT1 = 0;             // reset the timer
  OCR1A = PERIOD - 1;    // set the period
  TIMSK1 = _BV(OCIE1A);  // enable TIMERx_COMPA interrupt
  TCCR1B |= (1 << CS12); // ...and set the prescaler
  interrupts();

  Serial.begin(9600);
}

ISR(TIMER1_COMPA_vect)
{
  Serial.println(millis());  
}

void loop()
{
  Serial.println("Program is on");
  Serial.flush();
  sleepNow();
}

void sleepNow()
{

    // Choose our preferred sleep mode:
    set_sleep_mode(SLEEP_MODE_PWR_SAVE);

    // Set sleep enable (SE) bit:
    sleep_enable();

    // Put the device to sleep:
    sleep_mode();

    // Upon waking up, sketch continues from this point.
    sleep_disable();

}
6
  • Timer 1 cannot wake you up because... it's sleeping! Only Timer 2 is awake in SLEEP_MODE_PWR_SAVE. Commented Jun 16, 2016 at 15:01
  • Problem is that when I do that it never leaves the ISR and re-enters the loop. It just infinently prints out the time. It didn't use to do that before when I used IDLE and Timer 1. Commented Jun 16, 2016 at 15:11
  • What's strange is that when I move the print statement outside of the ISR function the program runs just fine. Commented Jun 16, 2016 at 15:22
  • 1
    Printing from within an ISR should be avoided, as the serial port relies on interrupts to send the data, and they are blocked inside the ISR. Commented Jun 16, 2016 at 15:46
  • Okay that makes sense, but I just have a quick housekeeping question. When I tried to move the interrupt setup into a separate method and then call it from the setup method the interrupts stop working. Is that because you NEED to setup your interrupt in the setup method? Commented Jun 16, 2016 at 15:53

1 Answer 1

1

It is certainly tempting to go into a very deep sleep in order to save power, but you still need to keep awake whatever peripheral you want to use as a wakeup source. If you want to use a 16 bit timer, there is no other choice than SLEEP_MODE_IDLE.

But it may not be that bad. In IDLE mode you can selectively disable the clock (and hence put to sleep) the peripherals you do not use. Or maybe put them all to sleep, and then wake up the few ones you do need. This is achieved with the power_*_disable() and power_*_enable() family of functions.

In the edited code below: all peripherals but the Timer 1 are disabled while the CPU sleeps. The USART is enabled only for the time it needs to send its message:

#include <avr/interrupt.h>
#include <avr/power.h>
#include <avr/sleep.h>
#include <util/delay.h>

const uint16_t PERIOD = 2150;  // 34.4 ms

void setup()
{
  // Disable all but the needed peripherals.
  power_all_disable();
  power_timer1_enable();

  // This is for debug pulses.
  pinMode(22, OUTPUT);

  // Configure Timer 1 to wake us up every 34.4 ms.
  TCCR1A = 0;            // undo the timer config done...
  TCCR1B = 0;            // ...by the Arduino core library
  TCNT1 = 0;             // reset the timer
  OCR1A = PERIOD - 1;    // set the period
  TIMSK1 = _BV(OCIE1A);  // enable TIMERx_COMPA interrupt
  TCCR1B = _BV(WGM12)    // CTC mode, TOP = OCR1A
         | _BV(CS12);    // ...and set the prescaler /256

  Serial.begin(9600);
}

ISR(TIMER1_COMPA_vect)
{
  // Send a debug pulse.
  digitalWrite(22, HIGH);
  _delay_ms(2);
  digitalWrite(22, LOW);
}

void loop()
{
  // Enable the USART only while needed.
  power_usart0_enable();
  Serial.println("Program is on");
  Serial.flush();
  power_usart0_disable();

  // And go to sleep in IDLE mode.
  sleep_mode();
}

Notice also that the ISR does not print to the serial port: it just sends a debug pulse to watch with the scope. This should be removed from production code. The delay is achieved with the avr-libc function _delay_ms() because the standard Arduino delay() would not work: it relies on Timer 0, which has been disabled.

3
  • But wouldn't this affect the functionality of the program since you are disabling the peripherals in the start of the program with power_all_disable(); since I'm not an expert on Arduino couldn't this introduce a whole bunch of new bugs? Lastly, if I am able to use very deep sleep and wake it up with the code I have above what would be the advantage of switching to the less power saving option of IDLE mode? Commented Jun 16, 2016 at 16:06
  • Also why would using a 16 bit timer force me to use IDLE? Commented Jun 16, 2016 at 16:24
  • @AngelLockhart: If you want to be on the safe side, you can disable only the peripherals you do not use. The nice thing about the IDLE mode is that you can wake up out of it. ;-) The datasheet has, on section 11.1, a table with the wakeup sources available in each sleep mode. You can see that most peripherals (“Other I/O”), including all the 16-bit timers, are only available in IDLE mode. Commented Jun 16, 2016 at 17:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.