mySleep Function with LowPowerSleep and WDT+SerialRx+PCI wake up (mod. of Nick)

Hello,

inspired by the posts of Nick Gammon eg. here, I thougt about creating a mySleep function where I can set the SleepMode, Wake sources (eg. Serial Rx, any PCI and WDT with periode of ms to stleep) and disabling all external ( eg. RTC, Sensors) and internal Hardware (eg. ADC, TWI).

The idea and difference to Nicks Code is saw is:

  • disable all ext. and int. Hardware at the very beginning of mySleep Function once,
  • then just once set the sleepMode ,
  • set the WDT Periode just ones and
  • then enable sleep_enable (); sleep_cpu (); in a while loop till the toSleep ms is smaler than the current WDT Periode.
  • Now continue with the next smaler WDT Periode...
  • till toSleep ms is smaler 16ms.
  • now at the end of mySleep Power up all int. and ext. Hardware just once at the end and go back to main loop.

Is this possible?

I have programmed it this way but have the problem, that my Meag2560* does not go to sleep or is immediately woken up and thus mySleep() returns very fast.

I would be very happy about some programming hints! Thanks a lot in advance.

*(PS: My Arduino Mega Board is "tuned" to consume only 77uA in LowPowerSleep with BOD and WDT on)

//### mySleep set wake sources, switch off Hardware and sleep uC (for given Periode / till Interrupt) ##
unsigned long mySleep(byte sleep_mode, long toSleep, bool wakeBySerialRx, byte pci_pins[] ,\
                      byte pin_cnt, byte extHwSet, byte intHwSet){
// Sleep Function, set: SleepMode, WDT Sleep Periode, if Serial waiks up*, PCI Pins to wake up uC*,
//                      Settings for external* and internal Hardware shutdown*
// *) ... not yet (fully) implemented
  
  long recent_sleeped = 0;                      // in recent mySleep call actual sleeped ms (in low Power + delay)
                                                // is -1 if WDT was off => no information abaut sleeped ms! (check RTC)
  
  byte oldSREG = SREG;                          // Save old interrupt state
  noInterrupts();                               // disable Interrupts
  sleep_en = 1;                                 // set save-way
  SREG = oldSREG;                               // restore old interrupt state (enable)
  
  PowerDownExternalHardware( extHwSet);     // shut down associated ext. Harware
  
  PowerDownInternalHardware( intHwSet);     // shut down associated int. Harware

  if((toSleep > 0) && (sleep_mode > 0)){ // enable wake by WDT only if WDT Periode is set
  // setup wdt to periode and sleep x times till periode is smaler then the WDT Sleep Periode

    // Set Sleep Mode
    byte oldSREG = SREG;                    // Save old interrupt state
    noInterrupts();                         // disable Interrupts
    switch (sleep_mode){
    //case 0: break; //no sleep mode, stay on, only delay
    case 1:  set_sleep_mode (SLEEP_MODE_IDLE);        break;
    case 2:  set_sleep_mode (SLEEP_MODE_ADC);         break;
    case 3:  set_sleep_mode (SLEEP_MODE_PWR_SAVE);    break;
    case 4:  set_sleep_mode (SLEEP_MODE_EXT_STANDBY); break;
    case 5:  set_sleep_mode (SLEEP_MODE_STANDBY);     break;
    //case 6: == default:
    default: set_sleep_mode (SLEEP_MODE_PWR_DOWN);  // Sleep Mode (lowest)   
    }//switch
    SREG = oldSREG;                         // restore old interrupt state (enable)
    
    WDT_while_loop(toSleep, recent_sleeped, WDT_8000MS); 
    WDT_while_loop(toSleep, recent_sleeped, WDT_1000MS); 
    WDT_while_loop(toSleep, recent_sleeped, WDT_128MS);  
    WDT_while_loop(toSleep, recent_sleeped, WDT_16MS);   // Sleep in WDT_Periode as long as toSleep > WDT_Periode
    
    // indicate that not sleeping anymore
    oldSREG = SREG;                       // Save old interrupt state
    noInterrupts();                       // disable Interrupts
    is_sleeping = 0;                      // indicate for ISR that NOT sleeping anymore
    SREG = oldSREG;                       // restore old interrupt state (enable)

    // 'Waste' rest time in Delay OR return early(er) to loop? (more Power saving possible with IDEL MODE or ...??)
    //byte shorten_sleep = 3;                   // ms to shorten sleep (only for this delay part, not for WDT part! 
    while(toSleep > shorten_sleep){
      toSleep -= 1;
      delayMicroseconds(DELAY_1SEC);                // correct measurement needed, then DELAY_1SEC [us]
      total_sleeped += 1;
      recent_sleeped += 1;                          // in this mySleep call actual sleeped ms 
    }
    
  }// if((toSleep > 0) && (sleep_mode > 0))
 
  else if(toSleep < 0){                   // no WDT Periode was given =>don't wake periodicly
                                             // wake only by Interrtupt from PCI, ADC, Serial RX ...
    // start sleeping...
    // disable sleeping after Interrupt (or better disable sleep in ISRs?)
     byte oldSREG = SREG;                    // Save old interrupt state
     noInterrupts();                         // disable Interrupts
     bool sleep_en_c = sleep_en;             // get local copie save
     SREG = oldSREG;                         // restore old interrupt state (enable)

     if(sleep_en_c){
        
        recent_sleeped = -1;                  // Sleeped ms are unknown! 
        
        noInterrupts();                       // disable Interrupts
         
        //setSleepMode(sleep_mode);           
        
        // for now just power down
        set_sleep_mode (SLEEP_MODE_PWR_DOWN);  
        
        is_sleeping = 1;                      // indicate for ISR that sleep is activ 
        
        sleep_enable ();                      
        interrupts ();                       
        sleep_cpu ();                                    
        //sleeps here...
        
        //...wakes in ISR and comes back here.
        
        // check if(is_sleeping) ?? NOT NEEDED HERE!
        sleep_disable ();                     
        // indicate that not sleeping anymore
        oldSREG = SREG;                       
        noInterrupts();                      
        is_sleeping = 0;                      // indicate for ISR that NOT sleeping anymore
        SREG = oldSREG;                       // restore old interrupt state (enable)
        
      
     }//if(sleep_en_c)
  }//else
   
  PowerUpInternalHardware(0b1111111);      // internal Hardware back on
  PowerUpExternalHardware(0b1111111);      // external Hardware back ton

  return recent_sleeped;                   // in this mySleep call actual sleeped ms, -1 unknown because WDT was not activ;
}// mySleep()





