How to wake from sleep when either a time is reached or a sensor pulse arrives?

I have just completed a water meter project using Bluetooth with a Nano 33 IoT. It's battery operated, where the batteries are charged from a solar panel. I'm in the UK where solar charging isn't reliable so to conserve power I want to put the device to sleep during the night and for it to wake up in the morning.

During daylight hours I might connect my central (Lightblue app) at anytime to check on water consumption, hence the need for it to sleep only at night when I won't be doing any checking.

I am already using an interrupt coming from the water flow sensor to count pulses and I can't see how to have that interrupt in place and also use it to wake the Nano from sleep while also having an internal one coming from the inbuilt real time clock to wake it from sleep in the morning.

Checking in various places I read that I can't have more than one interrupt "active" at the same time so I'm wondering if what I want is possible.

Any help would be appreciated.

Here's my sketch (now updated with the solution to my original question for anyone who stumbles into this in the future):


/*
  Battery-powered Water Meter with battery monitor

  Quick review of Bluetooth Low Energy terminology that's relevant to this sketch:
  o A device / gadget is known as a "peripheral".
  o It's made up of one or more "services", each of which are logical 
    groupings of one or more features / facilities - known as 
    "characteristics" in BLE terms.
  o A computer / mobile phone / tablet is known as a "central" and can
    connect to a peripheral to give instructions / receive data via
    characteristics

//=============================================================================

  This sketch creates a Bluetooth® Low Energy peripheral with three services:
   o A service for metering the water flow through a garden hose
     (that I'm using to drive a  humane cat scarer water sprayer to stop the
     local cats from using my back yard as a toilet).
   o A battery service for tracking the voltage of the battery powering
     the device.
   o A date / time service so that counts and volumes can be by reported
     just for today or for the whole month.
     The real time clock on Nano 33 IoTs is known for it's poor timekeeping so
     I've included a characteristic that allows the time to be adjusted by the 
     central by sending the number of minutes that are to be added / subtracted.
     Initially the date and time are set when the Nano 33 IoT has the sketch 
     uploaded and it uses the constants set below for it (myHours, myMinutes 
     etc). This is also done if it's powered on when there's no flash values
     to restore. 
  
  For water flow, characteristics are defined to report:
   o Volume of water that flowed in last flow event (litres per minute)
   o The frequency of the pulses in the flow event occuring now, averaged over
     the last 30 (set by a constant) pulses
   o Number of pulses counted for last flow event
   o Total volume of water flowed today so far (litres)
   o Total number of pulses today so far
   o Number of flow events today so far
   o Total volume of water that's flowed so far this month (litres)
   o Number of flow events so far this month

  The circuit components:
  - Arduino Nano 33 IoT
  - Clear Turbine Water Flow Sensor from the Pi Hut
    https://thepihut.com/products/clear-turbine-water-flow-sensor-with-3-pin-jst
    The sensor description on that page says:
    // each pulse is approximately 2.25 millilitres
    An alternative for describing how to calculate volume flow etc:
    https://wiki.seeedstudio.com/Water-Flow-Sensor/#the-formula-for-the-calculation-of-water-flow-sensor
    Sensor details
      Working Voltage: DC 5V to 15V
      Logic output/voltage: 5V
      Current Draw: 15mA (at DC 5V)
      Flow Rate Range: 1~30L/min
      Flow Pulse: Frequency (Hz) = (5.0*Q) ±3% where Q=L/Min
      Max Operating Temperature: <80 degree C
      Max Liquid Temperature: <120 degree C
      Operating Humidity: 35%~90%RH
      Max Water Pressure: <1.75MPa
      Storage Temperature: -25~+80 degree C
      Storage Humidity: 25%~95%RH
  - For Powering the Nano and water sensor:
    o Two 18650 Lithium Ion batteries in parallel (fully charged voltage is 4.2V). 
      To allow the voltage to be monitored, a wire goes from positive to a voltage
      divider so that the battery voltage is brought down to the 0-3.3V range that 
      the Nano 33 IoT can handle.

      --battery +ve (max of 4.2v)
      |
      2kΩ
      |
      -----------> (max of 3.24v) to pin D14
      |
      6.8kΩ
      --battery negative


    o A step-up voltage booster to take the battery voltage up to 5v. This connects
      into the Nano's micro USB and to the sensor to power them both.
    o A nominal 6V solar panel and a charging module to keep the batteries charged.
    o A voltage divider so the water flow sensor voltage is brought down to the 
      0-3.3V range that the Nano 33 IoT can handle.

      --sensor +ve (max of 5v)
      |
      4.7kΩ
      |
      -----------> (max of 3.3v) to pin D7 
      |
      9.1kΩ
      |
      --sensor negative


    o An LED and a 680Ω resistor in series, connected to an output pin. It's used as
      an indicator if there's ever a need to swap batteries.
      In case the batteries ever get too discharged (I'm in the UK and we can't rely 
      on the yellow thing in the sky to shine reliably) there's a button that allows
      for swapping batteries (or powering down for other reasons). When pressed it 
      triggers a function to store relevant data, date and time to flash storage. 
      The LED then flashes continually until power is removed.
      When power is re-applied, a flag that was set instructs the processing to 
      reinstate the values from flash memory and the LED fast-blinks 15 times to 
      indicate it's happened. 

  I'm using the generic Bluetooth® Low Energy central app, LightBlue (iOS and Android)
  to interact with the services and characteristics created in this sketch.

  This code is in the public domain.
*/

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

// Note that uploading a new (or recompiled) sketch zero-ises any used flash memory.
// The following are all stored in flash memory:
//  o batterySwapOrLowBatteryPowerDownFlag - 1 means you're swapping batteries and 2 means
//    a save happened automatically because the battery voltage got too low.
//    The flag is set to 1 when you press the button and the relevant stuff gets squirelled
//    away in flash, ready to be restored when you've swapped the batteries. When you do,
//    setup() is run as normal when power is applied again. In setup() the flag is read
//    and, because it indicates so, the data gets restored.
// The characteristic variables stored are:
//  o flowEventMeteredVolume
//  o currentAveragedPulseFrequency
//  o meteredVolumeToday
//  o numberOfFlowEventsToday
//  o meteredVolumeThisMonth
//  o numberOfFlowEventsThisMonth
//  o numberOfPulsesInFlowEvent
//  o numberOfPulsesToday

#define batteryPin 14
#define waterFlowSensorPulseIn 2
#define ledPin 5
#define buttonPin 6

