external interrupt and task handling

An external interrupt (from a DS3231 alarm) on pin 2 (int0) of an UNO needs to be handled if a certain program condition is met.
For example, regular tasks in the loop need to continuously be performed (i.e. line 41: LoRa listening), and if a specific variable (interVal) has a specific value (>30) then an additional task (call it Task for name’s sake) needs to be performed once only (line 67). This Task needs then to be performed at intervals equal to the value in interVal, only as long as interVal is greater than 30.

Edit: this interval may be seconds or minutes.

I can’t however find out where to place the Task code so that the above is executed correctly. Can anyone please enlighten me? I checked Nick Gammon’s “interrupt” pages, and elsewhere, but most of those handle sleep modi which are not applicable here.

#include <RTClibExtended.h>
#include <Wire.h>
#define DS3231_I2C_ADDRESS 0x68
#define wakePin 2    //use interrupt 0 INT0 (pin 2) and run function wakeUp when pin 2 gets LOW
#define ledPin 13   //use arduino on-board led for indicating sleep or wakeup status
RTC_DS3231 RTC;      //we are using the DS3231 RTC
// long unsigned int currentMillis;
// long unsigned int startMillis;
uint32_t currentMillis;
uint32_t startMillis;
uint8_t interVal = 31;
uint32_t interValMillis = interVal * 1000;
uint8_t offSet; // timing shift per transmitter: A=0, B=5, C=10, D=15
uint8_t startTime;
bool waitForRTC = true;
bool ledStatus = true;

//-------------------------------------------------
void isrTask() {       // here the interrupt is handled after trigger
}
//------------------------------------------------------------

void setup() {
  Serial.begin(9600); // Starting Serial Terminal
  pinMode(ledPin, OUTPUT);
  RTC.begin();
  RTC.armAlarm(1, false);
  RTC.clearAlarm(1);
  RTC.alarmInterrupt(1, false);
  DateTime now = RTC.now();
  startTime = ((now.second() + interVal - offSet) / interVal * interVal + offSet)  % 60;
  // startTime = ((now.minute() + interVal - offSet) / interVal * interVal + offSet)  % 60;
  RTC.setAlarm(ALM1_MATCH_SECONDS, startTime, 0, 0, 0);  //test setup: wake every "startTime" seconds
  // RTC.setAlarm(ALM1_MATCH_MINUTES, 0, startTime, 0, 0);
  RTC.alarmInterrupt(1, true); // assert the INT pin when Alarm1 occurs, set alarm1, if disable write false
  digitalWrite(ledPin, LOW);
}

void loop() {

  // Listen to LoRa.

  if (interVal > 30) // perform task, only if interVal > 30
  {
    if (waitForRTC == true)
    {
      attachInterrupt(0, isrTask, LOW);
      //use interrupt 0 (pin PD2) and run function wakeUp when pin 2 gets LOW
      ledStatus = false;
      digitalWrite(ledPin, LOW);
      detachInterrupt(0); //execution resumes from here after interrupt trigger

      RTC.armAlarm(1, false);
      RTC.clearAlarm(1);
      RTC.alarmInterrupt(1, false);
      waitForRTC = false; // value 0 = exit if when interrupt has been triggered
    }
    if (ledStatus == false) {
      ledStatus = true;
      digitalWrite(ledPin, HIGH); //built-in LED on pin13
    }
    Serial.println("interVal actief via RTC");
    DateTime now = RTC.now();
    // RTC.setAlarm(ALM1_MATCH_MINUTES, 0, ((now.minute() + interVal) % 60), 0, 0);
    RTC.setAlarm(ALM1_MATCH_SECONDS, ((now.second() + interVal) % 60), 0, 0, 0);

    // perform task once, only if InterVal > 30 and at interval periods equal to interVal

    waitForRTC = true;
    RTC.alarmInterrupt(1, true);
  }

  else
  {
    Serial.print(currentMillis);
    Serial.println(" = test currentMillis");
    Serial.print(startMillis);
    Serial.println(" = test startMillis");
    delay(1000);
    currentMillis = millis();

    if (currentMillis - startMillis >= interValMillis)
    {
      startMillis = currentMillis;
      Serial.println();
      Serial.print(currentMillis);
      Serial.println(" = currentMillis");
      Serial.print(currentMillis % 60000);
      Serial.println(" = currentMillis % 60000");
      Serial.print(interValMillis);
      Serial.println(" = interValMillis");
      Serial.println();
      digitalWrite(ledPin, HIGH);
      delay(500);
      digitalWrite(ledPin, LOW);
    }
  }
}

