Watchdog vs powerdown

For Adafruit customers who seek help with microcontrollers

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
efh
 
Posts: 19
Joined: Tue Oct 12, 2010 11:04 am

Watchdog vs powerdown

Post by efh »

Please correct the error in my reasoning.

The watchdog defaults to reset the uC, yes? How does this reset differentiate from a hard power down?

I have the MCUSR SM1 set to 1 for sleep mode. I am attempting to run either TIMER0 for PWM or WDT for sleep and power conservation depending on a poll of environmental conditions in main(). On powering the device, the condition is properly determined and the device responds properly. The uC will sleep when triggered and operate off of the WDT. I have the WDT set for 1 second. The delay when changes occurs are consistent with this period. The problem lies with the uC coming back online. I have paired LEDs that transist through various intensities via PWM on TIMER0. When I start the uC from cold, ie provide it with power, this functions as it should. When it comes back from the WDT, the LEDs pulse rapidly in sync with the same intensity. I had thought the WDT was still polling, but I do execute wdt_disable();

I have just finished looking at it some more. If I start with the WDT loop enabled, it never makes it back to the TIMER0 loop.

I think this is the pertinent code:

Code: Select all

int main()
{
    setup();

    if (readADC() >= 350)
    {
        wdt_disable();
        srandom(123);
        TCCR0B |= (1<<CS00); //Start the clock!
        MCUCR &= ~(1<<SM1);
    }
    else
    {
        TCCR0B &= ~(1<<CS00);
        MCUCR |= (1<<SM1);
        wdt_enable(WDTO_1S);
    }

    while(1)
        {
            sleep_mode();
        }
        return 0;
}
When the if condition is met from initial power up, the TIMER0 is kicked off and works fine. If the condition is false, the WDT starts off just fine as well. However, when the condition is met after the WDT starts, it never drops back into the TIMER0 loop. There is no condition in the TIMER0 loop that runs readADC() again. So when the device drops into the TIMER0 loop, it runs until removed from power. I will add a test to the loop later, when I can get it to run after the WDT loop. Currently, I am just trying to diagnose why it is not entering the TIMER0 loop after the WDT loop is enabled. I figure it is some simple referene that I have read past a dozen times or so. The clearest indication I get that the WDT loop is still dominant, is the fact that the program responds dynamically to the input changing. That is, it goes off when readADC() < 350 and flashes the LEDs at about 5-10 hz when readADC() >= 350.

Thank you for any assistance or insight!
Last edited by efh on Wed Oct 20, 2010 12:33 pm, edited 1 time in total.

uhe
 
Posts: 178
Joined: Mon Sep 03, 2007 4:50 pm

Re: Watchdag vs powerdown

Post by uhe »

Based on the register names you've used, I assume you're using some kind of AVR...
efh wrote:The watchdog defaults to reset the uC, yes?
No, the behavior depends on the hardware. You need to check the fuse settings and consult the datasheet of your device. When you buy a AVR somewhere (that is not preprogrammed) you should always check the fuses!
efh wrote:I have the MCUSR SM1 set to 1 for sleep mode.
What about the SE-bits? Have you set/reset them properly? I don't know if they are relevant to this problem but the datasheet and avr-libc say you should.

I've used the watchdog on the ATTiny2313 excessively but I've never used wdt_disable() just this little makro:
#define WD_STOP (WDTCR = 0x0)
Some people on the net suggests to write the WDRF bit in addition to wdt_disable(). I'd never had to but it might help.

User avatar
efh
 
Posts: 19
Joined: Tue Oct 12, 2010 11:04 am

Re: Watchdag vs powerdown

Post by efh »

Ah yes, sorry. I am utilizing an ATtiny45.

Datasheet as I read it says that the reset is the default behavior of the watchdog timer. However, the reset does not wholly seem to return to my main(). I may have to change it to an interrupt and just keep the program out of main() permanently after the interrupts start doing their thing.

The SE bits are handled in the avr-libc sleep_mode() those I have looked into.

I will try your method to kill the WDT. I will read more on the WDRF.

Thank you.

uhe
 
Posts: 178
Joined: Mon Sep 03, 2007 4:50 pm

Re: Watchdag vs powerdown

Post by uhe »

On the 2313 you can disable the reset mode with the WDTON fuse.
Your're right about the SE bits I think I mixed it up with sleep_cpu().

