Power-down sleep with WDT interrupt and clock pre-scaling with the ATMega328P

Hey all,

Background:
I am working on a conceptually simple project: a light sensing relay switch. The device will be running off solar power so I need to make sure I use as little power as possible. I’m using a 5v Arduino Pro Mini, a photoresistor, and a latching relay.

Issue:
I’ve found that pre-scaling and using the chip’s sleep functions were my best bets for saving power, but I am running into a problem when I use both. When I use the code below without changing introducing pre-scaling the chip seemingly goes to sleep for about 8 seconds. I say “about” because it’s not really 8, it’s more like 6, which makes me wonder thats where the problem lies. Alternatively, when I do change the pre-scale settings the device doesn’t even seem to sleep, it just zooms right past the sleep functions.

I have tried to put a good amount of comments so you know my thinking.

Code:

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

volatile int f_wdt = 1;
int relayPowerPin = 3; // I supply the relay with 5v power using this pin
int lightSensorPowerPin = 4; // I supply the photoresistor with 5v power using this pin
int relayDataPin = 2; // Output pin for switching relay
int lightDataPin = A2; // Input pin for reading photoresistance
int clockAdjustment; // I use this to manage my delays so things work smoother
bool lastReading; // Used to detect change from day-time to night-time

/* ==== ISR is run when after a the WDT interrupt ==== */
// I found this whole function online, can't find too many details about it
ISR(WDT_vect) {
  if(f_wdt == 0) {
    f_wdt = 1;
  } 
  else {
    Serial.println("**Watchdog overflow**");
  }
}

/* ==== Configures my Watchdog timer to be a nonresetting interrupt every 8 seconds ==== */
void configureWatchdog() {
  cli(); // disable system interrupts during watchdog configuration
  wdt_reset(); // reset the watchdog timer
  WDTCSR |= (1<<WDCE) | (1<<WDE); // follow unlocking procedure at the bottom of page 51 on the datasheet
  WDTCSR = 1<<WDP0 | 1<<WDP3; // 8 seconds - Page 55 of the datasheet
  WDTCSR |= _BV(WDIE); // Enable the WD interrupt (note no reset)
  sei(); // enable interrupts again, it's cool now
}

void configureRelay() {
  // creates starting values for the relay, makes the relay closed
  digitalWrite(relayPowerPin, HIGH);
  delay(2000/clockAdjustment);
  digitalWrite(relayDataPin, HIGH);
  digitalWrite(relayDataPin, LOW);
  delay(2000/clockAdjustment);
  digitalWrite(relayPowerPin, LOW);
  digitalWrite(lightDataPin, LOW);
  lastReading = true;
}

/* ==== setup Sets up my baud rate, listens on A1 for UV readings, writes to the relay on 13, sets my clock speed to 1MHz, and runs my watchdog configurating ==== */
void setup() {
  configureWatchdog(); // I put this above the pre-scaler in hopes it would help with the 8 second issue
  CLKPR = 0x80; // unlock prescaler
  CLKPR = 0x04; // set prescaler
  clockAdjustment = 16; // I change this to 16 if I uncomment the prescaler above for dev purposes
  pinMode(relayPowerPin,OUTPUT);
  pinMode(lightSensorPowerPin,OUTPUT);
  pinMode(relayDataPin,OUTPUT);
  pinMode(lightDataPin,INPUT);
  //digitalWrite(powerPin,HIGH);
  digitalWrite(relayDataPin,LOW);
  delay(200/clockAdjustment);
  configureRelay();
}

/* ==== sleepControl puts the processor to sleep ==== */
void sleepControl(void) {
  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // using power save sleep mode
  sleep_enable(); // enabling the possibility of sleeping
  sleep_mode(); // executing the set sleep mode
  // this is exactly where the code continues after waking up
  sleep_disable(); // disabling the possibility of sleeping
  power_all_enable(); 
}

/* ==== resets our watchdog flag so we don't go to sleep when we're not supposed to ==== */
void setFlagAndSleep() {
  if(f_wdt == 1) {
    f_wdt = 0;
    delay(2000/clockAdjustment); // if you don't use this delay the serial won't print fully before the device falls asleep. How cute.
    sleepControl();
    delay(1000/clockAdjustment); 
  }
}