use attachInterrupt() to invoke your "isrTask()" on the appropriate edge (RISING/FALLING) when your interrupt pin (wakePin) changes state.

you'll need to flesh out isrTask()

but if your code has direct access to the RTC, is there a way to simply poll the RTC for when it's alarm expires? an external interrupt is normally used to recognize an event from a ... "external" device

gcjr:
use attachInterrupt() to invoke your "isrTask()" on the appropriate edge (RISING/FALLING) when your interrupt pin (wakePin) changes state.

you'll need to flesh out isrTask()

but if your code has direct access to the RTC, is there a way to simply poll the RTC for when it's alarm expires? an external interrupt is normally used to recognize an event from a ... "external" device

Hi gcjr,

the issue with code in a ISR is that it is very restricted to what can or what is allowed to be performed there: no use of delay() or millis(), and only short and fast tasks are allowed.
So I am hoping to use the interrupt on INT0 (pin 2) to detect the alarm1 from the DS3231, and then only if interVal > 31 (for example).

Meanwhile other code must still be executed.

And yes, my code may be polling the RTC, but that defeats the purpose of using an interrupt, wouldn't it?

brice3010:
Meanwhile other code must still be executed.

And yes, my code may be polling the RTC, but that defeats the purpose of using an interrupt, wouldn't it?

there's no reason loop() can't conditionally or unconditionally perform multiple things. there can be multiple ifs, not just if/else

it sounds like either you just want to use an interrupt or feel there's no way to conditionally execute some code without it.

if necessary, the ISR can simply set a flag which is polled in loop() which conditional executes some code when the flag is set and presumably resets the flag.

it's not clear what your code is attempting to do. it tests for interval > 30, but interval is initialized to 31 and it's value isn't changed. i understand this is a work in progress did you intend the ISR to increment interval?

why is your code that is conditional on currentMillis() in the else condition. wouldn't you want it to be invoked whenever interValMillis is exceeded?

the issue with code in a ISR is that it is very restricted to what can or what is allowed to be performed there: no use of delay() or millis(),

Not strictly true as it is a common requirement to save the value of millis() or micros() in an ISR to save as a timestamp, and both work OK in an ISR for that purpose. Indeed, micros() will even update whilst in an ISR but whether or not that is a good thing is another matter

gcjr:
there’s no reason loop() can’t conditionally or unconditionally perform multiple things. there can be multiple ifs, not just if/else

it sounds like either you just want to use an interrupt or feel there’s no way to conditionally execute some code without it.

if necessary, the ISR can simply set a flag which is polled in loop() which conditional executes some code when the flag is set and presumably resets the flag.

it’s not clear what your code is attempting to do. it tests for interval > 30, but interval is initialized to 31 and it’s value isn’t changed. i understand this is a work in progress did you intend the ISR to increment interval?

why is your code that is conditional on currentMillis() in the else condition. wouldn’t you want it to be invoked whenever interValMillis is exceeded?

Good idea’s thank you.

  1. My intention is to use the DS3231 interrupt only when interVal > 30. When an interrupt on INT0 is perceived (D2 is pulled LOW by the RTC) then a specific task named TASK (named for the sake of it) needs to be executed once. After that: wait for another interrupt IF interVal still is >30.
  2. So your second point: indeed the ISR can be used to set a flag and execute TASK accordingly in the main loop. Very good idea, I had not thougth of this.
  3. The value of interVal is changed elsewhere in the code (future implementation).
  4. The “else” statement is to be executed if interVal =< 30; however that is an ad hoc usage and I might as well use the interrupt routine for that too. So please bear with me on that one for the time being.

