Detach interrupt

Hello, I am using an Autonomo board (similar to Arduino Zero) and I am encountering some behaviour I am unable to understand.

I am trying to make an application that is extremely low power, so I am using the deep sleep function of the SAMD21 to make the microprocessor sleep.

To wakeup my board I am using a few interrupts (internal and external). These interrupts each set a flag that I check for in my main loop.

These flags make sure my program does something in my main loop, one of which is detach the interrupts.

Here is the code I am currently using:

#include <DHT.h>
#include <Sodaq_RN2483.h>
#include <RTCZero.h>

#define DeurPIN   0
#define DHTpin    4
#define PirPin    10
#define LedGroen  13

RTCZero rtc;

bool led = false;

bool deurGeopend    = false;
bool deurDetectie   = false;
bool bewegingPIR    = false;
bool pirDetectie    = false;

uint8_t PIRCount    = 0;


unsigned long msNieuwDeur = 0;
unsigned long msOudDeur   = 0;

void setup()
{
  //Definieer pin 0, 4 en 10 als input, LED pin als output
  pinMode(DeurPIN,  INPUT);
  pinMode(PirPin,   INPUT);
  pinMode(DHTpin,   INPUT);
  pinMode(LedGroen, OUTPUT);

  attachInterrupt(DeurPIN,  ISRdeur,      FALLING);   //activeer ISR wanneer de deur opent
  attachInterrupt(PirPin,   ISRbeweging,  RISING);    //activeer ISR wanneer er beweging wordt gedetecteert

  //activeer ISR eens per minuut
  rtc.begin();
  rtc.setAlarmSeconds(10);
  rtc.enableAlarm(RTCZero::MATCH_SS); // alarm eens per minuut
  rtc.attachInterrupt(ISR60min); // attach een interrupt aan het RTC alarm

  //Set sleep mode
  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;

  // Set the XOSC32K to run in standby
  SYSCTRL->XOSC32K.bit.RUNSTDBY = 1;

  // Configure EIC to use GCLK1 which uses XOSC32K
  // This has to be done after the first call to attachInterrupt()
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(GCM_EIC) |
                      GCLK_CLKCTRL_GEN_GCLK1 |
                      GCLK_CLKCTRL_CLKEN;

  //Start delay of 5s to allow for new upload after reset
  delay(5000);
}

/* ******************************
 *                              *
    Interrupts
 *                              *
 * ******************************/

void ISRdeur()
{
  msOudDeur     = millis();
  deurDetectie  = true;
  
  digitalWrite(13, HIGH);
  delayMicroseconds(100000);
  digitalWrite(13, LOW);
}

void ISRbeweging()
{
  pirDetectie = true;
  PIRCount ++;
  SerialUSB.println("PIRCount =" + PIRCount);
  digitalWrite(13, HIGH);
  delayMicroseconds(100000);
  digitalWrite(13, LOW);
}

void ISR60min()
{
  led = true;
  DHTmeting = true;
}


/* ******************************
 *                              *
    main programma
 *                              *
 * ******************************/

void loop()
{
  msNieuwDeur = millis();

  /* ******************************
      Deurdetectie
   * ******************************/
  if ((deurDetectie) and ((unsigned long)(msNieuwDeur - msOudDeur) >= 1000))
  {
    deurDetectie  = false;

    digitalWrite(13, HIGH);
    delayMicroseconds(2000000);
    digitalWrite(13, LOW);

    if (not digitalRead(DeurPIN))
    {
      detachInterrupt(DeurPIN);
      SerialUSB.println("Deur geopend");
      deurGeopend   = true;
    }
  }
  /* ******************************
      Bewegingdetectie
   * ******************************/
  if (PIRCount >= 5)
  {
    PIRCount      = 0;

    digitalWrite(13, HIGH);
    delayMicroseconds(2000000);
    digitalWrite(13, LOW);

    detachInterrupt(PirPin);
    SerialUSB.println("Beweging gedetecteerd");
    bewegingPIR   = true;
  }

  if (led == true)
  {
    led = false;
    attachInterrupt(DeurPIN,  ISRdeur,      FALLING);
    attachInterrupt(PirPin,   ISRbeweging,  RISING);
    //Blink the LED for 0,5s
    digitalWrite(13, HIGH);
    delayMicroseconds(500000);
    digitalWrite(13, LOW);
  }
    if (not deurDetectie)
    {
    //Disable USB
    USB->DEVICE.CTRLA.reg &= ~USB_CTRLA_ENABLE;
    //Enter sleep mode
    __WFI();
    //...Sleep
    //Enable USB
    USB->DEVICE.CTRLA.reg |= USB_CTRLA_ENABLE;
    }
}

Currently I am using a 60 second alarm to periodically wakeup my board and reattach my interrupts. The behaviour I am currently encountering is that even if the board executes the printline “Beweging gedetecteerd” (So I assume it also executed the detach interrupt) it will still print the line “PIRCount = [pircount]” whenever the other external interrupt is handled.

How is that possible?

  delayMicroseconds(100000);

