Trouble using external interrupts with WDT for bird nest monitor

Hello!

I'm running an Arduino Pro Mini 3.3v/8MHz and I'm trying to get external interrupts to work with the watchdog timer. The purpose of the device is 1. to take temperature and humidity readings inside and outside of a bird nest box/burrow, with a timestamp, every 2 minutes and 2. to record the time of the bird(s) entering and exiting the nest, using two IR break beam sensors. If it isn't doing either of those, it goes to sleep. All data is written to an SD card, one file for climate, one for activity.

Currently, I have the climate readings working with the WDT. When I add and trigger the external interrupts for the IR sensors the program appears to hang.

I've been referencing Nick Gammon's power saving and interrupt posts, but my understanding of the interactions of external interrupts with the WDT is lacking. Specifically, I'm trying to merge Sketch I and Sketch J in this post.

I'd like each interrupt to wake the processor, record the sensor event and time stamp to variables, then go back to sleep, continuing the 8s WDT cycle it interrupted. Ideally, it would also write them to the SD card, but from everything I've read, I don't think that's something the ISR should do, even though I tested it and it worked, so I'm unsure. Any thoughts on writing to and SD card in an ISR?

I've searched many similar posts with similar issues, but the different formats or libraries in each makes it hard to make an apples-to-apples comparison. So I've made an account because I feel like I'm trying to push my head trough a brick wall. :upside_down_face:

Any other code tips are also appreciated!

// Libraries
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <SD.h>
#include <DS3231.h>
#include <Wire.h>
#include <ClosedCube_SHT31D.h>
#include <Arduino.h>

// Set Device ID - Maximum 3 characters
const String deviceID = "21A";
// Set the year - 2 characters
const String seasonYear = "25"; 

// SD Card Reader
const int chipSelect = 10;
File myFile;
String activityFileName, temperatureFileName;

//RTC Real Time Clock Module
DS3231 myRTC;
bool century = false;
bool h12Flag;
bool pmFlag;

// SHT30 Temp Sensor
ClosedCube_SHT31D sht3xd;
float temp1;
float hum1;
float temp2;
float hum2;

//status of break beam sensors
volatile int enStatus = 0;
volatile int enSecond = 0;
volatile int enMinute = 0;
volatile int exStatus = 0;
volatile int exSecond = 0;
volatile int exMinute = 0;

int sleepCount = 0; // Variable for sleep loop

// Watchdog Interrupt
ISR(WDT_vect){
  wdt_disable();
}

void setup() {

  Serial.begin(115200);
  pinMode(2, INPUT_PULLUP); //Entrance side sensor
  pinMode(3, INPUT_PULLUP); //Exit side Sensor
  pinMode(8, OUTPUT); //Temp sensor 1 power
  pinMode(9, OUTPUT); //Temp sensor 2 power

  Wire.begin(); 
  SD.begin(chipSelect);
  sht3xd.begin(0x44);

  Serial.print("Initializing SD card...");

  if (!SD.begin(chipSelect)) {
    Serial.println("initialization failed. Things to check:");
    Serial.println("1. is a card inserted?");
    Serial.println("2. is your wiring correct?");
    Serial.println("3. did you change the chipSelect pin to match your shield or module?");
    Serial.println("Note: press reset button on the board and reopen this Serial Monitor after fixing your issue!");
    while (true);
  }

  Serial.println("initialization done.");

  myRTC.setClockMode(false);
  myRTC.setYear(25);
  myRTC.setMonth(7);
  myRTC.setDate(7);
  myRTC.setHour(20);
  myRTC.setMinute(5);
  myRTC.setSecond(0);
 
  //turn off 32kHz pin
  byte temp_buffer = temp_buffer & 0b11110111;
  writeControlByte(temp_buffer, 1);

  //turn of the SQW signal
  temp_buffer =   0b01111111;
  writeControlByte(temp_buffer, 0);

  // Sets device ID and season in file name
  activityFileName = seasonYear + "ACT" + deviceID + ".txt";
  temperatureFileName = seasonYear + "TMP" + deviceID + ".txt";

  Serial.println("setup done");
}

