Chez Openest, nous réalisons souvent des prototypes rapides en associant des solutions « sur étagère » pour répondre aux besoins de startups. Dans cette optique, nous souhaitons enrichir notre boîte à outils par l’ajout de modules RFID simples à intégrer. Cet article présente notre retour d’expérience pour connecter un lecteur RFID rove 125 kHz à un Raspberry Pi Zero 2 W, ses limites et sa mise en œuvre rapide.
Présentation du module RFID 125 kHz Grove
e module Grove RFID 125 kHz de Seeds studio permet de lire des badges EM4100 (125 kHz) via deux sorties :
- UART (TTL 9600 bauds, 8N1)
- Wiegand (26 bits)
Limitation : ce lecteur est très basique : il ne gère pas la lecture simultanée de plusieurs tags, et il est impossible de détecter de manière fiable le retrait d’un tag. Autrement dit, il ne peut traiter qu’un seul tag à la fois et ne fournit pas d’indication lors de son retrait.
Pour autant, cette simplicité se traduit par une mise en œuvre rapide et un coût réduit.

Câblage avec le Raspberry Pi
J’ai monté le module sur un Raspberry Pi Zero 2 W équipé d’une carte Grove Base Hat. Le connecteur Grove fournit VCC, GND, TX et RX :
- VCC (rouge) → 5 V du Pi (Broche 2 ou 4) (la carte Grove ne délivre que 3,3 V, insuffisant pour le module)
- GND (noir) → GND du Pi
- TX (vert) du module → RX (GPIO 15, Pin 10) du Pi
- RX (blanc) du module ← TX (GPIO 14, Pin 8) du Pi
Pour contourner la limite de 3,3 V, j’ai coupé le fil rouge du connecteur Grove et l’ai relié manuellement à la broche 5 V du Pi.
Configuration du port UART
Par défaut, le Raspberry Pi Zero 2 W réserve /dev/ttyAMA0
au Bluetooth et /dev/ttyS0
comme UART principal. Pour utiliser le port série /dev/ttyAMA0
en tant que liaison UART vers notre module, j’ai activé l’UART matériel. Il faut pour cela éditer le fichier /boot/config.txt
et ajouter la ligne suivante :
enable_uart=1
Après un redémarrage, le port série /dev/ttyAMA0
est disponible pour communiquer avec le module via les GPIO 14 et 15 du header du Raspberry-Pi.
Test de lecture simple du lecteur rfid
Pour tester la connexion, j’ai écrit un petit script Python qui lit en boucle les données série du module. Il faut installer la bibliothèque pyserial au préalable (pip install pyserial
). Voici un exemple de programme minimal :
import serial
# Configuration du port série
ser = serial.Serial('/dev/ttyAMA0', baudrate=9600, timeout=1)
print("En attente de tag RFID...")
while True:
data = ser.read(14) # lire les octets envoyés par le module
if data:
tag_id = data.decode('utf-8', errors='ignore').strip()
print("Tag détecté :", tag_id)
Ce script se met en attente, puis lit les trames de 14 octets envoyées par le module (au format ASCII/hex). Lorsqu’un badge 125 kHz passe devant le capteur, son identifiant s’affiche dans le terminal. Par exemple, on peut voir quelque chose comme :
$ python3 simpletest.py
En attente de tag RFID…
Tag détecté : 6000DB571CF0
Gestion avancée en Python avec thread et callbacks
Pour aller plus loin, j’ai encapsulé la lecture RFID dans une classe Python exécutée dans un thread dédié, afin de pouvoir réagir aux événements de manière asynchrone (tag détecté). L’idée est de lancer en fond une boucle qui interroge le module en permanence. On garde en mémoire le dernier identifiant lu : si on lit un nouvel identifiant différent, cela déclenche le callback on_detect.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@file rfid_reader.py
@brief Threaded RFID reader for Grove 125KHz RFID Reader over UART
"""
import threading
import serial
import sys
import time
class RFIDReader(threading.Thread):
"""
@brief Threaded reader for EM4095-based Grove RFID module
"""
def __init__(self, port):
"""
@brief Initialize RFIDReader thread
@param port UART port (e.g. '/dev/ttyAMA0')
"""
threading.Thread.__init__(self, daemon=True)
# variable declarations
self.port = port
self.baudrate = 9600
self.callbacks = { 'tag_detected': [] }
self._serial = None
self._buffer = bytearray()
self._current_tag = None
self._last_seen = 0.0
self._stop_event = threading.Event()
# open and verify serial
try:
self._serial = serial.Serial(
port=self.port,
baudrate=self.baudrate,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=0
)
except serial.SerialException as e:
print(f"Error opening serial port: {e}", file=sys.stderr)
sys.exit(1)
def register_callback(self, event, callback):
"""
@brief Register a callback for an event
@param callback Callable(tag_id)
"""
if event in self.callbacks:
self.callbacks[event].append(callback)
else:
raise ValueError(f"Unknown event: {event}")
@staticmethod
def _extract_tag_from_buffer(buf):
"""
@brief Extract one complete tag frame from buffer
@param buf bytearray reference
@return tuple(str tag_id, int consumed)
"""
STX = 0x02
# find STX
i = buf.find(bytes((STX,)))
if i == -1 or len(buf) < i + 11:
return None, 0
frame = buf[i+1:i+11]
# decode ASCII hex
try:
tag_str = frame.decode('ascii')
except UnicodeDecodeError:
return None, i+1
# calculate consumed bytes including STX + frame
consumed = i + 11
# skip ETX/CR/LF
while consumed < len(buf) and buf[consumed] in (0x03, 0x0D, 0x0A):
consumed += 1
return tag_str, consumed
def run(self):
"""
@brief Thread main loop: read serial and dispatch events
"""
while not self._stop_event.is_set():
try:
n = self._serial.in_waiting
if n:
data = self._serial.read(n)
self._buffer.extend(data)
# process all complete tags
while True:
tag, consumed = self._extract_tag_from_buffer(self._buffer)
if not tag:
break
# remove consumed bytes
del self._buffer[:consumed]
# new tag detected
self._last_seen = time.time()
if tag != self._current_tag:
self._current_tag = tag
for cb in self.callbacks['tag_detected']:
cb(tag)
# brief pause
time.sleep(0.01)
except Exception as e:
# handle serial read errors
print(f"Error in RFIDReader thread: {e}", file=sys.stderr)
self.stop()
def stop(self):
"""
@brief Signal thread to stop and close serial
"""
self._stop_event.set()
if self._serial and self._serial.is_open:
self._serial.close()
On peut ensuite utiliser cette classe dans un programme comme ceci:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@file example.py
@brief Example usage of RFIDReader
"""
import time
from rfid_reader import RFIDReader
def on_tag_detected(tag_id):
print(f"[EVENT] Tag detected: {tag_id}")
def main():
"""
@brief Launch RFIDReader and register callbacks
"""
# Initialize reader on UART port
reader = RFIDReader(port='/dev/ttyAMA0')
# Register event callbacks
reader.register_callback('tag_detected', on_tag_detected)
# Start reader thread
reader.start()
print("Reader thread started. Approach a tag...")
try:
# Keep main thread alive
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\nStopping reader...")
reader.stop()
reader.join()
print("Reader stopped.")
if __name__ == "__main__":
main()
Conclusion sur ce lecteur RFID pour Raspberry-Pi
En suivant ces étapes, j’ai pu intégrer le lecteur Grove 125 kHz à mon Raspberry Pi Zero 2 W dans un prototype fonctionnel. Le principal obstacle était l’alimentation du module (nécessitant 5V), contourné en modifiant le câble Grove. Ensuite, la configuration de l’UART et le code Python (lecture simple ou gestion via thread) ont permis d’obtenir un flux de données stable. Ce retour d’expérience montre qu’avec quelques adaptations matérielles et un peu de code, nous pourrons intégrer ce module RFID dans des projets de prototypage.
Ce module lecteur RFID Grove 125 kHz combiné à un Raspberry-Pi, malgré sa simplicité (lecture mono-tag, absence d’événement de retrait), se révèle efficace et rapide à déployer pour des prototypes rapides. Chez Openest, il enrichit notre boîte à outils pour des démonstrations et POCs avant de faire évoluer ces prototypes vers des solutions industrialisables (avec NFC, UHF, multi-antennes, RSSI, etc.) mais qui necessites souvent d’évaluer des solutions plus complexes.
Le lecteur Grove 125 kHz gère principalement les tags EM4100 ou compatibles 125 kHz. Ces tags sont courants, peu chers et adaptés pour des prototypes simples.
Non, la puce EM4095 du module ne supporte pas l’anti-collision. Le module ne peut traiter qu’un seul tag à la fois. Placer plusieurs tags simultanément dans la zone de lecture conduit à des trames corrompues ou indétectables.
Le module n’émet des trames UART que lorsqu’il lit un tag. En l’absence de tag, il cesse simplement d’envoyer de données, sans signal explicite.
Oui, la sortie UART TTL est universelle. Il suffit de connecter TX/RX à un port série et d’adapter le code. De nombreux exemples Arduino/ESP32 existent pour la puce EM4095. Seeedsudio fourni une page de wiki à ce sujet.