watchdog without RTC?

I would like to log data on an SD card in a very low rate.
Something like 2 values per day, maybe ten at each time to average sensor jitter...

The device should be battery driven, so power consumption is of a concern.

In another device I used the internal 1 MHz clock of the atmega328 with its watchdog set to 1s sleep. Then the current time would be looked up at a RTC module.
After a couple weeks this is still accurate below one minute deviation.

Now I don't have the concern of time precision. As long as the deviation is within an hour after 2 weeks it should be fine.

Can I still use the watchdog feature and count the times it slept?
Then after 1s6060*12 = 43200 take a measurement (each 12 h)?

Last time I checked, I get around 6min offset each hour (needs to be remeasured once I programmed the device) plus something like a minute per day jitter.

Or is there a better attempt?
Would you advise to never use the watchdog without an external RTC module?

The maximum sleep time you can get with the WDT on the 328 is 8 seconds, and it's not very accurate. Having said that, it is quite easy to calibrate to include an adjustment and every 8 seconds you need do nothing more than increment a counter and wait.

Thanks for the reply. I guess the deviation is less on 8s sleep time than on 1s.
However, on my last test, the device was ignoring sleep times above 1s.
Is there a special requirement to get the 8s?

I’m using the following code. Using setup_watchdog(9) instead of setup_watchdog(6) doesn’t seem to work.

#include <avr/sleep.h>
#include <avr/wdt.h>
#ifndef cbi
#define cbi(sfr,bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#ifndef sbi
#define sbi(sfr,bit) (_SFR_BYTE(sfr) |= _BV(bit))

#define D_LED 13 //LED to display status
volatile boolean f_wdt = 1;

//0=16ms, 1=32ms, 2=64ms, 3=128ms, 4=250ms, 5=500ms, 6=1s, 7=2s, 8=4s, 9=8s
void setup_watchdog(int ii) {
  byte bb;
  if (ii > 9)ii = 9;
  bb = ii & 7;
  if (i > 7)bb |= (1 << 5);
  bb |= (1 << WDCE);

  MCUSR &= ~(1 << WDRF);
  WDTCSR |= (1 << WDCE) | (1 << WDE);
  WDTCSR = bb;
void setup() {
  cbi(SMCR, SE); //sleep mode enable
  cbi(SMCR, SM0); //power down mode
  cbi(SMCR, SM1); //power down mode
  cbi(SMCR, SM2); //power down mode
  setup_watchdog(6); //1s rest
void system_sleep() {
  cbi(ADCSRA, ADEN); //ADC off
  set_sleep_mode(SLEEP_MODE_PWR_DOWN); //sleep mode set
  sleep_mode(); //system sleeps till watchdog wakes up
  sleep_disable();//wake up system
  sbi(ADCSRA, ADEN); //ADC on
ISR(WDT_vect) { //watchdog wakes up
  f_wdt = 1;
void loop() {
  if (f_wdt == 1) { //once per watchdog-wakeup
    f_wdt = 0;
    //things to do after waking up
    pinMode(D_LED, INPUT); //save power in output pins
    pinMode(D_LED, OUTPUT); //restore pinmodes

arduino board?

I'm using an atmega328.
For programming I usually put it on a strip board and later solder an IC socket to a breadboard.
I use an arduino uno as a programmer then.

if (i > 7)bb |= (1 << 5); Where did the i come from? Maybe ii?

Your code is defunct or partially posted.

void setup_watchdog(int ii) {
  byte bb;
  if (ii > 9)ii = 9;
  bb = ii & 7; //This will disallow sleeps above 2 seconds
  if (i > 7)bb |= (1 << 5); //Where is "i" defined?
  bb |= (1 << WDCE);

  MCUSR &= ~(1 << WDRF);
  WDTCSR |= (1 << WDCE) | (1 << WDE);
  WDTCSR = bb; // This should have been "WDTCSR = bit(WDIE) | bb"
  WDTCSR |= _BV(WDIE); //This should not exist

If you are, actually, going to use a RTC then use the RTC as a timer. A DS3231 has two alarms and a INT o/p.

The alarm rate can be:

a minute
hours, minutes
date, hours, minutes
day, hours, minutes

The INT can be:
1Hz, ~1Khs, ~4Khz, ~8Khz

I use, in one project, the DS3231 1Hz interrupt, I find it accurate and realize.

Add the battery to the DS3231 so you will not have to reset the registers each time it is powered back on.

DS3231 datasheet

You could use millis, as you are not really wanting a watchdog but a timer.

@DKWatson, @Danois90
Thanks for the help!
I must have made a typo there. ii is meant.

The code seems to be originally from here: Lab3 - Laboratory for Experimental Computer Science

// 0=16ms, 1=32ms,2=64ms,3=128ms,4=250ms,5=500ms
// 6=1 sec,7=2 sec, 8=4 sec, 9= 8sec
void setup_watchdog(int ii) {

  byte bb;
  int ww;
  if (ii > 9 ) ii=9;
  bb=ii & 7;
  if (ii > 7) bb|= (1<<5);
  bb|= (1<<WDCE);

  MCUSR &= ~(1<<WDRF);
  // start timed sequence
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  // set new watchdog timeout value
  WDTCSR = bb;


According to this site: What is the watchdog and how to use it | KeyChainino
The WDTCSR is build up, so that bit 0,1,2 and bit 5 determine the sleep time.
That is why if the sleep is above 2s (=0b111) the bits above are trucnated (bb=ii&7) and the next bit is set instead (bb|=1<<5). Then instead of 9=0b1001 you get 0b10001, which should result in 8s sleep time.

With my typo the next bit was not set properly and thus it didn’t work.

WDTCSR = bb; // This should have been “WDTCSR = bit(WDIE) | bb”
WDTCSR |= _BV(WDIE); //This should not exist

Is there a problem writing this in 2 lines? It seems to me that this has the same result, but my code is writing two times and reading the value once more than yours.

Thanks for the advice and links.
I still plan to do this without an RTC.
But the idea is nice to use the alarm from RTC to wake up the atmega instead of waking it up on its own, checking the time and then letting it go to sleep again…
I have to read more into how to do this, though, if the accuracy without RTC is too much off.

I would recommend you to use the built in defines (WDP[0..3]) for the prescaler setup. If you want 8 seconds, use:

WDTCSR = bit(WDCE) | bit(WDE); //Start timed sequence
WDTCSR = bit(WDIE) | bit(WDP3) | bit(WDP0); //Select interrupt and set prescaler 
wdt_reset(); //Reset the WatchDog

Depending on the compiler optimization, it could be a problem to specify the second instruction as a two-liner, since writing "WDCE" to the control register starts a timed sequence which may not take longer than 4 clock cycles. Please refer the data sheet for the ATmega328P (sections 15.6 and 15.9.2).