First, delayMicroseconds() has an upper limit on the number of microseconds that ti can delay. It is not this high.

Second, DELAY DOES NOT BELONG IN ANY INTERRUPT SERVICE ROUTINE!

PaulS:

  delayMicroseconds(100000);

First, delayMicroseconds() has an upper limit on the number of microseconds that ti can delay. It is not this high.

Second, DELAY DOES NOT BELONG IN ANY INTERRUPT SERVICE ROUTINE!

the maximum value for delayMicroseconds() is 4294967295.

I know that using a delay in an ISR is bad practise. I will not use it in the final program, it is just there for debugging purposes.

I removed the delay from the ISR, just to make sure that wasn't the problem.

void setup()
{
  //Definieer pin 0, 4 en 10 als input, LED pin als output
  pinMode(DeurPIN,  INPUT);
  pinMode(PirPin,   INPUT);
  pinMode(DHTpin,   INPUT);
  pinMode(LedGroen, OUTPUT);

  attachInterrupt(DeurPIN,  ISRdeur,      FALLING);   //activeer ISR wanneer de deur opent
  attachInterrupt(PirPin,   ISRbeweging,  RISING);    //activeer ISR wanneer er beweging wordt gedetecteert

  //activeer ISR eens per minuut
  rtc.begin();
  rtc.setAlarmSeconds(10);
  rtc.enableAlarm(RTCZero::MATCH_SS); // alarm eens per minuut
  rtc.attachInterrupt(ISR60min); // attach een interrupt aan het RTC alarm

  //Set sleep mode
  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;

  // Set the XOSC32K to run in standby
  SYSCTRL->XOSC32K.bit.RUNSTDBY = 1;

  // Configure EIC to use GCLK1 which uses XOSC32K
  // This has to be done after the first call to attachInterrupt()
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(GCM_EIC) |
                      GCLK_CLKCTRL_GEN_GCLK1 |
                      GCLK_CLKCTRL_CLKEN;

  //Start delay of 5s to allow for new upload after reset
  delay(5000);
}

void ISRbeweging()
{
  pirDetectie = true;
  PIRCount ++;
  SerialUSB.println("PIRCount =" + PIRCount);
}

void ISR60min()
{
  led = true;
  DHTmeting = true;
}


/* ******************************
 *                              *
    Interrupts
 *                              *
 * ******************************/

void ISRdeur()
{
  msOudDeur     = millis();
  deurDetectie  = true;
}

void ISRbeweging()
{
  pirDetectie = true;
  PIRCount ++;
  SerialUSB.println("PIRCount =" + PIRCount);
}

void ISR60min()
{
  led = true;
  DHTmeting = true;
}


/* ******************************
 *                              *
    main programma
 *                              *
 * ******************************/

void loop()
{
  msNieuwDeur = millis();

  /* ******************************
      Deurdetectie
   * ******************************/
  if ((deurDetectie) and ((unsigned long)(msNieuwDeur - msOudDeur) >= 1000))
  {
    deurDetectie  = false;

    digitalWrite(13, HIGH);
    delayMicroseconds(2000000);
    digitalWrite(13, LOW);

    if (not digitalRead(DeurPIN))
    {
      detachInterrupt(DeurPIN);
      SerialUSB.println("Deur geopend");
      deurGeopend   = true;
    }
  }
  /* ******************************
      Bewegingdetectie
   * ******************************/
  if (PIRCount >= 5)
  {
    PIRCount      = 0;

    digitalWrite(13, HIGH);
    delayMicroseconds(2000000);
    digitalWrite(13, LOW);

    detachInterrupt(PirPin);
    SerialUSB.println("Beweging gedetecteerd");
    bewegingPIR   = true;
  }

  if (led == true)
  {
    led = false;
    attachInterrupt(DeurPIN,  ISRdeur,      FALLING);
    attachInterrupt(PirPin,   ISRbeweging,  RISING);
    //Blink the LED for 0,5s
    digitalWrite(13, HIGH);
    delayMicroseconds(500000);
    digitalWrite(13, LOW);
  }
    if (not deurDetectie)
    {
    //Disable USB
    USB->DEVICE.CTRLA.reg &= ~USB_CTRLA_ENABLE;
    //Enter sleep mode
    __WFI();
    //...Sleep
    //Enable USB
    USB->DEVICE.CTRLA.reg |= USB_CTRLA_ENABLE;
    }
}

However, I still notice the board responding to ISRBeweging eventhough it was supposed to be detached

the maximum value for delayMicroseconds() is 4294967295.

Are you sure of that ?

delayMicroseconds() takes an unsigned int as its parameter. Maybe some confusion with delay() which takes an unsigned long.

UKHeliBob:
Are you sure of that ?

delayMicroseconds() takes an unsigned int as its parameter. Maybe some confusion with delay() which takes an unsigned long.

Yes, I am aware it takes an unigned int as its parameter, whenI look up the max value for an unsigned int in the limits.h c library, it tells me the max value for an unsigned integer is 4294967295