void loop() {
 Serial.println(enStatus); //trying to see if the interrupt runs
delay(500);
  // only run if not awoken by the external interrupt, trying to preserve roughly 2 minute measuring intervals
  if (enStatus == 0 && exStatus == 0){ 
    clmateRecord(); // records external and internal temperature and humidity
    sleepCount++;
    }

  activityRecord(); // records entering and exiting activity



  ADCSRA = 0;  // disable ADC
  MCUSR = 0;  // clear various "reset" flags

  WDTCSR = bit (WDCE) | bit (WDE);  // allow changes, disable reset
  WDTCSR = bit (WDIE) | bit (WDP3) | bit (WDP0);   // set interrupt mode and an interval 
  //wdt_reset(); // pat the dog :)

  set_sleep_mode (SLEEP_MODE_PWR_DOWN);  
  noInterrupts();
  sleep_enable();

  attachInterrupt (digitalPinToInterrupt(2), enDet, CHANGE);
  attachInterrupt (digitalPinToInterrupt(3), exDet, CHANGE); 
  EIFR = bit (INTF0);
  EIFR = bit (INTF1);


  MCUCR = bit (BODS) | bit (BODSE);   // turn off brown-out enable in software
  MCUCR = bit (BODS); 

  interrupts();
  sleep_cpu();

  sleep_disable();
}

// Turn off RTC 32K and SQW
void writeControlByte(byte control, bool which) {
	Wire.beginTransmission(0x68);
	if (which) {
		Wire.write(0x0f);
	} else {
		Wire.write(0x0e);
	}
	Wire.write(control);
	Wire.endTransmission();
}

// Edge detect ISR to record activity
// Activity.txt format: ID, Sensor code (EN/EX), State after change, timestamp
//Enter Detect
void enDet (){
  sleep_disable();
  detachInterrupt (0);
  if (enStatus == 0){
    if (digitalRead(2) == LOW){
      enStatus = -1;
      enMinute = myRTC.getMinute();
      enSecond = myRTC.getSecond();
    }
    if (digitalRead(2) == HIGH){
      enStatus = 1;
      enMinute = myRTC.getMinute();
      enSecond = myRTC.getSecond();
    }
  }
} 

//Exit Detect
void exDet (){ 
  sleep_disable();
  detachInterrupt (1);
  if (exStatus == 0){
    if (digitalRead(3) == LOW){
      exStatus = -1;
      exMinute = myRTC.getMinute();
      exSecond = myRTC.getSecond();
    }
    if (digitalRead(3) == HIGH){
      exStatus = 1;
      exMinute = myRTC.getMinute();
      exSecond = myRTC.getSecond();
    }
  }
} 