const auto PRESSED = LOW; // for push button
const auto connectedToComputerForTesting = false;

RTCZero rtc;
uint8_t rtcToday, rtcThisMonth;
const uint8_t mySeconds = 5;
const uint8_t myMinutes = 06;
const uint8_t myHours = 21;
const uint8_t myDay = 27;
const uint8_t myMonth = 5;
const uint8_t myYear = 26;
// Note that once deployed, the minutes can be adjusted using the 
// respective characterstic. Just make sure to use a UTF-8 String when 
// writing the value.
// In the LightBlue app, look at the "Arduino minutes adjustment"
// characterstic and then tap where it says "Hex" at top right. Choose
// "UTF-8 String" and tap "Save". When you tap the "Write new value"
// button, you'll be able to type in the minutes adjustment and it'll
// work as expected. You can even use negative values to set the time 
// backwards. Examples: 23 adds 23 minutes to the current time. 180 
// adds 3 hours and -74 puts the clock back by 1 hour and 14 minutes.

char myBuff[12];
// used when converting floats to strings prior to updating characteristics
char myDateAndTimeBuffer[20];
//used for preparing to set the value for the equivalent bluetooth characteristic

uint32_t now, bedTimeEpochTime, wakeupEpochTime;
const uint8_t bedTimeHour = 0, wakeupTimeHour = 8;
const float batteryFullyChargedVoltage = 4.2f;
const float flashSaveVoltage = 2.9f;
const float recoveryVoltage = 3.5f;
const float volumePerPulse = 0.00225f; // 2.25mL per pulse
const float measuredToActualFlowVolumeMultFactor = 1.44f; 
// The volume of water sprayed by the cat scarer is 500mL. I captured
// the water from one spray in a bucket and then used a measuring jug.
// When measured by the sensor I got 154 pulses for one spray event. At 
// a value of 2.25mL per pulse, this gives a volume of water of 347mL.
// This is out by a factor of 1.44 (500/347) so this sketch multiplies 
// whatever it measures by this fiddle factor to come up with an actual
// value for the volume of water.
// Instead of routing the water via the cat scarer, for a further test I 
// just turned on the tap, varying the flow rate up and down slowly. The
// end result of that test was that a fiddle factor of 1.6 was needed. I
// was surprised as I expected the results to be pretty much the same.
// Anyway, seeeing this project is all about measuring how much water my
// cat scarer is using, I'm running with the fiddle factor of 1.44.


volatile int pulseCount = 0; // 'volatile' is required for interrupt variables

const int pulsesStoppedTimeout = 50; // 50 milliseconds
const int ignorePulsesThreshold = 50;
// equiv to 50 * 0.00225mL * 1.44 which is just over 150 mL
// When attaching hosepipe or switching on tap, a small amount of water
// will move, so this makes sure it's ignored.

int batteryRead;
float batteryVoltage, oldBatteryVoltage;
long previousMillis = 0;  // last time the battery voltage was checked, in ms

// Declare vars for characteristics and flow details. Set as global because of the need to 
// store them in flash during a battery swap and then restore them from flash in setup()
// once the batteries have been replaced making the power come back on.
int numberOfFlowEventsToday = 0, numberOfFlowEventsThisMonth = 0,
        numberOfPulsesInFlowEvent = 0, numberOfPulsesToday = 0;
float flowEventMeteredVolume = 0.00f, meteredVolumeToday = 0.00f, 
      meteredVolumeThisMonth = 0.00f, currentAveragedPulseFrequency = 0.00f;

FlashStorage(flashBatterySwapOrLowBatteryPowerDownFlag, byte);
FlashStorage(flashFlowEventMeteredVolume, float);
FlashStorage(flashCurrentAveragedPulseFrequency, float);
FlashStorage(flashMeteredVolumeToday, float);
FlashStorage(flashMeteredVolumeThisMonth, float);
FlashStorage(flashNumberOfFlowEventsToday, int);
FlashStorage(flashNumberOfFlowEventsThisMonth, int);
FlashStorage(flashNumberOfPulsesInFlowEvent, int);
FlashStorage(flashNumberOfPulsesToday, int);

FlashStorage(flashHours, uint8_t);
FlashStorage(flashMinutes, uint8_t);
FlashStorage(flashSeconds, uint8_t);
FlashStorage(flashDay, uint8_t);
FlashStorage(flashMonth, uint8_t);
FlashStorage(flashYear, uint8_t);

//===================================================
// Bluetooth® Low Energy Battery Service declarations

// 839860000-9fa2-49b6-a1eb-9bc4eaca3dee generated randomly and 0000 substituted in digit positions 5,6,7,8
BLEService myBatteryService("39860000-9fa2-49b6-a1eb-9bc4eaca3dee");

// The voltage of the batteries powering the water meter. They are charged with a nominal 6v solar panel
BLEStringCharacteristic batteryVoltageChar("39860001-9fa2-49b6-a1eb-9bc4eaca3dee", BLERead | BLENotify, 10);
BLEDescriptor batteryVoltageDescriptor("2901", "Battery voltage");

// Bluetooth® Low Energy Date and Time Service
// 9e620a5a-5fde-4d08-96e6-2d5a731c3263 generated randomly and 0000 substituted in digit positions 5,6,7,8
BLEService myDateAndTimeService("9e620000-5fde-4d08-96e6-2d5a731c3263");

//The current Date and Time stored on the Arduino Nano 33 IoT in dd/mm/yyy hh:mm:ss format
BLEStringCharacteristic dateAndTimeChar("9e620001-5fde-4d08-96e6-2d5a731c3263", BLERead | BLENotify, 20);
BLEDescriptor dateAndTimeDescriptor("2901", "Arduino date/time");

//The adjustment for minutes. When written to , it updates the Arduino clock's minutes
BLEStringCharacteristic minutesAdjustChar("9e620002-5fde-4d08-96e6-2d5a731c3263", BLERead | BLEWrite, 4);
BLEDescriptor minutesAdjustDescriptor("2901", "Arduino minutes adjustment");


// Bluetooth® Low Energy Metered Water Flow Service;
// 89090000-7d04-4d5a-a2cd-913481c1cdd7 generated randomly and 0000 substituted in digit positions 5,6,7,8
BLEService myWaterMeterService("89090000-7d04-4d5a-a2cd-913481c1cdd7");
// Bluetooth® Low Energy Metered Flow characteristic definitions follow