However, I don't think it is really that important for the code I am using, seeing as in the final code I won't need the delays (I am not going to use the led)

UKHeliBob:
Are you sure of that ?

delayMicroseconds() takes an unsigned int as its parameter. Maybe some confusion with delay() which takes an unsigned long.

In general this sort of limit depends on which microcontroller.

Calling detachInterrupt doesn’t work the way you think. (Its kind of broken really).

Rework your code so you never call it and all will be well.

What happens when you detach is that the specific interrupt is masked out, but that doesn’t
stop the interrupt bit being set when the pin next changes (rises, falls, whatever it was last
programmed to detect).

When you reattach the state of the interrupt bit is not cleared. So you can get an immediate
interrupt.

If you want to become selectively deaf to a particular condition, the idiom I recommend is use
a flag to gate the body of the ISR completely:

volatile boolean enable_beweging = true ;


void ISRbeweging()
{
  if (!enable_beweging)
    return ;
  pirDetectie = true;
  PIRCount ++;
  SerialUSB.println("PIRCount =" + PIRCount);
}

BTW I have no idea if SerialUSB calls can work from an ISR - probably not.

MarkT:
Calling detachInterrupt doesn't work the way you think. (Its kind of broken really).

Rework your code so you never call it and all will be well.

What happens when you detach is that the specific interrupt is masked out, but that doesn't
stop the interrupt bit being set when the pin next changes (rises, falls, whatever it was last
programmed to detect).

When you reattach the state of the interrupt bit is not cleared. So you can get an immediate
interrupt.

If you want to become selectively deaf to a particular condition, the idiom I recommend is use
a flag to gate the body of the ISR completely:

volatile boolean enable_beweging = true ;

void ISRbeweging()
{
  if (!enable_beweging)
    return ;
  pirDetectie = true;
  PIRCount ++;
  SerialUSB.println("PIRCount =" + PIRCount);
}



BTW I have no idea if SerialUSB calls can work from an ISR - probably not.

Hmm but that does mean the CPU sort of responds to the interrupt? It just does not do anything because it immediatly returns?

The issue I have with this is the following: If for example, I want to check for mice with the PIR detector (keep in mind that I want this to be as Low power as possible) Once I detected there is a mouse inside of the cabinet, I don't need to know that again untill I decide to reattach the interrupt, because it will just cost (relatively) a lot of power for redundant information.

The way interrupt channels work is that there is an interrupt bit and a mask bit for each channel. The
highest prority interrupt bit that is not masked will always generate an interrupt unless global
interrupts are disabled. Simply masking a bit won’t stop the bit getting set, it will defer the interrupt
being called until that channel is unmasked.

You can read and clear the interrupt bits by hand, they are automatically cleared upon calling the
relevant ISR. detachInterrupt does not clear then, neither does attachInterrupt. A relevant change on the
pin for that channel will set the bit and it will only clear when the ISR is called, even if thats days/years later.

You options are to manage the interrupt bits explicitly yourself, or use a flag as I’ve indicated which is
the most portable way to do things (interrupts are different on different processor architectures).

With external pin interrupts its worse than I’ve indicated because first enabling the interrupt
can set the bit (I forget the circumstances) even if the pin doesn’t change, because the pin state
gets copied to an internal flipflop which was in a different state to the pin (it is like this since reset).
That flip-flop output then changes state, setting the interrupt bit.

So for various reasons its best to use an explicit flag in the ISR or handler to protect against things
happening when you are not ready for them.

MarkT:
The way interrupt channels work is that there is an interrupt bit and a mask bit for each channel. The
highest prority interrupt bit that is not masked will always generate an interrupt unless global
interrupts are disabled. Simply masking a bit won't stop the bit getting set, it will defer the interrupt
being called until that channel is unmasked.

You can read and clear the interrupt bits by hand, they are automatically cleared upon calling the
relevant ISR. detachInterrupt does not clear then, neither does attachInterrupt. A relevant change on the
pin for that channel will set the bit and it will only clear when the ISR is called, even if thats days/years later.

You options are to manage the interrupt bits explicitly yourself, or use a flag as I've indicated which is
the most portable way to do things (interrupts are different on different processor architectures).

With external pin interrupts its worse than I've indicated because first enabling the interrupt
can set the bit (I forget the circumstances) even if the pin doesn't change, because the pin state
gets copied to an internal flipflop which was in a different state to the pin (it is like this since reset).
That flip-flop output then changes state, setting the interrupt bit.

So for various reasons its best to use an explicit flag in the ISR or handler to protect against things
happening when you are not ready for them.

I see! Thanks for the explanation!

Gutanoth:
Yes, I am aware it takes an unigned int as its parameter, whenI look up the max value for an unsigned int in the limits.h c library, it tells me the max value for an unsigned integer is 4294967295

However, I don't think it is really that important for the code I am using, seeing as in the final code I won't need the delays (I am not going to use the led)

It has nothing to do with the upper limit of an unsigned int. Go read the documentation for documentation for delayMocroseconds:

Pay particular attention to the second paragraph on that page.