Détecteur de mouvement PIR

Ce tutoriel explique comment mettre en oeuvre un détecteur de mouvement analogique PIR avec MicroPython.

Matériel requis

  1. Une carte d’extension de base Grove
  2. La carte NUCLEO-WB55
  3. Un module capteur de mouvement infrarouge PIR Grove

Le capteur de mouvement (PIR motion sensor) :

Ce capteur analyse le rayonnement infrarouge dans son champ de vision et en déduit une présence ou un mouvement. Il peut servir notamment pour gérer l’éclairage automatique d’un lieu ou encore dans un système d’alarme (cf exercice “Alarme”).

PIR motion sensor

Crédit image : Seeed Studio

Il faudra le brancher sur la broche / le connecteur D4.

Les interruptions

Le mécanisme des interruptions donne aux microcontrôleurs l’aptitude à réagir à des évènements extérieurs de façon presque instantanée. Un circuit intégré spécialisé, appelé contrôleur d’interruptions, contenu dans le microcontrôleur (le STM32WB55 en l’occurrence), reste à l’écoute des signaux en provenance de certaines broches spécifiées par le programmeur.

Si une activité (front de tension montant et/ou descendant) est détectée sur l’une des ces broches, le contrôleur d’interruptions suspend le programme courant exécuté par le microcontrôleur et lui communique un autre programme, qu’il devra exécuter en priorité. Ce programme “injecté”, pour répondre à l’urgence d’une situation imprévue, s’appelle la routine de service de l’interruption (ISR pour Interrupt Service Routine en anglais). Une fois qu’il a exécuté l’ISR, le microcontrôleur reprend le cours du programme suspendu.

Dans le code MicroPython qui suit, le capteur PIR est géré avec une interruption qui survient lorsque le signal en provenance du capteur PIR passe de 0V à 3.3V. En cas de mouvement, l’interruption provoque l’affichage prioritaire du message “Mouvement détecté” sur le terminal de l’USB User et la LED de la carte s’allume pendant un laps de temps donné (une seconde). Puis le système d’alarme se réarme.

Précision importante : Le module PIR Motion sensor de Grove que nous avons sélectionné renvoie un signal non nul pendant une seconde après qu’il ait détecté un mouvement. Si d’autres mouvements surviennent pendant cette seconde, il ne les distinguera pas de celui qui l’a activé en premier. Vous ne pourrez donc pas augmenter la fréquence de détection au-delà de 1 Hz.

Le code MicroPython

Les scripts présentés ci-après sont disponibles dans la zone de téléchargement.

Editez maintenant le script main.py du périphérique PYBLASH :

# Objet du script : Mettre en oeuvre un capteur de mouvement PIR
# Le module PIR Motion sensor de Grove que nous avons sélectionné renvoie un signal non nul pendant une seconde après
# qu'il ait détecté un mouvement. Si d'autres mouvements surviennent pendant cette seconde, il ne les distinguera 
# pas de celui qui l'a activé en premier.

from machine import Pin
from time import sleep

# Variable globale qui sera modifiée par la rouine de service de l'interruption
motion = False

# Routine de service de l'interruption
# Cette fonction ne fait qu'une chose : elle donne la valeur "True" à la variable globale "motion".
def handle_interrupt(pin):
	global motion
	motion = True

led = Pin('D8', Pin.OUT) # Broche de la LED

pir = Pin('D4', Pin.IN) # Broche du capteur PIR

# On "attache" l'interruption à la broche du capteur PIR.
# Cela signifie que le gestionnaire d'interruptions contenu dans le STM32WB55 va "surveiller"
# la tension sur la broche D4. Si cette tension augmente et passe de 0 à 3.3V (IRQ_RISING)
# alors le gestionnaire d'interruption forcera le STM32WB55 à exécuter la fontion "handle_interrupt".

pir.irq(trigger=Pin.IRQ_RISING, handler=handle_interrupt)

while True:
	if motion: # si motion = True alors, cela signifie que l'interruption a eue lieu.
		print('Mouvement détecté!')
		led.value(1) # Allume la LED
		sleep(1) # Temporise pendant 1 seconde
		led.value(0) # Eteint la LED
		motion = False
		print('En attente...')

Temporiser sans bloquer ?

Vous avez sans doute remarqué que nous faisons très souvent usage d’instructions de temporisation telles que “sleep()” ou encore “sleep_ms()”. Dans certaines situations, ces instructions ne sont pas souhaitables, car elles sont bloquantes : lorsqu’elles sont appelées, le script est suspendu et ne fait rien d’autre. Bien que l’exemple du capteur PIR, qui a une latence d’une seconde, ne soit pas idéal pour en démontrer l’utilité, sachez qu’il existe des “astuces” pour temporiser certaines parties d’un programme sans pour autant le bloquer. On peut par exemple suivre le temps écoulé et activer certaines actions en fonction de sa valeur. Ceci permet, par exemple, de faire clignoter simultanément plusieurs LED à des fréquences différentes. Pour réaliser ces temporisations non bloquantes, on utilise les fonctions “time.ticks_ms()” et “time.ticks_diff()”.

On utilise par ailleurs la méthode pyb.wfi() pour placer le microcontrôleur en mode économie d’énergie entre deux interruptions. Cette fonction doit être utilisée dans des scripts qui font aussi appel à des interruptions car elles sont nécessaires pour sortir sur commande le microcontrôleur de son sommeil. Notez bien que d’autres interruptions de MicroPython, qui ont lieu toutes les millisecondes, vont également réveiller le microcontrôleur.

Le code MicroPython

Les scripts présentés ci-après sont disponibles dans la zone de téléchargement.

Voici l’adaptation du code précédent avec ces instructions :

from machine import Pin
import time 

# Routine de service de l'interruption.
# Cette fonction ne fait qu'une chose : elle donne la valeur "True" à la variable globale "motion".
def handle_interrupt(pin):
	global motion
	motion = 1 # On signale le mouvement

led = Pin('D8', Pin.OUT) # Broche de la LED signalant l'alarme

pir = Pin('D4', Pin.IN) # Broche du capteur PIR

# On "attache" l'interruption à la broche du capteur PIR.
# Cela signifie que le gestionnaire d'interruptions contenu dans le STM32WB55 va "surveiller"
# la tension sur la broche D4. Si cette tension augmente et passe de 0 à 3.3V (IRQ_RISING)
# alors le gestionnaire d'interruption forcera le STM32WB55 à exécuter la fontion "handle_interrupt".

pir.irq(trigger=Pin.IRQ_RISING, handler=handle_interrupt)

motion = 0 # Variable globale qui sera modifiée par la rouine de service de l'interruption
start = 0 # Variable globale pour mesurer le temps écoulé

while True:
	
	# On mesure le temps en millisecondes écoulées depuis le démarrage de MicroPython
	now = time.ticks_ms()

	if led.value() == 0 and motion == 1 : # Si la LED est éteinte et que l'interruption a eue lieu...
		print('Mouvement détecté!')
		# On mémorise le le temps en millisecondes écoulées depuis le démarrage de MicroPython
		start = time.ticks_ms()
		led.value(1) # On allume la LED
	
	# Si la LED est allumée et que 1 seconde s'est écoulée depuis la dernière détection de mouvement...
	elif led.value() == 1 and time.ticks_diff(now, start) > 1000:
		print('En attente...')
		led.value(0) # On éteint la LED
		motion = 0 # On réinitialise l'indicateur d'occurence d'interruption
	
	# Place le microcontrôleur en sommeil en attendant la prochaine interruption
	pyb.wfi()