// volume that flowed in a single flow event - remote clients will be able to get notifications if this characteristic changes
BLEStringCharacteristic flowEventMeteredVolumeChar("89090001-7d04-4d5a-a2cd-913481c1cdd7", BLERead | BLENotify, 10);
BLEDescriptor flowEventMeteredVolumeDescriptor("2901", "Flow event metered volume");

// pulse frequency of current flow event - remote clients will be able to get notifications if this characteristic changes
BLEStringCharacteristic pulseFrequencyChar("89090002-7d04-4d5a-a2cd-913481c1cdd7", BLERead | BLENotify, 10);
BLEDescriptor pulseFrequencyDescriptor("2901", "Current ave pulse frequency");

// Total volume flow today so far - remote clients will be able to get notifications if this characteristic changes
BLEStringCharacteristic meteredFlowTodayChar("89090003-7d04-4d5a-a2cd-913481c1cdd7", BLERead | BLENotify, 10);
BLEDescriptor meteredFlowTodayDescriptor("2901", "Metered flow today");

// Number of individual flow events today so far - remote clients will be able to get notifications if this characteristic changes
BLEStringCharacteristic numberOfFlowEventsTodayChar("89090004-7d04-4d5a-a2cd-913481c1cdd7", BLERead | BLENotify, 10);
BLEDescriptor numberOfFlowEventsTodayDescriptor("2901", "Number of flow events today");

// Total volume flow this month so far - remote clients will be able to get notifications if this characteristic changes
BLEStringCharacteristic meteredFlowThisMonthChar("89090005-7d04-4d5a-a2cd-913481c1cdd7", BLERead | BLENotify, 10);
BLEDescriptor meteredFlowThisMonthDescriptor("2901", "Metered flow this month");

// Number of individual flow events this month so far - remote clients will be able to get notifications if this characteristic changes
BLEStringCharacteristic numberOfFlowEventsThisMonthChar("89090006-7d04-4d5a-a2cd-913481c1cdd7", BLERead | BLENotify, 10);
BLEDescriptor numberOfFlowEventsThisMonthDescriptor("2901", "Number of flow events this month");

// Number of pulses in this flow event - remote clients will be able to get notifications if this characteristic changes
BLEStringCharacteristic numberOfPulsesInFlowEventChar("89090007-7d04-4d5a-a2cd-913481c1cdd7", BLERead | BLENotify, 10);
BLEDescriptor numberOfPulsesInFlowEventDescriptor("2901", "Number of pulses in flow event");

// Number of pulses in flow events today so far - remote clients will be able to get notifications if this characteristic changes
BLEStringCharacteristic numberOfPulsesTodayChar("89090008-7d04-4d5a-a2cd-913481c1cdd7", BLERead | BLENotify, 10);
BLEDescriptor numberOfPulsesTodayDescriptor("2901", "Number of pulses today");


void setup() {
  // IMPORTANT Depending on how you've built the device you may need to 
  // make sure of the following:
  // When connecting / reconnecting the device, connect in this sequence:
  // 1) Battery voltage sense wire to its voltage divider resistor chain.
  //    This is because when the Nano is powered and the startup delay has
  //    finished, it checks the battery voltage. By then if the wire isn't 
  //    connected it'll get a random reading from pin D14 (as it'll be 
  //    floating). By chance, that reading may represent a battery voltage 
  //    that's too low and so it will incorrectly do a battery swap power down.
  // 2) Water flow sensor wire from its voltage divider resistor chain to the 
  //    interrupt pin (pulse_input: 2)
  // 3) Water sensor power -ve to Arduino board's ground
  // 4) USB connector from battery pack to Arduino board's USB socket
  // 5) Water sensor +5v power in 
  //
  rtc.begin(); // initialize RTC
  if (connectedToComputerForTesting) {
    // Note: When connecting to a computer you need to connect charged batteries 
    // to the battery voltage divider. If you don't, the sketch will conclude that
    // the batteries are below the save voltage and get everyone confused!!!
    Serial.begin(9600); // initialize serial communication for when connected to computer
    while (!Serial);
    Serial.println("starting");
  }
  delay(5000);
  // Give time for batteries to be properly inserted if that's what's
  // happening.
  // It's needed because there may be a few moments where the insertion of the
  // first battery doesn't initially make a perfect connection and instead the
  // connection 'bounces'. The delay gives time for that connection of power to 
  // settle down.
  //


  pinMode(LED_BUILTIN, OUTPUT);
  // initialize the built-in LED pin to indicate when a central is connected
  
  pinMode(waterFlowSensorPulseIn, INPUT_PULLUP);
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);

  //attachInterrupt(digitalPinToInterrupt(waterFlowSensorPulseIn), countPulse, RISING);  
  LowPower.attachInterruptWakeup(waterFlowSensorPulseIn, countPulse, RISING);
  // begin initialization
  if (!BLE.begin()) {
    if (connectedToComputerForTesting) {
      Serial.println("starting Bluetooth® Low Energy module failed!");
    }
    while (1);
  }

  if (flashBatterySwapOrLowBatteryPowerDownFlag.read() == 1) {
    // 1 is for a user-triggered battery swap
    restoreFromFlash();
    // Reset the flag to show all vals read
    flashBatterySwapOrLowBatteryPowerDownFlag.write(0);

    // fast blink the LED 15 times to show data from flash has been restored
    blinker(100, 15);
    if (connectedToComputerForTesting) Serial.println("flash read");
  } else if (flashBatterySwapOrLowBatteryPowerDownFlag.read() == 2) {
    // 2 is for low voltage save. Either you got to it in time
    // and replaced the batteries or you didn't and the solar
    // panel has charged them enough so that the Arduino is 
    // now powered. Whichever happened, as long as the battery
    // voltage is above the recovery voltage things can 
    // continue automatically. This can really only happen if
    // you got to it in time and changed the batteries because 
    // if you didn't, there won't have been enough time for the
    // batteries to get charged up to the recovery voltage.
    if ( analogRead(batteryPin) * (batteryFullyChargedVoltage / 1023.0f) >= recoveryVoltage ) {
      restoreFromFlash();
      // Reset the flag to show all vals read
      flashBatterySwapOrLowBatteryPowerDownFlag.write(0);
      // fast blink the LED 15 times to show data from flash has been restored
      blinker(100, 15);
      if (connectedToComputerForTesting) Serial.println("flash read");
    } else { // the recovery voltage HASN'T been reached
      // It's a user decision as to what to do because if too much time
      // has passed, too many flow events may have been missed.
      // 
      if (connectedToComputerForTesting) Serial.println("Recovery voltage not reached - replace batteries");
      while (1) {
        // Will loop forever - this blink pattern is unique to this status
        // and the only option really is to swap the batteries for charged ones.
        blinker(80, 3);
        blinker(300, 2);
      }
    }
  } else {
    // flashBatterySwapOrLowBatteryPowerDownFlag not set so use constants defined 
    // above for setting time. This happens on the first time it runs or whenever 
    // your sketch is uploaded to the Nano, so make sure you set the constants first.
    rtc.setTime(myHours, myMinutes, mySeconds);
    rtc.setDate(myDay, myMonth, myYear);
    rtcToday = rtc.getDay();
    rtcThisMonth = rtc.getMonth();
    if (connectedToComputerForTesting) Serial.println("just set time using constants");
  }
  setUpBlueTooth();
}

void setUpBlueTooth() {
/* Set a local name for the Bluetooth® Low Energy device
   This name will appear in advertising packets and can be used by remote devices to 
   identify this Bluetooth® Low Energy device. The name can be changed but maybe be 
   truncated based on space left in advertisement packet
*/
  BLE.setLocalName("Arduino Water Meter");

  // -------------------------------------- Battery Service
  BLE.setAdvertisedService(myBatteryService); // add the service UUID
  myBatteryService.addCharacteristic(batteryVoltageChar); // add the battery voltage characteristic
  batteryVoltageChar.addDescriptor(batteryVoltageDescriptor);
  
  BLE.addService(myBatteryService); // Add the battery service
  batteryRead = analogRead(batteryPin); // battery voltage is available immediately
  oldBatteryVoltage = batteryRead * (batteryFullyChargedVoltage / 1023.0f); // Calculate for later
  batteryVoltageChar.writeValue("waiting..."); // set initial value for this characteristic

  // ------------------------------------- Date and Time Service
  BLE.setAdvertisedService(myDateAndTimeService); // add the service UUID
  // Date and time
  myDateAndTimeService.addCharacteristic(dateAndTimeChar); // add the dat and time characteristic
  dateAndTimeChar.addDescriptor(dateAndTimeDescriptor);

  myDateAndTimeService.addCharacteristic(minutesAdjustChar); // add the minutes adjustment characteristic
  minutesAdjustChar.addDescriptor(minutesAdjustDescriptor);

  BLE.addService(myDateAndTimeService); // Add the date and time service
  // date and time is available immediately so update characteristic with it dd/mm/yy hh:mm:ss
  sprintf(myDateAndTimeBuffer, "%02u/%02u/%u %02u:%02u:%02u", rtc.getDay(), rtc.getMonth(), rtc.getYear(), rtc.getHours(), rtc.getMinutes(), rtc.getSeconds());
  dateAndTimeChar.setValue(myDateAndTimeBuffer);

  // ------------------------------------- Water Meter Service
  BLE.setAdvertisedService(myWaterMeterService); // add the service UUID
  // Flow event
  myWaterMeterService.addCharacteristic(flowEventMeteredVolumeChar); // add the water volume flow characteristic
  flowEventMeteredVolumeChar.addDescriptor(flowEventMeteredVolumeDescriptor);

  // pulse frequency of current flow event
  myWaterMeterService.addCharacteristic(pulseFrequencyChar); // add the pulse frequency characteristic
  pulseFrequencyChar.addDescriptor(pulseFrequencyDescriptor);

  // Total volume flow today so far
  myWaterMeterService.addCharacteristic(meteredFlowTodayChar); // add the water volume flow characteristic
  meteredFlowTodayChar.addDescriptor(meteredFlowTodayDescriptor);

  // Number of flow events today so far
  myWaterMeterService.addCharacteristic(numberOfFlowEventsTodayChar);  // add the water flow daily triggers characteristic
  numberOfFlowEventsTodayChar.addDescriptor(numberOfFlowEventsTodayDescriptor);

  // Total volume flow this month so far
  myWaterMeterService.addCharacteristic(meteredFlowThisMonthChar); // add the water volume flow characteristic
  meteredFlowThisMonthChar.addDescriptor(meteredFlowThisMonthDescriptor);

  // Number of flow events this month so far
  myWaterMeterService.addCharacteristic(numberOfFlowEventsThisMonthChar);  // add the water flow daily triggers characteristic
  numberOfFlowEventsThisMonthChar.addDescriptor(numberOfFlowEventsThisMonthDescriptor);

  // Number of pulses in flow event
  myWaterMeterService.addCharacteristic(numberOfPulsesInFlowEventChar); // add the number of pulses in flow event characteristic
  numberOfPulsesInFlowEventChar.addDescriptor(numberOfPulsesInFlowEventDescriptor);

  // Number of pulses today
  myWaterMeterService.addCharacteristic(numberOfPulsesTodayChar); // add the number of pulses today characteristic
  numberOfPulsesTodayChar.addDescriptor(numberOfPulsesTodayDescriptor);
  
  BLE.addService(myWaterMeterService); // Add the water flow service


  minutesAdjustChar.setValue("waiting..."); // set initial value for this characteristic
  flowEventMeteredVolumeChar.setValue("waiting..."); // set initial value for this characteristic
  pulseFrequencyChar.setValue("waiting..."); // set initial value for this characteristic
  meteredFlowTodayChar.setValue("waiting..."); // set initial value for this characteristic
  numberOfFlowEventsTodayChar.setValue("waiting...");
  meteredFlowThisMonthChar.setValue("waiting..."); // set initial value for this characteristic
  numberOfFlowEventsThisMonthChar.setValue("waiting...");
  numberOfPulsesInFlowEventChar.setValue("waiting...");
  numberOfPulsesTodayChar.setValue("waiting...");
  /* 
    Start advertising - it'll start continuously transmitting Bluetooth® Low Energy
     advertising packets and will be visible to remote Bluetooth® Low Energy central
     devices until it receives a new connection.
  */
  BLE.advertise();

  if (connectedToComputerForTesting) Serial.println("Bluetooth® device active, waiting for connection...");
}