UKHeliBob:
Not strictly true as it is a common requirement to save the value of millis() or micros() in an ISR to save as a timestamp, and both work OK in an ISR for that purpose. Indeed, micros() will even update whilst in an ISR but whether or not that is a good thing is another matter

Thank you for that! However, too much code needs to be executed after an interrupt for it to be safely implemented in an isr. So I would preferably solve this without it being in an isr.

brice3010:

  1. My intention is to use the DS3231 interrupt only when interVal > 30. When an interrupt on INT0 is perceived (D2 is pulled LOW by the RTC) then a specific task named TASK (named for the sake of it) needs to be executed once. After that: wait for another interrupt IF interVal still is >30.
  2. So your second point: indeed the ISR can be used to set a flag and execute TASK accordingly in the main loop. Very good idea, I had not thougth of this.
  3. The value of interVal is changed elsewhere in the code (future implementation).
  4. The “else” statement is to be executed if interVal =< 30; however that is an ad hoc usage and I might as well use the interrupt routine for that too. So please bear with me on that one for the time being.

so there are a number of conditional and unconditional tasks that can be performed within loop(). these include updating interVal which may be one of the conditions to perform any of the tasks. one or more of these tasks can be performed in each iteration of loop() and if not the current iteration, more than likely a subsequent iteration.

but again, using an interrupt may be unnecessary if there’s a bit in the RTC than can simply be polled in loop()

even an I/O pin can be polled within loop(). an interrupt is most often required when something needs to be performed very precisely at the time the interrupt occurs.

gcjr:
(…)
even an I/O pin can be polled within loop(). an interrupt is most often required when something needs to be performed very precisely at the time the interrupt occurs.

If possible I prefer an interrupt for this task since the DS3231 can be programmed to provide an interrupt when a programmable alarm occurs.

I included your interesting suggestion for a flag to be set in the isr: see below.

However, for some reason the interrupt is executed only once (with the valueIntrpt value = 20), then nothing.
Why?

#include <RTClibExtended.h>
#include <Wire.h>
#define DS3231_I2C_ADDRESS 0x68
#define wakePin 2    //use interrupt 0 INT0 (pin 2) and run function wakeUp when pin 2 gets LOW
#define ledPin 13   //use arduino on-board led for indicating sleep or wakeup status
#define led8 8
RTC_DS3231 RTC;      //we are using the DS3231 RTC
// long unsigned int currentMillis;
// long unsigned int startMillis;
uint32_t currentMillis;
uint32_t startMillis;
uint8_t interVal = 31;
uint32_t interValMillis = interVal * 1000;
uint8_t offSet; // timing shift per transmitter: A=0, B=5, C=10, D=15
uint8_t startTime;
uint32_t valueIntrpt;
bool waitForRTC = true;
bool ledStatus = true;
bool interruptFlag = false;

//-------------------------------------------------
void isrTask() {
  // here the interrupt is handled after trigger
  interruptFlag = true;
}
//------------------------------------------------------------

void setup() {
  Serial.begin(9600); // Starting Serial Terminal
  pinMode(ledPin, OUTPUT);
  pinMode(led8, OUTPUT);
  RTC.begin();
  RTC.armAlarm(1, false);
  RTC.clearAlarm(1);
  RTC.alarmInterrupt(1, false);
  RTC.writeSqwPinMode(DS3231_OFF); // disable square wave and enable interrupt output
  DateTime now = RTC.now();
  startTime = ((now.second() + interVal - offSet) / interVal * interVal + offSet)  % 60;
  // startTime = ((now.minute() + interVal - offSet) / interVal * interVal + offSet)  % 60;
  RTC.setAlarm(ALM1_MATCH_SECONDS, startTime, 0, 0, 0);  //test setup: wake every "startTime" seconds
  // RTC.setAlarm(ALM1_MATCH_MINUTES, 0, startTime, 0, 0);
  RTC.alarmInterrupt(1, true); // assert the INT pin when Alarm1 occurs, set alarm1, if disable write false
  digitalWrite(ledPin, LOW);
}

