3

I need to put my arduino into the sleep mode in order to consume less power. My arduino receives IR data and do some work with it, but I need to make it sleep until there is no data. If the data has appeared it should wake up and do something with data and go back to sleep mode.
Is there any way to do this.
I would be grateful for any help.

3

2 Answers 2

7
+50

This is what I have managed to achieve ...


Library

IRLib - GitHub

Version 1.51 March 2015 Copyright 2013-2015 by Chris Young http://tech.cyborg5.com/irlib/

This library is a major rewrite of IRemote by Ken Shirriff which was covered by GNU LESSER GENERAL PUBLIC LICENSE ...


Code

Modified code (from the demo IRrecord example):

/* Example program for from IRLib – an Arduino library for infrared encoding and decoding
 * Version 1.5  June 2014
 * Copyright 2014 by Chris Young http://cyborg5.com
 * Based on original example sketch for IRremote library 
 * Version 0.11 September, 2009
 * Copyright 2009 Ken Shirriff
 * http://www.righto.com/
 */

/*
 * IRrecord: records IR signals 
 * An IR detector/demodulator must be connected to the input RECV_PIN.
 * Record a value by pointing your remote at the device 
 */

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

const byte LED_PIN = 8;
const byte RECV_PIN = 11;
const unsigned long BAUD_RATE = 115200;
const unsigned long NO_ACTIVITY_TIMEOUT = 2000;  // milliseconds

IRrecv My_Receiver(RECV_PIN);
IRdecode My_Decoder;
IRsend My_Sender;
/*
 * Because this version of the library separated the receiver from the decoder,
 * technically you would not need to "store" the code outside the decoder object
 * for this overly simple example. All of the details would remain in the object.
 * However we are going to go ahead and store them just to show you how.
 */
// Storage for the recorded code
IRTYPES codeType;          // The type of code
unsigned long codeValue;   // The data bits if type is not raw
int codeBits;              // The length of the code in bits

// These values are only stored if it's an unknown type and we are going to use
// raw codes to resend the information.
unsigned int rawCodes[RAWBUF]; // The durations if raw
int rawCount;                   //The number of interval samples

bool GotOne, GotNew; 

// added by Nick Gammon
EMPTY_INTERRUPT (PCINT0_vect);

void setup()
{
  GotOne=false; GotNew=false;
  codeType=UNKNOWN; 
  codeValue=0; 
  Serial.begin(BAUD_RATE);
  while(!Serial);//delay for Leonardo
  Serial.println(F("Send a code from your remote and we will record it."));
  Serial.println(F("Type any character and press enter. We will send the recorded code."));
  My_Receiver.enableIRIn(); // Start the receiver
}

// Stores the code for later playback
void storeCode(void) {

  // added by Nick Gammon
  pinMode (LED_PIN, OUTPUT);
  digitalWrite (LED_PIN, HIGH);

  GotNew=true;
  codeType = My_Decoder.decode_type;
  if (codeType == UNKNOWN) {
    Serial.println("Received unknown code, saving as raw");
    // To store raw codes:
    // Drop first value (gap)
    // As of v1.3 of IRLib global values are already in microseconds rather than ticks
    // They have also been adjusted for overreporting/underreporting of marks and spaces
    rawCount = My_Decoder.rawlen-1;
    for (int i = 1; i <=rawCount; i++) {
      rawCodes[i - 1] = My_Decoder.rawbuf[i];
    };
    My_Decoder.DumpResults();
    codeType=UNKNOWN;
  }
  else {
    Serial.print(F("Received "));
    Serial.print(Pnames(codeType));
    if (My_Decoder.value == REPEAT) {
      // Don't record a NEC repeat value as that's useless.
      Serial.println(F("repeat; ignoring."));
     }
     else {
       codeValue = My_Decoder.value;
       codeBits = My_Decoder.bits;
     }
     Serial.print(F(" Value:0x"));
     Serial.println(My_Decoder.value, HEX);
  }

  // added by Nick Gammon
  pinMode (LED_PIN, INPUT);
  digitalWrite (LED_PIN, LOW);

}


void loop() {

  // added by Nick Gammon
  static unsigned long lastActivity;  // when we last woke
  // sleep if no activity for a while
  if (millis () - lastActivity >= NO_ACTIVITY_TIMEOUT)
    {
    Serial.flush ();  // wait for Serial to finish outputting
    Serial.end ();    // shut down Serial
    noInterrupts ();  // timed sequence coming up

    // pin change interrupt for D11
    PCMSK0 |= bit (PCINT3);  // want pin 11
    PCIFR  |= bit (PCIF0);   // clear any outstanding interrupts
    PCICR  |= bit (PCIE0);   // enable pin change interrupts for D8 to D13

    set_sleep_mode (SLEEP_MODE_STANDBY);  
    sleep_enable();

    byte old_ADCSRA = ADCSRA;
    // disable ADC to save power
    ADCSRA = 0;  
    power_all_disable ();  // turn off various modules
    interrupts ();
    sleep_cpu ();  
    sleep_disable();
    power_all_enable ();   // enable modules again
    ADCSRA = old_ADCSRA;   // re-enable ADC conversion
    Serial.begin(BAUD_RATE);
    lastActivity = millis ();
    }  // end of no activity for a couple of seconds


 if (My_Receiver.GetResults(&My_Decoder)) {
    My_Decoder.decode();
    GotOne=true;
    storeCode();
    delay(500);
    My_Receiver.resume(); 
  }
}