/* ==== reads our UV Sensor ==== */
void readLight() {
  digitalWrite(lightSensorPowerPin,HIGH);
  delay(2000/clockAdjustment);
  byte numberOfReadings = 10;
  unsigned int runningValue = 0; 

  for (int x = 0 ; x < numberOfReadings ; x++) {
    runningValue += analogRead(lightDataPin);
  }
  runningValue /= numberOfReadings;
  digitalWrite(lightSensorPowerPin,LOW);
  reviewLight(runningValue);
}

/* ==== Evaluates last UV reading ==== */
void reviewLight(int lightValue) {
  bool dayTime = false;
  if (lightValue > 75) {
    dayTime = true;
  }
  setLight(dayTime);
}

/* ==== Sends appropriate signal to the relay based on our last UV reading ==== */
void setLight(bool isDay) {
  if ((isDay) && (!lastReading)) { // this statement checks to see if there is a change from night to day
    digitalWrite(relayPowerPin, HIGH);
    delay(2000/clockAdjustment); // I have these delays to give the arduino enough time to ramp up the pin to 5v
    digitalWrite(relayDataPin, HIGH);
    digitalWrite(relayDataPin, LOW);
    delay(2000/clockAdjustment);
    digitalWrite(relayPowerPin, LOW);
    digitalWrite(lightDataPin, LOW);
  }
  else if ((!isDay) && (lastReading)) { // this statement checks to see if there is a change from day to night
    digitalWrite(relayPowerPin, HIGH);
    delay(2000/clockAdjustment);
    digitalWrite(relayDataPin, LOW);
    digitalWrite(relayDataPin, HIGH);
    delay(2000/clockAdjustment);
    digitalWrite(relayPowerPin, LOW);
    digitalWrite(lightDataPin, LOW);
  }
  else { // in the event of no change do absolutely nothing
    digitalWrite(relayPowerPin, LOW);
    digitalWrite(lightDataPin, LOW);
  }
  lastReading = isDay;
}

void loop() {
  readLight(); // readLight() >> reviewLight() >> setLight()
  setFlagAndSleep(); // setFlagAndSleep >> sleepControl()
}

Thank you:
Any help is super appreciated, I am just getting into this lower-level aspect of these ATMega chips and I am getting it slowly. Let me know if I could provide more information to help troubleshoot an answer. Also any code critique is definitely welcome! If I could design it again I’d probably do a few things different, but I’ll probably do that at the end once everything is working.

6 or 8 seconds, that is the watchdog timer timeout. It is not very accurate. I don't use a slower clock. I let the ATmega run at full speed (8MHz or 16MHz) and when a delay is needed, I do a 'light' sleep mode instead. I'm using the narcoleptic library for a 'heavy/deep' sleep: https://code.google.com/archive/p/narcoleptic/

Is the watchdog really that inaccurate? I just tested an Uno and it's watchdog (at 5V and about 20°C) is 8.5s, or 124kHz. Supposedly the nominal value is 128kHz although the datasheet has graphs indicating the nominal value is more like 116kHz. That seems weird.

Anyway, I tried your code with all of the light and relay read/control stuff removed and the processor slept just fine whether the system prescaler was changed or not.

Maybe the problem is with all of those long delays in your code?

I think that going into idle mode like Koepel is suggesting won't reduce the current as much as cutting the clock frequency like you're doing. If the exact length of those delays isn't critical why not use the watchdog timer and put your processor to sleep?

Also: It's a bad idea to print in an ISR, although you don't even initialize the serial in your sketch. And instead of delaying to get the print output before sleeping, use Serial.flush() instead.

Thanks for the awesome reply! I tried to remove all the serial prints I had before posting this to improve readability. It was late, so some artifacts remain. The delays were helping me troubleshoot and tune my photoresistor. I'll take them out when I get home and report back. Also I will look into Serial.flush.

http://www.gammon.com.au/forum/?id=11497

Ray