I'd set the WDIE bit and go with an interrupt handler. Using the WD reset as a 'loop replacement' doesn't feel good to me.

User avatar
efh
 
Posts: 19
Joined: Tue Oct 12, 2010 11:04 am

Re: Watchdag vs powerdown

Post by efh »

Ditching the reset was my first inclination, but it seems that the Watchdog Interrupt Enable is toggled off automatically on each interrupt. As long as I am sure to either turn off the WDT or renable the interrupt trigger I should be OK. Will put that together over the next few days and see if it serves better.
Last edited by efh on Mon Oct 18, 2010 12:36 pm, edited 1 time in total.

uhe
 
Posts: 178
Joined: Mon Sep 03, 2007 4:50 pm

Re: Watchdag vs powerdown

Post by uhe »

efh wrote:Ditching the rest was my first inclination, but it seems that the Watchdog Interrupt Enable is toggled off automatically on each interrupt.
That's how I'd read the description of the WDIE in the datasheet.
Setting the WDIE in the corresponding ISR again should do the trick.

Mowserson2
 
Posts: 5
Joined: Sun Oct 17, 2010 8:01 pm

Re: Watchdag vs powerdown

Post by Mowserson2 »

Brief version: If you do not have a watchdog signal handler, the default action of a gcc-compiled program is to perform a (very) soft reset. If you set WDE to true, then WDIE is reset every interrupt. If you set WDE to false, WDIE does not change. You can find out why the device reset using MCUSR.


In more detail, and using assembly rather than AVR-libc since I worked from the data-sheets from Atmel rather than the docs from avr-libc:

I used the watchdog timer just to sleep for a second, so my watchdog signal handler actually does nothing at all:

Code: Select all

#include <avr/interrupt.h>
/* AtTinyX5 specific name, WDT_vect */
ISR( WDT_vect, ISR_NAKED ) {
        asm volatile( "reti" );
}
To turn on the watchdog timer as an interrupt make sure to set both WDE and WDIE correctly. Your description is what happens when you set them to (1,1). To use the watchdog to sleep until interrupt (and otherwise, not use the watchdog) you should set them to (0,1).

Code: Select all

int main(void) {
  /* ... setup ... */
  // interrupt only, trigger every 1 second
  WDTCR  = (0<<WDIF  ) | (1<<WDIE  ) | (1<<WDCE  ) | (0<<WDE   ) | (0<<WDP3 ) | (1<<WDP2 ) | (1<<WDP1 ) | (0<<WDP0 );
  // the classic blunder is to forget to enable interrupts
  asm volatile("sei");
  for(;;) {
    /* ... loop ... */
  }
}
To enter a deep sleep that only draws current in the 5μA range, I use:

Code: Select all

static void PowerDown(void) {
        uint8_t a;
        asm volatile(
                "eor %0,%0              \n\t"
                "out %[adcsra], %0      \n\t"
                "ldi %0, %[prrV1]       \n\t"
                "out %[prrA], %0        \n\t"
                "ldi %0, %[bods1]       \n\t"
                "out %[mcucr], %0       \n\t"
                "ldi %0, %[bods2]       \n\t"
                "out %[mcucr], %0       \n\t"
                "sleep                  \n\t"
                "ldi %0, %[prrV2]       \n\t"
                "out %[prrA], %0        \n\t"
                : "=d" (a)
                :
                [prrV1] "M" ((1<<PRTIM1) | (1<<PRTIM0) | (1<<PRUSI ) | (1<<PRADC )),
                [prrV2] "M" ((1<<PRTIM1) | (1<<PRTIM0) | (1<<PRUSI ) | (0<<PRADC )),
                [bods1] "M" ((1<<BODS  ) | (1<<SE    ) | (1<<SM1   ) | (0<<SM0   ) | (1<<BODSE) | (1<<ISC01) | (1<<ISC00) | (1<<PUD  )),
                [bods2] "M" ((1<<BODS  ) | (1<<SE    ) | (1<<SM1   ) | (0<<SM0   ) | (0<<BODSE) | (1<<ISC01) | (1<<ISC00) | (1<<PUD  )),
                [mcucr] "I" (_SFR_IO_ADDR(MCUCR)),
                [adcsra] "I" (_SFR_IO_ADDR(ADCSRA)),
                [prrA ] "I" (_SFR_IO_ADDR(PRR))
        );
}
This turns off peripherals, including the timers, the serial interrupter, the ADC, the brown out detector, and other interrupters. It sleeps, waits for the interrupt generated by the watchdog, and then wakes up, and turns the ADC partially back on. The next call to the analog reader then turns the ADC completely on by setting ADCSRA appropriately. My goal was low current consumption, and the 10 or 15 extra cycles is negligible since I spend millions of cycles sleeping.