Explanation

Main changes are in loop (near the bottom) where it sleeps if there is no activity for two seconds. This "no activity" test gives it time to catch an incoming pulse stream.

Before sleeping it turns off Serial (to save power), and sets up to detect the pin-change interrupt. It also turns off the ADC, and calls power_all_disable to disable various internal modules.

Note that it uses pin-change interrupts on Arduino pin D11 (pin 17 on the chip). For a different processor than the Atmega328P, or a different input pin, you would have to change which pin-change interrupt is used.

I found that I had to use SLEEP_MODE_STANDBY rather than SLEEP_MODE_PWR_DOWN, otherwise it seemed to take too long to wake up and handle the incoming pulse.

For testing, I flashed D8 pin to make sure it was working when hooked up in low-power configuration.


Power consumption

I tested with an AX-1838T IR-receiver that I had to hand (same pin-outs as in the Adafruit article).

Measured stand-alone (ie. not connected to the Arduino) it consumed 630 µA, so therefore we could not expect lower power consumption than that.

Connected up to a "bare-bones" board (ie. just the Atmega328P chip, decoupling capacitors, etc.) I measured 1.158 mA when idle.

Bare bones board

My tests on my page about power indicate that SLEEP_MODE_STANDBY uses 480 µA more power than SLEEP_MODE_PWR_DOWN, so this sounds about right.

630 + 480 = 1110 µA

You might be able to save more power by operating at a lower voltage. This particular receiver can operate at a lower voltage. As an indication, I tried the setup at 3.3 V (which is out of spec for the processor, but whatever, I was just testing) and it consumed 978 µA when idle. This is a modest saving over the 5 V test.


References

7
  • oh my god, this is an answer! I need time to understand and test it ! Thanks a lot Nick ! Sep 22, 2015 at 11:25
  • 1
    Great page. About the low power mode: the low frequency clock is useful in scenarios where one has to do relatively frequent operations, like debouncing: the debouncing can happen at low frequency and then the clock gets cranked up only if the wakeup was real. So that a noisy environment doesn't kill the battery. Sep 22, 2015 at 20:02
  • LOL !!! Nick, you are the author of that page, is really great !!! I didn't notice it. Sep 22, 2015 at 20:14
  • 1
    @IgorStoppa - indeed, however in this particular case the IR receiver uses the bulk of the power. Still, it may well be possible to get a lower powered one.
    – Nick Gammon
    Sep 22, 2015 at 21:21
  • 1
    I didn't get why you sleep and the next command is power all enabled - the sleep call is the one where it stops, maybe for hours.
    – Nick Gammon
    Sep 23, 2015 at 21:14
-1

Connect the IR receiver to INT0 or INT1 (pins 2 or 3 respectively). You can use the attachInterrupt function. E.g. attachInterrupt(0, yourfunctionname, LOW). The function being called can be empty, if you only use it to wake up the MCU.

To wake from sleep you need to use the LOW trigger mode. Once waken up you probably want to remove the interrupt handler (detachInterrupt), or it will swamp the cpu with interrupts.

(Note that most IR receiver will still use something like 3mA.)

5
  • It has been confirmed by Atmel that any level change will wake the processor from sleep. See Interrupts for a quote of their email.
    – Nick Gammon
    Jul 5, 2015 at 3:00
  • @NickGammon I never got it to work on anything other than LOW trigger. The datasheet says in table 10.1, note 3; For INT1 and INT0, only level interrupt. The example on the page you link to also only uses LOW triggers.
    – Gerben
    Jul 5, 2015 at 11:39
  • 1
    Most of my examples were written before someone on the Arduino forum raised the issue. Until then I had believed the datasheet. I raised a ticket at Atmel and got this response: "Our design team has confirmed that “Note-3 mentioned under Table 10-1” is a datasheet bug. So you can use any type of interrupt (Rising edge/ Falling edge / Low level / Any logical change) to wake up from sleep mode. Sorry for the inconvenience caused."
    – Nick Gammon
    Jul 5, 2015 at 20:39
  • For example see this thread External RTC to wake Arduino up on interrupt. The poster there said, after I recommended changing from LOW to FALLING: "Wow. Nick you were right. Tried it with FALLING and it works like a charm. Thanks!" As you noted @Gerben, using LOW has its own problems.
    – Nick Gammon
    Jul 5, 2015 at 20:46
  • I guess I need to give it another try on my next project. Thanks
    – Gerben
    Jul 6, 2015 at 12:25

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.