Get interrupt number from within ISR

Hi guys,

I would like to use interrupts on my Nano, so I would use pins D2 and D3. Since the actions are pretty similar (but not equal), I'd like to avoid writing the same (or a similar) function twice (one for each pin).
Is there a way how to know from within the ISR which pin / interrupt number was calling the ISR? With this I could specify which action to run.

Thanks and best regards!

Even if you can do this why bother ?

ISRs should be short and snappy. Write 2 ISRs, have them set a flag indicating which ISR was triggered and use the flag value in loop() to do whatever you need

This is the simple way

To learn. :smiley:

I already set a flag with the ISR and do the rest outside (I need to, because my sensors are objects), but the proper way would be to "connect" the ISR to a specific sensor class instance.
I already did it the way you suggested, but I like the clean way, especially if I would use more than two pins (e. g. on a Mega).

What else do the ISRs do apart from setting a flag ?

Please post an example of the code

i have a program that uses 2 separate encoders, an ISR associated with each. each ISR invoked an inline function with different parameters for each encoder


#include <Arduino.h>

#include "encoder.h"
#include "pins.h"

// -----------------------------------------------------------------------------
//  table containing action indexed by inputs BA and state

typedef enum { ___, For, Rev, Skp, ActSize } Act_t;

const char* actStr [] = { "___", "For", "Rev", "Skp", "ActSize" };

Act_t  encAct [ActSize][ActSize] = {
// state  00   01   10   11      inputs
       { ___, Rev, For, Skp },  // 00
       { For, ___, Skp, Rev },  // 01
       { Rev, Skp, ___, For },  // 10
       { Skp, For, Rev, ___ },  // 11
};

int encAst  = 0;
int encBst  = 0;

// ------------------------------------------------
// read brake
inline void readEncoder (
    byte  dt,
    byte  clk,
    int & brkPos,
    int & encSt )
{
    byte  val = digitalRead (dt) << 1 | digitalRead (clk);

    switch (encAct [val][encSt])  {
    case For:
        brkPos++;
        break;

    case Rev:
        brkPos--;
        break;

    default:
        break;
    }

    encSt = val;

 // digitalWrite (LED, ! digitalRead (LED));
}

// -------------------------------------
void IRAM_ATTR isrEncA (void)
{
    readEncoder (Enc_A_Dt, Enc_A_Clk, encApos, encAst);
}

// -------------------------------------
void IRAM_ATTR isrEncB (void)
{
    readEncoder (Enc_B_Dt, Enc_B_Clk, encBpos, encBst);
}

// -------------------------------------
void
encoderInit (void)
{
    // encoders
    pinMode (Enc_A_Clk, INPUT_PULLUP);
    pinMode (Enc_A_Dt,  INPUT_PULLUP);

    attachInterrupt (Enc_A_Clk, isrEncA, CHANGE);
    attachInterrupt (Enc_A_Dt,  isrEncA, CHANGE);

    pinMode (Enc_B_Clk, INPUT_PULLUP);
    pinMode (Enc_B_Dt,  INPUT_PULLUP);

    attachInterrupt (Enc_B_Clk, isrEncB, CHANGE);
    attachInterrupt (Enc_B_Dt,  isrEncB, CHANGE);
}

Yes, read the EIFR flag register. Depended on which interrupt was fired - INT0 or INT1 - the EIFR bits INTF0 / INTF1 will be set.

 if (EIFR & (1<<INTF0))    // INT0 was fired
{
...  INT0 code...
}
else if  (EIFR & (1<<INTF1))
{
 ...  INT1 code...
}

sorry, not tested

impossible. (there are exist methods but using it thisis incorrect logic)
because INT0 only for D2
INT1 only for D3
so when INT0 is calling its mean that INT1 is not fired (no changes on that pin)

you are right but

  1. TS wants only one code for both :slight_smile: I mean if we have if..else statement its look like pretty same as one func (int0) for if, other func (int1) for else . so no difference
  2. when I tested INTF flag I have noticed that some time it clears immediately when we came into ISR function but some time when we exiting from ISR. so for example when INF1 is triggered in some case while program came in point where we check "else if (INTF1)" at that time maybe INF1 will be 0

