Watchdog Reset Code Question

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

I wanted to try and get a soft reset working using a watchdog timer on an atmega328p.  At first, my code did not work.  I did some google research and found that "newer" AVRs will restart with the watchdog still enabled, but at the lowest time setting.  This results in the AVR getting stuck in a reset loop until you add this bit of code:

 

void wdt_init(void) __attribute__((naked)) __attribute__((section(".init3")));

void wdt_init(void)
{
	MCUSR = 0;
	wdt_disable();
	return;
}

I found this in a FAQ on atmel's page.  That's great it works, but I want to understand why.  I've read up a bit on naked attributes and all I've got so far is that they don't have prologue/epilogue sequences generated by the compiler.  I'm not sure how that makes the code execute before the watchdog restarts the AVR.  Furthermore, the above code only includes the prototype and implementation - its never called so how is it working?  In fact, calling wdt_init() from within the main AVR program will cause the fix to stop working.

 

Last question is on the location of this code.  Does it have to live within the main.c program or can it be placed in a header file?  I could try it out and see if it works, but I've learned enough to know just because it works doesn't mean it's a good idea. 

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

You should be able to find past threads discussing this.  It might be helpful to note that the situation, and approach for "solution", will be toolchain-dependent.  So it is the GCC gurus that will need to comment in detail.

 

As you noted, it is at the lowest time setting.  That is 16ms on that AVR model, right?  I'd think that nearly all AVR8 apps would get to main() within that time, so have you tried a simple WDR at the head of main()?

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

The user manual for the compiler you are using...

 

http://www.nongnu.org/avr-libc/u...

 

Two pages you will probably find interesting,..

 

1) http://www.nongnu.org/avr-libc/u...

(although the formatting is screwed up you'll recognise where Atmel got their .init3 code from)

 

2) http://www.nongnu.org/avr-libc/u...

That explains about what .init 3 is

 

The fact is the .init0 to .init9 (and the .fini sections) are "drop through code sections within the CRT. The code you put in .init3 is "naked" which means it doesn't end with RET. It is just placed "inline".

 

The CRT itself starts with a JMP at the start of ".vectors" which jumps to the code in .ini2 that initializes the CPU then in .init4 is the code that sets up .data and .bss. So if you add some naked code in .init3 it slots in between these two things. The .init2 code (CPU init) drops into your WDT code which then drops into the .data/.bss code in .init4.

 

As for putting code in header files - don't EVER do that. You can put this .init3 code in any .c source file you like (the fact that it is ".iint3" will ensure it goes into the code in the right place) but do NOT put it in a .h file just as you would never do with the code of any function.

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

The code you put in .init3 is "naked" which means it doesn't end with RET. It is just placed "inline".

But the OP's posted code that "works" looks like a function with a return;   Are you sure the initN code chunks aren't called?  Just curious.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

A "return" in a void function will be ignored completely. The last "}" itself is usually an implied "return" anyway. But when you make a function "naked" the very term "naked" means "no prologue and no epilogue" and RET occurs at the end of the epilogue so you don't get any RET (or PUSH/POP protection either)

 

What you therefore end up with is:

$ cat naked.c
#include <avr/io.h>

void __attribute__((naked, section(".init3"))) inthebuff(void) {
  PORTB = 0x55;
  return;
}

int main(void) {
  while(1);
}
$ avr-gcc -save-temps -g -mmcu=attiny13 -Os naked.c -o naked.elf
$ avr-objdump -S naked.elf 

naked.elf:     file format elf32-avr


Disassembly of section .text:

00000000 <__vectors>:
   0:   09 c0           rjmp    .+18            ; 0x14 <__ctors_end>
   2:   10 c0           rjmp    .+32            ; 0x24 <__bad_interrupt>
   4:   0f c0           rjmp    .+30            ; 0x24 <__bad_interrupt>
   6:   0e c0           rjmp    .+28            ; 0x24 <__bad_interrupt>
   8:   0d c0           rjmp    .+26            ; 0x24 <__bad_interrupt>
   a:   0c c0           rjmp    .+24            ; 0x24 <__bad_interrupt>
   c:   0b c0           rjmp    .+22            ; 0x24 <__bad_interrupt>
   e:   0a c0           rjmp    .+20            ; 0x24 <__bad_interrupt>
  10:   09 c0           rjmp    .+18            ; 0x24 <__bad_interrupt>
  12:   08 c0           rjmp    .+16            ; 0x24 <__bad_interrupt>

00000014 <__ctors_end>:
  14:   11 24           eor     r1, r1
  16:   1f be           out     0x3f, r1        ; 63
  18:   cf e9           ldi     r28, 0x9F       ; 159
  1a:   cd bf           out     0x3d, r28       ; 61

0000001c <inthebuff>:
#include <avr/io.h>

void __attribute__((naked, section(".init3"))) inthebuff(void) {
  PORTB = 0x55;
  1c:   85 e5           ldi     r24, 0x55       ; 85
  1e:   88 bb           out     0x18, r24       ; 24
  20:   02 d0           rcall   .+4             ; 0x26 <main>
  22:   02 c0           rjmp    .+4             ; 0x28 <_exit>

00000024 <__bad_interrupt>:
  24:   ed cf           rjmp    .-38            ; 0x0 <__vectors>

00000026 <main>:
  return;
}

