SLEEP_MODE_PWR_DOWN not working on Arduino Micro

Post here about your Arduino projects, get help - for Adafruit customers!

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
bhearsum
 
Posts: 11
Joined: Sat Aug 16, 2014 9:22 am

SLEEP_MODE_PWR_DOWN not working on Arduino Micro

Post by bhearsum »

I've been fighting with this for a few days now. I originally thought that I was making mistakes in my code, but after cutting down to the barebones and trying two different libraries that assist with sleeping, I'm pretty sure that the code is not the problem. I've been primarily working with the JeeLib library, which provides a convenient single function way to sleep for an extended period of time. Here's the code in question:

Code: Select all

#include <Ports.h>

int led_pin = 12;
ISR(WDT_vect) { Sleepy::watchdogEvent(); } // Setup the watchdog

void setup() {
    Serial.begin(57600);
    while (!Serial);
    Serial.println("in setup");
    pinMode(led_pin, OUTPUT);
}
  
void loop() {
    Serial.println("on");
    // Turn the LED on and sleep for 5 seconds
    digitalWrite(led_pin, HIGH);
    Sleepy::loseSomeTime(5000);

    Serial.println("off");
    // Turn the LED off and sleep for 5 seconds
    digitalWrite(led_pin, LOW);
    Sleepy::loseSomeTime(5000);
}
When I run this code the LED flickers on and off a couple of times per second. At first I thought this was loseSomeTime returning rapidly, but the Serial output continues to print "in setup" - so it appears that the board is resetting right after the first loseSomeTime call. I also tried the "Low-Power" library (https://github.com/rocketscream/Low-Power), and got the same behaviour.

I'm a bit of a loss, but also wondering if powering the Micro over USB is having an effect, or perhaps having it in a breadboard is causing some interrupt or something else to fire. The interrupt thing seems unlikely, because I never attach them. In my more complex set-up, I have an RTC hooked up to D2/D3, which are also external interrupts. But I get the same behaviour without the RTC, so it seems unlikely to be a factor.

Any help here would be appreciated.

User avatar
adafruit_support_mike
 
Posts: 67446
Joined: Thu Feb 11, 2010 2:51 pm

Re: SLEEP_MODE_PWR_DOWN not working on Arduino Micro

Post by adafruit_support_mike »

I'm afraid I don't know either of those libraries, and the function names don't provide enough information about what's happening behind the scenes to guess where the trouble might be occurring.

A couple of details from the datasheet caught my eye as potentially relevant though:

- The ATmega32u4's watchdog timer can emit a RESET event as well as a WDT event. Make sure the library is using the right one.

- A couple of the ATmega32u4's low-power modes shut off the watchdog timer's clock. Make sure the functions you're calling set the correct low-power behavior before dropping the microcontroller into sleep mode.

Also check the libraries to make sure they're compatible with the ATmega32u4. If they're only written for the ATmega328P of the standard Arduino, you might be seeing code that compiles but does The Wrong Thing when executed.

User avatar
bhearsum
 
Posts: 11
Joined: Sat Aug 16, 2014 9:22 am

Re: SLEEP_MODE_PWR_DOWN not working on Arduino Micro

Post by bhearsum »

I ended up trying to do this with raw avr code instead. I've tried a bunch of different things, but I always end up with the Micro resetting itself instantly after finishing its sleep cycle. The very simple code I'm trying as a test is as follows:

Code: Select all

#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>

void setup() {
    pinMode(12, OUTPUT);
    pinMode(11, OUTPUT);
    wdt_disable();
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    cli();
    MCUSR = 0;
    // Need to set this bit in order to change prescaler
    WDTCSR =    B00001000;
    // Enable watchdog interrupt, and change prescaler to ~8s
    WDTCSR =    B01100001;
    sei();
    wdt_enable(WDTO_8S);
}

void loop() {
    digitalWrite(12, HIGH);
    delay(500);
    digitalWrite(12, LOW);
    delay(500);

    sleep_mode();

    digitalWrite(11, HIGH);
    delay(500);
    digitalWrite(11, LOW);
}
Per the datasheet, I've reset all of MCUSR (though I think I only need to reset WDRF), set the WDCE bit to allow for changes to the prescaler, and then changed the prescaler to ~8s (WDP3 and WDP0) and enabled the watchdog interrupt by setting WDIE. When I program my Micro and let the code run the following happens:
* LED in pin 12 turns on for 500ms.
* Micro sleeps for ~8s.
* Repeat.

If I add serial code in it clearly shows the device going through setup over and over, and this is also evidenced by the LED on pin 11 never turning on.

I tried this code while using my computer's USB port, and with a wall socket (in case there was some sort of interrupt coming through the USB port).

User avatar
bhearsum
 
Posts: 11
Joined: Sat Aug 16, 2014 9:22 am

Re: SLEEP_MODE_PWR_DOWN not working on Arduino Micro

Post by bhearsum »

Oh, and I should mention that I checked my fuse bits - WDTON is not set.

User avatar
bhearsum
 
Posts: 11
Joined: Sat Aug 16, 2014 9:22 am

Re: SLEEP_MODE_PWR_DOWN not working on Arduino Micro

Post by bhearsum »

On a whim, I decided to spit out WDTCSR at the start of loop(). Turns out that WDE is set and WDIE is _not_. This explains why the watchdog is in reset instead of interrupt mode, but I'm not sure why or how. The latest code is as follows:

Code: Select all

#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>

void setup() {
    Serial.begin(57600);
    while (!Serial);
    pinMode(12, OUTPUT);
    pinMode(11, OUTPUT);
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    cli();
    MCUSR = 0;
    // Need to set this bit in order to change prescaler
    WDTCSR =    B00011000;
    // Enable watchdog interrupt, and change prescaler to ~8s
    WDTCSR =    B00110001;
    sei();
    wdt_enable(WDTO_8S);
}

void loop() {
    for (int i=7; i >= 0; i--) {
        Serial.println(bitRead(WDTCSR, i));
    }
    digitalWrite(12, HIGH);
    delay(500);
    digitalWrite(12, LOW);
    delay(500);

    sleep_mode();

    digitalWrite(11, HIGH);
    delay(500);
    digitalWrite(11, LOW);
}
And the output is:

Code: Select all

Terminal ready
0
0
1
0
1
0
0
1

User avatar
adafruit_support_mike
 
Posts: 67446
Joined: Thu Feb 11, 2010 2:51 pm

Re: SLEEP_MODE_PWR_DOWN not working on Arduino Micro

Post by adafruit_support_mike »

In most cases, it would make sense for the watchdog timer to be in RESET mode.

With embedded systems, the question isn't "will the code get wedged?" so much as "how long will it be until the code gets wedged?" Even the best system can go wonky because of a glitch in the power supply. In the environment where Arduinos live, SRAM overruns and other things that would be segfaults in a full-scale operating system are common.

We know from experience that Arduinos will reset in the event of a fault, so I'd guess the bootloader sets the WDT to RESET mode as a default.

User avatar
bhearsum
 
Posts: 11
Joined: Sat Aug 16, 2014 9:22 am

Re: SLEEP_MODE_PWR_DOWN not working on Arduino Micro

Post by bhearsum »

When you say the bootloader sets the WDT to RESET mode, is that different than the fuses? The hfuse on my Micro reads as d8, which according to calculators I've used, says that WDTON is _not_ set.

User avatar
adafruit_support_mike
 
Posts: 67446
Joined: Thu Feb 11, 2010 2:51 pm

Re: SLEEP_MODE_PWR_DOWN not working on Arduino Micro

Post by adafruit_support_mike »

WDTON is the fuse bit, but you still have to check WDE in the WDTCSR register and WDRF in the MCUSR register.

WDE actually controls whether the watchdog timer emits a system reset.

WDE is overridden by WDRF, and WRDF gets set any time a watchdog reset event occurs. You can't clear WDE while WDRF is set, so you have to clear WDRF then clear WDE. Changing the WDE setting also has to be done in a timed sequence which is described starting on page 55 of the ATmega32u4 datasheet.

WDTON sets WDE and disables the option to change it. Clearing WDTON doesn't automatically clear WDE though.

User avatar
bhearsum
 
Posts: 11
Joined: Sat Aug 16, 2014 9:22 am

Re: SLEEP_MODE_PWR_DOWN not working on Arduino Micro

Post by bhearsum »

I figured out the problem. Turns out that the interrupt MUST be handled even though I don't need to do anything there. My final code is:

Code: Select all

#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>

void setup() {
    //Serial.begin(57600);
    pinMode(12, OUTPUT);
    pinMode(11, OUTPUT);

    set_sleep_mode(SLEEP_MODE_PWR_SAVE);

    // Disable interrupts to make sure none are received during the watchdog
    // configuration, which is timing sensitive.
    cli();

    // Clear WDRF in MCUSR. Required before making any changes to the watchdog
    // timer configuration.
    MCUSR &= ~(1<<WDRF);

    // Timed sequence required for changing the watchdog timer. Writing a 1 to
    // both WDCE and WDE is required regardless of their current value.
    WDTCSR |= (1<<WDCE) | (1<<WDE);

    // Set-up the watchdog in interrupt mode and change the prescaler value.
    // WDIE = interrup mode
    // WDP3 + WDP0 = ~8s prescaler.
    WDTCSR = (1<<WDIE) | (1<<WDP3) | (1<<WDP0);

    // Re-enable interrupts
    sei();
}

// Even though we don't need to do anything, this interrupt MUST be handled,
// otherwise the device will reset.
ISR(WDT_vect) {}

void loop() {
    digitalWrite(12, HIGH);
    delay(1000);
    digitalWrite(12, LOW);
    //for (int i=7; i >= 0; i--) {
    //    Serial.println(bitRead(WDTCSR, i));
    //}
    //Serial.println("");
    delay(1000);

    sleep_mode();

    digitalWrite(11, HIGH);
    delay(1000);
    digitalWrite(11, LOW);
    delay(1000);
}

User avatar
adafruit_support_mike
 
Posts: 67446
Joined: Thu Feb 11, 2010 2:51 pm

Re: SLEEP_MODE_PWR_DOWN not working on Arduino Micro

Post by adafruit_support_mike »

I should have caught that one myself..

Under the hood, the interrupt table is a list of pointers to locations in memory. An interrupt tells the CPU to stop executing the code currently on the program counter, load the appropriate address from the interrupt table into the program counter, and resume execution.

If you don't define an interrupt service routine.. even a trivial one that returns immediately.. the interrupt table won't have a usable value in that slot.

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

Return to “Arduino”