void testLED(int value) {
  digitalWrite(led8, HIGH);
  delay(value);
  digitalWrite(led8, LOW);
  Serial.println("interVal actief via ISR");
}

void loop() {

  // Listen to LoRa.

  if (interVal > 30) // perform task, only if interVal > 30
  {
    attachInterrupt(0, isrTask, LOW);

    if (interruptFlag == true)
    {
      //use interrupt 0 (pin PD2) and run function wakeUp when pin 2 gets LOW
      digitalWrite(ledPin, HIGH);
      detachInterrupt(0); //execution resumes from here after interrupt trigger
      testLED(1000);
      RTC.armAlarm(1, false); // clear any pending alarms
      RTC.clearAlarm(1);
      RTC.alarmInterrupt(1, false);
      DateTime now = RTC.now();
      // RTC.setAlarm(ALM1_MATCH_MINUTES, 0, ((now.minute() + interVal) % 60), 0, 0);
      RTC.setAlarm(ALM1_MATCH_SECONDS, ((now.second() + interVal) % 60), 0, 0, 0);
      valueIntrpt = (now.second() + interVal) % 60;
      Serial.println("interVal perform task once");
      Serial.print(valueIntrpt);
      Serial.println(" = modulo value interValsecond");
      // perform task once, only if InterVal > 30 and at interval periods equal to interVal
      interruptFlag = false; // value 0 = exit if when interrupt has been triggered
      delay(1500);
      digitalWrite(ledPin, LOW); //built-in LED on pin13
    }
    RTC.alarmInterrupt(1, true);
  }
  else
  {
    currentMillis = millis();
    if (currentMillis - startMillis >= interValMillis)
    {
      startMillis = currentMillis;
      testLED(500);
    }
  }
}

good effort. not sure ... but the code is awkward

attachInterrupt () should just be invoked once, in setup(). there's no reason to call detachInterrupt in your application. noInterrupts() can be called to temporarily disable interrupts, but isn't necessary either.

the interrupt should be configured for FALLING unless you want it to be repeatedly called while LOW. don't you want just one interrupt when it goes low?

isn't the interrupt pin 2. looks like you've configure pin 0, the RX pin

in general, some feature of a language, processor or hardware is best explored in isolation, not as part of a larger application. as my Bell Labs manages would say, if you're designing a levitating car, you work on levitation first before worrying about the car.

DS3231 can be programmed to provide an interrupt when a programmable alarm occurs.

Although it is called an interrupt, what the DS3231 actually does is to change the state of a pin when an alarm occurs, assuming that you have got one of its control registers set correctly, and you can use this change of state to trigger an interrupt, but you could poll the pin instead and not use an interrupt

gcjr:
(…)
attachInterrupt () should just be invoked once, in setup(). there’s no reason to call detachInterrupt in your application. noInterrupts() can be called to temporarily disable interrupts, but isn’t necessary either.

the interrupt should be configured for FALLING unless you want it to be repeatedly called while LOW. don’t you want just one interrupt when it goes low?

isn’t the interrupt pin 2. looks like you’ve configure pin 0, the RX pin

in general, some feature of a language, processor or hardware is best explored in isolation, not as part of a larger application. as my Bell Labs manages would say, if you’re designing a levitating car, you work on levitation first before worrying about the car.

AttachInterrupt, right that should be in setup(). And no detachInterrupt either. And “Falling”: totally right!!
However, in attachInterrupt, the 0 refers to INT0 which is linked to D2 on an UNO.
But when done, the interrupt occurs every 2 to 3 seconds, something still wrong.

#include <RTClibExtended.h>
#include <Wire.h>
#define DS3231_I2C_ADDRESS 0x68
#define wakePin 2    //use interrupt 0 INT0 (pin 2) and run function wakeUp when pin 2 gets LOW
#define ledPin 13   //use arduino on-board led for indicating sleep or wakeup status
#define led8 8
RTC_DS3231 RTC;      //we are using the DS3231 RTC
// long unsigned int currentMillis;
// long unsigned int startMillis;
uint32_t currentMillis;
uint32_t startMillis;
uint8_t interVal = 31;
uint32_t interValMillis = interVal * 1000;
uint8_t offSet; // timing shift per transmitter: A=0, B=5, C=10, D=15
uint8_t startTime;
uint32_t valueIntrpt;
bool waitForRTC = true;
bool ledStatus = true;
bool interruptFlag = false;

