I’m prototyping a closet light to operate for >3 months on 1000mAh 3.3v, built around Xiao SAMD21 (green led removed). Want deep sleep (0.1mA), woken by motion sensor. I’ve tried ArduinoLowPower, SleepyDog, RTC, samd21lpe, Stuarts. Nothing works. I’d settle for 500ms deep sleep woken by timer. Has anyone else hit the same major snag? I’d be eternally grateful to anyone who could provide a complete working code example that proves sleep/interrupt works on this platform.
/**
* @file xiao_sleep_fail_example.ino
* @brief Demonstrates the failure of a Seeed Studio Xiao SAMD21 to reliably
* wake from deep sleep (STANDBY) using an external interrupt.
*
* @version 1.0
* @date 2025-06-18
*
* HARDWARE:
* - Seeed Studio Xiao SAMD21
* - LED connected to the built-in LED pin (D13)
* - A switch or wire connecting Pin D9 to GND to trigger the interrupt.
*
* GOAL:
* - Enter STANDBY deep sleep mode (~µA consumption).
* - Wake up instantly when Pin D9 is pulled LOW.
* - Toggle an LED.
* - Successfully re-enter sleep and repeat the cycle indefinitely.
*
* OBSERVED BEHAVIOR:
* - On reset, the board sleeps correctly.
* - On the FIRST interrupt, the board wakes, toggles the LED, and then hangs.
* - It becomes unresponsive and requires a reset.
* - This behavior has been confirmed on multiple, separate Xiao SAMD21 units.
*
* NOTES:
* - This code incorporates all known best practices and workarounds, including
* software debouncing, system stabilization delays, and the specific
* USB/SysTick workaround discussed on the Seeed Studio forum.
* - The failure persists even with these measures in place.
*/
// Required for access to the USBDevice object for the forum's workaround
#include "USB/USBAPI.h"
// --- Configuration ---
#define EXT_INT_PIN 9 // The pin used for the external wake-up interrupt
#define LED_PIN LED_BUILTIN // The built-in orange LED
#define DEBOUNCE_DELAY_MS 250 // Cooldown period to prevent switch bounce
// --- Global Variables ---
// Flag to communicate from the ISR to the main loop
volatile bool interruptFired = false;
// Timer for software debouncing
volatile unsigned long lastInterruptTime = 0;
/**
* @brief Interrupt Service Routine (ISR) called by the hardware.
* Implements a software debounce to ignore noise.
*/
void interruptHandler() {
if (millis() - lastInterruptTime > DEBOUNCE_DELAY_MS) {
interruptFired = true;
lastInterruptTime = millis();
}
}
/**
* @brief Standard Arduino setup function.
*/
void setup() {
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
// Configure the interrupt pin with an internal pull-up.
// The interrupt will trigger when the pin is connected to GND.
pinMode(EXT_INT_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(EXT_INT_PIN), interruptHandler, FALLING);
// Configure the system for DEEP sleep (STANDBY mode) using a direct
// write to the System Control Block register.
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
// Prime the debounce timer to ignore any false triggers during startup.
lastInterruptTime = millis();
}
/**
* @brief Standard Arduino loop function.
*/
void loop() {
// Check if the ISR has signaled that a valid interrupt occurred.
if (interruptFired) {
digitalWrite(LED_PIN, !digitalRead(LED_PIN)); // Perform action (toggle LED)
interruptFired = false; // Reset the flag
// A brief delay to allow internal systems to stabilize after waking.
delay(100);
}
// --- Seeed Studio Forum Workaround: PRE-SLEEP ---
// These steps are intended to prevent a hang-on-wake.
SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk; // Disable SysTick interrupt
USBDevice.detach(); // Detach the native USB port
// --- Enter Sleep ---
__DSB(); // Data Synchronization Barrier
__WFI(); // Wait For Interrupt (CPU halts here)
// --- Seeed Studio Forum Workaround: POST-WAKE ---
// Execution resumes here after the interrupt wakes the CPU.
USBDevice.attach(); // Re-attach USB port
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk; // Re-enable SysTick interrupt
}