Why does the Nano 33 IoT internal rtc keep resetting on wakeup from sleep via external interrupt?

I have been playing around with Bluetooth and a water flow meter. I want to extend battery life by putting the Nano 33 IoT to sleep at night. If there's a water flow event while it's asleep, I want it to wake up, deal with it and then go back to sleep again.

The problem I'm having is that when a water flow event wakes it from sleep, very frequently the internal rtc year somehow has been set to Zero.

I'm using the Arduino LowPower library and have an external sensor configured via an interrupt on pin 2:

LowPower.attachInterruptWakeup(waterFlowSensorPulseIn, countPulse, RISING);

If it's not asleep and a water flow event happens, the countPulse function executes just fine.

I send it to sleep with:

LowPower.sleep(( (wakeupTimeHourTemp * 60) - (rtc.getHours() * 60 + rtc.getMinutes()) ) * 60000);

If no water flow event occurs it wakes up at the time set by the wakeup time.

If it's asleep and a water flow event happens, it wakes up and, more often than not, the rtc getYear() function returns 0.

I've minimised the code to try to make it easy on anyone who wants to take a look - so all bluetooth and non-relevant functionality has been removed. I've tested it and the issue still remains.

Any help greatly appreciated. Here's the minimal code:

#include <RTCZero.h>
#include "ArduinoLowPower.h"

#define waterFlowSensorPulseIn 2
const byte ledPinGreen = 5;
const byte ledPinOrange = 6;
const byte ledPinWhite = 7;

const int ignorePulsesThreshold = 10;                // 50 unless debugging
const uint8_t bedTimeHour = 23, wakeupTimeHour = 1;  // 23 and 9 normally - unless debugging

RTCZero rtc;
const uint8_t mySeconds = 45;
const uint8_t myMinutes = 58;
const uint8_t myHours = 22;
const uint8_t myDay = 15;
const uint8_t myMonth = 6;
const uint8_t myYear = 26;

volatile int pulseCount = 0;          // 'volatile' is required for interrupt variables
const int pulsesStoppedTimeout = 50;  // 50 milliseconds
long previousMillis = 0;              
byte loopActiveCount = 0;

void setup() {
  rtc.begin();  // initialize RTC
  delay(1000);  // make sure rtc is fully initialized
  pinMode(LED_BUILTIN, OUTPUT);
  // initialize the built-in LED pin

  pinMode(waterFlowSensorPulseIn, INPUT_PULLUP);
  pinMode(ledPinGreen, OUTPUT);
  pinMode(ledPinOrange, OUTPUT);
  pinMode(ledPinWhite, OUTPUT);

  LowPower.attachInterruptWakeup(waterFlowSensorPulseIn, countPulse, RISING);

  rtc.setTime(myHours, myMinutes, mySeconds);
  rtc.setDate(myDay, myMonth, myYear);
}

void countPulse() {
  pulseCount++;
}

void blinker(long mySpeed, int numBlinks, byte ledNum) {
  long myCount = 0, previousMillis = 0, currentMillis = millis();
  bool blinkingIsToContinue = true, ledState = LOW;
  while (blinkingIsToContinue) {
    currentMillis = millis();
    if (currentMillis - previousMillis > mySpeed) {
      previousMillis = currentMillis;
      ledState = !ledState;
      myCount++;
      digitalWrite(ledNum, ledState);
      if (myCount == numBlinks * 2) {
        blinkingIsToContinue = false;
      }
    }
  }
}