int main(void) {
  26:   ff cf           rjmp    .-2             ; 0x26 <main>

00000028 <_exit>:
  28:   f8 94           cli

0000002a <__stop_program>:
  2a:   ff cf           rjmp    .-2             ; 0x2a <__stop_program>

I built that for tiny13 as I know it to have a short IVT so the listing wouldn't generate a lot of noise.

 

As you can see my naked/init3 routine has done nothing but add:

  1c:   85 e5           ldi     r24, 0x55       ; 85
  1e:   88 bb           out     0x18, r24       ; 24

Just before the "CALL main". I didn't have any .data or .bss variables so the _do_copy_data or _do_clear_bss that would be after those two opcodes but before the CALL main are not present (again to shorten things)

 

In effect the odd numbered .init sections (1,3,5,7 and curiously 8 not 9) are "user intercept points" which allow you to add code "in between" any stages of the CRT just by flagging a routine as __attribute__((section(".initN"))) but, to make it "inline" you also need "naked" too.

Last Edited: Fri. Jan 30, 2015 - 05:38 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
In fact, calling wdt_init() from within the main AVR program will cause the fix to stop working.
That is a consequence of the function being naked.  That was adequately (expertly) explained by Cliff.  However it isn't immediately clear that by calling wdt_init() from within main you will wind up jumping into the middle of the CRT, which would by many appearances seem like a software reset.  Don't do it ;)

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"Read a lot.  Write a lot."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

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

Thanks for the great explanation.  I did read the avr/wdt.h, but was confused by that bit of code, so I opted for he solution from the Atmel page.  They both work (and probably do the same thing).  With the detailed description above, I think the code from avr/wdt.h seems more clear.  I've still got more learning/research to do before it all becomes immediately obvious...

 

I tried reducing the start up time of the AVR to the shortest possible interval (258 CK 14CK + 4.1 ms with an external oscillator running at 8mHz) and putting WDR as the first line of main() and it didn't work.

EDIT: ADDING A WDR TO THE BEGINNING OF MAIN() CAN WORK, SEE MY LAST POST IN THIS THREAD FOR DETAILS

 

I did remove the "return" and the code still worked, as was indicated by Clawson.

 

Again, thanks for the insight everyone.  I really appreciate it!

Last Edited: Sat. Jan 31, 2015 - 06:05 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I tried reducing the start up time of the AVR to the shortest possible interval (258 CK 14CK + 4.1 ms with an external oscillator running at 8mHz) and putting WDR as the first line of main() and it didn't work.

 

That's interesting.  I'd think the only time-consuming prologue stuff would be loading initial values from flash to SRAM.  It would take a lot to chew up many milliseconds.

 

I took one of my largest AVR8 apps, Mega1280 about 75% full.  I use CodeVision. I have very little initialized data.  Yet the startup time to main was 50,000 cycles.

 

 

Now, I clear all SRAM at startup, and there is 8KB, so if the clear loop takes e.g. 4/5/6 cycles per byte -- there you go.

 

Lessee--at 8MHz, that would be about 6ms, right?  Hmm--still nowhere close to 16ms. 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

AKnick wrote:
I tried reducing the start up time of the AVR to the shortest possible interval (258 CK 14CK + 4.1 ms with an external oscillator running at 8mHz)
The startup time is not relevant.  The AVR is already in reset.  That's what the startup time means.  RESET is asserted as long as mandated by the startup time, after which RESET is deasserted, the core starts to execute instructions, and the watchdog timer starts to count.  So you can use the longest startup time if you like.  The AVR will still have 16 ms before a WDT reset kicks in.  At 1 MHz, that's 16,000 cycles.  At 8 MHz, it's 128,000 cycles.  As noted, init3() will run almost immediately (way less than 100 cycles).

 

Quote:
and putting WDR as the first line of main() and it didn't work.
What didn't work?

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"Read a lot.  Write a lot."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

Last Edited: Fri. Jan 30, 2015 - 09:54 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What didn't work?

In context, I assume it is my suggestion of WDR at top of main, and in OP's case it did not stop cascading watchdogs.  I'd think chewing up 16ms would be tough, even for GCC.  (OP--perhaps fire up  Studio simulator like I did, "start debugging and break", and see what the stopwatch says?)

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

EDIT: IGNORE THIS POST, IT CONTAINS INCORRECT INFORMATION.  PLEASE SEE MY LAST POST.

 

joeymorin wrote:

Quote:

and putting WDR as the first line of main() and it didn't work.

What didn't work?

 

Adding a WDR command as the first line of main() to stop the WDT reset loop.

 

Interestingly enough, my simulator indicates the AVR will get to the first line of main() within 4us given the bare bones test program I wrote.  So, that's proof the start up time doesn't matter - I just had to test it out ;-)

 

