attachInterrupt using array of function pointer?

First some notes:

  • Use the volatile qualifier for variables that are shared between interrupt service routines and the main program.
  • Using interrupts for switches is probably a poor design choice.
  • On ESP32, you can use FunctionalInterrupt with a lambda functions.

That being said, you could solve this using compile-time recursion to generate an arbitrary number of functions:

struct FSwitch {
    FSwitch(uint8_t pin) : pin(pin) {}
    const uint8_t pin;
    volatile bool changed = false;
    volatile bool value = false;
};

FSwitch fswitches[] {27, 33, 15, 32, 14};
volatile bool sFlag = false;

void IRAM_ATTR run_isr(unsigned interrupt) {
    auto &fs = fswitches[interrupt];
    fs.changed = true;
    fs.value = digitalRead(fs.pin);
    sFlag = true;
}

using isr_func_ptr = void(*)(void);

template <class T, size_t N>
constexpr size_t len(const T(&)[N]) { return N; }

template <unsigned NumISR>
static constexpr isr_func_ptr get_isr_N(unsigned interrupt) {
    return interrupt == NumISR - 1
               ? []() IRAM_ATTR { run_isr(NumISR - 1); }
               : get_isr_N<NumISR - 1>(interrupt); // Compile-time tail recursion
}
template <>
constexpr isr_func_ptr get_isr_N<0>(unsigned) {
    return nullptr; // Base case
}

isr_func_ptr get_isr(unsigned interrupt) {
    return get_isr_N<len(fswitches)>(interrupt);
}

void setup() {
    Serial.begin(115200);
    for (auto &fs : fswitches) {
        pinMode(fs.pin, INPUT); 
        fs.value = digitalRead(fs.pin); // initial state of each switch
    }
    for (unsigned i = 0; i < len(fswitches); ++i)
        attachInterrupt(digitalPinToInterrupt(fswitches[i].pin), get_isr(i), CHANGE);
}

This generates a different function for each interrupt automatically:

If you compile these functions,

template <unsigned NumISR>
static constexpr isr_func_ptr get_isr_N(unsigned interrupt) {
    return interrupt == NumISR - 1
               ? []() IRAM_ATTR { run_isr(NumISR - 1); }
               : get_isr_N<NumISR - 1>(interrupt); // Compile-time tail recursion
}
template <>
constexpr isr_func_ptr get_isr_N<0>(unsigned) {
    return nullptr; // Base case
}

/* constexpr */ isr_func_ptr get_isr(unsigned interrupt) {
    return get_isr_N<len(fswitches)>(interrupt);
}

the compiler generates the following anonymous functions for you, and the get_isr function returns a function pointer to them.

get_isr_N<5u>(unsigned int)::{lambda()#1}::_FUN():
        entry   sp, 32
        movi.n  a10, 4
        call8   _Z7run_isrj
        retw.n
get_isr_N<4u>(unsigned int)::{lambda()#1}::_FUN():
        entry   sp, 32
        movi.n  a10, 3
        call8   _Z7run_isrj
        retw.n
get_isr_N<3u>(unsigned int)::{lambda()#1}::_FUN():
        entry   sp, 32
        movi.n  a10, 2
        call8   _Z7run_isrj
        retw.n
get_isr_N<2u>(unsigned int)::{lambda()#1}::_FUN():
        entry   sp, 32
        movi.n  a10, 1
        call8   _Z7run_isrj
        retw.n
get_isr_N<1u>(unsigned int)::{lambda()#1}::_FUN():
        entry   sp, 32
        movi.n  a10, 0
        call8   _Z7run_isrj
        retw.n
get_isr(unsigned int):
        entry   sp, 32
        l32r    a8, .LC3
        beqi    a2, 4, .L6
        l32r    a8, .LC4
        beqi    a2, 3, .L6
        l32r    a8, .LC1
        beqi    a2, 2, .L6
        l32r    a8, .LC0
        beqi    a2, 1, .L6
        l32r    a8, .LC2
        movi.n  a9, 0
        movnez  a8, a9, a2
.L6:
        mov.n   a2, a8
        retw.n
2 Likes