LowPower.standby() has insomnia (it won't stay asleep)

I'm using:

  • SAMD21 XPRO board with this variant.cpp
  • The RocketScream AVR & SAMD power management library, v 2.1

My sketch is designed to go into STANDBY mode until a button press interrupt wakes it. I've verified that the button press generates an interrupt as expected.

The problem is that the call to LowPower.standby() returns immediately at every call. My hunch is that I need to clear an interrupt or something equivalent. Any suggestions or examples for that?

Sketch follows...

#include <stdbool.h>
#include <LowPower.h>

#define WAKE_DURATION_MS 5000ul
// Arduino pin 13 = PA15 on SAMD21
//                = EXT3.6 on SAMD21 XPRO edge connector
//                = user button (low true, needs pullup)
#define USER_BUTTON_PIN 13

volatile bool s_button_pressed;

void setup(void) {
  Serial.begin(115200);
  while (!Serial) {
    // wait for serial port to connect.
  }
  Serial.println("\nPower Test: Arduino v 0.1");
  user_led_setup();
  pinMode(USER_BUTTON_PIN, INPUT_PULLUP);
  attachInterrupt(USER_BUTTON_PIN, on_button_press, FALLING);
}

void loop() {
  s_button_pressed = false;
  user_led_off();
  Serial.print("Sleeping until button pressed...");
  // while (!s_button_pressed) {  }   // wait for button press (buzzing)
  LowPower.standby();                 // BUG: this returns immediately

  // arrive here after button pressed.
  user_led_on();
  Serial.print("Button pressed.  Awake for ");
  Serial.print(WAKE_DURATION_MS);
  Serial.println(" milliseconds");
  delay(WAKE_DURATION_MS);
}

void on_button_press(void) {  s_button_pressed = true; }

void user_led_setup(void) {
  pinMode(LED_BUILTIN, OUTPUT);
  user_led_off();
}
void user_led_on(void) { digitalWrite(LED_BUILTIN, LOW); }
void user_led_off(void) { digitalWrite(LED_BUILTIN, HIGH); }

Update:

I tried adding this before calling LowPower.standby();, but no improvement:

  NVIC_DisableIRQ(EIC_IRQn);
  NVIC_ClearPendingIRQ(EIC_IRQn);
  NVIC_ClearPendingIRQ(RTC_IRQn);
  NVIC_EnableIRQ(EIC_IRQn);

I also printed out the value of NVIC->ISPR[0] (pending interrupt bits, right?) and it was zero. So maybe it's not a pending interrupt that's preventing it from entering STANDBY mode?

Serial.flush() before trying to enter standby?

Excellent idea, but didn't help.

Hi @rdpoor

The issue is that placing the SAMD21 in standby turns off the clock to the External Interrupt Controller (EIC) peripheral, therefore in sleep mode it's not possible to detect falling or rising edge interrupts.

In this instance, it's necessary to use level detection instead, replacing FALLING with LOW.

Hi @rdpoor

Here's the code that I used:

void setup(void) {
  pinMode(A1, INPUT_PULLUP);                      // Intialise button input pin and activate internal pull-up resistor
  pinMode(LED_BUILTIN, OUTPUT);                   // Initialise the LED_BUILTIN output
  attachInterrupt(A1, NULL, LOW);                 // Activate a LOW level interrupt on the button pin
  NVMCTRL->CTRLB.bit.SLEEPPRM = NVMCTRL_CTRLB_SLEEPPRM_DISABLED_Val;    // Prevent the flash memory from powering down in sleep mode
  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;              // Select standby sleep mode
  SerialUSB.begin(115200);                        // Intialise the native USB port
  while (!SerialUSB);                             // Wait for the console to open
}

void loop() {
  digitalWrite(LED_BUILTIN, LOW);                 // Turn off the LED
  SerialUSB.println(F("Sleeping Zzzz...wait for button to wake"));  // Send sleep message to the console
  USBDevice.detach();                             // Detach the native USB port
  __DSB();                                        // Ensure remaining memory accesses are complete
  __WFI();                                        // Enter sleep mode and Wait For Interrupt (WFI)
  USBDevice.attach();                             // Re-attach the native USB port
  delay(500);                                     // Wait for half a second (seems to be necessary to give time for the USB port to re-attach)
  while (!SerialUSB);                             // Wait for the console to re-open
  SerialUSB.println(F("Button depress...waking up")); // Send a wake up message
  digitalWrite(LED_BUILTIN, HIGH);                // Turn on the LED
  delay(500);                                     // Wait half a second
}

@MartinL Thank you for the example code, specifically because it doesn't depend on any external libraries.

(Side comment: I'm not sure it's always true that "SAMD21 in standby turns off the clock to the External Interrupt Controller (EIC) peripheral". In this Arduino sketch, I'm trying to replicate code I wrote in Microchip Studio / ASF4 in which the EIC runs off the 32KHz clock, in which case edge interrupts will wake the processor.)

Unfortunately, it doesn't cure the insomnia problem: the SAMD21 XPRO still immediately wakes from the __WFI(). I tried a couple of things, including switching from SerialUSB to Serial (and calling Serial.flush(); Serial.end(); before sleeping).

I also tried stripping out all serial I/O and just using the LED to indicate state:

#define USER_BUTTON_PIN 13

void setup(void) {
  pinMode(USER_BUTTON_PIN, INPUT_PULLUP);        // Intialise button input pin and activate internal pull-up resistor
  pinMode(LED_BUILTIN, OUTPUT);                  // Initialise the LED_BUILTIN output
  attachInterrupt(USER_BUTTON_PIN, NULL, LOW);   // Activate a LOW level interrupt on the button pin
  NVMCTRL->CTRLB.bit.SLEEPPRM = NVMCTRL_CTRLB_SLEEPPRM_DISABLED_Val;    // Prevent the flash memory from powering down in sleep mode
  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;             // Select standby sleep mode
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);  // Turn off the LED
  __DSB();                          // Ensure remaining memory accesses are complete
  __WFI();                          // Enter sleep mode and Wait For Interrupt (WFI)
  delay(500);                       // Keep LED off long enough to see that we passed the __WFI
  digitalWrite(LED_BUILTIN, LOW);   // Turn on the LED
  delay(2000);                      // Wait a bit
}

... but that also blows right through the __WFI.

I'm now wondering if something in the setup code is generating interrupts, e.g. SysTick or some peripheral enabled to run in STANDBY mode. Still digging.

@rdpoor I tested the code and it worked on my custom SAMD21 board based on the Arduino Zero, but it was using some ancient copy of the Arduino SAMD core code.

I'm just going to check it out the code on my Arduino Zero board that employs more up-to-date core code.

In the meantime, regarding the enabling and disabling the systick timer interrupts.

To enable:

SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;            // Enable SysTick interrupts

To disable:

SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk;           // Disable SysTick interrupts

HI @rdpoor

I managed to get it working on the Arduino Zero using the latest Arduino SAMD Board core code (version 1.8.11), however the code required some minor changes.

  1. The newer core code didn't like the use of a NULL pointer for the attachInterrupt() function and required a dummy function to be provided instead.

  2. Upon reattachment, the native serial port required an additional newline to be inserted before printing to the console.

Here's the revised code:

void setup(void) {
  pinMode(A1, INPUT_PULLUP);                      // Intialise button input pin and activate internal pull-up resistor
  pinMode(LED_BUILTIN, OUTPUT);                   // Initialise the LED_BUILTIN output
  attachInterrupt(A1, dummyFunc, LOW);                 // Activate a LOW level interrupt on the button pin
  NVMCTRL->CTRLB.bit.SLEEPPRM = NVMCTRL_CTRLB_SLEEPPRM_DISABLED_Val;    // Prevent the flash memory from powering down in sleep mode
  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;              // Select standby sleep mode
  SerialUSB.begin(115200);                        // Intialise the native USB port
  while (!SerialUSB);                             // Wait for the console to open
}

void loop() {
  digitalWrite(LED_BUILTIN, LOW);                 // Turn off the LED
  SerialUSB.println(F("Sleeping Zzzz...wait for button to wake"));  // Send sleep message to the console
  USBDevice.detach();                             // Detach the native USB port
  __DSB();                                        // Ensure remaining memory accesses are complete
  __WFI();                                        // Enter sleep mode and Wait For Interrupt (WFI)
  USBDevice.attach();                             // Re-attach the native USB port
  delay(500);                                     // Wait for half a second (seems to be necessary to give time for the USB port to re-attach)
  while(!SerialUSB);                              // Wait for the console to re-open
  SerialUSB.println();                            // Add a newline
  SerialUSB.println(F("Button depress...waking up")); // Send a wake up message
  digitalWrite(LED_BUILTIN, LOW);                 // Turn on the LED
  delay(500);                                     // Wait half a second
}

void dummyFunc() {}

Heh! We converged on about the same answer: I discovered that attachInterrupt() really wanted a function pointer. And cortex_handlers.c does enable SysTick interrupts, so I still needed to disable / enable SysTick across the STANDBY. Otherwise, my working code is similar to yours (minus the serial I/O):

#include <stdbool.h>

#define USER_BUTTON_PIN 13

static volatile bool s_button_pressed;

void setup(void) {
  pinMode(LED_BUILTIN, OUTPUT);                  // Initialise the LED_BUILTIN output
  digitalWrite(LED_BUILTIN, LOW);   // Turn on the LED
  pinMode(USER_BUTTON_PIN, INPUT_PULLUP);        // Intialise button input pin and activate internal pull-up resistor
  attachInterrupt(USER_BUTTON_PIN, on_button_press, LOW);   // Activate a LOW level interrupt on the button pin
  NVMCTRL->CTRLB.bit.SLEEPPRM = NVMCTRL_CTRLB_SLEEPPRM_DISABLED_Val;    // Prevent the flash memory from powering down in sleep mode
  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;             // Select standby sleep mode
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);  // Turn off the LED
  SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk;  // Disable SysTick interrupts
  __DSB();                          // Ensure remaining memory accesses are complete
  __WFI();                          // Enter sleep mode and Wait For Interrupt (WFI)
  SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;   // Enable SysTick interrupts  
  delay(500);                       // Keep LED off long enough to see that we passed the __WFI
  digitalWrite(LED_BUILTIN, LOW);   // Turn on the LED
  delay(2000);                      // Wait a bit
}

void on_button_press(void) {
  s_button_pressed = true;
}

MANY thanks for pointing me in the right direction(s). With help like yours, I'll get the hang of the Arduino world eventually...

@rdpoor That's great, glad to hear you got it working.

@MartinL: quick question:

I implemented your SerialUSB statements. Now, the system blocks until I start a terminal (e.g. PuTTY) before printing. That's okay, but when the app goes into STANDBY mode, PuTTY reports "PuTTY Fatal Error: Error reading from serial device" and closes the connection.

Is there some way to prevent the port from closing, or is there some terminal app for which this isn't an issue? (Yes, I'm running Windows...)
Is there an app (or some trick) I can use

Hi @rdpoor

Checking the forums, it appears that Putty doesn't have an auto re-connect feature.

I tested the code on the Arduino IDE's console, this does auto reconnect to the board once it has woken up. It looks like the issue is a function of the terminal rather than the board itself. The microcontroller is waiting for the terminal reestablish the connection.

Using Putty, it's possible to manually reestablish a new connection by right clicking the appliction's header and selecting: Restart Session.