void activityRecord(){
  if (enStatus == -1){
    myFile = SD.open(activityFileName, FILE_WRITE);
    myFile.print(deviceID);
    myFile.print(',');
    myFile.print("EN,LOW,");
    myFile.print(myRTC.getYear());
    myFile.print('/');
    myFile.print(myRTC.getMonth(century));
    myFile.print('/');
    myFile.print(myRTC.getDate());
    myFile.print(' ');
    myFile.print(myRTC.getHour(h12Flag, pmFlag));
    myFile.print(':');
    myFile.print(enMinute);
    myFile.print(':');
    myFile.print(enSecond);   
    myFile.println(',');
    myFile.close();
    enStatus = 0;
    enMinute = 0;
    enSecond = 0;
  }
  if (enStatus == 1){
    myFile = SD.open(activityFileName, FILE_WRITE);
    myFile.print(deviceID);
    myFile.print(',');
    myFile.print("EN,HIGH,");
    myFile.print(myRTC.getYear());
    myFile.print('/');
    myFile.print(myRTC.getMonth(century));
    myFile.print('/');
    myFile.print(myRTC.getDate());
    myFile.print(' ');
    myFile.print(myRTC.getHour(h12Flag, pmFlag));
    myFile.print(':');
    myFile.print(enMinute);
    myFile.print(':');
    myFile.print(enSecond);    
    myFile.println(',');
    myFile.close();  
    enStatus = 0;
    enMinute = 0;
    enSecond = 0;
  }

  if (exStatus == -1){
    myFile = SD.open(activityFileName, FILE_WRITE);
    myFile.print(deviceID);
    myFile.print(',');
    myFile.print("EX,LOW,");
    myFile.print(myRTC.getYear());
    myFile.print('/');
    myFile.print(myRTC.getMonth(century));
    myFile.print('/');
    myFile.print(myRTC.getDate());
    myFile.print(' ');
    myFile.print(myRTC.getHour(h12Flag, pmFlag));
    myFile.print(':');
    myFile.print(exMinute);
    myFile.print(':');
    myFile.print(exSecond);    
    myFile.println(',');
    myFile.close();
    exStatus = 0;
    exMinute = 0;
    exSecond = 0;
  }
  if (exStatus == 1){
    myFile = SD.open(activityFileName, FILE_WRITE);
    myFile.print(deviceID);
    myFile.print(',');
    myFile.print("EX,HIGH,");
    myFile.print(myRTC.getYear());
    myFile.print('/');
    myFile.print(myRTC.getMonth(century));
    myFile.print('/');
    myFile.print(myRTC.getDate());
    myFile.print(' ');
    myFile.print(myRTC.getHour(h12Flag, pmFlag));
    myFile.print(':');
    myFile.print(exMinute);
    myFile.print(':');
    myFile.print(exSecond);     
    myFile.println(',');
    myFile.close();  
    exStatus = 0;
    exMinute = 0;
    exSecond = 0;
  }
}

void clmateRecord(){
    // set to 1 for debugging, normally 15
    if (sleepCount == 1) {
    digitalWrite(8, HIGH);
    printResult1("Pooling Mode", sht3xd.readTempAndHumidity(SHT3XD_REPEATABILITY_HIGH, SHT3XD_MODE_POLLING, 50));
    digitalWrite(8, LOW);
    digitalWrite(9, HIGH);
    printResult2("Pooling Mode", sht3xd.readTempAndHumidity(SHT3XD_REPEATABILITY_HIGH, SHT3XD_MODE_POLLING, 50));
    digitalWrite(9, LOW);
    // Temperature readings
    // File name format YYTMP##x.txt: YY - season year, TMP - Temperature designation, IDx - Device ID, 2 numbers, 1 letter maximum
    // YYTMPIDx.txt format: ID, Terperature 1, Humidity 1, Temperature 2, Humidity 2, Timestamp
    myFile = SD.open(temperatureFileName, FILE_WRITE);
    myFile.print(deviceID);
    myFile.print(',');
    myFile.print(temp1);
    myFile.print(',');
    myFile.print(hum1);
    myFile.print(','); 
    myFile.print(temp2);
    myFile.print(',');
    myFile.print(hum2);
    myFile.print(','); 
    myFile.print(myRTC.getYear());
    myFile.print('/');
    myFile.print(myRTC.getMonth(century));
    myFile.print('/');
    myFile.print(myRTC.getDate());
    myFile.print(' ');
    myFile.print(myRTC.getHour(h12Flag, pmFlag));
    myFile.print(':');
    myFile.print(myRTC.getMinute());
    myFile.print(':');
    myFile.print(myRTC.getSecond());    
    myFile.println(',');
    myFile.close(); 
    sleepCount = 0;
    }
}
// Temp sensor readouts
void printResult1(String text, SHT31D result) {
	if (result.error == SHT3XD_NO_ERROR) {
    temp1 = result.t;
    hum1 = result.rh;
	}
}
void printResult2(String text, SHT31D result) {
	if (result.error == SHT3XD_NO_ERROR) {
    temp2 = result.t;
    hum2 = result.rh;
	}
}