//### WDT while Loop (waste Time in sleep Mode with WDT on till toSleep ms is < WDT Intervall)##
void WDT_while_loop(long &toSleep, long &recent_sleeped, unsigned int wdt_intervall){
  if(toSleep >= wdt_intervall){                   
    
      byte oldSREG = SREG;                    // Save old interrupt state
      noInterrupts();                         // disable Interrupts
      bool sleep_en_c = sleep_en;             // get local copie save
      SREG = oldSREG;                         // restore old interrupt state (enable)
      
      byte wdt_periode = 0;                   // set WDT periode (see Datasheet)
      switch (wdt_intervall){
        case 10000 ... 6500:  wdt_periode = 0b100001; break;  // 8sec
        // skip 4 and 2 sec
        case 1500 ... 600:    wdt_periode = 0b000110; break;  // 1sec
        // skip 512, 256ms
        case 200 ... 80:      wdt_periode = 0b000011; break;  // 128ms
        //skip 64, 32ms
        //case 4 ... 30:      wdt_periode = 0b000000; break;  // 16ms
        default:              wdt_periode = 0b000000; break;  // 16ms
      }
      
      myWatchdogEnable(wdt_periode);           // set to wdt periode and enable
      
      while(sleep_en_c && toSleep >= wdt_intervall){   // sleep for x * 8sec
        toSleep -= wdt_intervall;
         
        // final prepare sleeping...
        noInterrupts();                       
        is_sleeping = 1;                      // indicate for ISR that sleep is activ 
        digitalWrite (LED_AWAKE, LOW);        // Idicate CPU is off
        //myWatchdogEnable(wdt_periode);           // set to wdt periode and enable
        set_sleep_mode (SLEEP_MODE_STANDBY);
        sleep_enable ();                      
        interrupts ();                       
        sleep_cpu ();                                    
        //sleeps here...
        
        //...wakes here or in ISR and comes back here.
        
        // check if(is_sleeping) ?? not needed!?
       // sleep_disable ();                     // precautio, here or at end of this loop? ???
        //wdt_disable();                           
        digitalWrite (LED_AWAKE, HIGH);        // Idicate CPU is on     
        
        //if(is_sleeping){                       // if an other ISR, not WDT, woke this will be wrong! => check RTC for current time
        total_sleeped += wdt_intervall;             // store sleeped ms for millis_sleep() (might be wrong! Tasks are done to early.)
        recent_sleeped += wdt_intervall;            // in this mySleep call actual sleeped ms
        
        oldSREG = SREG;                         
        noInterrupts();                          
        sleep_en_c = sleep_en;                  
        SREG = oldSREG;                          
      }//while
      
      wdt_disable();                           
      
    }//if(WDT Periode > 8sec)

}//void WDT_while_loop()



