Using interrupts in a world of classes and multiple pins

I am building a class library to handle user interactions with buttons and other devices. The library needs to support multiple pins (one per button). I want to use interrupts. I see that the interrupt service handler (ISR) does not have any parameters, and I also see that I cannot use a class member as the ISR - it must be a static method. If I register that same static ISR on multiple pins, I really don't have a way to know which class instance of my control class is relevant for that interrupt. The only solution I have found is to register all of my classes in a global list and check each one of them and read their pin to see if their pin is the one triggering it and hopefully from that evidence being able to identify the control class instance that is relevant. As you can imagine, that only works when you have a single pin pressed.

Is there a pattern we can use to have a class library that can be used with multiple instances and a pin is registered for each instance such that I can reliably handle the event and know which class is involved? The only other thing I can think of is to have a custom class for each pin that is a wrapper to another function in the library that is the ISR, but I really don't want to force the primary progam to have to do that. I might be able to swallow that pill if I used macros to create the interrupt, but that still feels dirty... Are there any other ideas? I really wish there was a way to pass in a parameter on the attachInterrupt() call so that when my method gets dispatched I can get that value and know the context and act accordingly.

Thanks!

don't use an interrupt to read a (bouncing) button. Imho it makes not so much sense to head for speed using interrupts and then introduce measures against bouncing. Just poll the button state like in the IDE example State Change Detection.

An example of a simple button class used in one of my last posts:

So the questions is more: what makes you think you need an interrupt to read a button? Can you show a minimum example with two buttons?

Although it's generally a bad idea to use interrupts to read buttons, what you're after is possible:

1 Like

I want to use interrupts so that I can reliably capture the button clicks and not miss them. The challenge is in many libraries being slow and the main loop being busy doing something and miss the event. Take for instance a simple TFT library and clearing the screen - this can take 150ms+. I have built a library that uses a gpio expander and I am using interrupts. The accuracy of the library for capturing the button clicks is perfect, and it is not subject to slow loops. What I am having an issue of however is in supporting multiple gpio expanders, which surfaces as multiple instances of my class and multiple pins. From there I need to be able to identify which expander is raising the interrupt pin on the MCU so I know which expander to go to for the interrupt details. shan-mcarthur/GpioExpanderLib: Library to handle buttons with interrupts and FreeRtos queues using background tasks (github.com)

If that is a problem for, don't use the library, but write your own code so it can clear the screen differently and include the button tests in the code.

This doesn't answer my question at all. I am asking to see if there is a better pattern for when I choose to use interrupts with a class library and want to support multiple instances of my class. That toggle library doesn't even have an interrupt.

Paul, that approach doesn't scale well. I don't want to have to rewrite every library I use (think wireless, bluetooth, TFT displays, etc, just to integrate my button response in a tight loop. Professional approaches to this type of problem is to keep your main loop tight and do all other work in background threads and use hardware interrupts. That is the approach I want to take and I just wanted to see if there is a better pattern for how I can support multiple instances of my class, keeping them separate from each other without having to keep my own global list of the class instances to loop through and see which one of them is responsible for the interrupt.

Correct, so your loop's not that tight if it misses button presses. The best human response time is 120ms (200ms avg). You must have blocking code to deal with or interrupts or multitasking is the only solution I know of.

Geez, the MCP23017 has 2 interrupt outputs, one for each port which could represent the status of 16 push button inputs (total). Why not use them to signify that a data byte read needs to be processed, hence my suggestion for the Toggle library could be useful for processing byte data and provide clean, glitch-free results .
(there I go again talking about code I can't see)

The solution for doing that was provided to you back in Post #3.

Alternately, if you're coding for an ESP8266, ESP32, or STM32 processor, you can pass a std::function to attachInterrupt(). That type can wrap a class member function.

1 Like

I didn't realize Pieter's response was a link to an entire post. Now that I read it, I realize it is using the same pattern I am projecting - to use a global index of instances and dynamic ISRs to be hard coded with the appropriate index and pass on the right context to the function that is going to service the interrupt. This is akin to what I was saying to use a global index and macros to generate the ISR routine.

In reality, I really wish the hardware interrupt methods of the Arduino framework would be void IsrFunction (void *param) and the param being the value that is passed along with an attachInterrupt (pin, function, mode, param) call that registers it. That would give us much better patterns to build upon.

1 Like

Not necessarily global. See my code Here where I've adopted the technique to be complete within the class.

Unfortunately, wishing for it won't make it so. But, an overload of attachInterrupt() that takes a 'void *' parameter is available on ESP8266 / ESP32 boards, perhaps others. It's just not standard.

Except it's a static variable in a class, not a global, and no macros are required.

How would the Arduino developers implement such a feature? You need a global/static array of pointers to the arguments that you index into based on the interrupt, there's no way around that. If you look at the source code for the ESP8266/ESP32 attachInterrupt with parameter implementation, you'll see that that's exactly what they're doing as well:

2 Likes

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