void restoreFromFlash() {
    // Executed if a battery swap (1) or a low battery voltage powerdown (2) happened.
    // Use time stored in flash - and add 10 mins (great for a battery swap, useless 
    // for a low battery powerdown!)
    // If more than a few moments have passed, you'll need to correctly set the clock
    // using the minutes adjustment characteristic
    rtc.setTime(flashHours.read(), flashMinutes.read(), flashSeconds.read());
    rtc.setDate(flashDay.read(), flashMonth.read(), flashYear.read());
    rtc.setEpoch(rtc.getEpoch() + 600);
    rtcToday = rtc.getDay();
    rtcThisMonth = rtc.getMonth();
    if (connectedToComputerForTesting) {
      Serial.println("just set time using flash and added 10 minutes");
      Serial.println(rtc.getHours());
      Serial.println(rtc.getMinutes());
      Serial.println(rtc.getDay());
      Serial.println(rtc.getMonth());
      Serial.println(rtc.getYear());
    }

    // Read relevant characteristic and flow values from flash
    flowEventMeteredVolume = flashFlowEventMeteredVolume.read();
    currentAveragedPulseFrequency = flashCurrentAveragedPulseFrequency.read();
    meteredVolumeToday = flashMeteredVolumeToday.read();
    meteredVolumeThisMonth = flashMeteredVolumeThisMonth.read();
    numberOfFlowEventsToday = flashNumberOfFlowEventsToday.read();
    numberOfFlowEventsThisMonth = flashNumberOfFlowEventsThisMonth.read();
    numberOfPulsesInFlowEvent = flashNumberOfPulsesInFlowEvent.read();
    numberOfPulsesToday = flashNumberOfPulsesToday.read();

    // write values to characteristics so they start off correctly
    // Note that battery voltage isn't stored in flash as there's no need
    dtostrf(flowEventMeteredVolume, 9, 2, myBuff);
    flowEventMeteredVolumeChar.setValue(myBuff);

    dtostrf(meteredVolumeToday, 9, 2, myBuff);
    meteredFlowTodayChar.setValue(myBuff);

    sprintf(myBuff, "%u", numberOfFlowEventsToday);
    numberOfFlowEventsTodayChar.setValue(myBuff);

    dtostrf(currentAveragedPulseFrequency, 9, 2, myBuff);
    pulseFrequencyChar.setValue(myBuff);

    dtostrf(meteredVolumeThisMonth, 9, 2, myBuff);
    meteredFlowThisMonthChar.setValue(myBuff);

    sprintf(myBuff, "%u", numberOfFlowEventsThisMonth);
    numberOfFlowEventsThisMonthChar.setValue(myBuff);

    sprintf(myBuff, "%u", numberOfPulsesInFlowEvent);
    numberOfPulsesInFlowEventChar.setValue(myBuff);

    sprintf(myBuff, "%u", numberOfPulsesToday);
    numberOfPulsesTodayChar.setValue(myBuff);
}

char *dtostrf (double val, signed char width, unsigned char prec, char *sout) {
/*
  dtostrf - Emulation for dtostrf function from avr-libc
  Copyright (c) 2013 Arduino.  All rights reserved.
  Written by Cristian Maglie <c.maglie@bug.st>

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/
  char fmt[20];
  sprintf(fmt, "%%%d.%df", width, prec);
  sprintf(sout, fmt, val);
  return sout;
}

void countPulse() {
  pulseCount++;
//  delayMicroseconds(11000);
}

void updateBatteryVoltage() {
// Read the current voltage  on the batteryPin input pin.
// and update the characteristic.

batteryRead = analogRead(batteryPin);
batteryVoltage = batteryRead * (batteryFullyChargedVoltage / 1023.0f);
// Debug: Serial.print("Battery voltage is now: ");
// Debug: Serial.println(batteryVoltage);
  if (batteryVoltage != oldBatteryVoltage) {  // if the battery voltage has changed
    dtostrf(batteryVoltage, 9, 2, myBuff);
    batteryVoltageChar.setValue(myBuff);
    oldBatteryVoltage = batteryVoltage; // save the voltage for next comparison
  }
  if (batteryVoltage < flashSaveVoltage) {
    // make sure all saved to flash and blink the LED forever (until power switched off)
    if (connectedToComputerForTesting) {
      Serial.print("Battery voltage is less than save voltage. It is: "); // print it
      Serial.println(batteryVoltage);
      Serial.print("save voltage is: "); // print it
      Serial.println(flashSaveVoltage);
    }
    // Set the flag to show values are to be restored
    flashBatterySwapOrLowBatteryPowerDownFlag.write(2); // 2 means low battery voltage triggered save to flash
    batterySwapOrLowBatteryPowerDown();
  }
}

void batterySwapOrLowBatteryPowerDown() {
  // Executed if pushbutton pressed or battery voltage falls below batterysave voltage

  // Write real time clock values to flash
  flashHours.write(rtc.getHours());
  flashMinutes.write(rtc.getMinutes());
  flashSeconds.write(rtc.getSeconds());
  flashDay.write(rtc.getDay());
  flashMonth.write(rtc.getMonth());
  flashYear.write(rtc.getYear());

  // Write relevant characteristic and flow values to flash
  flashFlowEventMeteredVolume.write(flowEventMeteredVolume);
  flashMeteredVolumeToday.write(meteredVolumeToday);
  flashMeteredVolumeThisMonth.write(meteredVolumeThisMonth);
  flashNumberOfFlowEventsToday.write(numberOfFlowEventsToday);
  flashNumberOfFlowEventsThisMonth.write(numberOfFlowEventsThisMonth);
  flashNumberOfPulsesInFlowEvent.write(numberOfPulsesInFlowEvent);
  flashNumberOfPulsesToday.write(numberOfPulsesToday);
  if (connectedToComputerForTesting) {
    Serial.println("flash written");
    Serial.print("Disconnected from central");
  }
  digitalWrite(LED_BUILTIN, LOW);

  while (1) {
    // Slow blink the LED to indicate vals all stored in flash.
    blinker(1000, 1);
    // Leave it blinking forever as a reminder to swap the 
    // batteries (assuming you notice).
    // When the old batteries are removed, it'll switch off and
    // when the new ones are put in, processing will start at 
    // setup() as normal.

    // If battery voltage had fallen to the level where things 
    // were automatically saved to flash, two things might happen
    // from there:
    // 1) You get to it (while the Arduino is still being powered)
    //    and replace the batteries, meaning the Arduino will get 
    //    powered down as part of that. When you put the new ones
    //    in, processing will start at setup() as normal.
    // 2) You don't get to it, the batteries eventually deplete 
    //    so the Arduino becomes powered down.
    //    As the solar panels charge the batteries, eventually
    //    they will be charged enough to power the Arduino again.
    //    When they are, processing will start at setup() as 
    //    normal. You'll have to decide how to cope with this.
  }
}
    
void dealWithPossibleDayOrMonthAdvance(){
  if ( rtcThisMonth != rtc.getMonth() ) { // must be next month!
    rtcThisMonth = rtc.getMonth();
    meteredVolumeThisMonth = 0.0f;
    dtostrf(meteredVolumeThisMonth, 9, 2, myBuff);
    meteredFlowThisMonthChar.setValue(myBuff);
    numberOfFlowEventsThisMonth = 0;
    sprintf(myBuff, "%u", numberOfFlowEventsThisMonth);
    numberOfFlowEventsThisMonthChar.setValue(myBuff);
  }
  if ( rtcToday != rtc.getDay() ) { // must be tomorrow!
    rtcToday = rtc.getDay();
    flowEventMeteredVolume = 0;
    dtostrf(flowEventMeteredVolume, 9, 2, myBuff);
    flowEventMeteredVolumeChar.setValue(myBuff);
    meteredVolumeToday = 0;
    dtostrf(meteredVolumeToday, 9, 2, myBuff);
    meteredFlowTodayChar.setValue(myBuff);
    numberOfFlowEventsToday = 0;
    sprintf(myBuff, "%u", numberOfFlowEventsToday);
    numberOfFlowEventsTodayChar.setValue(myBuff);
    numberOfPulsesToday = 0;
    sprintf(myBuff, "%u", numberOfPulsesToday);
    numberOfPulsesTodayChar.setValue(myBuff);
  }
}

void blinker(long mySpeed, int numBlinks) {
/* These values work well
blinker(80, 3);
blinker(300, 2);
*/
long myCount = 0, previousMillis = 0, currentMillis = millis();
bool blinkingIsToContinue = true, ledState = LOW;
  while (blinkingIsToContinue) {
    currentMillis = millis();
    if (digitalRead(buttonPin) == PRESSED) blinkingIsToContinue = false;
    // allow early exit from blinker
    if (blinkingIsToContinue){
      if (currentMillis - previousMillis > mySpeed){
        previousMillis = currentMillis;
        myCount++;
        digitalWrite(ledPin, ledState);
        ledState = !ledState;
        if (myCount == numBlinks * 3) return;
      }
    }
  }
}