//### Set Watchdogtimer Intervall and enable WDT  
void myWatchdogEnable (const byte interval){
  // clear various "reset" flags
  MCUSR = 0;     
  // allow changes, disable reset
  WDTCSR = bit (WDCE) | bit (WDE);
  // set interrupt mode and an interval 
  WDTCSR = bit (WDIE) | interval;    // set WDIE, and requested delay
  wdt_reset();                       // pat the dog, only 1.st time needed? is it possible to let it running while in 
                                     //sleep-loops??
  
}

Code is to long, so the most important Part is above. The rest her as Download. :slight_smile:

_150904_SleepModul_V003d_WDT_Rx_PCI.ino (25.9 KB)

Ok I might shorten my Question!

I can brake the problem down to a not correct wake by Serial Rx (PCI on Pin 0, on Mega PCINT8 on PCI1).

I took this Code on Sleep a ATtiny85 with WDT and Serial wake of Nick Gammon as a start and modified it to my ideas/needs to:

/*
 * Original from Nick Gammon at http://gammon.com.au/forum/?id=11497&reply=6#reply6
 * Changed by Jim Beam on 05/09/15 to work with Meag2560, changed toSleep Function
 * Thanks to the guys of the Arduino Forum and Nick Gammon!
 * To-DO: add Wake by Serial Rx (D0) by PCI, simplify more PCI Pins, disable more Hardware, 
 *        set sleepMode for toSleep Function, Auomatic detect WDT Periodes and bytes,
 *        add setable ext-Hardware-FCN and setable int. Hwardware FCN
 */

#include <avr/sleep.h>    // Sleep Modes
#include <avr/power.h>    // Power management
#include <avr/wdt.h>      // Watchdog timer

const byte LED = 12;  // pin 13
const byte LEDWAKE = 13;  // pin 13
const byte SWITCH = 10; // pin 10 / PCINT4
volatile bool sleep_enable = 1;     // 0= stop sleeping, go back to loop, set 0 by any ISR
volatile bool serial_isr = 0;       // Serial Rx isr was activ



ISR (PCINT0_vect)   // Wake Pin 10
 {
 // do something interesting here
  sleep_disable (); // precaution
  wdt_disable();
  sleep_enable = 0;
 }  // end of PCINT0_vect



 ISR (PCINT1_vect)  // Serial
 {
 // do something interesting here
  
  sleep_disable (); // precaution
  wdt_disable();
  sleep_enable = 0;
  serial_isr = 1;
 }  // end of PCINT0_vect


 
