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); }
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?
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.
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.
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.
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.
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...
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
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.