EmbeddedRelated.com
Forums

[ESP32] [ESPIDF] Simoultaneous interrupt handling

Started by vinibreda 1 year ago16 replieslatest reply 1 year ago518 views

I'm working with multiple (16) buttons as the input of the system and it is 100% dependant on them to work. The problem is that they can be pressed at the same time.

If all GPIOs are configured to edge based interrupts, both rising and falling edge, and more than one button is pressed at the exact same time, is there a chance that one of the interrupts might get lost?

I'm using an older ESP32-WROOM-32 for development and some of the functionality that I'd love to have is the dedicated gpio grouping that ESP32S2 has, but unfortunatelly it's not available for me.

Any help is welcomed!

Edit: Sorry for the delayed response. We had an extended holiday here in Brazil and my account was only logged into my work computer.

Multiple button pressed at once need to be detected, even if all of them are pressed at the same time. All of them have the same priority, meaning they all need to be accounted for in the end.

As for level based interrupts I tried but, as the button can be held, it was triggering many interrupts and causing the board to reset. It can be due to some implementation "feature", but it didn't work so well for me.

My interrupt structure isn't quite as big. The gpio interrupt calls an ISR that handles debounces and button reads. Debounce is handled just like one would in Arduino using milis().

I'm using interrupts to just update a state parameter inside the button object. When BUTTON_GetState() is called, in interrupt mode, it retrieves the last state updated by the interrupts.

In poll mode the BUTTON_GetState() calls the ISR from within itself and updates the state the same way the ISR would, but much more frequently.

[ - ]
Reply by jeffgableSeptember 6, 2023

This is the kind of thing I would sample at a fixed frequency, say 100 or 200 Hz, where the sample period is far shorter than any reasonable duration of touch. Then, you can process the digital inputs however you'd like without any fear that a touch will be missed.  

That doesn't answer your question about whether interrupts could be dropped.  I briefly looked through the technical reference manual and I'm not 100% sure of the answer, but it sidesteps the question entirely and is how I would likely design the code for the system. Hope that helps.

[ - ]
Reply by CustomSargeSeptember 6, 2023

Sampling at any frequency presumes you're not doing much of anything else - maybe valid - or not. Being INT based, I'll presume a background task set that gets modified by key input - ergo using INTs.

[ - ]
Reply by jeffgableSeptember 6, 2023

Sampling a set of GPIOs is extremely fast.  You could easily do it in an ISR that runs off a timer, a periodic RTOS task, or as part of a timed superloop, and still have plenty of processor time to do other things. 

[ - ]
Reply by CustomSargeSeptember 6, 2023

Can't disagree, but event driven wastes fewer cycles looking when nobody's there, so long as INTs aren't needed for other stuff.

[ - ]
Reply by vinibredaSeptember 11, 2023

Hi! I just edited the post with more info!

[ - ]
Reply by KocsonyaSeptember 6, 2023

Mechanical buttons tend to bounce, which means that when you press or release them, it is not a single transition from one state to the other, but a series of transitions back and forth until the button settles in the new state. The time between those transitions and the number of transitions depends on the mechanical properties of the button, but you can expect a few to a few tens of millisecs between them, and 2-3 to up to maybe 10-15 transitions back and forth.

If your buttons are individual ones (not a matrix arrangement) then you can use an RC circuit and a Schmidt trigger to get rid of the bounce, but that costs extra components and is not a commonly used method: software is cheaper and does not consume board space.

As 'jeffgable' has already said, you are generally better off with polling the buttons at a regular frequency, do the debouncing (i.e. you don't change the button's internal state until you see the new level to be stable for a certain number of consecutive samples). 

If you must conserve every microjoule of power and you need to keep the CPU asleep unless there is a change in the button state, then you use the edge triggered interrupt to wake the chip up. But then you start the polled scanning and when you determined the new button state and performed the relevant action, you go back to sleep and wait for the next IRQ to come. Since the actual button state is determined by the polling routine, it doesn't matter how many IRQs wake the chip up simultaneously, your polled button state decoder will report every button change.


[ - ]
Reply by vinibredaSeptember 11, 2023
Hi! I just edited the post with more info!
[ - ]
Reply by skstrobelSeptember 7, 2023

I agree with those who have suggested that using 16 interrupts is overkill, but if the ESP32 has the hardware support for it built in (I'm not familiar with that MCU specifically), I don't see any reason it can't work well.  The key is to do as little as possible in the ISRs.  I would suggest making the ISR for each button push info about which button was pressed or released onto a queue which you can then read and process outside the ISR.  You definitely should _not_ make the ISR do a bunch of logic to respond to the button press.  If you are using an RTOS that provides a queue, use that.  Otherwise, you need to either use a lock-free queue or block interrupt processing while executing the dequeue operation outside of the ISR.

Juvi's argument about why you won't miss detecting a button press matches my experience dealing with interrupts on several other MCUs; it seems likely to be true on the ESP32 as well.

If you want to debounce the button presses, polling the button states as suggested by jeffgable can make it a lot easier.  The drawback is that it adds constant overhead, but that might not matter in your application.  If you have a "main loop" design that _consistently_ runs fast enough (<10mS or so), you can poll the buttons from there (check a timer and skip reading the buttons as needed to keep the average polling rate consistent).  Otherwise, you can poll from a timer interrupt.  

Regarding debouncing, I haven't found a need to delay recognizing a button press until it has been held long enough.  Instead, I effectively start a 50mS timer every time the button hardware input is read and found to be pressed, and treat the button as pressed as long as the timer is running.  So the button press is handled immediately, and the release isn't processed until 50mS after the last bounce.  If you are polling, say every 10mS, you can do that really efficiently.  Read each hardware input into one bit of a 16-bit integer (a bitfield works well if programming in C).  Store the last five such integers.  Each time you take a new reading and store it, bitwise OR the five stored readings to get the debounced reading for all 16 inputs at once (if any of the readings in the last 50mS indicated that the button was pressed, the bit will be set in the ORed result).  The timing is implicit in the 10mS polling rate and the number of readings.  If you store the previous debounced reading, you can also easily use bitwise operations to determine which buttons were just pressed and just released as well.

[ - ]
Reply by vinibredaSeptember 11, 2023
Hi! I just edited the post with more info!
[ - ]
Reply by CustomSargeSeptember 6, 2023

Ok, still vague.... are multi-button presses valid? Is there a priority amongst buttons? IF multi is valid AND no priority, you still created a monster by having edge triggered on each button. I'll suggest 1 interrupt driven by an OR of All the buttons And NOT edge, but level triggered. The INT routine scans the buttons to get value. Debounce is a matter of saving first value, then since level triggered, another INT will follow, compare and act on same value. Done by flagging first vs second INT. This is WAY oversimplified!

Chasing a complex INT structure is the bane of debugging. Best answer is Don't build it poorly. Been ASM coder 50+ yrs, you'll learn - or not.


[ - ]
Reply by jmford94September 6, 2023

This would be my solution.  It's the best of both worlds.  You don't waste time polling, and you can deal with multiple button presses.  And having a lot of interrupts is indeed the bane of debugging.

John


[ - ]
Reply by vinibredaSeptember 11, 2023
Hi! I just edited the post with more info!
[ - ]
Reply by JuviSeptember 7, 2023

I'm not familiar with the ESP32 but if I remember correctly, there should be an interrupt flag register which contains any interrupts risen. So 16 buttons would correspond to a register of 16 bits, and you could potentially read it in your interrupt routine and handle any interrupt in the order you choose. These flag bits will not get turned off unless you handle them. So to give you an answer, no. There is no chance that one of the interrupts get lost without you handling them.

[ - ]
Reply by vinibredaSeptember 11, 2023
Hi! I just edited the post with more info!

And thanks for the answer regarding the interrupts!

[ - ]
Reply by tcfkatSeptember 8, 2023
void actbutt0(void); // prototypes of functions
void actbutt1(void); // if button is pressed
void actbutt2(void);
void actbutt3(void); // ... and so on ... till actbutt15

// array of function pointers to these functions,
// to be completed till actbutt15
void (*buttacthi[16])(void) = {actbutt0, actbutt1, actbutt2, actbutt3, ...};

static volatile uint16_t buttraw[5] = {0, 0, 0, 0, 0};

// call this in your main loop periodically
void checkbuttons(void)
{
static bool buttstat[16] = {false, false, false, false, false, false, false, false,  false, false, false, false, false, false, false, false};


uint16_t buttlo, butthi;

  buttlo = buttraw[0] | buttraw[1] | buttraw[2] | buttraw[3] | buttraw[4];

  butthi = buttraw[0] & buttraw[1] & buttraw[2] & buttraw[3] & buttraw[4];

  for (uint8_t i = 0; i < 16; i++){
    if ( (butthi & (1 << i)) && !butstat[i] ){
      buttstat[i] = true; // button just pressed
      (*buttacthi[i])(); // call function for this button ONCE!
    }
    if ( !(buttlow & (1 << i)) && butstat[i] ){
      buttstat[i] = false; // button just released
      // (*buttactlo[i])(); // action for button released, if needed
    }

 }

}


void timerISR10ms(void)
{
static volatile uint8_t ptr = 0;

  buttraw[ptr++] = readGPIO();
  if (ptr > 4)
    ptr = 0;
}


My suggestion, untested, just written on my smartphone.

Debounces by 50ms, if a button is pressed the appropriate function is called ONCE until this button is released. Minimal ISR load.

Debouncing is a must if not done by hardware, especially if buttons are aging.


Edit: formatting not better possible

[ - ]
Reply by vinibredaSeptember 11, 2023

Thanks for the implementation tip!