Hi there,
I am trying to calibrate the Watchdog Timer so that I can do PowerDown sleep with a better accuracy.
To do that I am first sleeping for 15Ms in Idle mode with the Timer0 on so that I can look for how many micro seconds my arduino slept. I will then use this information to sleep for long period of time with better accuracy.
The problem I have is that it seems that the timer0 wakes up the micro controller before the watchdog.
How can I have the timer0 on (so that I can do micros() and millis() ) but without interfering with the watchdog timer?
Here is my code. I tried with LowPower library and then with my own code with the same effect.
#include <avr/power.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
uint32_t start_cal=0;
uint32_t stop_cal=0;
uint32_t wdt_duration=0;
uint32_t cal_cycle=0;
uint32_t start_sleep=0;
uint32_t stop_sleep=0;
void setup() {
Serial.begin(250000);
calibrate();
Serial.println("will sleep for: " +String(cal_cycle,DEC)+" Periods");
delay(100);
}
void loop() {
sleep_5s(cal_cycle);
Serial.print("Slept for ");
Serial.print((stop_sleep-start_sleep));
Serial.println("milli seconds");
delay(100);
}
void calibrate(){
start_cal=micros();
sleep_idle(WDTO_15MS);
stop_cal=micros();
wdt_duration=stop_cal-start_cal;
cal_cycle=5000000UL/wdt_duration;
}
void sleep_5s(uint32_t sleep_periods){
start_sleep=millis();
if (sleep_periods > 255) {sleep_idle(WDTO_4S);sleep_periods=sleep_periods-256;}//Serial.println("Slept 256");delay(100);}
if (sleep_periods > 127) {sleep_idle(WDTO_2S);sleep_periods=sleep_periods-128;}//Serial.println("Slept 128");delay(100);}
if (sleep_periods > 63) {sleep_idle(WDTO_1S);sleep_periods=sleep_periods-64;}//Serial.println("Slept 64");delay(100);}
if (sleep_periods > 31) {sleep_idle(WDTO_500MS);sleep_periods=sleep_periods-32;}//Serial.println("Slept 32");delay(100);}
if (sleep_periods > 15) {sleep_idle(WDTO_250MS);sleep_periods=sleep_periods-16;}//Serial.println("Slept 16");delay(100);}
if (sleep_periods > 7) {sleep_idle(WDTO_120MS);sleep_periods=sleep_periods-8;} //Serial.println("Slept 8");delay(100);}
if (sleep_periods > 3) {sleep_idle(WDTO_60MS);sleep_periods=sleep_periods-4;} //Serial.println("Slept 4");delay(100);}
if (sleep_periods > 1) {sleep_idle(WDTO_30MS);sleep_periods=sleep_periods-2;} //Serial.println("Slept 2");delay(100);}
if (sleep_periods > 0) {sleep_idle(WDTO_15MS);} //delay(100);}
stop_sleep=millis();
}
void sleep_idle(byte watchdog_time){
byte _ADCSRA=ADCSRA;
ADCSRA = 0; // disable ADC
power_adc_disable(); // ADC converter
power_spi_disable(); // SPI
power_usart0_disable();// Serial (USART)
power_timer1_disable();// Timer 1
power_timer2_disable();// Timer 2
power_twi_disable(); // TWI (I2C)
MCUSR = 0; // clear various "reset" flags
WDTCSR = bit (WDCE) | bit (WDE);// allow changes, disable reset
// set interrupt mode and an interval
WDTCSR = bit (WDIE) | watchdog_time; // set WDIE, and 1 second delay
wdt_reset(); // pat the dog
set_sleep_mode (SLEEP_MODE_IDLE);
noInterrupts (); // timed sequence follows
sleep_enable();
interrupts (); // guarantees next instruction executed
sleep_cpu ();
sleep_disable(); // cancel sleep as a precaution
power_all_enable();
ADCSRA=_ADCSRA;
}
ISR (WDT_vect)
{
wdt_disable(); // disable watchdog
} // end of WDT_vect
olivier_arduino:
The problem I have is that it seems that the timer0 wakes up the micro controller before the watchdog.
Yup. When the timer overflows.
How can I have the timer0 on (so that I can do micros() and millis() ) but without interfering with the watchdog timer?
Disable the overflow interrupt. But then you are limited to 255 * 4 = 1020 μs.
Use a flag. Have the WDT ISR set the flag. Put sleep_cpu() in a while loop that doesn't exit until the flag has been set.
I'm going to tell you this though, the watchdog timer SUCKS for accuracy. I found that out on a project I did for school. The frequency varies significantly with supply voltage and temperature, so it should not be used for anything requiring any degree of accuracy.
If you need accuracy, you can use Timer2 with an external 32.768 kHz watch crystal to keep time in Power-save mode.
Thank you Coding Badly and Jiggy-Ninja for your replies. That was very useful!
I found a solution!
Here is the code below in case someone else tries to do the same thing.
I ended up using some code from the Narcoleptic library.
I now use Timer1 for the calibration with a pre-scaler of 64 so that it does not overflow.
The sketch wait for a serial input to start and then send a serial byte when the sleep is completed. With a simple software in VB.net on my computer I was able to time how long the arduino sleep. I was able to reach an accuracy of a few ms over a 5 seconds period. It is not great but a lot better than without calibration!
For my application I will re-calibrate each time the temperature change too much.
#include <LowPower.h>
#include <avr/wdt.h>
float watchdogTime_us = 16000.0;
//uint32_t millisCounter = 0;
uint32_t start_cal=0;
uint32_t stop_cal=0;
uint32_t wdt_duration=0;
uint32_t cal_cycle=0;
void setup() {
Serial.begin(2000000);
calibrate();
//Serial.println("will sleep for: " +String(cal_cycle,DEC)+" Periods");
//delay(100);
}
void loop() {
while (!Serial.available()) {}
byte b = Serial.read();
sleep_5s(cal_cycle);
Serial.print("1");
delay(10);
}
void sleep_5s(uint32_t sleep_periods){
if (sleep_periods > 255) {LowPower.powerDown(SLEEP_4S, ADC_OFF,BOD_OFF);sleep_periods=sleep_periods-256;}//Serial.println("Slept 256");delay(100);}
if (sleep_periods > 127) { LowPower.powerDown(SLEEP_2S, ADC_OFF, BOD_OFF);sleep_periods=sleep_periods-128;}//Serial.println("Slept 128");delay(100);}
if (sleep_periods > 63) { LowPower.powerDown(SLEEP_1S, ADC_OFF, BOD_OFF);sleep_periods=sleep_periods-64;}//Serial.println("Slept 64");delay(100);}
if (sleep_periods > 31) { LowPower.powerDown(SLEEP_500MS, ADC_OFF, BOD_OFF);sleep_periods=sleep_periods-32;}//Serial.println("Slept 32");delay(100);}
if (sleep_periods > 15) { LowPower.powerDown(SLEEP_250MS, ADC_OFF, BOD_OFF);sleep_periods=sleep_periods-16;}//Serial.println("Slept 16");delay(100);}
if (sleep_periods > 7) { LowPower.powerDown(SLEEP_120MS, ADC_OFF, BOD_OFF);sleep_periods=sleep_periods-8;} //Serial.println("Slept 8");delay(100);}
if (sleep_periods > 3) { LowPower.powerDown(SLEEP_60MS, ADC_OFF, BOD_OFF);sleep_periods=sleep_periods-4;} //Serial.println("Slept 4");delay(100);}
if (sleep_periods > 1) { LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_OFF);sleep_periods=sleep_periods-2;} //Serial.println("Slept 2");delay(100);}
if (sleep_periods > 0) { LowPower.powerDown(SLEEP_15Ms, ADC_OFF, BOD_OFF); sleep_periods = sleep_periods - 1; }
}
void calibrate() {
// Calibration needs Timer 1. Ensure it is powered up.
uint8_t PRRcopy = PRR;
PRR &= ~_BV(PRTIM1);
uint8_t TCCR1Bcopy = TCCR1B;
TCCR1B &= ~(_BV(CS12) | _BV(CS11) | _BV(CS10)); // Stop clock immediately
// Capture Timer 1 state
uint8_t TCCR1Acopy = TCCR1A;
uint16_t TCNT1copy = TCNT1;
uint16_t OCR1Acopy = OCR1A;
uint16_t OCR1Bcopy = OCR1B;
uint16_t ICR1copy = ICR1;
uint8_t TIMSK1copy = TIMSK1;
uint8_t TIFR1copy = TIFR1;
// Configure as simple count-up timer
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
TIMSK1 = 0;
TIFR1 = 0;
// Set clock to /64 (16ms should take approx. 4000 cycles at 16MHz clock)
TCCR1B = _BV(CS11) | _BV(CS10);
LowPower.idle(SLEEP_15Ms, ADC_OFF, TIMER2_OFF, TIMER1_ON, TIMER0_OFF, SPI_OFF, USART0_OFF, TWI_OFF);
uint16_t watchdogDuration = TCNT1;
TCCR1B = 0; // Stop clock immediately
// Restore Timer 1
TIFR1 = TIFR1copy;
TIMSK1 = TIMSK1copy;
ICR1 = ICR1copy;
OCR1B = OCR1Bcopy;
OCR1A = OCR1Acopy;
TCNT1 = TCNT1copy;
TCCR1A = TCCR1Acopy;
TCCR1B = TCCR1Bcopy;
// Restore power reduction state
PRR = PRRcopy;
watchdogTime_us = watchdogDuration * (64 * 1000000 / F_CPU); // should be approx. 16000
cal_cycle = (uint16_t)(5000000.0 / watchdogTime_us + 1.5);
}
Power-save mode with a 32.768 kHz crystal looks like it would actually save you power compared to the watchdog timer.
Section 30.8 of the ATmega's datasheet (Typical Electrical characteristics) has copious amounts of graphs, the two important ones being Figure 30-341.ATmega328P: Power-Down Supply Current vs. VCC (Watchdog Timer Enabled) and Figure 30-342.ATmega328P: Power-Save Supply Current vs. VCC (Watchdog Timer Disabled and 32kHz Crystal
Oscillator Running).
Power-down mode with the WDT running ranges between 4 and 7.5 uA of current at room temerature, while power-save with the crystal maxes out at 1.4 uA under the same conditions.
So there's a savings of a few microamps. Whether those microamps matter or not depends on the answers to the following questions:
How important is accuracy to your requirements?
Is there space in your project for the external crystal?
How much battery capacity are you planning to use?
What is the shelf life of the batteries?
How often will you be in the sleeping? What percentage of the time with the CPU and all the peripherals be running?
How long of a battery life are you shooting for?
What other things are you powering, and what is their current consumption? Any external voltage dividers need to be included too.
Jiggy-Ninja,
I first overlooked the solution you proposed but I came back to it. It is quite amazing, in power save mode with timer2 on and a 32kHz crystal, I get 800 nA power consumption while sleeping and keeping a very accurate time!
This is great for my application where most of the battery capacity was previously used by the WDT.
Thank you!!!