void loop() {
  static unsigned long timePulseReceived = 0;
  static int previousPulseCount = 0;
  static long elapsedTime = 0;
  static bool flowEventInProgress = false;
  uint8_t wakeupTimeHourTemp;

  // deal with possible water flow event. The interrupt service routine, countPulse, is fired
  // whenever a pulse comes in from the sensor and increments pulseCount
  if (pulseCount > 0) {  // water is flowing, wait until it stops before calculating volumes etc.
    // Debug: zero year - trying to find out why date sometimes gets messed up but time is ok
    if (rtc.getYear() == 0) {  // this test always fails here
      while (1) {
        blinker(80, 5, ledPinOrange);
        blinker(120, 2, ledPinOrange);
      }
    }
    flowEventInProgress = true;
    if (pulseCount == previousPulseCount) {
      // no additional pulse received yet, keep waiting / allowing for another
      // pulse to arrive within pulsesStoppedTimeout
      elapsedTime = millis() - timePulseReceived;
      if (elapsedTime > pulsesStoppedTimeout) {    // the flow event is finished
        if (pulseCount > ignorePulsesThreshold) {  // connecting a hosepipe can cause a few pulses so ignore them
          // this is where all the bluetooth characteristics would get updated (removed in this minimal debug version)
        }
        // prepare for next flow event
        flowEventInProgress = false;
        pulseCount = 0;
        previousPulseCount = 0;
        elapsedTime = 0;
      }
    } else {  // another pulse has arrived
      timePulseReceived = millis();
      previousPulseCount = pulseCount;
    }
  }
  if (!flowEventInProgress) {
    wakeupTimeHourTemp = wakeupTimeHour;
    if (bedTimeHour > wakeupTimeHour) wakeupTimeHourTemp += 24;  // wakeup time must be on following day
    if ((rtc.getHours() >= bedTimeHour) && (rtc.getHours() < wakeupTimeHourTemp)) {
      // Debug: zero year - trying to find out why date sometimes gets messed up
      if (rtc.getYear() == 0) {  // this test always fails here ie never == 0
        while (1) {
          blinker(80, 10, ledPinOrange);
          blinker(120, 2, ledPinGreen);
        }
      }
      // It should be asleep so send it to bed!
      wakeupTimeHourTemp = wakeupTimeHour;
      if ((rtc.getHours() * 60 + rtc.getMinutes()) > (wakeupTimeHour * 60)) wakeupTimeHourTemp += 24;
      LowPower.sleep(((wakeupTimeHourTemp * 60) - (rtc.getHours() * 60 + rtc.getMinutes())) * 60000);  // sleep until wakeup time
      // NB The Nano will also wake up on the arrival of the first pulse of a new flow event.
      // When the flow event is finished (and as long as it's still bedtime) the Nano
      // will be put back to sleep.
      blinker(120, 4, ledPinOrange);  // show that it's woken up
    }
  }
  // Debug: trying to find out why date sometimes gets messed up but time is ok
  if (rtc.getYear() == 0) {
    // if the Nano is woken up by pulses arriving, the test very frequently
    // succeeds ie rtc.getYear() == 0. Why??
    while (rtc.getYear() == 0) {  // just in case, give the rtc time to settle down???
      blinker(480, 4, ledPinWhite);
      blinker(480, 4, ledPinOrange);
      blinker(480, 4, ledPinGreen);
    }
  } else {
    loopActiveCount++;
    if (loopActiveCount == 64) blinker(20, 1, ledPinGreen);
    // Show that it's looping, ie not asleep.
    // As it's a byte, it'll reach 255 then go back to zero
  }
}

I beleive your code is doing exactly what you told it to do. Every time it is reset it runs the setup() function that runs the above lines of code. Once you run the program comment out those two lines and I expect your RTC will quit resetting.

Try putting a blink function or simple a serial print in setup telling you it is active. If that output is occurring when coming out of sleep it is going into a reset mode.

Another thing I noticed: At 00:30, rtc.getHours() is 0, so it is not >= 23. The code thinks it is not bedtime anymore. I typically use minutes-since-midnight logic instead.

Thanks for your time and feedback, @gilshultz

While testing (and once in production), the Nano is never reset so the setup() function runs only once and that's when power is first applied. After that it should keep on running indefinitely.

Even if setup() did keep getting run, the rtc.getYear() would only ever return 26 and never 0 - but of course it does sometimes give 0, which is the issue I'm trying to resolve. Note that nowhere does my code set the rtc year to 0.

You're right about:

While I was simplifying / clarifying to make the minimal code, I messed up the logic. Even so, for the 1 hour (between 23:00 and midnight) where it was still bedtime, the issue of the rtc.getYear() often returning 0 after waking from sleep because of a water flow event, remained.