The objective of this post is to explain how to use external pin interrupts on MicroPython running on the ESP32. The tests were performed using a DFRobot’s ESP-WROOM-32 device integrated in a ESP32 FireBeetle board.
The objective of this post is to explain how to use external pin interrupts on MicroPython running on the ESP32. Please note that some of the code we are going to use here was explained in more detail on this previous post about timer interrupts.
First of all, we will import the machine module, which we will use to configure the external interrupts.
Next we will declare a global variable that will be used by the interrupt handling function to communicate to the main program that an interrupt has occurred. This variable will be a counter, in order for us to not loose interrupts. You can check at this previous post why using a counter instead of a flag for communication between the interrupt and the main code.
Note that we should not perform long operations inside interrupt service routines (such as printing content to the serial console), and thus we will design it to execute as fast as possible. So, as mentioned the interrupt service routine will just inform the main code (by incrementing the counter) that the interrupt occurred, and it will be the main code that will actually handle it.
interruptCounter = 0
We will also use another variable to keep track of how many interrupts have occurred since the program started executing. We will increment it and print its value each time an interrupt occurs.
totalInterruptsCounter = 0
Next we will define the callback function to be executed when the interrupt occurs. This function has an input parameter in which an object of class Pin will be passed when the interrupt happens. Nonetheless, we will not use it in this tutorial.
The actual implementation of the function will consist on incrementing the previously defined interruptCounter variable. Note that this variable needs to be declared as global before we are able to modify it inside the function.
def callback(pin): global interruptCounter interruptCounter = interruptCounter+1
Next we need to create an object of class Pin, which is used to control GPIO pins in MicroPython . You can check all the available parameters for the constructor here. For our program, we will need to pass as input the number of the pin we want to use, the mode of the pin and if it has a pull resistor associated.
For this example, I’m using pin 25, but you can use other. Please note that for some ESP32 boards, the ESP32 GPIO numbers may not match the ones labeled on the board.
We will also set the pin mode to input, with the IN constant of the Pin class.
Finally, we will set the pin to use its pull up resistor, which will guarantee that it will be in a known state (VCC) when no electrical signal is applied. This is done by passing the PULL_UP constant of the Pin class.
p25 = machine.Pin(25, machine.Pin.IN, machine.Pin.PULL_UP)
Next we will specify how the interrupt will be triggered and what is the callback function to execute, by calling the irq function of our Pin object.
For this example, we will specify that the interrupt should be triggered when a falling edge is detected in the input signal connected to the pin. To do it, we need to pass the IRQ_FALLING constant of the Pin class as the trigger argument of the irq function. You can check here all the trigger types available.
We will also specify the handling function by passing our previously defined interrupt function as the handler parameter.
Since all the configurations are now finished, we will enter in a polling loop to check for our interruptCounter variable. Naturally, we are polling only because our program is very simple and we are not doing anything else. Nonetheless, in a real application, we would most likely be performing some computation or the board would be in sleep mode to save energy, instead of being constantly checking the value of the variable.
When we detect that its value is greater than 0, we will treat the interrupt. First, we will decrement the counter value, to signal that it is going to be handled.
Note that since this variable is shared with the interrupt service routine code, we will previously disable the interrupts, only then decrement the counter, and then re-enable the interrupts, to avoid racing conditions.
The disabling and re-enabling of interrupts is done with the disable_irq and enable_irq functions of the machine module. You can check in more detail how to use them in the previous post.
Finally, we will increment the total number of interrupts counter and print its value. In this case, since this variable is not shared with the interrupt service routine, we don’t need to disable interrupts to change its value.
The final source code for the MicroPython script can be seen below, and already includes the interrupt checking loop.
import machine interruptCounter = 0 totalInterruptsCounter = 0 def callback(pin): global interruptCounter interruptCounter = interruptCounter+1 p25 = machine.Pin(25, machine.Pin.IN, machine.Pin.PULL_UP) p25.irq(trigger=machine.Pin.IRQ_FALLING, handler=callback) while True: if interruptCounter>0: state = machine.disable_irq() interruptCounter = interruptCounter-1 machine.enable_irq(state) totalInterruptsCounter = totalInterruptsCounter+1 print("Interrupt has occurred: " + str(totalInterruptsCounter))
Testing the code
To test the code, simply upload the previous script to your ESP32 and run it. Upon running, the easiest way to trigger an interrupt without the need for external hardware is to connect and disconnect the pin where the interrupt was attached to the board’s GND pin.
Please be careful when executing this procedure to avoid connecting the GND pin to the wrong GPIO and damage the board.
You can check below at figure 1 the result from the interrupts.
Figure 1 – ESP32 MicroPython external pin interrupts.
- ESP32 MicroPython: Timer interrupts
- ESP32 Arduino: Timer interrupts
- ESP32 Arduino: External interrupts