void loop() {
static unsigned long timePulseReceived = 0;
static int previousPulseCount = 0;
static long elapsedTime = 0;
const byte NumberOfPulsesToAverageFrequencyOver = 30;
static int numberOfPulsesReceivedForAveraging = 0, timeTakenForPulsesBeingAveraged = 0; 
static bool flowEventInProgress = false;
  // First, allow for a battery swap / power down
  if (digitalRead(buttonPin) == PRESSED) {
    // Debug: Serial.println("button press detected");
    // write relevant characteristic vals to flash, set the flag and light the LED
    // Set the flag to show values are to be restored
    // First, blink with this pattern to acknowledge button press
    blinker(100,2);
    blinker(300,1);
    blinker(100,2);
    delay(1000);
    flashBatterySwapOrLowBatteryPowerDownFlag.write(1); // 1 means battery swap requested by user
    batterySwapOrLowBatteryPowerDown();
  }
  // allow for a Bluetooth® Low Energy central to connect
  BLEDevice central = BLE.central();

  if (central) {
    if (minutesAdjustChar.written()) {
      rtc.setEpoch(rtc.getEpoch()+60UL*((long)minutesAdjustChar.value().toInt()));
    }
  }

  // check battery level every 200 ms
  long currentMillis = millis();
  // if 600 ms have passed, check battery level and record clock:
  if (currentMillis - previousMillis >= 600) {
    previousMillis = currentMillis;
    updateBatteryVoltage();
    sprintf(myDateAndTimeBuffer, "%02u/%02u/%u %02u:%02u:%02u", rtc.getDay(), rtc.getMonth(), rtc.getYear(), rtc.getHours(), rtc.getMinutes(), rtc.getSeconds());
    dateAndTimeChar.setValue(myDateAndTimeBuffer);
  }
  // now deal with water flow. 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.
    flowEventInProgress = true;
    if (connectedToComputerForTesting) Serial.println("Flow event in progress");
    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 so calculate volumes etc
        if (connectedToComputerForTesting) Serial.println("Flow event just stopped");
        if (pulseCount > ignorePulsesThreshold) { // connecting a hosepipe can cause a few pulses so ignore them
          numberOfPulsesInFlowEvent = pulseCount;
          numberOfFlowEventsToday++;
          numberOfFlowEventsThisMonth++;
          numberOfPulsesToday = numberOfPulsesToday + numberOfPulsesInFlowEvent;
          // can now calculate volume of water in this flow period
          flowEventMeteredVolume = (float)numberOfPulsesInFlowEvent * volumePerPulse * measuredToActualFlowVolumeMultFactor;
          meteredVolumeToday += flowEventMeteredVolume;
          meteredVolumeThisMonth += flowEventMeteredVolume;
          dtostrf(flowEventMeteredVolume, 9, 3, myBuff);
          flowEventMeteredVolumeChar.setValue(myBuff);
          dtostrf(meteredVolumeToday, 9, 2, myBuff);
          meteredFlowTodayChar.setValue(myBuff);
          sprintf(myBuff, "%u", numberOfFlowEventsToday);
          numberOfFlowEventsTodayChar.setValue(myBuff);
          dtostrf(meteredVolumeThisMonth, 9, 2, myBuff);
          meteredFlowThisMonthChar.setValue(myBuff);
          sprintf(myBuff, "%u", numberOfFlowEventsThisMonth);
          numberOfFlowEventsThisMonthChar.setValue(myBuff);
          sprintf(myBuff, "%u", numberOfPulsesInFlowEvent);
          numberOfPulsesInFlowEventChar.setValue(myBuff);
          sprintf(myBuff, "%u", numberOfPulsesToday);
          numberOfPulsesTodayChar.setValue(myBuff);
        }
        // prepare for next flow event
        flowEventInProgress = false;
        pulseCount = 0;
        previousPulseCount = 0;
        elapsedTime = 0;
      }
    } else { // another pulse has arrived
      numberOfPulsesReceivedForAveraging++;
      timeTakenForPulsesBeingAveraged = timeTakenForPulsesBeingAveraged + millis() - timePulseReceived;
      if(numberOfPulsesReceivedForAveraging == NumberOfPulsesToAverageFrequencyOver) {
        currentAveragedPulseFrequency = 1000.0f * (float)numberOfPulsesReceivedForAveraging/(float)timeTakenForPulsesBeingAveraged;
        dtostrf(currentAveragedPulseFrequency, 9, 0, myBuff);
        pulseFrequencyChar.setValue(myBuff);
        numberOfPulsesReceivedForAveraging = 0;
        timeTakenForPulsesBeingAveraged = 0;
      }
      timePulseReceived = millis();
      previousPulseCount = pulseCount;
    }
  }
  if (!flowEventInProgress) {
    dealWithPossibleDayOrMonthAdvance();
    if (rtc.getHours() < wakeupTimeHour) {
      // Check to see if it's worth doing the full check - no point if it's later than
      // the wakeup hour
      // temporarily store time in "now" while working out the epoch values needed
      now = rtc.getEpoch();
      // represent bedTime and Wakeup as epoch numbers so they can easily be compared
      // Get epoch time of bedtimeHour
      rtc.setTime(bedTimeHour, 0, 0);
      bedTimeEpochTime = rtc.getEpoch();
      // Get epoch time of wakeupTimeHour
      rtc.setTime(wakeupTimeHour, 0, 0);
      wakeupEpochTime = rtc.getEpoch();
      // put the time back to now
      rtc.setEpoch(now);
      if ( (now > bedTimeEpochTime) && (now < wakeupEpochTime) ) {
        // It should be asleep so send it to bed!
        if (connectedToComputerForTesting) {
          Serial.print("going to sleep, rtc mins is: ");
          Serial.println(rtc.getMinutes());
          Serial.flush();
          Serial.end();
          delay(500); // give it a chance to fully end before sleep
        }
        LowPower.sleep((wakeupEpochTime - now) * 1000); // sleep until wakeup time
        // NB the first pulse of a new flow event would also wake up the Nano. When
        // the flow event is finished (and as long as it's still bedtime) the Nano
        // will be put back to sleep.
        if (connectedToComputerForTesting) {
          Serial.begin(9600);
          while(!Serial);
          Serial.println("awake");
        }
      }
    }
  }
}

