
At Openest, we often build rapid prototypes by combining off-the-shelf solutions to meet startup needs. To expand our toolkit, we added a simple RFID module that’s easy to integrate. In this article, we share our experience connecting a 125 kHz Grove RFID reader to a Raspberry Pi Zero 2 W—its limitations and a quick, practical implementation of an rfid reader Raspberry-Pi setup.
Overview of the Grove 125 kHz RFID Module
The Grove 125 kHz RFID module from Seeed Studio reads EM4100 (125 kHz) badges and offers two output modes:
- UART (TTL 9600 baud, 8N1)
- Wiegand (26 bits)
Limitation: This reader is very basic: it can only handle one tag at a time and cannot reliably detect when a tag is removed. In other words, it supports a single-tag rfid reader Raspberry-Pi use case and provides no “tag removed” notification.
Despite its simplicity, this module is fast to deploy and very affordable.
Wiring the RFID reader to the Raspberry-Pi
I mounted the module on a Raspberry Pi Zero 2 W with a Grove Base Hat. The Grove connector provides VCC, GND, TX, and RX:
- VCC (red) → Pi 5 V (pin 2 or 4)
(The Grove Hat only supplies 3.3 V, which isn’t enough for this module.) - GND (black) → Pi GND
- TX (green) → Pi RX (GPIO 15, pin 10)
- RX (white) ← Pi TX (GPIO 14, pin 8)
To supply 5 V, I cut the Grove cable’s red wire and manually connected it to the Pi’s 5 V pin.
Configuring the UART Port
By default, the Pi Zero 2 W reserves /dev/ttyAMA0
for Bluetooth and uses /dev/ttyS0
as the main UART. To use /dev/ttyAMA0
for our Grove module, enable the hardware UART by editing /boot/config.txt
enable_uart=1
After rebooting, /dev/ttyAMA0 becomes available for the rfid reader Raspberry-Pi connection via GPIO 14 and 15.
Simple RFID Reading Test in Python
To verify the connection, I wrote a short Python script that continuously reads serial data. First, install the pyserial library:
pip install pyserial
Here’s a minimal example:
import serial
# Configure serial port
ser = serial.Serial('/dev/ttyAMA0', baudrate=9600, timeout=1)
print("Waiting for RFID tag...")
while True:
data = ser.read(14) # read bytes from the module
if data:
tag_id = data.decode('utf-8', errors='ignore').strip()
print("Tag detected:", tag_id)
This script waits for and reads 14-byte frames in ASCII/hex. When a 125 kHz badge passes, its ID appears in the terminal:
$ python3 simpletest.py
Waiting for RFID tag…
Tag detected: 6000DB571CF0
Advanced Python Handling with Threads and Callbacks
For a more robust RFID reader Raspberry-Pi integration, I wrapped the serial reading in a Python class running in its own thread. This allows asynchronous event handling: when a new tag is read, it triggers a callback. The reader keeps track of the last seen ID and only fires the on_detect
callback when it changes.
#!/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()
You can then use this class in like this:
#!/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 on This Raspberry-Pi RFID Reader
By following these steps, I integrated the Grove 125 kHz RFID module into a Raspberry Pi Zero 2 W for a functional prototype. The main hurdle was powering the module at 5V, solved by modifying the Grove cable. Configuring the UART and writing Python code—whether a simple script or a threaded rfid reader Raspberry-Pi class—provides a stable data stream.
Despite its single-tag limitation and lack of removal detection, this Grove RFID reader plus Raspberry-Pi combo is both fast to deploy and cost-effective for quick prototypes. At Openest, it enriches our toolbox for demos and POCs before moving to more industrial solutions (NFC, UHF, multi-antenna, RSSI, etc.), which often require more sophisticated hardware.
It supports EM4100 or compatible 125 kHz tags. These are common, inexpensive, and ideal for simple prototypes.
No. The EM4095 chip doesn’t support anti-collision. Presenting multiple tags simultaneously leads to corrupted or missed frames.
The module only sends UART frames when it reads a tag. In the absence of a tag, it simply does nothing.
Yes. Its TTL UART output is universal. Just connect TX/RX to a serial port and adapt the code. Many Arduino/ESP32 examples for the EM4095 chip are available. Seeds studio wiki page provides exactly this.