If you use the analog comparator, you need to turn it off too.

Code: Select all

ACSR = 1<<ACD
You also asked:
How does this reset differentiate from a hard power down?
You can determine what caused a reset by looking at MCUSR before it is cleared by the initialization functions created by GCC. An example of doing this is in the source code to the kit "Lux Spectralis", at http://www.sparkfun.com/commerce/produc ... ts_id=9287 (source code not available anywhere else, as far as I can see).

Roughly it goes:

Code: Select all

void init(void) __attribute__ ((naked, section(".init3")));
void init(void) {
  if(bit_is_set(MCUSR, PORF)) {
    // Power turned on 
  } else if(bit_is_set(MCUSR, EXTRF)) {
    // Reset pin held high
  } else if(bit_is_set(MCUSR, BORF)) {
    // Brown out occurred
  } else if(bit_is_set(MCUSR, WDRF)) {
    // Watchdog reset, not interrupt
  } else {
   // The compiler has betrayed us and cleared MCUSR before we could read it!
  }
}
The ".init3" stuff is only necessary if you find yourself in the last else. My brief tests (this is a new technique for me) show that it is not needed in my setup. Since the GCC initialization code is very behind-the-scenes, it seems difficult to detect this.

User avatar
efh
 
Posts: 19
Joined: Tue Oct 12, 2010 11:04 am

Re: Watchdag vs powerdown

Post by efh »

@Mowserson First a big thank you for the detailed response

The datasheet confused me on the WDE and the WDIE bits. I read it as you needed to use WDE to initiate things, then manipulate WDIE on-the-fly. This seems to be the root of my problems as you did indeed note. Though, I was not convince, because I had thought I took the description of those two bits into account. Before I made another post though, I though I would try to turn on just the WDIE bit and see where it led. Well it lead pretty much exactly where I needed to be. Still some bugs left, but they are of a new nature!!! Thanks. I was more than a little frustrated by this, but the frustration is the best tool to be sure the lesson sticks!

Now it seems that if I stop TIMER0, the LEDs don't go out, but stay at their last value. I would think the PWM would cease when the timer did. Hmmmmm. Well I could simply set it to 0? Nope, that is the same behavior, so something else is leaving them where they are at. Twiddling the output pins seems like a hack. Any ideas?


WARNING!
Below is the babble of strange reasoning, or lack thereof that I had written up, before I tried to fiddle with some things. Read if you care or dare
_________________________________________________________________
Second, an update of where I am at. I have a result more consistent with a runaway WDT. That is, the LEDs I have attached flash at the rate for which I've set the WDT. I have tested both at 1s delay and a 4s delay.

In my first iteration, i did have WDE and WDIE set to 1 and 0 respectively. I was attempting to use the reset to loop through my main() 1 time per second to perform a test and stay on if true. I did this to try and avoid the need to pass to interrupts to make the coding a bit easier for a newb.

I thought that running it through the interrupts would be a wiser way to do things as was reinforced by the input above. Now I have the code in the interrupt. The WDT does not disable, nor does the Timer0 seem to start. I get a brief flash when the WDT interval has elapsed.

Code: Select all

ISR (WDT_vect)
{
	if (readADC() >= 350)
	{
		wdt_disable();
		srandom(123);
		MCUCR &= ~(1<<SM1);
		TCCR0B |= (1<<CS00); //Start the clock!
	}
	else
	{
		TCCR0B &= ~(1<<CS00);
		MCUCR |= (1<<SM1);
		wdt_enable(WDTO_1S);
		WDTCR |= (1<<WDIE);
	}
}
If conditions are such that readADC() > 350, nothing happens. This is somewhat good as it shows a reaction to the stimulus. The behavior in this branch is correct.

If conditions are such that readADC() < 350, I get a pulse on the LEDs at the WDT's set interval. The behavior in this branch is not correct. Rather than a brief flash, there should be steady pulsating. This loops works fine when run in the absence of all the routines around the WDT or in the event that I enable the pulsating loop before initiating the WDT.