Before anyone asks, I don't recall why I included an 11000 microsecond delay in the interrupt service routine. I think I put it there for testing to limit the number of pulses per second that could be registered, thus keeping things to known limits.

Anyway, for this sketch it doesn't do any harm as 11 milliseconds represents 90 pulses per second which is twice the frequency that can happen in this application.

I will be removing it though :)

Why don't you try, write simple test sketch configuring both.
Also, consider adjusting advertising interval for better battery life.

Not a problem. The interrupt service routine associated with each interrupt just sets a global flag to say it happened, and the main program checks the flags and takes appropriate action(s).

modern microcontrollers can have multiple interrupts enabled
when an interrupt occurs the corresponding ISR (Interrupt Service Routine) is called
if multiple interrupts occur

  1. they may be taken in order as they occur
  2. some microcontrollers have the facility to set interrupt priority levels where a device interrupt service routine can be interrupted by higher priority devices

e.g. PIC24FJ256GA705 using the MPLABX IDE the interrupt priorities were set using the MMC (MPLAB Code Configurator)

timer interrupt is level 1 the I2C interface is level 2 therefore I2C can interrupt timer ISR

in general the advice is to keep ISR as short as possible and avoid calling complex library function - in particular no Serial Monitor IO!
set a volatile flag variable in the ISR and do complex operations in loop()

How fast are the pulses from the water meter? If they are per litre pulses there are probably no more than two or three per second at most.

If that's the case the pulses don't need to be a interrupt - they can just be a regular input. Wake the board from sleep using the RTC a few times per second and check if the pulse input has changed, and update the counter if it has. This will mean the controller can spend 99% of its life asleep for minimal power usage.

You could disable this during the day if you want to leave the controller active for your remote reading.

I built a water meter reader detecting the rotating disk on an Itron water meter using this technique and it works very well. You just need to be sure that you wake the controller often enough that there is no risk of missing pulses.

One pulse represents approx 0.00225 litres. I get around 30 pulses per second or thereabouts - but thanks for the suggestion, @sciroccotorc :smiley:

Oh my! What a trip down the rabbit hole I've been on!

As this applies to the other repliesas well, I'll reply to my own opening post and mention everyone so they can see where I've got to with their input....

Thanks for your suggestion @kmin :D

Thanks all for your feedback / suggestions so far... I've been misled and it's taken ages to get to the bottom of it....

