In this tutorial, we will learn how to use an RIR motion sensor with Raspberry Pi Pico and configure external interrupts of Raspberry Pi Pico using MicroPython. We will demonstrate the use of external interrupts through an example with a PIR motion sensor and an LED.
Prerequisites
Before we start this lesson, make sure you are familiar with and have the latest version of Python3 installed in your system and set up MicroPython in your Raspberry Pi Pico. Additionally, you should have a running Integrated Development Environment(IDE) to do the programming. We will be using the same Thonny IDE as we have done previously when we learned how to blink and chase LEDs in MicroPython here:
If you are using uPyCraft IDE, you can check this getting started guide:
First let’s see an introduction of interrupts and what are the applications of interrupts, especially, in embedded systems.
Interrupts Introduction
Interrupts are used to handle events which do not happen during the sequential execution of a program. For example, we want to perform certain tasks and these tasks execute sequentially in your MicroPython program. But there are a few tasks which only execute when a special event occurs such as an external trigger signal to the digital input pin of a microcontroller.
An external interrupt or a ‘hardware interrupt’ is caused by the external hardware module. For example, there is a Touch Interrupt which happens when touch is detected and a GPIO interrupt when a key is pressed down.
Difference Between Sequential and Event Driven Execution?
In our previous tutorial, where we controlled the LED connected to one of the GPIO pins of Raspberry Pi Pico with the help of a push button. This push button was connected with a digital input pin of Raspberry Pi Pico. In that MicroPython code, we must continuously monitor the state of digital input pin to which push button connected. In response to the state of digital input, we were controlling the LED. But one main disadvantage of this method was that we must check the state of digital input every time during sequential execution even if the user does not press the push button. It is a waste of microcontroller processing power and resources.
What happens when Interrupt Occurs?
With interrupt, we do not need to continuously check the state of digital input pin. When an interrupt occurs (a change is detected), the processor stops the execution of the main program and a function is called upon known as ISR or the Interrupt Service Routine. The processor then temporarily works on a different task (ISR) and then gets back to the main program after the handling routine has ended. This is shown in the figure below.
An example can be that of pressing a push button or motion detection with a PIR Sensor. In both cases, push button or a PIR motion sensor can be used to trigger an interrupt. Therefore, when an external event occurs, the processor stops what it is doing and executes the interrupt service routine which we define for the respective event. After that, it returns to the current program. External Interrupts are extremely useful because with their help we do not have to constantly monitor the digital input pin state.
Raspberry Pi Pico Interrupt Pins
All GPIO pins can be configured as an external interrupt pin on the following four changes on the state of GPIO pins:
- Level high
- Level Low
- Positive edge ( transition from active low to active high)
- Negative edge ( transition from active high to active low)
The diagram below shows the pin out of the GPIO pins of Raspberry Pi Pico.
Configuring External Interrupts Raspberry Pi Pico
Now let us look at how to set up external interrupts in our Raspberry Pi Pico using MicroPython. The following steps need to be followed.
Defining an Interrupt Handling Function
def handle_interrupt(pin):
The interrupt handling function is defined in an extremely easy way. It needs to be simple as that the processor is able to return back to the main program swiftly. This can be achieved by making a global variable which can be accessed by all in our case we will use the ‘Pin’ variable. The pin variable will act as a parameter inside our interrupt handling function as follows handle_interrupt(pin). This line shows the GPIO associated with the pin which caused an interrupt to occur.
Setting the GPIO as an input that will act as an interrupt pin
input=Pin(2,Pin.IN)
To get the value of a GPIO, we will create a Pin object and set it as an input using ‘Pin.IN’ as the second argument in our Pin() class. In the example above the input is on GPIO2 which we specified in the first argument. This is stored in the object ‘input’. You can choose whatever object name you want to.
Attaching an Interrupt to the Pin
input.irq(trigger=Pin.IRQ_RISING, handler=handle_interrupt)
In this step we will attach the interrupt to the pin which we specified before by using the irq() method. In this method we pass two arguments namely trigger and handler:
- trigger: There are three different conditions we can choose from.
Pin.IRQ_RISING: This is used to trigger the interrupt when the pin goes from LOW to HIGH.
Pin.IRQ_FALLING: This is used to trigger the interrupt when the pin goes from HIGH to LOW.
Pin.IRQ_FALLING|Pin.IRQ_RISING: This is used to trigger the interrupt when any change is detected meaning it can be both HIGH-LOW or LOW-HIGH.
MicroPython also supports trigger events at low or high level using Pin.IRQ_LOW_LEVEL and Pin.IRQ_HIGH_LEVEL, respectively.
- handler: handle_interrupt() function is passed as the second parameter in the irq() method. This function is called whenever an interrupt is detected.
PIR Motion Sensor with Raspberry Pi Pico
Now we will learn how to handle interrupts in the Raspberry Pi Pico using a PIR sensor and an LED. For demonstration purposes, we will use a PIR motion detector sensor. When a PIR sensor will detect motion, we will turn on the LED for 5 seconds as shown in the figure below:
The following components are required:
- Raspberry Pi Pico
- One PIR Sensor
- One 5mm LED
- One 220 ohm resistor
- Breadboard
- Connecting Wires
Assemble your circuit as shown in the diagram below:
In the above schematic, we can see that GPIO14 is connected with the anode pin of LED through the 220 ohm resistor, and the cathode pin is connected with the common ground.
The PIR Sensor which we are using in this tutorial consists of three pins. Two of them are power supply pins such as VCC and ground pins. We can power the PIR motion sensor directly from the Raspberry Pi Pico 3.3V power pin as shown in the above schematic diagram. The center pin is an output pin which provides an active high pulse whenever motion is detected. Otherwise, this pin remains active low. That means a rising edge occurs when a PIR sensor detects motion. We can detect this rising edge with the help of interrupt pins of Raspberry Pi Pico.
How PIR sensor works with External Interrupt?
The PIR Sensor acts as a source for the external interrupt. That means we connect the output of PIR sensor with the interrupt pin of Raspberry Pi Pico. Furthermore, we attach the rising edge triggered interrupt to this GPIO pin. That means this GPIO pin will trigger the interrupt whenever it will sense rising edge on its input.
Initially, the status LED will blink after a delay of 1 seconds. When a PIR sensor detects motion, an external interrupt is caused, the LED will stay on for 10 seconds and then return back to its normal blinking stage (on/off with a delay of 1 second). The following figure shows the process.
Raspberry Pi Pico Interrupts and PIR Motion Sensor MicroPython Script
Create a new file in uPyCraft or Thonny IDE and copy this code to your newly created file. After that download and run this MicroPython script on Raspberry Pi Pico.
from machine import Pin #importing classes
from time import sleep #Import sleep from time class
Motion_Detected = False #Global variable to hold the state of motion sensor
def handle_interrupt(Pin): #defining interrupt handling function
global Motion_Detected
Motion_Detected = True
led=Pin(14,Pin.OUT) #setting GPIO14 led as output
PIR_Interrupt=Pin(13,Pin.IN) # setting GPIO13 PIR_Interrupt as input
#Attach external interrupt to GPIO13 and rising edge as an external event source
PIR_Interrupt.irq(trigger=Pin.IRQ_RISING, handler=handle_interrupt)
while True:
if Motion_Detected:
print('Motion is detected!')
led.value(1)
sleep(10)
led.value(0)
print('Motion is stopped!')
Motion_Detected = False
else:
led.value(1) #led is on
sleep(1) #delay of 1 second
led.value(0) #led is off
sleep(1) #delay of 1 second
How Code Works?
Importing MicroPython Libraries
Now, let’s see the working of a MicroPython script. To use interrupts in MicroPython, we should import the GPIO class from machine module. Because GPIO class contains methods to configure interrupts for Raspberry Pi Pico. We should also import the sleep module to insert delay in our MicroPython script.
from machine import Pin
from time import sleep
Create Global Variable MicroPython
Next, create a global variable with any name to indicate the state of PIR motion sensor. We have defined a global variable with a name of “Motion_Detected” and the value of this global variable can be either True or False. To indicate the state of PIR sensor, we will set the global variable value to True whenever an interrupt occurs due to motion detected by PIR sensor. Initially set the value of this variable to False.
Motion_Detected = False #Global variable to hold the state of motion sensor
Define Interrupt Handler Function
Next, we define the interrupt handling function about which we learned in the interrupt handling section. The pin parameter to the interrupt handling function such as handle_interrupt(), is an instance of the pin class. This instance shows which GPIO pin has caused an interrupt.
def handle_interrupt(Pin): #defining interrupt handling function
global Motion_Detected
Motion_Detected = True
Inside the interrupt handler function, we set the global variable “Motion_Detected” to a logic True value and we will use this value inside the while loop to execute code which executes only when an interrupt occurs. You must be wondering why we are not executing the code related to PIR sensor inside the handle_interrupt() function instead of executing it inside the while() loop. This is because it is recommended to keep interrupt service routines as short as possible to improve interrupt latency.
That means we should execute the minimum possible number of instructions inside the interrupt callback function. That is why we have only changed the state of the “Motion_Detected” variable inside the ISR and will perform the remaining functionality inside the main code.
Note: “Motion_Detected” is a global variable. Therefore, we can use it throughout the execution of the program.
Create Pin Instances
Create two instances of Pin class one for output LED and the second one for external interrupt input pins.
To set a GPIO ON or OFF, we will set it as an output. The first argument in the Pin() class is the pin number on which we are configuring the output. The output is on GPIO14 which is connected to the LED. The second argument shows the pin mode e.g. digital input or digital output mode. As we are configuring pin 14 as digital output we gave, specifies it as ‘Pin.Out’. This is stored in the object ‘led’
led=Pin(14,Pin.OUT) //setting GPIO14 led as output
To enable external interrupt on GPIO13, create a Pin object (PIR_Interrupt ) and set it as a digital input pin using ‘Pin.IN’ as the second argument in our Pin() method. The input is on GPIO13 which we specified in the first argument and is connected to the PIR Sensor.
PIR_Interrupt=Pin(13,Pin.IN) # setting GPIO13 PIR_Interrupt as input
Attach Interrupt to GPIO Pin
Now as we have defined our input/output GPIOs. The next step is to attach the interrupt to the GPIO13 pin. As we want the PIR Sensor rising edge pulse to trigger the interrupt which acts as an input to GPIO13 as follows:
#Attach external interrupt to GPIO13 and rising edge as an external event source
PIR_Interrupt.irq(trigger=Pin.IRQ_RISING, handler=handle_interrupt)
This irq() method is used to configure the interrupt handler which will be called when the trigger event for that particular interrupt happens. In our case, this trigger event is an external rising edge on the GPIO13 pin.
We set the irq() method’s first argument as ‘trigger=Pin.IRQ_RISING.’ This means we want the interrupt to happen when the transition of signal happens from LOW to HIGH state namely rising edge of signal.
Motion Detection Code
Inside the loop, we check if the global variable is True. If it is True, we turn on the LED for 10 seconds and after that turn off the LED.
if Motion_Detected:
print('Motion is detected!')
led.value(1)
sleep(10)
led.value(0)
print('Motion is stopped!')
Motion_Detected = False
If the global variable “Motion_Detected” is False. LED stays ON for one seconds and turns OFF again for one second. This shows a constant blinking effect of the led after every 1 second.
led.value(1) #led is on
sleep(1) #delay of 1 second
led.value(0) #led is off
sleep(1) #delay of 1 second
Note: “Motion_Detected” variable will become Ture only when motion is detected and in response interrupt handling function executes which will set this variable value to True.
Now create a new file in uPyCraft IDE or Thonny IDE and upload this MicroPython Script to Raspberry Pi Pico. You will see that the LED blinks with a rate of one second when no motion is detected and when PIR sensor detects motion, LED stays on for 10 seconds.
We can view the motion detected messages in the shell console.
In this tutorial, we learned how to handle external interrupts in Raspberry Pi Pico in Micropython using a PIR motion sensor.
You may also like to read: