Learn how to interface a PIR motion sensor with the Raspberry Pi Pico to detect motion in your surroundings. We’ll show you how to wire the sensor to the Pico board and we’ll program the board using MicroPython firmware to execute a certain task when motion is detected.
Table of Contents:
Throughout this tutorial, we’ll cover the following topics:
- Introducing the PIR Motion Sensor
- Wiring a PIR Motion Sensor to the Pi Pico
- PIR Motion Sensor – Detect Motion (MicroPython)
- Introducing Interrupts
- Raspberry Pi Pico with PIR Motion Sensor Using Interrupts – MicroPython Code
Prerequisites – MicroPython Firmware
To follow this tutorial you need MicroPython firmware installed in your Raspberry Pi Pico board. You also need an IDE to write and upload the code to your board.
The recommended MicroPython IDE for the Raspberry Pi Pico is Thonny IDE. Follow the next tutorial to learn how to install Thonny IDE, flash MicroPython firmware, and upload code to the board.
Introducing the PIR Motion Sensor
A PIR motion sensor detects changes in infrared light in its field of view. This makes it ideal for detecting humans or animals because it will pick up living things (or heat-emitting objects) that move within their range but not inanimate objects.
You can program the Pi to react to changes in infrared light by triggering an event such as turning on a light, sounding an alarm, sending a notification, or any other task. In this tutorial, we’ll print a message on the console, and we’ll light up an LED.
There are different PIR motion sensor modules, but all act in a similar way. You’ll have a power pin, GND, and data.
The PIR motion sensor outputs a HIGH signal on the Data pin when it detects movement, or a LOW signal if it doesn’t. It only has three pins: VCC, GND, and Data.
Some models like the HC-SR501 might have two potentiometers (those two orange potentiometers in the picture below) to adjust the sensitivity and the time delay.
- Sensitivity potentiometer: this adjusts the sensor’s detection range. Clockwise increases sensitivity, counterclockwise decreases it.
- Time delay potentiometer: this controls how long the sensor remains triggered after detecting motion. Clockwise increases the delay, and counterclockwise decreases it.
Wiring a PIR Motion Sensor to the Pi Pico
The PIR motion sensor has three pins: VCC, GND, and Data. You should connect VCC to the 3V3 pin (or 5V depending on the model), GND to a GND pin, and the Data pin to a suitable Raspberry Pi Pico GPIO—we’ll connect it to GPIO 22 (take a look at the Raspberry Pi Pico pinout). We’ll also add an LED to GPIO 21 to add visual feedback to your project.
PIR Motion Sensor | Raspberry Pi |
Vin/3v3 | 3.3V or 5V depending on the model |
Data | GPIO 22 (or another GPIO of your choice) |
GND | GND |
We’ll use the AM312 Mini PIR Motion sensor because it works with 3V3 volts, which is perfect for the Raspberry Pi. But you can use any other PIR motion sensor module. The working principle is the same.
Here’s a list of components you need for this project:
You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!
PIR Motion Sensor – Detect Motion (MicroPython)
There are many different ways to write a program to detect motion using a PIR motion sensor. The simplest way is to simply read the state of the PIR sensor as you would read any other digital input.
For example:
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-motion-pir-micropython/
from machine import Pin
from time import sleep
pir_pin = Pin(22, Pin.IN)
led_pin = Pin (21, Pin.OUT)
while True:
if pir_pin.value():
print("Motion detected!")
led_pin.value(1)
sleep(1)
led_pin.value(0)
However, this might not be the best solution, because the PIR motion sensor data pin stays in a HIGH state for a few seconds before getting back to LOW. The best way is to detect the precise moment when the state goes from LOW to HIGH (rising mode) and have a better responsiveness. For that, we can use interrupts.
Introducing Interrupts
Interrupts are useful for making things happen automatically in microcontroller programs and can help solve timing problems. With interrupts, you don’t need to constantly check the current pin value. When a change is detected, an event is triggered (a function is called—this function is often called an ISR (interrupt service routine).
When an interrupt happens, the processor stops the execution of the main program to execute a task, and then gets back to the main program as shown in the figure below.
This is especially useful to trigger an action whenever motion is detected or whenever a pushbutton is pressed without the need for constantly checking its state.
Raspberry Pi Pico Setting Up an Interrupt in MicroPython
To set up an interrupt in MicroPython, you need to follow the next steps:
1. Define an interrupt handling function (ISR). The interrupt handling function should be as simple as possible, so the processor gets back to the execution of the main program quickly. The best approach is to signal the main code that the interrupt has happened by using a global variable, for example.
The interrupt handling function should accept a parameter of type Pin. This parameter is returned to the callback function and it refers to the GPIO that caused the interrupt. You should avoid doing tasks that take a long time inside the ISR. You should also avoid using the regular print function inside.
def handle_interrupt(pin):
2. Set up the GPIO that will act as an interrupt pin as an input. For example, in our case we’re using GPIO 22:
pir = Pin(22, Pin.IN)
3. Attach an interrupt to that pin by calling the irq() method:
pir.irq(trigger=Pin.IRQ_RISING, handler=handle_interrupt)
The irq() method accepts the following arguments:
- trigger: this defines the trigger mode. There are different conditions in MicroPython:
- Pin.IRQ_FALLING: to trigger the interrupt whenever the pin goes from HIGH to LOW;
- Pin.IRQ_RISING: to trigger the interrupt whenever the pin goes from LOW to HIGH.
- Pin.IRQ_LOW_LEVEL: interrupt occurs when a pin is LOW (not supported on the Pico board at the time of writing this article).
- Pin.IRQ_HIGH_LEVEL: interrupt occurs when a pin is HIGH (not supported on the Pico board at the time of writing this article).
- Pin.IRQ_FALLING | Pin.IRQ_RISING: interrupt on both the rising edge and the falling edge.
- handler: this is a function that will be called when an interrupt is detected, in this case, the handle_interrupt() function.
Raspberry Pi Pico with PIR Motion Sensor Using Interrupts – MicroPython Code
The following MicroPython code for the Raspberry Pi Pico monitors a PIR motion sensor connected. When the PIR sensor detects motion, it turns on an LED for visual feedback and prints “Motion detected!” to the console. When motion stops, it turns off the LED and prints “Motion stopped” to the console. The code continuously runs in a loop until interrupted by a keyboard command or when an exception occurs.
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-motion-pir-micropython/
from machine import Pin
from time import sleep
# Define the PIR sensor input pin
pir_pin = Pin(22, Pin.IN)
# Define the LED pin (optional, for visual feedback)
led_pin = Pin(21, Pin.OUT)
# Flags to indicate motion detection state
motion_detected = False
motion_stopped_printed = False
# Callback function to handle motion detection
def pir_interrupt(pin):
global motion_detected
if pin.value() == 1: # Rising edge (motion detected)
motion_detected = True
led_pin.value(1) # Turn on the LED
else: # Falling edge (motion stopped)
motion_detected = False
led_pin.value(0) # Turn off the LED
# Configure the interrupt on the PIR pin for both rising and falling edges
pir_pin.irq(trigger=(Pin.IRQ_RISING | Pin.IRQ_FALLING), handler=pir_interrupt)
try:
while True:
if motion_detected and not motion_stopped_printed:
print("Motion detected!")
motion_stopped_printed = True # Set the flag to indicate motion detected
elif not motion_detected and motion_stopped_printed:
print("Motion stopped")
motion_stopped_printed = False # Reset the flag
sleep(0.1) # Main loop delay
except KeyboardInterrupt:
print("Keyboard interrupt")
pir_pin.irq(trigger=0) # Disable the interrupt
led_pin.value(0) # Turn off the LED
except Exception as e:
print("An unexpected exception occurred:", e)
How Does the Code Work?
Continue reading to learn how the code works or skip to the Demonstration section.
Importing Libraries
The following lines import the necessary libraries for working with GPIO pins and implementing time delays.
from machine import Pin
from time import sleep
Pin Definitions
In this section, the code defines the pins for the PIR sensor input and an optional LED for visual feedback. We’re connecting the sensor to GPIO 22 and the LED to GPIO 21. Alternatively, you can use any other suitable pins (just make sure you modify the code with the pins you’re using).
# Define the PIR sensor input pin
pir_pin = Pin(22, Pin.IN)
# Define the LED pin (optional, for visual feedback)
led_pin = Pin(21, Pin.OUT)
Motion Detection Flags
We create two variables to indicate the state of motion detection. The motion_detected is set to True when motion is detected, and the motion_stopped_printed is used to track whether the “Motion stopped” message has been printed on the console (this is to prevent writing the same message multiple times when motion is not being detected).
# Flags to indicate motion detection state
motion_detected = False
motion_stopped_printed = False
Callback Function for Motion Detection
A callback function named pir_interrupt is defined to handle the interrupts generated by the PIR sensor. This function will be called every time motion is detected.
# Callback function to handle motion detection
def pir_interrupt(pin):
global motion_detected
if pin.value() == 1: # Rising edge (motion detected)
motion_detected = True
led_pin.value(1) # Turn on the LED
else: # Falling edge (motion stopped)
motion_detected = False
led_pin.value(0) # Turn off the LED
The pir_interrupt function has an input parameter (pin) in which an object of class Pin will be passed when the interrupt happens (it indicates which pin caused the interrupt).
This function toggles the motion_detected variable and controls the LED based on the rising and falling edges of the PIR sensor signal. If the pin.value() is 1, which means the PIR sensor data pin is HIGH, we set the motion_detected variable to True and turn on the LED.
if pin.value() == 1: # Rising edge (motion detected)
motion_detected = True
led_pin.value(1) # Turn on the LED
If pin.value() is not 1, it means that the PIR motion sensor data pin went from HIGH to LOW. So, we can set the motion_detected variable to False and turn off the LED because motion is no longer detected.
else: # Falling edge (motion stopped)
motion_detected = False
led_pin.value(0) # Turn off the LED
Configuring Interrupts
The following line configures the interrupt on the PIR pin to trigger on both rising and falling edges. The pir_interrupt function will be called when these edges are detected. This means we’ll detect when motion is detected and when motion stops being detected.
pir_pin.irq(trigger=(Pin.IRQ_RISING | Pin.IRQ_FALLING), handler=pir_interrupt)
While Loop
The main loop continuously checks the state of the motion_detected variable and provides feedback accordingly. It prints messages when motion is detected or stopped. To prevent printing the same message multiple times, we also check the state of the motion_stopped_printed variable.
try:
while True:
if motion_detected and not motion_stopped_printed:
print("Motion detected!")
motion_stopped_printed = True # Set the flag to indicate motion detected
elif not motion_detected and motion_stopped_printed:
print("Motion stopped")
motion_stopped_printed = False # Reset the flag
sleep(0.1) # Main loop delay
Exception Handling
The code includes exception handling to handle keyboard interrupts (e.g., when stopping the script) and other unexpected exceptions. It disables the interrupt and turns off the LED when the script is stopped.
except KeyboardInterrupt:
print("Keyboard interrupt")
pir_pin.irq(trigger=0) # Disable the interrupt
led_pin.value(0) # Turn off the LED
except Exception as e:
print("An unexpected exception occurred:", e)
Demonstration
Save the code to your Raspberry Pi Pico board using Thonny IDE or any other MicroPython IDE of your choice.
Copy the code provided to a new file on Thonny IDE.
With the code copied to the file, click on the Save icon. Then, select Raspberry Pi Pico.
Save the file with the following name: main.py.
Note: when you name a file main.py, the Raspberry Pi Pico will run that file automatically on boot. If you call it a different name, it will still be saved on the board filesystem, but it will not run automatically on boot.
Click the little green button “Run Current Script” or press F5.
Now, test your setup. Wave your hand in front of the motion sensor. It will print “Motion Detected!” in the console.
And the LED will light up. The LED will be lit as long as the PIR sensor data pin is high (this might depend on the PIR sensor model you’re using).
After a few seconds, if motion is no longer detected. You’ll get a “motion stopped” message and the LED will turn off.
Wrapping Up
In this tutorial, you learned how to use a PIR sensor with the Raspberry Pi Pico to detect motion. You learned how to use interrupts to execute tasks when motion is detected.
To keep things simple, we just turned an LED on and off. In a real-world scenario, you may want to send a notification to your smartphone, send an email, turn on a lamp, send a message via MQTT, or any other task.
We hope you’ve found this tutorial useful. We have other Raspberry Pi Pico tutorials that you may like:
- Raspberry Pi Pico: Control Digital Outputs and Read Digital Inputs (MicroPython)
- Raspberry Pi Pico: PWM Fading an LED (MicroPython)
- Raspberry Pi Pico: DHT11/DHT22 Temperature and Humidity Sensor (MicroPython)
- Raspberry Pi Pico: DS18B20 Temperature Sensor (MicroPython) – Single and Multiple
Check out all our Raspberry Pi Pico Guides »
Learn more about the Raspberry Pi Pico with our exclusive eBook:
Thanks. Nice guide. Very clear. I liked the interrupt example.
Cut back, ultra simplified version here:
from machine import Pin
import time
pir_pin = Pin(16, Pin.IN)
led_pin = Pin(25, Pin.OUT)
def pir_interrupt(pin):
if pin.value() == 1:
led_pin.value(1)
else:
led_pin.value(0)
Configure the interrupt on the PIR pin for both rising and falling edges
pir_pin.irq(trigger=(Pin.IRQ_RISING | Pin.IRQ_FALLING), handler=pir_interrupt)
looping = True
while looping == True:
print(“Just looping…”)
time.sleep(1)
I am developing Physical Computing at my school. Thank you.