Mega328P - Power saving and the watchdog timer over an extended time period

I've been experimenting with the low power library from RocketScream to gain some knowledge about low power operation and eventually trying to run a Mega328P of a CR2032 lithium battery for an extended period of time.

For anybody who is interested in the watchdog timer, extended sleep times and low power operation etc, I thought I'd share this.

The code is quite simple, it sleeps for 1 hour, wakes up, prints a full stop character (+CR&LF) to the serial port and goes back to sleep again for another hour.

#include "LowPower.h"

// 1 hour = 3600 seconds
const uint16_t delayOneHour = 3600;

void setup()
{
  Serial.begin(9600);
  Serial.println(F("Low Power Sleep Demo"));
  Serial.println(F("Using WDT to wake every hour -ish!"));
  Serial.flush();
  delay(1000);
}

void loop()
{
  Serial.println(".");
  Serial.flush();

  longSleep( delayOneHour );
}

void longSleep( uint16_t sleepInSeconds )
{
  if ( sleepInSeconds & 0x01 ) LowPower.powerDown(SLEEP_1S, ADC_OFF, BOD_OFF);
  if ( sleepInSeconds & 0x02 ) LowPower.powerDown(SLEEP_2S, ADC_OFF, BOD_OFF);
  if ( sleepInSeconds & 0x04 ) LowPower.powerDown(SLEEP_4S, ADC_OFF, BOD_OFF);

  while ( sleepInSeconds & 0xFFF8 ) {
    sleepInSeconds = sleepInSeconds - 8;
    LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
  }
}

I left it running for about a day with the IDE serial monitor generating a timestamp each time the 328P woke up and printed to the serial port. This is the output from the serial monitor:

16:24:09.946 -> Low Power Sleep Demo
16:24:09.946 -> Using WDT to wake every hour -ish!
16:24:10.996 -> .
17:24:50.772 -> .
18:25:32.029 -> .
19:26:12.896 -> .
20:26:54.528 -> .
21:27:35.270 -> .
22:28:15.707 -> .
23:28:55.938 -> .
00:29:36.059 -> .
01:30:15.984 -> .
02:30:55.757 -> .
03:31:35.496 -> .
04:32:15.228 -> .
05:32:54.898 -> .
06:33:34.540 -> .
07:34:14.113 -> .
08:34:53.622 -> .
09:35:33.096 -> .
10:36:12.641 -> .
11:36:53.565 -> .
12:37:35.515 -> .
13:38:18.985 -> .
14:39:03.378 -> .
15:39:48.886 -> .
16:40:34.937 -> .

As you can see, it oversleeps by roughly 40 seconds per hour, resulting in it oversleeping by around 16 minutes a day. The watchdog timer clock isn't particularly accurate but it's good enough to wake the processor up every so often to carry out a task.

Anyways, hope this is of interest.

Very interesting, but this would be more interesting if you reported the current measurements before and after doing this.

Using the watchdog timer like this gives an average sleep current on a Atmega328P of circa 6uA.

There are peaks as the ATmega wakes up, increments the counter, and then goes back to sleep. However, the time the processsor is active (every 8 seconds) is so short that the affect on battery life is minimal.

vaj4088:
Very interesting, but this would be more interesting if you reported the current measurements before and after doing this.

I looked into this but my basic multimeter doesn't have the ability to measure currents that small. It seems a more specialised bench multimeter would be required, not to mention the requirement for a proper calibration, which is out of my budget.

I am hoping to set up a custom 328P board with an RFM69CW and power it off a CR2032 battery. I'll then get the board to wake-up every hour (actually multiple sleeps) to report battery voltage and temperature (DS18B20), and just see how long the board will operate for.

markd833:
I am hoping to set up a custom 328P board with an RFM69CW and power it off a CR2032 battery. I'll then get the board to wake-up every hour (actually multiple sleeps) to report battery voltage and temperature (DS18B20), and just see how long the board will operate for.

Get it right and the answer is a long time.

The ability of the watchdog timer to be used as a wakeup for very low power projects seems often not to be appreciated.

Sure its not accurate time wise, but is it really important that the temperature in your green house is monitored every 15 minutes versus every 14 ?

And of course using the watchdog timer as a sleep wakeup needs no extra components.

My own project, that wakes up every 15minutes and sends the sensor data via LoRa looks like the 155mAhr battery (a tiny Lipo) may have a life of 2 years ..............

@srnet - couldn't agree more!

In my case, I don't care that the hourly reporting doesn't happen on the hour. If I did, I'd get an RTC and set an alarm to wake the board up.

But, of course, like you say, that needs extra components and that means more demands on the power source (however small that demand may be).

Thank you for your reply.

You can try my approach by computing FIRST the estimated time (statistically)
(mine at the moment for specific pro mini is estimated8SecMillis = 8792)

and changing the work() and workImplementation() functions
according to your need/experiments
I have also some eeprom function utility for my experiment...You can throw them.

It is working fine....i mean...relativellly fine.

long workedWithBatteriesResult;

