Help with ATtiny85, WDT, and WDTON fuse

Go To Last Post
8 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm trying to work with WDTON (WDT enable fuse bit) on an ATtiny85 and am having some difficulty.

1) I can get the WDT to work as expected in either interrupt or reset mode as long as I don't set WDTON.

2) If I get WDTON, my program runs until the WDT times out and then resets. That's to be expected except that I'm calling wdt_reset().

I must be missing something but I can't figure out what. Anyone have a good example of how to setup the WDT when WDTON is set and how to reset the timer and prevent the reset. I've read the ATtiny85 spec sheet over and over and there's just not quite enough information.

Thanks...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

That's to be expected except that I'm calling wdt_reset().

The key thing is WHERE are you calling wdt_reset() and will that point always be executed with a shorter period than that of the timeout?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think I've tracked down the problem. I was following the example code in the ATtiny85 datasheet showing the "C method" of changing the WDTCL register when you are in "Safety Mode 2". You have only "4 clock cycles" to change the WDT prescaler after enabling the change. But when doing it in C the generated code takes too long.

I recoded that using ASM and it works fine. After looking at the generated code I can see that indeed there are too many instructions to make it within the 4 cycle limit. I'll post some code when I get a chance. It's possible I'm still doing something wrong as I'm pretty new to AVR architecture.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

I recoded that using ASM and it works fine. After looking at the generated code I can see that indeed there are too many instructions to make it within the 4 cycle limit. I'll post some code when I get a chance. It's possible I'm still doing something wrong as I'm pretty new to AVR architecture.

Please don't tell me that you are yet another person using AS5+AVR Toolchain who's just been bitten by Atmel's madness of making the default build -O0 (that is no optimisation). if so someone at Atmel should be shot!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes (face ashamedly red!) I guess that would be me! I didn't change the compiler optimization from the default as I didn't think that something like:

WDTCTL = 0x00;

Could take much optimization... Seems obvious now, but them most problems seem opbious once you know the answer...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Here's a recap of the situation and what I finally did to resolve it.

First, I was trying to make use of the WDTON fuse to ensure that the WDT is always active. No problem setting the fuse but every time I programmed it, my device just continuously ran through RESET at the DEFAULT rate, not the rate I was trying to set. It was like the AVR was not performing my code to change the prescaler for the WDT.

Here is the code I was starting with, based loosely on the code in the ATtiny85 document page 46:

int main(void)
{
	// Initialize the WDT
	wdt_reset();					// Atmel docs say to execute WDR first
	MCUSR = 0x00;					// Then clear the MCUSR flags
	WDTCR |= (1<<WDCE) | (1<<WDE);	// Write WDCE and WDE to 1 to "unlock" WDTCR and let us change it
	WDTCR = (1<<WDP2) | (1<<WDP1);	// Set prescale for 128K (~1.0 sec) with 0110 in WDP3-WDP0
	
    while(1)
    {
        //TODO:: Please write your application code 
    }
}

Since I'm in "Safety level 2" (described on page 45) I have to:

Quote:

1. In the same operation, write a logical one to WDCE and WDE. Even though the WDE
always is set, the WDE must be written to one to start the timed sequence.
2. Within the next four clock cycles, in the same operation, write the WDP bits as desired,
but with the WDCE bit cleared. The value written to the WDE bit is irrelevant.

I would have thought that the:

WDTCR = (1<<WDP2) | (1<<WDP1);

would be able to meet the 4 clock cycle limit imposed on the WDTCR register. But it sure
seemed like it didn't work!

Looking at the generated ASM code (see the .lss file in the project folder) you can see the
AVR ASM instructions generated:

	WDTCR = (1<<WDP2) | (1<<WDP1);	// Set prescale for 128K (~1.0 sec) with 0110 in WDP3-WDP0
  54:	81 e4       	ldi	r24, 0x41	; 65
  56:	90 e0       	ldi	r25, 0x00	; 0
  58:	26 e0       	ldi	r18, 0x06	; 6
  5a:	fc 01       	movw	r30, r24
  5c:	20 83       	st	Z, r18	

As you can see we have 3 LDIs which take 1 cycle each, a MOVW which takes 1, and
an ST which takes 2 cycles. That totals 6 CLOCK CYCLES! No wonder this doesn't work
we missed our "window" by 2 clocks.

I chalked this up to a crappy C compiler and just wrote it in ASM (I started out with ASM on
the MOS 6502 CPU a long long time ago.) I modified the code to be:

	// Initialize the WDT
	wdt_reset();					// Atmel docs say to execute WDR first
	MCUSR = 0x00;					// Then clear the MCUSR flags
	WDTCR |= (1<<WDCE) | (1<<WDE);	// Write WDCE and WDE to 1 to "unlock" WDTCR and let us change it
	//WDTCR = (1<<WDP2) | (1<<WDP1);	// Set prescale for 128K (~1.0 sec) with 0110 in WDP3-WDP0
	
	// The above C code takes to long, try it in ASM
	asm("PUSH R18");		// Save R18 in case the compiler assumes it's not changed (1 clock)
	asm("LDI R18, 0x06");	// Get our value (1<<WDP2) | (1<<WDP1) into R18 (1 clock)
	asm("OUT 0x21, R18");	// Save in WDTCR (address 0x21) (1 clock)
	asm("POP R18");			// Restore R18.  Takes 1 clock but doesn't matter now.
	//

Now we get the 0x06 written to the WDTCR in 3 clocks which is within our 4 clock limit. And after testing I
find that this works. But it turns out this is the HARD way around. User clawson helpfully pointed out that
the default C optimization on the AVRGCC compiler is not a wise choice. So I tried with optimization turned on
instead. So I selected "-O1" from the compuiler optimization level and went back to my original code. And now
the compiler does a much better job of generating the code. Better than I did because it knows what registers
it needs to save:

	WDTCR = (1<<WDP2) | (1<<WDP1);	// Set prescale for 128K (~1.0 sec) with 0110 in WDP3-WDP0
  3a:	86 e0       	ldi	r24, 0x06	; 6
  3c:	81 bd       	out	0x21, r24	; 33
	//asm("LDI R18, 0x06");	// Get our value (1<<WDP2) | (1<<WDP1) into R18 (1 clock)

Now we see a much more efficient bit of code but more importantly, code that gets the needed value
into WDTCR in LESS than the 4 clock limit.

I'll follow this up with a more detailed WDT/WDTON fuse bit article when I'm done with this. I really didn't find
any good articles on this topic, which is what got me started down this rabbit-hole to begin with...

Enjoy and I hope this helps someone else new to AVR programming out in some way.

As always, any feedback or comments are welcome.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

I chalked this up to a crappy C compiler and just wrote it in ASM

Which is exactly why Atmel should not have screwed it up in the IDE like they have.

It's clear why they did so that the "Debug" debugging session would work as the user expects. But it's pretty lame to nobble the entire operation of the C compiler just to achieve this small gain.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for the tip on this.