RFID Raspberry-pi

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:

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:

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.

Which RFID tags work with this module?

It supports EM4100 or compatible 125 kHz tags. These are common, inexpensive, and ideal for simple prototypes.

Can it detect multiple tags at once?

No. The EM4095 chip doesn’t support anti-collision. Presenting multiple tags simultaneously leads to corrupted or missed frames.

Why can’t it reliably detect when a tag is removed?

The module only sends UART frames when it reads a tag. In the absence of a tag, it simply does nothing.

Can I use this reader on other platforms (Arduino, ESP32)?

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.

Leave a Reply

Your email address will not be published. Required fields are marked *