As for the title, this is the strangest thing I have seen so far. Anyway, I will try to explain as clear as possible. First what the program is supposed to do, then the problem, then the code, finally some of my experiences so far. Bear with me.
What the program is supposed to do:
The program, on ARDUINO FIO/using 1.04, is meant to perform measurements periodically and send them over through an XBEE radio, every 10 minutes. To achieve this, the Watch Dog Timer (WDT) is configured as a wake up interrupt, and by counting interrupts, I can do approximate 10 min interval measurements.
Because the WDT can be horribly inaccurate (10% off in timing is common), I do a small calibrate method at the beginning of the program and every 3 hours, or whenever it needs to. This is done by timing one or more WDT interrupts with the millis() function, after which the number of interrupts is adjusted to achieve 10 min. intervals.
Because the interval setting for the calibration is shorter than the sleep interval setting, I wrote one method that takes an integer as argument, after which a switch statement sets the WDT to the interval specified by that integer, which represents seconds (can be 1,2,4 or 8, default is 8 ).
Now for something on the WDT of the 328P. For those in the know, you can probably skip to the next paragraph.
The WDT is set primarily by setting the WDTCSR register on the ATMEL, together with one bit in the MUCSR. This last register holds reset flags of various sub-systems, and the WDRF flag (bit3) signals a system reset by the WDT. The setting of this bit is a common cause for those dreaded WDT infinite reset loops.
The WDTCSR contains the following 8 bits with their respective functions (Im mentioning only the ones I use):
-WDE/WDIE (WD enable/WD interrupt enable) The setting of these bits determines the state of the WDT. This can be off (0-0), interrupt only (0-1), system reset (1-0), or interrrupt->system reset (1-1). As I use the WDT as a sleep interrupt I only set WDIE to 1 when in use. Note that WDE is overwritten by WDRF in register MUCSR, switching the WDT to one of the reset modes.
-WDCE (WD change enable) This is bit is set to signal a change to the WDT settings. It is cleared automatically again after the changes.
-WDP(0-3) (WD prescaler) These four bits determine the time-out of the WDT, i.e. when an interrupt or reset is triggered. This can be between 8 secs and 16 ms. Ill save you the table. For a more detailed look into the 328P and the WDT, here's the datasheet: http://www.atmel.com/Images/doc8161.pdf (page 50 for WDT)
The problem:
Now, some time ago I started experiencing those dreaded WDT infinite reset loops. After printing a whole bunch of debug statements, I traced the problem to the WDTCSR register being set incorrectly. In fact, besides the timing bits being of some times, the biggest problem was that for some reason the WDE bit was set, causing the program to go into reset-mode.
I started playing with the lines defining the WDT in the switch case, added and removed delays surrounding them, all to figure out what caused it and to prevent it. After all of it, my conclusion, however strange it might sound, is that the code in one switch case (which is not being called upon) is interfering with the switch case that is being used! Not only that, but that it also only happens when using certain settings for the WDT. The code will probably explain this better.
My code:
The following is just a small part of the code I use, the relevant parts. Its semi pseudo, but that should make it more readable:
PLease note that testable, functional code can be found in Reply#5...please test!
const int WDTInterval = x; //Specifies the WDT interval (seconds) when the main program is running
const int WDTCalInterval = y; //Specifies the WDT interval (seconds) during calibration. Both x and y can be {1,2,4,8}
setup(){
....other stuff
calibrateWDT();
}
loop(){
if(>10 minutes){
measure();
}
sleep();
}
calibrateWDT(){
setWDT( WDTCalInterval ); //Set bits to calibrate settings (not switching WDT on)
delay(10,000); //delay added during debugging, relevant later on
...measure interrupts //WDT on, measure millis(), WDT off (by toggling WDIE);
...adjust some settings accordingly //Like interrupts before measuring, etc
setWDT( WDTInterval ); //Set bits to normal setting (not switching WDT on yet)
}
setWDT(int seconds){
int testInt = seconds;
switch(testInt){
case 1:
WDTCSR &= ~_BV(WDIE); //Make sure the WDT is switched off by setting WDIE to zero
MCUSR &= ~(1<<WDRF); //Clearing the WD reset flag from MCUSR
WDTCSR |= (1<<WDCE) | (1<<WDE); //Signal coming change (WDCE) and setting WDE which MUST be done. Afterwards, 4 clockcyles to set the WDP bits
WDTCSR = 1<<WDP1 | 1<<WDP2; //Bit settings for 1 second interval
break;
case 2:
...same as above
WDTCSR = 1<<WDP0 | 1<<WDP1 | 1<<WDP2; //Bit settings for 2 second interval
break;
case 4:
...same as above
WDTCSR = 1<<WDP3; //Bit settings for 4 second interval
break;
case 8:
...same as above
WDTCSR = 1<<WDP3 | 1<<WDP0; //Bit settings for 8 second interval
break;
default:
...same as above
WDTCSR = 1<<WDP3 | 1<<WDP0; //Bit settings for 8 second interval as default case
}
}