// watchdog interrupt
ISR (WDT_vect) 
{
   //wdt_disable();  // disable watchdog
}  // end of WDT_vect



  
void setup ()
  {
  resetWatchdog (0b100001);  // do this first in case WDT fires
  pinMode (LED, OUTPUT);
  pinMode (LEDWAKE, OUTPUT);
  digitalWrite (LEDWAKE, HIGH);
  
  Serial.begin(115200);
  Serial.print(F("\n\nStarting...\n File: " __FILE__ "\n Date: " __DATE__ "\n Time: " __TIME__ "\n IDE : "));
  Serial.println(ARDUINO);
  Serial.println();
  
 
  pinMode (SWITCH, INPUT);
  digitalWrite (SWITCH, HIGH);  // internal pull-up


    //set PCI for Pin 0, Provide PCI ISR!!
    // pin change interrupt (example for D0) Mega 2560! to Waik by Serial if wanted!
    PCMSK1 |= bit (PCINT8); // want pin 0
    PCIFR  |= bit (PCIF1);   // clear any outstanding interrupts
    PCICR  |= bit (PCIE1);   // enable pin change interrupts for D0 to D7
     
    PCMSK0 |= bit (PCINT4); // want pin 10
    PCIFR  |= bit (PCIF0);   // clear any outstanding interrupts
    PCICR  |= bit (PCIE0);   // enable pin change interrupts for D0 to D7
  }  // end of setup



void loop ()
  {
  Serial.print(F("\n Loop"));
  checkSerial();
    
  for(byte i=0;i<15;i++){
    digitalWrite (LED, HIGH);
    delay (100); 
    digitalWrite (LED, LOW);
    delay (100);
  }
  doSerial();
  Serial.flush(); 
  goToSleep (15999L);
  }  // end of loop


  
void goToSleep (long msToSleep)
  {
  noInterrupts ();       // timed sequence coming up
  sleep_enable = 1;
  byte old_ADCSRA = ADCSRA;
  // disable ADC
  ADCSRA = 0;  

  PCMSK1 |= bit (PCINT8); // want pin 0
  PCIFR  |= bit (PCIF1);   // clear any outstanding interrupts
  PCICR  |= bit (PCIE1);   // enable pin change interrupts for D0 to D7
  
  interrupts ();         // interrupts are required now
  
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);
  
  power_all_disable ();  // power off ADC, Timer 0 and 1, serial interface

  UCSR0B &= ~bit (RXEN0);  // disable receiver
  UCSR0B &= ~bit (TXEN0);  // disable transmitter


  wdtWhileLoop(msToSleep, 8000UL, 0b100001);  //8Sec  //Change: just give msToSleep, max WDT Periode and byte will be
                                                      //determined in FCN by switch case
  wdtWhileLoop(msToSleep, 1000UL, 0b000110);  //1Sec
  wdtWhileLoop(msToSleep, 128UL,  0b000011);  //128ms
  wdtWhileLoop(msToSleep, 16UL,   0b000000);  //16ms
  

  //  now disable wdt once ( not in wdt isr) is that ok so?
  wdt_disable();
  
  power_all_enable ();   // power everything back on
  ADCSRA = old_ADCSRA;
  PCICR  &= ~bit (PCIE1);   // disable pin change interrupts for Serial D0 Pin0 (Mega)
  UCSR0B |= bit (RXEN0);  // enable receiver
  UCSR0B |= bit (TXEN0);  // enable transmitter
  }  // end of goToSleep 



void wdtWhileLoop(long &msToSleep, int wdtPeriodeMs, byte wdtPeriodeByte){

  //Change: just give msToSleep, max WDT Periode and byte will be
   //determined in FCN by switch case
   
  noInterrupts ();       // timed sequence coming up
  resetWatchdog (wdtPeriodeByte);      // get watchdog ready
  bool sleep_enable_c = sleep_enable;
  interrupts ();         // interrupts are required now
  
  while((msToSleep >= wdtPeriodeMs) && sleep_enable_c){
    msToSleep -= wdtPeriodeMs;
    noInterrupts (); // timed sequence coming up 
    digitalWrite (LEDWAKE, LOW);
    sleep_enable (); // ready to sleep
    interrupts (); // interrupts are required now
    sleep_cpu (); //

    sleep_disable (); // precaution
    digitalWrite (LEDWAKE, HIGH);
    
    noInterrupts ();       // timed sequence coming up
    sleep_enable_c = sleep_enable;
    interrupts ();         // interrupts are required now
    
  }//while 
}



