RTC wakeup from low power sleep on MKR boards

I'm attempting to use a DS3232 RTC to wakeup an MKR WAN 1310 from a low power state. I have previously managed this fine on a Nano using the DFRobot_DS323X and avr/sleep libraries, however neither are compatible on MKR boards so have had to now use other libraries. I have successfully managed to get alarms to trigger using alarm_ex6 from Jack Christensen's DS3232RTC.h library for MKRWAN board, but am having difficulties getting it to integrate with the Arduino Low Power library. The below code compiles fines, but causes the Arduino to drop off the COM port when uploading (and require rebooting). Furthermore, the LED is on the whole time, despite being set to LOW in the code and only going HIGH when triggering, which suggests that none of the code is working. I've tried tweaking it many times, but now am at a bit of a loss as to what to try next?

alarm_ex6 (this works fine)

// Arduino DS3232RTC Library
// https://github.com/JChristensen/DS3232RTC
// Copyright (C) 2018 by Jack Christensen and licensed under
// GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html
//
// Example sketch to demonstrate usage of the alarm interrupt
// for alarm 1 and alarm 2.
//
// Notes:
// Using the INT/SQW pin for alarms is mutually exclusive with using
// it to output a square wave. However, alarms can still be set when
// a square wave is output, but then the alarm() function will need
// to be used to determine if an alarm has triggered. Even though
// the DS3231 power-on default for the INT/SQW pin is as an alarm
// output, it's good practice to call DS3232RTC::squareWave(SQWAVE_NONE)
// before setting alarms.
//
// I recommend calling DS3232RTC::alarm() before DS3232RTC::alarmInterrupt()
// to ensure the RTC's alarm flag is cleared.
//
// The RTC's time is updated on the falling edge of the 1Hz square
// wave (whether it is output or not). However, the Arduino Time
// library has no knowledge of this, as its time is set asynchronously
// with the RTC via I2C. So on average, it will be 500ms slow
// immediately after setting its time from the RTC. This is seen
// in the sketch output as a one-second difference because the
// time returned by now() has not yet rolled to the next second.
//
// Tested with Arduino 1.8.5, Arduino Uno, DS3231.
// Connect RTC SDA to Arduino pin A4.
// Connect RTC SCL to Arduino pin A5.
// Connect RTC INT/SQW to Arduino pin 3.
//
// Jack Christensen 01Jan2018

#include <DS3232RTC.h>      // https://github.com/JChristensen/DS3232RTC
#include <Streaming.h>      // https://github.com/janelia-arduino/Streaming

constexpr uint8_t SQW_PIN {6};  // RTC provides an interrupt signal on this pin
                                // Can use Pin 2 (INT0) or Pin 3 (INT1) with Arduino Uno
DS3232RTC myRTC;

void setup()
{
    Serial.begin(115200);

    // initialize the alarms to known values, clear the alarm flags, clear the alarm interrupt flags
    myRTC.begin();
    myRTC.setAlarm(DS3232RTC::ALM1_MATCH_DATE, 0, 0, 0, 1);
    myRTC.setAlarm(DS3232RTC::ALM2_MATCH_DATE, 0, 0, 0, 1);
    myRTC.alarm(DS3232RTC::ALARM_1);
    myRTC.alarm(DS3232RTC::ALARM_2);
    myRTC.alarmInterrupt(DS3232RTC::ALARM_1, false);
    myRTC.alarmInterrupt(DS3232RTC::ALARM_2, false);
    myRTC.squareWave(DS3232RTC::SQWAVE_NONE);

    // setSyncProvider() causes the Time library to synchronize with the
    // external RTC by calling myRTC.get() every five minutes by default.
    setSyncProvider(myRTC.get);
    Serial << "RTC Sync";
    if (timeStatus() != timeSet) {
        Serial << " FAIL!";
    }
    Serial << endl;

    printDateTime(myRTC.get());
    Serial << "  Current RTC time\n";

    // configure an interrupt on the falling edge from the SQW pin
    pinMode(SQW_PIN, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(SQW_PIN), alarmIsr, FALLING);

    // set alarm 1 for 20 seconds after every minute
    myRTC.setAlarm(DS3232RTC::ALM1_MATCH_SECONDS, 20, 0, 0, 1); // daydate parameter should be between 1 and 7
    myRTC.alarm(DS3232RTC::ALARM_1);                            // ensure RTC interrupt flag is cleared
    myRTC.alarmInterrupt(DS3232RTC::ALARM_1, true);

    // set alarm 2 for every minute
    myRTC.setAlarm(DS3232RTC::ALM2_EVERY_MINUTE, 0, 0, 0, 1);   // daydate parameter should be between 1 and 7
    myRTC.alarm(DS3232RTC::ALARM_2);                            // ensure RTC interrupt flag is cleared
    myRTC.alarmInterrupt(DS3232RTC::ALARM_2, true);
}

volatile bool alarmIsrWasCalled = false;

void alarmIsr()
{
    alarmIsrWasCalled = true;
}

void loop()
{
    if (alarmIsrWasCalled) {
        if (myRTC.alarm(DS3232RTC::ALARM_1)) {
            printDateTime( myRTC.get() );
            Serial << "  Alarm 1\n";
        }
        if (myRTC.alarm(DS3232RTC::ALARM_2)) {
            printDateTime( myRTC.get() );
            Serial << "  Alarm 2\n";
        }
        alarmIsrWasCalled = false;
    }
}

void printDateTime(time_t t)
{
    Serial << ((day(t)<10) ? "0" : "") << _DEC(day(t));
    Serial << monthShortStr(month(t)) << _DEC(year(t)) << ' ';
    Serial << ((hour(t)<10) ? "0" : "") << _DEC(hour(t)) << ':';
    Serial << ((minute(t)<10) ? "0" : "") << _DEC(minute(t)) << ':';
    Serial << ((second(t)<10) ? "0" : "") << _DEC(second(t));
}

My code, with RTC interrupt attached for Arduino Low power modes (not working)

// Arduino DS3232RTC Library
// https://github.com/JChristensen/DS3232RTC
// Copyright (C) 2018 by Jack Christensen and licensed under
// GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html
//
// Example sketch to demonstrate usage of the alarm interrupt
// for alarm 1 and alarm 2.
//
// Notes:
// Using the INT/SQW pin for alarms is mutually exclusive with using
// it to output a square wave. However, alarms can still be set when
// a square wave is output, but then the alarm() function will need
// to be used to determine if an alarm has triggered. Even though
// the DS3231 power-on default for the INT/SQW pin is as an alarm
// output, it's good practice to call DS3232RTC::squareWave(SQWAVE_NONE)
// before setting alarms.
//
// I recommend calling DS3232RTC::alarm() before DS3232RTC::alarmInterrupt()
// to ensure the RTC's alarm flag is cleared.
//
// The RTC's time is updated on the falling edge of the 1Hz square
// wave (whether it is output or not). However, the Arduino Time
// library has no knowledge of this, as its time is set asynchronously
// with the RTC via I2C. So on average, it will be 500ms slow
// immediately after setting its time from the RTC. This is seen
// in the sketch output as a one-second difference because the
// time returned by now() has not yet rolled to the next second.
//
// Tested with Arduino 1.8.5, Arduino Uno, DS3231.
// Connect RTC SDA to Arduino pin A4.
// Connect RTC SCL to Arduino pin A5.
// Connect RTC INT/SQW to Arduino pin 3.
//
// Jack Christensen 01Jan2018

#include <DS3232RTC.h>      // https://github.com/JChristensen/DS3232RTC
#include <Streaming.h>      // https://github.com/janelia-arduino/Streaming
#include <ArduinoLowPower.h>

const int SQW_PIN = 6;  // RTC provides an interrupt signal on this pin
                                
                             
DS3232RTC myRTC;

void setup()
{
    Serial.begin(9600);

    pinMode(LED_BUILTIN, OUTPUT);

    // initialize the alarms to known values, clear the alarm flags, clear the alarm interrupt flags
    myRTC.begin();
    myRTC.setAlarm(DS3232RTC::ALM1_MATCH_DATE, 0, 0, 0, 1);
    myRTC.setAlarm(DS3232RTC::ALM2_MATCH_DATE, 0, 0, 0, 1);
    myRTC.alarm(DS3232RTC::ALARM_1);
    myRTC.alarm(DS3232RTC::ALARM_2);
    myRTC.alarmInterrupt(DS3232RTC::ALARM_1, false);
    myRTC.alarmInterrupt(DS3232RTC::ALARM_2, false);
    myRTC.squareWave(DS3232RTC::SQWAVE_NONE);

    // setSyncProvider() causes the Time library to synchronize with the
    // external RTC by calling myRTC.get() every five minutes by default.
    setSyncProvider(myRTC.get);
    Serial << "RTC Sync";
    if (timeStatus() != timeSet) {
        Serial << " FAIL!";
    }
    Serial << endl;

    // configure an interrupt on the falling edge from the SQW pin
    pinMode(SQW_PIN, INPUT_PULLUP);
    LowPower.attachInterruptWakeup(SQW_PIN, alarmIsr, RISING);

    // set alarm 1 for 20 seconds after every minute
    myRTC.setAlarm(DS3232RTC::ALM1_MATCH_SECONDS, 20, 0, 0, 1); // daydate parameter should be between 1 and 7
    myRTC.alarm(DS3232RTC::ALARM_1);                            // ensure RTC interrupt flag is cleared
    myRTC.alarmInterrupt(DS3232RTC::ALARM_1, 1);

    // set alarm 2 for every minute
    myRTC.setAlarm(DS3232RTC::ALM2_EVERY_MINUTE, 0, 0, 0, 1);   // daydate parameter should be between 1 and 7
    myRTC.alarm(DS3232RTC::ALARM_2);                            // ensure RTC interrupt flag is cleared
    
    myRTC.alarmInterrupt(DS3232RTC::ALARM_2, 1);
    //LowPower.attachInterruptWakeup(RTC_ALARM_WAKEUP, alarmIsr, CHANGE);
}

volatile int alarmIsrWasCalled = 0;

void alarmIsr()
{
    alarmIsrWasCalled = 1;
}

void loop() {

    if (alarmIsrWasCalled == 1) {
        if (myRTC.alarm(DS3232RTC::ALARM_1)) {
          digitalWrite(LED_BUILTIN, HIGH);
          delay(5000);
            printDateTime( myRTC.get() );
            Serial << "  Alarm 1\n";
        }
        else if (myRTC.alarm(DS3232RTC::ALARM_2)) {
          digitalWrite(LED_BUILTIN, HIGH);
          delay(5000);
            printDateTime( myRTC.get() );
            Serial << "  Alarm 2\n";
        }
               
        alarmIsrWasCalled = 0;
        
        }
    else 
        //do some measurement functions in here
        digitalWrite(LED_BUILTIN, LOW);
        
        LowPower.deepSleep();          
 }
  


void printDateTime(time_t t)
{
    Serial << ((day(t)<10) ? "0" : "") << _DEC(day(t));
    Serial << monthShortStr(month(t)) << _DEC(year(t)) << ' ';
    Serial << ((hour(t)<10) ? "0" : "") << _DEC(hour(t)) << ':';
    Serial << ((minute(t)<10) ? "0" : "") << _DEC(minute(t)) << ':';
    Serial << ((second(t)<10) ? "0" : "") << _DEC(second(t));
}```

Hi @br15

To recover the COM port after sleep, it's necessary to first detach the USB connection before entering sleep mode with the USBDevice.detach() function, followed by UsbDevice.attach() plus a 500ms delay after waking up.

Here's a code example, the microcontroller being woken on A1 being pulled low:

// Program to test COM port receovery after sleep mode

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
  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
  USBDevice.attach();                             // Re-attach the native USB port
  digitalWrite(LED_BUILTIN, HIGH);                // Turn on the LED
  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
  delay(500);                                     // Wait half a second
}

void dummyFunc() {}
1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.