//*********AVR LIBRARIES TO LOAD*********
#include <avr/io.h>
#include <util/delay.h>
#include <avr/sfr_defs.h>
#include <stdlib.h>
#include <avr/wdt.h>
#include <avr/interrupt.h>
/*Modern AVRs will restart from a watchdog reset with the timer
active with the lowest time value.  This often results in the AVR
getting stuck in a reset loop.  The following code is the work
around. */
uint8_t mcusr_mirror __attribute__ ((section (".noinit")));
void get_mcusr(void) \
__attribute__((naked)) \
__attribute__((section(".init3")));
void get_mcusr(void)
{
	mcusr_mirror = MCUSR;
	MCUSR = 0;
	wdt_disable();
}

int main(void)
{
	//_delay_ms(2000);
	//to properly use software watchdog, disable interrupts, reset WD timer, disable WD, enable WD with correct time
	cli();
	wdt_reset(); //resets watchdog timer
	wdt_disable(); //disables watchdog timer
	WDTCSR = (1<<WDCE)|(1<<WDE); //configure WDTCSR register for AVR reset
	WDTCSR = (1<<WDE) | (1<<WDP2) |(1<<WDP1) | (1<<WDP0);
	sei();
	//configure IO pins
	DDRB |= (1<<PB0)|(1<<PB2);	//set DDRB0 & DDRB2 to one, configure points B0 & B2 as outputs
	PORTB |= (1<<PB0);
}

Last Edited: Sat. Jan 31, 2015 - 06:04 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

AKnick wrote:
joeymorin wrote:

What didn't work?

Adding a WDR command as the first line of main() to stop the WDT reset loop.

But (on a modern AVR) it couldn't possibly do that.  All wdr does is reset the watchdog timer, not disable it.  At best it would buy you another 16 ms, but unless you disable the watchdog timer (or keep feeding it with wdr), it will overflow and trigger a reset.

 

Quote:
I just had to test it out ;-)
Glad you sorted it out.

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"Read a lot.  Write a lot."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

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

joeymorin wrote:

 

 

But (on a modern AVR) it couldn't possibly do that.  All wdr does is reset the watchdog timer, not disable it.  At best it would buy you another 16 ms, but unless you disable the watchdog timer (or keep feeding it with wdr), it will overflow and trigger a reset.

 

 

 

That's true.  The test was weather or not a WDR reset at the beginning of main() would prevent the WDT from resetting the AVR before it reached the WDT config/initialization that change the WDT from 16ms to 4s. 

 

However adding a WDR to the beginning of main() will work, provided you disable or re-scale the WDT before the 16ms runs out.  I was incorrect in my previous post stating that this would not work, I had the 2s delay in the wrong location to allow for proper testing.  This code uses the WDT to blink and LED on 2 second intervals, it works because the AVR makes it to lines 12/13 before the 16ms WDT expires.  So, there is no need for the naked "init3" bit of code in this case.  I'm not saying its a good idea to omit it, just proving myself wrong blush I'll go edit my previous post so it's less confusing to anyone who reads this in the future.

 

//*********AVR LIBRARIES TO LOAD*********
#include <avr/io.h>
#include <util/delay.h>
#include <avr/sfr_defs.h>
#include <stdlib.h>
#include <avr/wdt.h>
#include <avr/interrupt.h>
int main(void)
{
	//to properly use software watchdog, disable interrupts, reset WD timer, disable WD, enable WD with correct time
	cli();
	wdt_reset(); //resets watchdog timer
	wdt_disable(); //disables watchdog timer
	WDTCSR = (1<<WDCE)|(1<<WDE); //configure WDTCSR register for AVR reset
	WDTCSR = (1<<WDE) | (1<<WDP3); //starts 4s watchdog
	sei();
	_delay_ms(2000);
	//configure IO pins
	DDRB |= (1<<PB0)|(1<<PB2);	//set DDRB0 & DDRB2 to one, configure points B0 & B2 as outputs
	PORTB |= (1<<PB0);  //turn on PB0
}

 

Last Edited: Sat. Jan 31, 2015 - 06:09 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

All my watchdog needs on the 328p is the code:

//Write a logical one to WatchDog Change Enable (WDCE) and WatchDog Enable (WDE)
WDTCSR = (1 << WDCE) | (1 << WDE) ;

//Set up Watchdog (and clear WDCE): Clear I flag | Interrupt only | 64ms timeout
WDTCSR = (1 << WDIE) | (1 << WDP1) ;

Where WDP1 is just for prescaling and therefore not crucial for the operation.

sol i sinne - brun inne

Last Edited: Sat. Jan 31, 2015 - 04:28 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

try this:

 

void setup()

{

   cli();

   wdt_reset();

   MCUSR=0;

   sei();

   wdt_disable();

 

//...

}

trk

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

If they didn't solve it in the last 3.5 years do you think they are still sitting around trying to find a solution ??

 

Also your method is NOT the best solution anyway. Use a function in .init1 or .init3 to intercept the CRT early - don't wait for setup() - it can be a long time after startup before setup() is eventually called.

 

In fact OP already used the .init3 technique in #1. That IS the best approach (in avr-gcc).