This is an example code because I'm just playing with the "real" code:

volatile bool interruptFlagD2 = false;
volatile bool interruptFlagD3 = false;

void isrD2() {
  interruptFlagD2 = true;
}

void isrD3() {
  interruptFlagD3 = true;
}



class sensor {
  private:
  uint8_t pin=99;
  bool inverted=false;
  bool analog=false;
  bool interruptDetected=false;
  String topic="";



  public:
  sensor(uint8_t setPin, bool setInverted=false, bool setAnalog=false) {
    pin = setPin;
  }

  int getValue() {
    if(analog) {
      return analogRead(pin);
    }
    else if(!analog && inverted) {
      return !digitalRead(pin);
    }
    else if(!analog && !inverted) {
      return digitalRead(pin);
    }
    else {
      // ERROR HANDLING
    }
  }

  void setInterruptDetected(bool setInterruptDect) {
    interruptDetected = setInterruptDect;
  }

  void setTopic(String newTopic) {
    topic = newTopic;
  }

  String getTopic() {
    return topic;
  }
};

sensor sensor1(2);
sensor sensor2(3);

void setup() {
  // put your setup code here, to run once:
  attachInterrupt(digitalPinToInterrupt(2), isrD2, CHANGE);
  attachInterrupt(digitalPinToInterrupt(3), isrD3, CHANGE);
}

void loop() {
  // put your main code here, to run repeatedly:
  if(interruptFlagD2) {
    // Handling all the actions resulting of the interrupt
    // - LED
    // - MQTT => sensor.getTopic(), sensor.getValue()
    interruptFlagD2 = false;
  } else if(interruptFlagD3) {
    // Handling all the actions resulting of the interrupt
    // - LED
    // - MQTT => sensor.getTopic(), sensor.getValue()
    interruptFlagD3 = false;
  }
}

With this point it seems that I can't reliably check which interrupt was fired.

What type of board are you using? If it's an ESP8266 / ESP32 - based board, then you can:

#include "FunctionalInterrupt.h"

That will allow you to pass a std::function<void, (void)> as the ISR. That can be a lambda expression that captures the 'this' pointer and calls the appropriate class member function.

If you're using AVR, ARM, etc, you can use the technique described here: https://forum.arduino.cc/t/interrupt-in-class-esp32/1039326/44?u=gfvalvo

its sound that you need pcint
there are one int for 8 pins. you get what you want

It is allowed to call functions from within an ISR.

So write two ISRs and have them call the a function that would otherwise be identical code within the ISRs.

A passed argument from the ISR lets the function know which was responsible for the calls it gets.

a7

True, but (as a warning to the OP) that function should not attempt to do any serial I/O, because interrupts will still be turned off, and it should be short and fast.

I think b707 was right if you are using edge-triggered interrupts. If I understand the datasheet correctly, at the beginning of the ISR you would read the EIFR register into a variable. Then you could test the value of the variable to see which pin triggered the interrupt.

But apparently EIFR is cleared automatically if the interrupts are level-triggered. But if using level triggers, you could just test the current value of D2/D3 to see which interrupted. I don't know into which case CHANGE falls.

This assumes an Atmega328P of course.

Does anyone know that? This would be the important case for me. Of course I could just test it, but this wouldn't be very reliable except of running a represantive quantity of tests...

Why it so important to you?
Your code in post#9 do not needs any additional flags to distinguish between interrupts.

any blocking serial I/O (HardwareSerial would work for example)

of course you are right that it's not a good practice and interrupts should be kept as short as possible

Yes, it does, because the MQTT topics are different. The topic to send the value to depends on the sensor.

@gfvalvo:
I'm using an Arduino Nano (see initial post :slight_smile: ), so this seems not usable for my case.

@J-M-L:
I'm not planning to do any serial I/O within the ISR, just setting a variable with the pin number which triggered the interrupt. I just wrote down the new code and will try it. After that I'll give a feedback.

then just write separate ISRs...