/*
 * Worked till 3.3 Volt =
 * 
 * Start Voltage = 
 * Starting Date = 
 * End Voltage = 
 * Ending Date =
 * 
 * 
 * This is with power Down but removed also regulator
 * 
 * 15/12 07:56 -> 4.37
 */

#define USE_LOW_POWER
//http://gammon.com.au/power







#include <EEPROM.h>
byte tempAddress=981;
long currWorkingTimes;

long romReadLong(int address) {//saving some Flash not using EEPROM.put,get
  byte b = EEPROM.read(address); ++address;
  long out = b;  out <<= 8;
  b = EEPROM.read(address); ++address;
  out += b;      out <<= 8;
  b = EEPROM.read(address); ++address;
  out += b;      out <<= 8;
  out += EEPROM.read(address);
  return out;
}
void romUpdateLong(int address, long out) {
  address += 3;
  EEPROM.update(address, out); --address;
  out >>= 8;
  EEPROM.update(address, out); --address;
  out >>= 8;
  EEPROM.update(address, out); --address;
  out >>= 8;
  EEPROM.update(address, out);
}


unsigned long awakeEvery = 7200000ul;//2 hours
unsigned long awakeForMin = 60000;//1 minute
unsigned long awakeForMax = 120000;//2 minute

unsigned long mustDelayOrSleep;//this is only for debug
unsigned long timesPowerDown8Seconds;//this is only for debug
unsigned long millisRemainForNormalDelay;//this is only for debug
unsigned long estimated8SecMillis = 8792;


#ifdef USE_LOW_POWER
#include <LowPower.h>
#endif
void myDelay(unsigned long someTime) {//finally this function release from RTC need...good enough

#ifdef USE_LOW_POWER
  //Serial.println("Power down");
  timesPowerDown8Seconds = someTime / estimated8SecMillis;
  millisRemainForNormalDelay = someTime % estimated8SecMillis;
  unsigned long times = timesPowerDown8Seconds; //hold this to be able to show it after for debug
  //WARNING .This delay must be before while because???
  //is not working fine if after
  delay(millisRemainForNormalDelay);
  while (times > 0) {
    LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
    --times;
  }
  //delay(millisRemainForNormalDelay);//not working here
#endif

#ifndef USE_LOW_POWER
  //Serial.println("delay() down");
  delay(someTime);
#endif

}

void workingStep() {
  unsigned long startMillis = millis();//reference to when on this line
  work();
  mustDelayOrSleep = awakeEvery ;
  mustDelayOrSleep += startMillis;
  mustDelayOrSleep -= millis();
  myDelay(mustDelayOrSleep);
}






const int buzzer = 9;

void setup() {
  Serial.begin(9600);
  pinMode(13, OUTPUT);
  Serial.println("setup()");
  pinMode(buzzer, OUTPUT);
  randomSeed(analogRead(0));
  Serial.print("Previuslly ");
  Serial.print(romReadLong(tempAddress));
  Serial.println(" Working Times");
  
}

void loop() {
  workingStep();
}


void work() {
  Serial.print(currWorkingTimes);
  Serial.print(" work() ");
  digitalWrite(13, HIGH);

  workImplementation();
  Serial.println();
  ++currWorkingTimes;
  romUpdateLong(tempAddress,currWorkingTimes);
  digitalWrite(13, LOW);

}
void workImplementation() {
  long workTime = awakeForMin + random(awakeForMax-awakeForMin);
  unsigned long startTime = millis();
  unsigned long endTime = startTime + workTime;
  Serial.print(" ,Work for ");
  Serial.print(workTime);
  buzzRandom(awakeForMin / 2);
  startTime = millis();
  if (startTime < endTime) {
    endTime -= startTime;
    Serial.print(" ,Pseydo-Delaying "); Serial.print(endTime);
    delay(endTime);
  }
  Serial.print(" Finish ");
}

//Rated Current* : ≤30mA
//Resonant Frequency : 2300 ±300Hz
void buzzRandom(unsigned long buzzTime) {
  Serial.print(" ,Buzz for ");
  Serial.print(buzzTime);
  int del = 100;
  int mul=1;
  unsigned int freq = 22;
  while (buzzTime > 0) {
    tone(buzzer, freq);
    delay(del);
    buzzTime -= del;
    if(buzzTime%10==0){
      mul=-mul;
    }
    if (buzzTime % 3 > 0) {
      freq += mul*random(100);
    }
    else {
      freq -= mul*random(100);
    }
    if(freq>2500 || freq<10){
      freq=22+random(500);
      //Serial.print(" ,frq");
    }
    else{
      //Serial.print(" ,");Serial.print(freq);
    }
  }

  noTone(buzzer);

  //Serial.println("buzz off");

}

I would just say that if there are other devices that may not be so easy to put into very low current mode, such as regulators, RF modules, sensors, SD cards, etc., then it may be that the best way to extend battery life is to turn off the power completely during that hour. That's when adding an RTC module and a P-channel mosfet make sense. But I agree - if it's just the processor, and other things that can also be put to sleep, then even a small battery can last a long time at 6uA.

Adding an external watch crystal and running Timer 2 in asynchronous mode makes more precise timing and consumes < 1 uA.