//-------------------------------------------------
void isrTask() {
  // here the interrupt is handled after trigger
  interruptFlag = true;
}
//------------------------------------------------------------

void setup() {
  Serial.begin(9600); // Starting Serial Terminal
  pinMode(ledPin, OUTPUT);
  pinMode(led8, OUTPUT);
  RTC.begin();
  RTC.armAlarm(1, false);
  RTC.clearAlarm(1);
  RTC.alarmInterrupt(1, false);
  RTC.writeSqwPinMode(DS3231_OFF); // disable square wave and enable interrupt output
  DateTime now = RTC.now();
  startTime = ((now.second() + interVal - offSet) / interVal * interVal + offSet)  % 60;
  // startTime = ((now.minute() + interVal - offSet) / interVal * interVal + offSet)  % 60;
  RTC.setAlarm(ALM1_MATCH_SECONDS, startTime, 0, 0, 0);  //test setup: wake every "startTime" seconds
  // RTC.setAlarm(ALM1_MATCH_MINUTES, 0, startTime, 0, 0);
  RTC.alarmInterrupt(1, true); // assert the INT pin when Alarm1 occurs, set alarm1, if disable write false
  digitalWrite(ledPin, LOW);
  attachInterrupt(0, isrTask, FALLING);
}

void testLED(int value) {
  digitalWrite(led8, HIGH);
  delay(value);
  digitalWrite(led8, LOW);
  Serial.println("interVal actief via ISR");
}

void loop() {

  // Listen to LoRa.

  if (interVal > 30) // perform task, only if interVal > 30
  {

    if (interruptFlag == true)
    {
      //use interrupt 0 (pin PD2) and run function wakeUp when pin 2 gets LOW
      digitalWrite(ledPin, HIGH);
      // detachInterrupt(0); //execution resumes from here after interrupt trigger
      testLED(1000);
      RTC.armAlarm(1, false); // clear any pending alarms
      RTC.clearAlarm(1);
      RTC.alarmInterrupt(1, false);
      DateTime now = RTC.now();
      // RTC.setAlarm(ALM1_MATCH_MINUTES, 0, ((now.minute() + interVal) % 60), 0, 0);
      RTC.setAlarm(ALM1_MATCH_SECONDS, ((now.second() + interVal) % 60), 0, 0, 0);
      valueIntrpt = (now.second() + interVal) % 60;
      Serial.println("interVal perform task once");
      Serial.print(valueIntrpt);
      Serial.println(" = modulo value interValsecond");
      // perform task once, only if InterVal > 30 and at interval periods equal to interVal
      interruptFlag = false; // value 0 = exit if when interrupt has been triggered
      delay(1500);
      digitalWrite(ledPin, LOW); //built-in LED on pin13
    }
    RTC.alarmInterrupt(1, true);
  }
  else
  {
    currentMillis = millis();
    if (currentMillis - startMillis >= interValMillis)
    {
      startMillis = currentMillis;
      testLED(500);
    }
  }
}

Top comment from your Bell Labs manager, I will remeber that one! :slight_smile:

UKHeliBob:
Although it is called an interrupt, what the DS3231 actually does is to change the state of a pin when an alarm occurs, assuming that you have got one of its control registers set correctly, and you can use this change of state to trigger an interrupt, but you could poll the pin instead and not use an interrupt

Does this mean "state change detection"? Probably indeed simpler, I will try that too.

Does this mean "state change detection"?

Yes, but probably easier to understand and test than an ISR

gcjr:
good effort. not sure … but the code is awkward

attachInterrupt () should just be invoked once, in setup(). there’s no reason to call detachInterrupt in your application. noInterrupts() can be called to temporarily disable interrupts, but isn’t necessary either.