I am inclined to believe that the WDT does not turn off. The pulse of light at the interval hints that Timer0 may indeed start, but then dies. The AVR-libc docs do state that wdt_disable() attempts to turn off the WDT "if possible". Is there a way I can encourage this possibility?

Mowserson2
 
Posts: 5
Joined: Sun Oct 17, 2010 8:01 pm

Re: Watchdag vs powerdown

Post by Mowserson2 »

Glad to help.

Regarding your code snippet: I've been advised that it is a good idea to have interrupt handlers be quite short. In particular, the readADC() call might be too much from within an interrupt handler.

That might just be a matter of style, but here are some weird things when running from within ISR(): interrupts have been disabled. In particular, analog digital conversion completion interrupt is disabled and timer/counter overflow interrupts are disabled. Also, if you have any assembly, you need to be extra careful since interrupts can be called in the middle of a function. GCC is usually careful about this, but only for C code, not for assembly. I've been tricked by: r1 not being 0, r0 and r1 need to be restored afterwards, the carry/equality flags need to be restored afterwards, etc.

My interrupt handlers just set global variables, like "timeToRead", and then my main loop is just:

Code: Select all

int main(void) {
  setup();
  for(;;) {
    if(timeToRead) readADCAndReact(); // and set timeToRead=0
    if(timeToBlink) alterPWMSettings(); // and set timeToBlink=0
    PowerDown();
  }
}
This keeps all of the fancy processing happening outside of the interrupt.

User avatar
efh
 
Posts: 19
Joined: Tue Oct 12, 2010 11:04 am

Re: Watchdag vs powerdown

Post by efh »

I have taken heed of the comments regarding keeping the ISR code short and sweet. I concur that the the calls were too long. I was hoping for roughing in functionality before making it pretty. Though, I spent some time cleaning things up last night and trimmed about 30 bytes off compile size at the same time. It may be that my calls being long may be part of my issue. I will clean those up this evening. I appreciate the example on triggering global variables from the ISR call and showing how to get them back to main(). I was following the logic of exection incorrectly. I was thinking the interrupt coming from powerdown() would return where it left off and run powerdown() again. I can see that it would actually be passed back to the top of the for loop. This change will be substantial enough that I expect to see some change in the behaviour on execution, if not a whole remedy. More input for the learning.

edit: With a delay of 1 or 4 seconds for the WDT I didn't expect the interrupts to overlap, thus my putting off the cleanliness of the project. The Timer0 loop on the other hand could collapse in on itself, potentially. Oddly enough, the Timer0 loop seems to work fine. Oh the joys of unexpected issues! Keeps it fun.

further edit: I think I found my problem in this post http://www.avrfreaks.net/index.php?name ... highlight=
I will update this evening if I can indeed find the time to make all these changes. Though all the changes make more concise and easier to read code!

User avatar
efh
 
Posts: 19
Joined: Tue Oct 12, 2010 11:04 am

Re: Watchdog vs powerdown

Post by efh »

Here is what is working now:

Code: Select all

void timerStop()
{
    TCCR0A = 0;
    TCCR0B = 0;   //Start Timer0
    TCCR0B &= ~(1<<CS00); //Stop Timer0
    pulseTime = 0;
}
My stop timer routine, with the global flag at the time that main() reads to not pulse() anymore.

Code: Select all

MCUCR &= ~(1<<SM1);   //Disable Deep Sleep
    TCCR0A |= (1<<WGM00) | (1<<WGM01) | (1<<COM0A1) | (1<<COM0B1); //WGM 00 and 01 set for Fast PWM. COMA and B on for noninverted PWM.
    TCCR0B = (1<<CS00);   //Start Timer0
    pulseTime = 1;
My timer start routine, not that it is the whole initialization all over again. With the flag on the end to signal main() to pulse().

I did similar with my ISR (WDT_vect) where it also just sets a flag. I need to slow the cycle some now that I have cleaned it up and put more inline commands in. Less time swapping values between variables. Working on putting constants in as appropriate, but have the bulk of the functionality that I sought!!!

woot!

edit: Now i can add 3k worth of bloat....er...., I mean refinements!

Locked
Please be positive and constructive with your questions and comments.

Return to “Microcontrollers”