What is WDT acronym for?

Nevermind I see it now :upside_down_face:

I'm not clear on how the WDT interrupt every 8 seconds gives you a 2-minute cycle. But more fundamentally, if you have a DS3231 RTC, why do you need the WDT?

You could set the RTC to generate an alarm every two minutes, which would trigger an interrupt which wakes up the processor. The ISR would just set a flag variable, and then the loop() would see that flag and do the environment stuff. Then it would clear that flag and the alarm flag in the RTC, set the new alarm for two minutes later, and go back to sleep.

Then you could have a separate interrupt for bird traffic, which would set a flag for that, then the loop() would see that flag, record the timestamp, then go back to sleep.

So the loop() would begin by testing both flags, then if neither is set it would go to sleep. On wakeup, it would determine which flag was set, then clear it and do the processing. Then the loop rolls over and both flags are tested again at the top. So even if the second interrupt occurs while processing the first one, you won't lose it.

The processor will use less current while sleeping if the WDT is disabled.

A bit of a wild guess here, but the datasheet for the 328P says that in the power down modes:

Only an External Reset, a Watchdog System Reset, a Watchdog Interrupt, a Brown-out Reset, a 2-wire Serial Interface address match, an external level interrupt on INT0 or INT1, or a pin change interrupt can wake up the MCU.

I wonder if your attachInterrupt functions need to use the LOW state.

There's another note which may or may not apply to your scenario:

If a level triggered interrupt is used for wake-up from Power-down, the required level must be held long enough for the MCU to complete the wake-up to trigger the level interrupt. If the level disappears before the end of the Start-up Time, the MCU will still wake up, but no interrupt will be generated.

Hi ShermanP,

Thank you for your input!

I count the number of 8 second cycles and take measurements when I complete the 15th cycle. For debugging, I have it set to only 1 cycle.

As for the alarm, I wish I had a spare external interrupt to do that, but the two IR sensors take both available interrupt pins. I haven't found any way (without adding more hardware) to get both sensors on one pin and still have a way to tell which one was tripped first to determine an enter/exit event. The WDT was the next best thing.

You can put the RTC's interrupt on some other pin using a pin change interrupt. These are not directly provided for in the Arduino IDE, but can be easily coded. You can make any GPIO an interrupt pin. You would set it as INPUT_PULLUP and connect the RTC's INT/SQW pin to it. I would have its own ISR.

https://www.youtube.com/watch?v=3vjpapdfjwA

Hi markd833,

Thank you for the reply!

I have tested the interrupts independently in a separate test sketch and they work with both state changes. So I've definitely broken something.

The length of time for the trigger I've been testing with has been very reasonable. I'm sliding a bit of cardboard in and out, and these seabirds are quite slow on land. I imagine the signal must less than a handful of processor cycles to manage that. Thank you for the thought, though!

This needs the main clock to run, which it doesn't in sleep mode.

EDIT: according the figure in the datasheet it shouldn't work, but the text says it will.

According to this from the datasheet, both the "hardware" and pin change interrupts work in power-down mode, which has all oscillators stopped.

Well, in fact, I built a TV remote control that has all of the row pins set up as pin change. I go into power-down sleep, and any keypress wakes up the processor. So it definitely does work.

1 Like

Thank you for all the input!

I did some more digging and discovered that calling the RTC.getSecond() in the ISR was the issue. It just doesn't want to run. I initially had it there believing I needed to save the timestamp of the event since the actual writing of the data would happen up to 8s later, because of the WDT.

With that issue out of the way, the external interrupts work, I added the WDT back in and that works too. I was worried about the 8s cycles getting cut off by the interrupts but testing shows that all I needed to do for that was remove wdt_reset(); . Duh. So the 8s Cycles are never cut short, my climate timings will always be ~2 min and activity is written right away. The best case scenario that I was worried wasn't possible. Yay!

1 Like

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