I did lots of searching on line and in these forums and have been pulled hither and thither (don't get to say that very often!)

I was misled by this example: https://docs.arduino.cc/learn/electronics/low-power/

I just couldn't get it to work with my Nano 33 IoT:

  • Can only use pin 2 or 3 for interrupts on the Nano 33 IoT. That sketch uses pin 8 and nothing to warn about the Nano 33 IoT needing to use 2 or 3. Lost hours on that.
  • I used a few Serial.println statements to help me see what was going on. I didn't realise it but on waking, the serial port wasn't working anymore, so thought it wasn't waking. I finaly found I needed to use Serial.end() before putting the Nano to sleep (I threw in a Serial.flush() just prior to the end() be sure)
    I wish official examples had a gotchas section for beginners!

So I'm ready now to try the next step, somehow using my existing attached interrupt: attachInterrupt(digitalPinToInterrupt(waterFlowSensorPulseIn), countPulse, RISING);
in place of the: LowPower.attachInterruptWakeup(pin, repetitionsIncrease, CHANGE);

I can't quite get my head around it right now, more cogitation needed... The thing that's getting my brain in a twist is that I need to tell my existing interrupt to wake up the Nano (and I did read somewhere on my travels (travails??!) that any interrupt would wake it but I'm not sure if that's true - gotta try it).

Thanks @jremington and @horace your replies have helped me so far :D

@hightonridley

Would be glad if you kindly briefly describe the work flow of your project.

Not true. The Nano 33 IoT has the SAMD21 M0 processor, and any pin can be used as an external interrupt. It sounds like you are not looking in the right places for information, like the (very complicated) datasheet, or examples posted on the web for other boards using that same processor.

To wake from deep sleep requires some more work, as you need to set up a clock to run the subsystem that checks pins for activity, which in turn triggers the interrupt. Here is one example for that processor.

Note: EIC stands for the processor subsystem External Interrupt Controller.

/*
 * wait for external interrupt of SAMD21 in standby mode
 * RISING and FALLING interrupts work as expected
 * - 2 µA standby current at 3.0 V excl. USB/LED
 */

 // library at https://github.com/jnsbyr/arduino-samd21lpe
#include <System.h>
using namespace SAMD21LPE;

#define EXT_INT_PIN 6

void externalInterruptHandler() {
}

void setupEIC() {
  noInterrupts();

  // enable external interrupt on pin
  pinMode(EXT_INT_PIN, INPUT_PULLUP);
  attachInterrupt(EXT_INT_PIN, externalInterruptHandler, RISING);

  // configure low power clock generator to run at 1 kHz
  const byte GCLKGEN_ID_1K = 6;
  System::setupClockGenOSCULP32K(GCLKGEN_ID_1K, 4); // 2^(4+1) = 32 -> 1 kHz

  // change clock generator for EIC from 0 (DFLL48M, assigned by attachInterrupt) to 6 (OSCULP32K), which stays enabled in standby (required for input edge detection)
  System::enableClock(GCM_EIC, GCLKGEN_ID_1K);

  interrupts();
}

void setup() {

  pinMode(LED_BUILTIN,OUTPUT);
  
  // setup external interrupt controller
  setupEIC();

  System::setSleepOnExitISR(false);
  System::setSleepMode(System::STANDBY);
}

void loop() {
digitalWrite(LED_BUILTIN,1); //blink LED then sleep, repeat on external interrupt
delay(200);
digitalWrite(LED_BUILTIN,0);
System::sleep();
}

remembered the Nano 33 IoT is a SAMD21 Cortex®-M0+ 32bit low power ARM MCU so tried lowpower example on an MKRFOX

// MKRFOX low power test

// adapted from https://docs.arduino.cc/learn/electronics/low-power/

// NOTE: programming in sleep is not possible
//  "quickly double tap the reset button" to put it into bootloader mode on a fixed com port

#include "ArduinoLowPower.h"

// Blink sequence number
// Declare it volatile since it's incremented inside an interrupt
volatile int repetitions = 1;

// Pin used to trigger a wakeup
const int pin = 8;

void setup() {
  delay(5000);
  Serial.begin(115200);
  Serial.print("\n\nMKRFOX low power test - repetitions ");
  Serial.println(repetitions);
  pinMode(LED_BUILTIN, OUTPUT);
  // Set pin 8 as INPUT_PULLUP to avoid spurious wakeup
  pinMode(pin, INPUT_PULLUP);
  // Attach a wakeup interrupt on pin 8, calling repetitionsIncrease when the device is woken up
  LowPower.attachInterruptWakeup(pin, repetitionsIncrease, CHANGE);
}

void loop() {
  for (int i = 0; i < repetitions; i++) {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(500);
    digitalWrite(LED_BUILTIN, LOW);
    delay(500);
  }
  Serial.println("going to low power sleep in 1 second - CHANGE pin 8 to wakeup");
  delay(500);
  Serial.end();
  delay(500);
  // Triggers an infinite sleep (the device will be woken up only by the registered wakeup sources)
  // The power consumption of the chip will drop consistently
  LowPower.sleep();
  delay(5000);
  Serial.begin(115200);
  Serial.print("\nMKRFOX wakeup in loop() - repetitions ");
  Serial.println(repetitions);
}

void repetitionsIncrease() {
  // This function will be called once on device wakeup
  // You can do some little operations here (like changing variables which will be used in the loop)
  // Remember to avoid calling delay() and long running functions since this functions executes in interrupt context
  repetitions++;
}

serial monitor output

MKRFOX low power test - repetitions 1
going to low power sleep in 1 second - CHANGE pin 8 to wakeup
MKRFOX wakeup in loop() - repetitions 3
going to low power sleep in 1 second - CHANGE pin 8 to wakeup
MKRFOX wakeup in loop() - repetitions 5
going to low power sleep in 1 second - CHANGE pin 8 to wakeup
MKRFOX wakeup in loop() - repetitions 7
going to low power sleep in 1 second - CHANGE pin 8 to wakeup
MKRFOX wakeup in loop() - repetitions 9
going to low power sleep in 1 second - CHANGE pin 8 to wakeup

I am using a mechanical switch to CHANGE pin 8 state - probably getting some switch bounce

after waking from low power it can be difficult to get the Serial operational hence all the delays

I believe that's only the case for edge-triggered interrupts on the SAMD21. The clock is not required for level-triggered interrupts.

Correct, which according to the post title, is what the OP wants "when sensor pulse arrives".

How weird! After getting it to work using pin 2, I then went back and tried pin 8. Couldn't get it to work with pin 8.

For good measure, I tried with pin 7 as well but no joy with that either. Went back to pin 2 and it worked again. Then tried pin 3. It worked.

So I'm confused. Any ideas what might be happening?

This is so strange. I copied your code into a new sketch. Using pin 8 it didn't wake (connectiing pin 8 to ground).

I changed it to pin 2 and it did wake up. I changed it to pin 7, no joy. Changed it to pin 3 and it worked.

Is there a version of the Ardino Nano 33 IoT which this would happen on but not on others?

Hi @hightonridley

Here's an example sketch that puts the SAMD21 microcontroller into deep sleep and awakes upon a button depress. It also takes the steps necessary to restart the serial port afterwards.

Note that you might need to replace SerialUSB with Serial in the code.

Furthermore, the delay functions' duration appears to be operating system dependent, with Windows only requiring 500ms delays to recover the console:

// 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
  delay(500);                                     // Wait half a second
  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(2000);                                    // Wait for two seconds (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(2000);                                    // Wait for two seconds
}

void dummyFunc() {}                               // Dummy ISR function

I'll try ;)

Initialisation

  • Set up all bluetooth stuff, working constants etc
  • When an interrupt arrives increment pulseCount

Main loop:

Housekeeping:

  • Read the battery voltage and update the bluetooth "characteristic" for reporting
  • If the battery voltage drops below a constant set voltage, save everything to flash memory and wait for a battery swap
  • when power is reapplied as the new batteries are plugged in, reinstate everything from flash and carry on as though nothing happened
  • If the button is pressed, do the same
  • If a new day or new month comes along, set all daily / monthly counts / volumes to zero

Water flow:

  • Use pulseCount to determine if water is flowing and hasn't stopped yet.
  • (no more pulses arriving after a timeout period means flow event has stopped)
  • While it's flowing increment counters, frequency and volumes for bluetooth reporting
  • (to calculate the frequency, count a constant-determined number of pulses and the time taken to receive them all then take the average)
  • When water flow stops, reset stuff ready for next flow event

That's pretty much it. Is that what you were after, @GolamMostafa ?

Not without seeing the code. Please post the code, using code tags. and also a wiring diagram, with pins, parts and connections clearly labeled. Hand drawn is preferred.

That looks interesting, @MartinL

I'll give it a spin a little later. Thanks a lot for sharing it :D