void resetWatchdog (byte interval)
  {
  //byte interval = 0b100001; //8sec
   // clear various "reset" flags
  MCUSR = 0;     
  // allow changes, disable reset
  WDTCSR = bit (WDCE) | bit (WDE);
  // set interrupt mode and an interval 
  WDTCSR = bit (WDIE) | interval;    // set WDIE, and requested delay
  wdt_reset();                       // pat the dog, only 1.st time needed? is it possible to let it running while in 
                                     //sleep-loops??
  }  // end of resetWatchdog



void doSerial(){
  Serial.println();
  Serial.print(F("Echo: "));
  while(Serial.available()){
    Serial.print(Serial.read());
  }
  Serial.println();
}


void checkSerial(){
  noInterrupts (); 
  bool serial_isr_c = serial_isr;
  interrupts ();

  if(serial_isr_c){
  noInterrupts (); 
  serial_isr = 0;;
  interrupts ();

  Serial.println(F("\nWoke by Serial. pls. reenter Command now within 3 sec."));
  }
}

But now it seems to just skip one WDT-Sleep period if receiving Serial, but not stopping entire sleep period. But with the PCI from switch it does skip recent entire sleep periode and goes back to loop. What is the Error?

Thanks a lot! :slight_smile:

I found that the PCI ISR is not carried out if using Baudrates higher than 9600 Baud! :o
Only the uC is been woken, but all flags in ISR are not set!!
Who can that be? Are the Serial Data pulses at e.g. 115200 too short for detecting which PCI it was??? So uC wakes but simply skippes the PCI ISR ?

A test with a wire connected to Pin 0 and an (internal) Pullup did work fine and the “Serial Rx-Flag” and test-LED were set correct!!

The idea was, setting a flag to know if uC was woken by Serial Rx or WDT or PCI Pin xyz. But if the flags are not being set in ISR… or am I doing something wrong?

I would be happy if some one could give me a hint with that. :slight_smile:

Attached my modified Code of Nick Gammon.

_150905_WaikingSerialfromSleep_WDT_Gammon_V002b1web.ino (10.2 KB)

jim_beam:
I found that the PCI ISR is not carried out if using Baudrates higher than 9600 Baud! :o
Only the uC is been woken, but all flags in ISR are not set!!
Who can that be? Are the Serial Data pulses at e.g. 115200 too short for detecting which PCI it was??? So uC wakes but simply skippes the PCI ISR ?

It wouldn't surprise me. At 115200 baud, one bit is 1/115200 seconds long, that is 8.7 µs.

Depending on your fuse settings, it may take 1 ms or more to wake from sleep mode: power-down.

Yes, it looks like that is the problem. (found some hints in the Datasheet)

Page 109 External Interrupts
Note that if a level triggered interrupt is used for wake-up from Power-down, the required level must be held long enough for the MCU to complete the wake-up to trigger the level interrupt. If the level disappears before the end of the Start-up Time, the MCU will still wake up, but no interrupt will be generated. The start-up time is defined by the SUT and CKSEL Fuses as described in “System Clock and Clock Options” on page 39.

Its talking about level Interrupt.
What are PCIs level and/or edge Interrupts?? (Where an how to be configerd?)
But can/could edge interrupts be deteced without clock?

So letting the oszillator run in Power-save might be a solution to get the pci isr from serial rx!?

Ps. Thank you very much for the great and understandable information on your website and in this forum Nick!

Ok I think I got it at least regarding the PCI, as the name says it: CHANGE !

Page 109
The Pin change interrupt PCI2 will trigger if any enabled PCINT23:16 pin toggles, Pin change interrupt PCI1 if any enabled PCINT15:8 toggles and Pin change interrupts PCI0 will trigger if any enabled PCINT7:0 pin toggles. PCMSK2, PCMSK1 and PCMSK0 Registers control which pins contribute to the pin change interrupts. Pin change interrupts on PCINT23:0 are detected asynchronously. This implies that these interrupts can be used for waking the part also from sleep modes other than Idle mode.

Reading the Datasheet carefully does a good job :wink: