0
\$\begingroup\$

I have two GPIO interrupt sources running in two separate threads:

  1. GPIO pulses following line frequency(60 Hz)
  2. button press

How can I use both these interrupts concurrently, considering my controller(ESP32) multiplexes all GPI0 peripheral interrupts into one CPU interrupt?

Currently, the program allows only one of them to work at a time.

Thanks

\$\endgroup\$
  • 2
    \$\begingroup\$ MCUs don't run concurrent code (servicing an interrupt or otherwise) so, what do you actually mean? \$\endgroup\$ – Andy aka Apr 23 '18 at 12:56
  • \$\begingroup\$ Just checked according to Wikipedia there are esp32 systems with Tensilica Xtensa LX6 dual-core processors. So you could handle them each by a separate processor core. But I would need a day or two to read up if that can be done. I have other things to do though.... \$\endgroup\$ – Oldfart Apr 23 '18 at 13:05
  • \$\begingroup\$ @Andyaka I am running a multi-threaded program using FreeRTOS on my MCU, which allows for concurrent operation of various threads. \$\endgroup\$ – Rohit Garg Apr 23 '18 at 13:17
  • \$\begingroup\$ @oldfart thanks, I'll take a look. Would appreciate any further insights. \$\endgroup\$ – Rohit Garg Apr 23 '18 at 13:18
  • \$\begingroup\$ Are those interrupt sources connected to the same GPIO or are they two different pins? \$\endgroup\$ – Arsenal Apr 23 '18 at 14:11
1
\$\begingroup\$

I'm writing this without ever using an ESP32 (or any other dual core) or FreeRTOS. From my understanding of FreeRTOS (searching very quickly), you implement interrupt handlers on your own, but have to call some OS-functions from within.

So regardless of the GPIO the interrupt happens on, you end up in the same interrupt function, because there is only a single interrupt vector for GPIO.

But the ESP32 has some registers to tell you which interrupts are pending. And you have registers which tell you which interrupt is enabled. The combination of those registers allows you to decide which pins are causing an interrupt.

So based on this you can do different things depending on which GPIO triggered the interrupt.

I'm going to give you the idea, not an implementation, so following is pseudocode:

if ((GPIO_InterruptPendingRegister & GPIO_InterruptEnabledRegister) == GPIO_0_Interrupt)
{
    executeGPIO_0_Function();
    // or
    volatile GPIO_0_Flag = true;
    // depends on how the peripheral handles this, usually needed
    Reset_GPIO_0_PendingFlag();
}
if ((GPIO_InterruptPendingRegister & GPIO_InterruptEnabledRegister) == GPIO_1_Interrupt)
{
    executeGPIO_1_Function();
    // or
    volatile GPIO_1_Flag = true;
    // depends on how the peripheral handles this, usually needed
    Reset_GPIO_1_PendingFlag();
}
etc.

This way you can split the interrupt into multiple execution functions. I prefer the way using functions, but I'm programming in C++ and try to avoid global variables.

\$\endgroup\$
0
\$\begingroup\$

First check if the firmware/SDK lets you split the interrupts. You may need to jump through a few additional hoops to be able to do so. Many times the arduino based SDK will dumb down the available options to make it easier on beginners and unify the experience one different boards. And check the spec of the microcontroller to see exactly what will happen when interrupts come in while already handling another one.

Otherwise you can check what triggered the interrupt inside the interrupt routine and then set a volatile flag to handle it in your main loop.

\$\endgroup\$
  • \$\begingroup\$ Thanks. I am actually using the ESP-IDF environment, and not the Arduino SDK. I'm new to firmware development, hence I'm having some trouble understanding the exact low-level operation of the interrupt handling. \$\endgroup\$ – Rohit Garg Apr 23 '18 at 14:26
0
\$\begingroup\$

Unless a device has per-pin latching of interrupt events, your best bet is probably to keep track of the last acknowledged state of each I/O port register [in latched_port, below], and then do something like:

// Assume interrupts are disabled in this section
uint32_t port_now = GPIOREG->inputs;
uint32_t port_prev = latched_port;
latched_rising_edges |= port_now & ~port_prev;
latched_falling_edges |= ~port_now & port_prev;
latched_port = port_prev;

If any bit of interest is set in latched_rising_edges or latched_falling_edges, code should atomically clear the bit (perhaps by disabling interrupts, using linked load/conditional store, or other means) and handle the appropriate condition. Note that if a pin state changes and then changes back before it gets observed by the above code, the transition should be lost.

Any time the system is going to go to sleep based on a pin-change interrupt, it should clear the pin-change interrupt flag, then check whether the port state matches latched_port, and only go to sleep if it does match. That way, if the port pin changes before it was read, the code will notice that, and if it changes after it's read, the hardware will notice it. Some platforms make it impossible to totally avoid race conditions, but good platforms will guarantee that events which occur between clearing the flag and reading the port state will get triggered by the hardware in addition to being reported in software. Consequently, even if an event that occurs precisely when the port is being read doesn't show up in the read, it would still register as a hardware wake-up.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.