the interrupt should be configured for FALLING unless you want it to be repeatedly called while LOW. don’t you want just one interrupt when it goes low?

isn’t the interrupt pin 2. looks like you’ve configure pin 0, the RX pin

in general, some feature of a language, processor or hardware is best explored in isolation, not as part of a larger application. as my Bell Labs manages would say, if you’re designing a levitating car, you work on levitation first before worrying about the car.

Yesterday I discovered why my code, after your and UKHeliBob’s interventions still did not work: I had forgotten to connect the DS3231 interrupt output to the AVR interrupt input (D2 on the Uno). …“the sound of headbanging”…

Thank you for your help!

Below is the latest, now working, version:

#include <RTClibExtended.h>
#include <Wire.h>
#define DS3231_I2C_ADDRESS 0x68
#define wakePin 2    //use interrupt 0 INT0 (pin 2) and run function wakeUp when pin 2 gets LOW
#define ledPin 13   //use arduino on-board led for indicating sleep or wakeup status
#define led8 8
RTC_DS3231 RTC;      //we are using the DS3231 RTC
// long unsigned int currentMillis;
// long unsigned int startMillis;
uint32_t currentMillis;
uint32_t startMillis;
uint8_t interVal = 10;
uint32_t interValMillis = interVal * 1000;
uint8_t offSet; // timing shift per transmitter: A=0, B=5, C=10, D=15
uint8_t startTime;
uint32_t valueIntrpt;
bool waitForRTC = true;
bool ledStatus = true;
bool interruptFlag = false;

//-------------------------------------------------
void isrTask() {
  // here the interrupt is handled after trigger
  interruptFlag = true;
}
//------------------------------------------------------------

void setup() {
  Serial.begin(9600); // Starting Serial Terminal
  pinMode(ledPin, OUTPUT);
  pinMode(led8, OUTPUT);
  RTC.begin();
  RTC.armAlarm(1, false);
  RTC.clearAlarm(1);
  RTC.alarmInterrupt(1, false);
  RTC.writeSqwPinMode(DS3231_OFF); // disable square wave and enable interrupt output
  DateTime now = RTC.now();
  startTime = ((now.second() + interVal - offSet) / interVal * interVal + offSet)  % 60;
  // startTime = ((now.minute() + interVal - offSet) / interVal * interVal + offSet)  % 60;
  RTC.setAlarm(ALM1_MATCH_SECONDS, startTime, 0, 0, 0);  //test setup: wake every "startTime" seconds
  // RTC.setAlarm(ALM1_MATCH_MINUTES, 0, startTime, 0, 0);
  RTC.alarmInterrupt(1, true); // assert the INT pin when Alarm1 occurs, set alarm1, if disable write false
  digitalWrite(ledPin, LOW);
  attachInterrupt(0, isrTask, FALLING);
}

void testLED(int value) {
  digitalWrite(led8, HIGH);
  delay(value);
  digitalWrite(led8, LOW);
  Serial.println("interVal actief via ISR");
}

void loop() {

  // Listen to LoRa.


  if (interruptFlag == true) // uitvoeren zodra interval verstreken is
  {
    //use interrupt 0 (pin PD2) and run function wakeUp when pin 2 gets LOW
    digitalWrite(ledPin, HIGH);
    testLED(300);
    RTC.armAlarm(1, false); // clear any pending alarms
    RTC.clearAlarm(1);
    RTC.alarmInterrupt(1, false);
    DateTime now = RTC.now();
    valueIntrpt = (now.second() + interVal) % 60;
    // RTC.setAlarm(ALM1_MATCH_MINUTES, 0, ((now.minute() + interVal) % 60), 0, 0);
    RTC.setAlarm(ALM1_MATCH_SECONDS, (valueIntrpt % 60), 0, 0, 0);
    Serial.println("interVal perform task once");
    Serial.print(valueIntrpt);
    Serial.println(" = modulo value interValsecond");
        
    interruptFlag = false; // value 0 = exit if when interrupt has been triggered
    delay(100);
    digitalWrite(ledPin, LOW); //built-in LED on pin13
  }
  RTC.alarmInterrupt(1, true);
}

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