Trouble setting watchdog timer period on ATmega32M1 - Page 1
Forum section: electronics / general

author: HwAoRrDk
author_rank: Super Contributor
timestamp: August 28, 2018, 10:02:55 pm
content: I have some existing AVR code that uses the watchdog timer to asynchronously flash an LED at varying rates (e.g. 1Hz, 2Hz, etc.). I've used it before (with minor adaptations) on ATmega328P, ATtiny, etc. But I'm having trouble trying to get it to work on an ATmega32M1.Here's the code I'm using:Code:#define LED_FLASH_1_HZ (_BV(WDP2) | _BV(WDP1))#define LED_FLASH_2_HZ (_BV(WDP2) | _BV(WDP0))#define LED_FLASH_4_HZ (_BV(WDP2))#define LED_FLASH_8_HZ (_BV(WDP1) | _BV(WDP0))void led_flash_start(uint8_t frequency) {frequency &= _BV(WDP0) | _BV(WDP1) | _BV(WDP2) | _BV(WDP3); // Mask WDPn bitsif(frequency > (_BV(WDP0) | _BV(WDP3))) frequency = (_BV(WDP0) | _BV(WDP3)); // Ensure value is not in 'reserved' rangeATOMIC_BLOCK(ATOMIC_RESTORESTATE) {WDTCSR |= _BV(WDCE) | _BV(WDE);WDTCSR = _BV(WDIE) | frequency;}}ISR(WDT_vect) {PINB |= _BV(PINB3); // Toggle LED.}As far as I can see, the 32M1's watchdog timer works in the same way as the 328P's, so the code above is exactly the same as that I've successfully used on an Arduino board. But yet, it doesn't work as expected. It just goes into a reset loop at the point (or immediately after) thatled_flash_start()is called. "Well, duh! Sounds like the watchdog is causing the reset!", I hear you say. But I'm not leaving it in reset mode! WDE should be back to zero after I set WDIE and the prescaler bits, so therefore it should be operating in interrupt-only mode.If I change the code to avoid setting WDE in the first place (i.e. setting WDCE only), the prescaler bit changes don't take effect - the timer stays at the default of 16ms. So it seems that both WDCE and WDE need to both be set to change things. But yet setting WDE somehow triggers the resetting behaviour.So I have reached an impasse and I don't know how I'm supposed to get this to work on this MCU.Help!By the way, one other bizarre thing I observed is that the reset-loop behaviour persists even after re-flashing with the offending code commented out, and cycling power!Although, I suspect this might be due to also having a USB UART adapter connected that may perhaps be inadvertently powering the MCU through the IO pins when the main supply is disconnected. When I disconnected everything from the board, normal operation was resumed.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: August 29, 2018, 12:35:58 am
content: IthinkI figured out what the problem was. I believe it is a problem with the compiler generating machine code that doesn't quite do things in the same order as the C code.The assembly code generated for theled_flash_start()function code as previously given was as follows:Code:000000ca <led_flash_start>:ca:87 72andir24, 0x27; 39cc:2f b7inr18, 0x3f; 63ce:f8 94clid0:90 91 60 00ldsr25, 0x0060; 0x800060 <__TEXT_REGION_LENGTH__+0x7e0060>d4:98 61orir25, 0x18; 24d6:90 93 60 00sts0x0060, r25; 0x800060 <__TEXT_REGION_LENGTH__+0x7e0060>da:82 32cpir24, 0x22; 34dc:08 f0brcs.+2; 0xe0 <led_flash_start+0x16>de:81 e2ldir24, 0x21; 33e0:80 64orir24, 0x40; 64e2:80 93 60 00sts0x0060, r24; 0x800060 <__TEXT_REGION_LENGTH__+0x7e0060>e6:2f bfout0x3f, r18; 63e8:08 95retI don't really know what I'm doing with assembly, but I can recognise what I think are the instructions writing to the WDTCSR register (sts 0x0060...- 0x60 being the address of the reg), and there are4instructions between the two instances. Given that the datasheet specifies that the changes to the watchdog configuration must be done within four clock cycles, this code would appear to overrun that timing requirement!I'm not 100% sure what the stuff in-between is, but it looks like (because of the branch instruction) it might be theif()statement that ensures thefrequencyvariable is not a reserved value.If I comment thatif()line out and re-compile, the generated assembly code is drastically different:Code:000000ca <led_flash_start>:ca:2f b7inr18, 0x3f; 63cc:f8 94clice:e0 e6ldir30, 0x60; 96d0:f0 e0ldir31, 0x00; 0d2:90 81ldr25, Zd4:98 61orir25, 0x18; 24d6:90 83stZ, r25d8:87 72andir24, 0x27; 39da:80 64orir24, 0x40; 64dc:80 83stZ, r24de:2f bfout0x3f, r18; 63e0:08 95retI don't quite recognise what's going on - the only reference to the memory address of the WDTCSR register (0x60) is in loading that value to another register - but hazarding a guess that the twostinstructions are the ones writing to WDTCSR, there are now only two instructions separating them, which now complies with the timing requirements. And, this code actually works!Bloody compilers, re-arranging my code!I think I'll just remove theif()statement, as it's not strictly necessary, just a sanity check against if one were to pass some random value into the function.I also think I'll add a line to reset the watchdog at the beginning of the atomic section, as the datasheet notes that when changing the timer period to a shorter value, you might get a premature time-out, and resetting prior will mitigate that.

author: cv007
author_rank: Frequent Contributor
timestamp: August 29, 2018, 07:04:29 am
content: QuoteJust as the datasheet says.You could make an enum to limit the values passed without having to check for valid values, something like-Code:typedef enum { WDTO_10MS = 0, ..., WDTO_8S = 0x21 } WDTO_t;//the WDIE bit could also be incorporated into each enum, if only using wd irqvoid led_flash_start(WDTO_t frequency){ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {__asm__ __volatile__("wdr"); //or something already defined in headers//clear wdif, set wdce,wde, prescaler bits unchanged (protected), wdie also clearedWDTCSR = _BV(WDIF) | _BV(WDCE) | _BV(WDE);//now set wdie and prescaler, clear wde (irq mode)WDTCSR = _BV(WDIE) | frequency;}}QuoteWDRF flag overrides WDE. Maybe the recycling power didn't really get you a power on reset (power to micro never really dropped far enough- ext reset only). Maybe not the case, but if no one is taking care of the WDRF flag, then enabling change to watchdog but not meeting time/cycle requirement wde will remain enabled which gets you a real watchdog reset after the first watchdog irq reset (wdie not protected, so still will get set). After watchdog reset, WDRF is set so WDE is also set. No one touches WDRF, so- Loop.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: August 29, 2018, 03:59:43 pm
content: Quote from: cv007 on August 29, 2018, 07:04:29 amThanks for the suggestion - I already did that, actually. Don't really know why I did it with macros in the first place.Good point about clearing WDIF too, I will do that. Can't hurt to take a belt-and-braces approach.Quote from: cv007 on August 29, 2018, 07:04:29 amDoh! I forgot about WDRF persisting in MCUSR! That explains what was going on.I should have read the datasheet more thoroughly, as the example code given clears WDRF. I'll add that to my code too.