This tutorial guides you through the process of connecting an RC522 RFID reader to a Raspberry Pi 5 via the SPI interface and reading the Unique ID (UID) from RFID cards using a minimalist Python library. Many existing tutorials for the RC522 use the RPi.GPIO
library, which is no longer readily supported on the Raspberry Pi 5. This tutorial solves that problem by using a lean Python library built on the modern and compatible gpiod
library.
✨ Core Features of the Library
- Lean Design: All necessary functions are contained within a single, lightweight file.
- Easy Integration: The library can be easily integrated by copying the file into your project.
- Raspberry Pi 5 Compatible: Uses the modern
gpiod
library instead of the outdatedRPi.GPIO
. - SPI Focused: Optimized for stable and fast SPI communication.
Hardware: Requirements and Wiring
First, let’s connect the RC522 reader to the Raspberry Pi.
Required Hardware
- Raspberry Pi 5
- RC522 RFID Reader Module
- RFID Cards or Tags (e.g., MIFARE Classic 1K)
- Jumper Wires (Dupont-style)
Wiring Diagram (SPI)
Connect your RC522 module to your Raspberry Pi’s GPIO pins as shown in the table below.
RC522 Pin | Pi 5 Pin (Physical) | GPIO (BCM) | Description |
SDA/SS | Pin 24 | GPIO 8 | Chip Select (CS) |
SCK | Pin 23 | GPIO 11 | SPI Clock |
MOSI | Pin 19 | GPIO 10 | Master Out -> Slave In |
MISO | Pin 21 | GPIO 9 | Master In <- Slave Out |
RST | Pin 22 | GPIO 25 | Reset |
GND | Pin 6, 9, etc. | – | Ground |
VCC | Pin 1 or 17 | – | 3.3V Power |
Important Note: Connect the RC522 module only to a 3.3V pin. Using a 5V pin can permanently damage the module!
Software: System Setup
Before we can run the code, the SPI interface must be enabled and the necessary libraries installed.
a) Enable SPI Interface
If you haven’t already, enable the SPI interface on your Raspberry Pi.
- Open a terminal and enter
sudo raspi-config
. - Navigate to
3 Interface Options
->I4 SPI
. - Confirm “Yes” to the prompt asking if you want to enable the SPI interface.
- Exit the configuration tool. A reboot may be necessary.
b) Install System Dependencies
Install the spidev
and libgpiod
Python libraries for SPI communication and GPIO control.
Bash
sudo apt update
sudo apt install python3-spidev python3-libgpiod -y
Library and Example Code
The entire project consists of just two files: the library itself and an example script demonstrating its use. You can find the code here, or download it from our GitHub repository.
a) The Library File: rc522_spi_library.py
This is the actual library. It is self-contained and includes all the classes, constants, and functions needed to communicate with the RC522. Copy the following code and save it in a file named rc522_spi_library.py
.
# A lean Python library for the RC522 RFID reader on the Raspberry Pi 5 via SPI.
# Combines the necessary classes and constants for easy integration.
#
# Pollux Labs
# polluxlabs.io
import time
import logging
try:
import gpiod
import spidev
except ImportError:
print("Important Note: The hardware libraries 'gpiod' and 'spidev' could not be imported.")
print("This library is intended for use on a Raspberry Pi with the SPI interface enabled.")
gpiod = None
spidev = None
# --- Constants ---
# From `constants.py`
class RC522Registers:
COMMAND_REG = 0x01
COM_IRQ_REG = 0x04
DIV_IRQ_REG = 0x05
ERROR_REG = 0x06
STATUS2_REG = 0x08
FIFO_DATA_REG = 0x09
FIFO_LEVEL_REG = 0x0A
CONTROL_REG = 0x0C
BIT_FRAMING_REG = 0x0D
TX_CONTROL_REG = 0x14
CRC_RESULT_REG_MSB = 0x21
CRC_RESULT_REG_LSB = 0x22
VERSION_REG = 0x37
T_MODE_REG = 0x2A
T_PRESCALER_REG = 0x2B
T_RELOAD_REG_H = 0x2C
T_RELOAD_REG_L = 0x2D
MODE_REG = 0x11
TX_AUTO_REG = 0x15
class RC522Commands:
IDLE = 0x00
CALC_CRC = 0x03
TRANSCEIVE = 0x0C
MF_AUTHENT = 0x0E
SOFT_RESET = 0x0F
class MifareCommands:
REQUEST_A = 0x26
ANTICOLL_1 = 0x93
SELECT_1 = 0x93
HALT = 0x50
READ = 0x30
AUTH_A = 0x60
class StatusCodes:
OK = 0
ERROR = 1
TIMEOUT = 3
AUTH_ERROR = 5
DEFAULT_KEY = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]
# --- Exceptions ---
# From `exceptions.py`
class RC522Error(Exception):
"""Base exception for RC522 operations."""
pass
class RC522CommunicationError(RC522Error):
"""Exception for communication errors with the RC522."""
pass
# --- Main Class ---
# Combined and simplified logic from `rc522_reader.py`
class RC522SPILibrary:
"""
A lean and standalone Python library for the RC522 RFID reader
on the Raspberry Pi 5, focusing on SPI communication.
"""
def __init__(self, spi_bus=0, spi_device=0, rst_pin=22, debug=False):
"""
Initializes the reader.
Args:
spi_bus (int): The SPI bus (default: 0).
spi_device (int): The SPI device (default: 0 for CE0).
rst_pin (int): The GPIO pin for the reset (BCM numbering).
debug (bool): Enables detailed log output.
"""
self.logger = logging.getLogger(__name__)
if debug:
self.logger.setLevel(logging.DEBUG)
if not spidev or not gpiod:
raise RC522CommunicationError("The hardware libraries 'spidev' and 'gpiod' are not available.")
self.spi = spidev.SpiDev()
self.spi.open(spi_bus, spi_device)
self.spi.max_speed_hz = 1000000 # 1 MHz
self.spi.mode = 0
# GPIO setup for the reset pin using gpiod
try:
self.gpio_chip = gpiod.Chip('gpiochip4') # Chip for physical pins on the Pi 5
self.rst_line = self.gpio_chip.get_line(rst_pin)
self.rst_line.request(consumer="RC522_RST", type=gpiod.LINE_REQ_DIR_OUT)
except Exception as e:
raise RC522CommunicationError(f"Error initializing GPIO pin via gpiod: {e}")
self._initialized = False
self.initialize()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.cleanup()
def _write_register(self, reg, value):
self.spi.xfer2([reg << 1 & 0x7E, value])
def _read_register(self, reg):
return self.spi.xfer2([(reg << 1 & 0x7E) | 0x80, 0])[1]
def _set_bit_mask(self, reg, mask):
current = self._read_register(reg)
self._write_register(reg, current | mask)
def _clear_bit_mask(self, reg, mask):
current = self._read_register(reg)
self._write_register(reg, current & (~mask))
def _reset(self):
"""Performs a hardware reset of the RC522."""
self.rst_line.set_value(0)
time.sleep(0.05)
self.rst_line.set_value(1)
time.sleep(0.05)
def initialize(self):
"""Initializes the RC522 chip."""
self._reset()
self._write_register(RC522Registers.COMMAND_REG, RC522Commands.SOFT_RESET)
time.sleep(0.05)
self._write_register(RC522Registers.T_MODE_REG, 0x8D)
self._write_register(RC522Registers.T_PRESCALER_REG, 0x3E)
self._write_register(RC522Registers.T_RELOAD_REG_L, 30)
self._write_register(RC522Registers.T_RELOAD_REG_H, 0)
self._write_register(RC522Registers.TX_AUTO_REG, 0x40)
self._write_register(RC522Registers.MODE_REG, 0x3D)
self.antenna_on()
self._initialized = True
self.logger.info("RC522 initialized successfully.")
def antenna_on(self):
if not (self._read_register(RC522Registers.TX_CONTROL_REG) & 0x03):
self._set_bit_mask(RC522Registers.TX_CONTROL_REG, 0x03)
def cleanup(self):
"""Resets the RC522 and releases resources."""
if self._initialized:
self._reset()
if hasattr(self, 'rst_line') and self.rst_line:
self.rst_line.release()
if hasattr(self, 'gpio_chip') and self.gpio_chip:
self.gpio_chip.close()
self.spi.close()
self.logger.info("RC522 resources have been released.")
def _communicate_with_card(self, command, send_data, timeout=0.1):
"""Internal method for card communication."""
irq_en = 0x77
wait_irq = 0x30
self._write_register(RC522Registers.COMMAND_REG, RC522Commands.IDLE)
self._write_register(RC522Registers.COM_IRQ_REG, 0x7F)
self._set_bit_mask(RC522Registers.FIFO_LEVEL_REG, 0x80)
for byte in send_data:
self._write_register(RC522Registers.FIFO_DATA_REG, byte)
self._write_register(RC522Registers.COMMAND_REG, command)
if command == RC522Commands.TRANSCEIVE:
self._set_bit_mask(RC522Registers.BIT_FRAMING_REG, 0x80)
start_time = time.time()
while time.time() - start_time < timeout:
n = self._read_register(RC522Registers.COM_IRQ_REG)
if n & wait_irq:
break
self._clear_bit_mask(RC522Registers.BIT_FRAMING_REG, 0x80)
if time.time() - start_time >= timeout:
return StatusCodes.TIMEOUT, [], 0
if self._read_register(RC522Registers.ERROR_REG) & 0x1B:
return StatusCodes.ERROR, [], 0
status = StatusCodes.OK
back_data = []
back_len = 0
if n & 0x01:
status = StatusCodes.ERROR
if command == RC522Commands.TRANSCEIVE:
fifo_size = self._read_register(RC522Registers.FIFO_LEVEL_REG)
last_bits = self._read_register(RC522Registers.CONTROL_REG) & 0x07
if last_bits != 0:
back_len = (fifo_size - 1) * 8 + last_bits
else:
back_len = fifo_size * 8
if fifo_size == 0:
fifo_size = 1
if fifo_size > 16:
fifo_size = 16
for _ in range(fifo_size):
back_data.append(self._read_register(RC522Registers.FIFO_DATA_REG))
return status, back_data, back_len
def request(self):
"""
Scans for cards in the antenna field.
Returns:
Tuple[int, Optional[List[int]]]: Status code and card type (ATQA).
"""
self._write_register(RC522Registers.BIT_FRAMING_REG, 0x07)
status, back_data, _ = self._communicate_with_card(RC522Commands.TRANSCEIVE, [MifareCommands.REQUEST_A])
if status != StatusCodes.OK or len(back_data) != 2:
return StatusCodes.ERROR, None
return status, back_data
def anticoll(self):
"""
Performs an anti-collision procedure to get a card's UID.
Returns:
Tuple[int, Optional[List[int]]]: Status code and the card's UID (4 bytes).
"""
self._write_register(RC522Registers.BIT_FRAMING_REG, 0x00)
status, back_data, _ = self._communicate_with_card(RC522Commands.TRANSCEIVE, [MifareCommands.ANTICOLL_1, 0x20])
if status == StatusCodes.OK and len(back_data) == 5:
# Checksum of UID
checksum = 0
for i in range(4):
checksum ^= back_data[i]
if checksum != back_data[4]:
return StatusCodes.ERROR, None
return StatusCodes.OK, back_data[:4]
return StatusCodes.ERROR, None
b) The Example File: example.py
This script imports the library you just created and shows how to implement card detection and UID reading in a simple loop. Save this code in the same directory as the library file, under the name example.py
.
# -*- coding: utf-8 -*-
# Pollux Labs
# polluxlabs.io
import time
import logging
# Import the previously created library
from rc522_spi_library import RC522SPILibrary, StatusCodes
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def main():
"""
General example for reading the UID from any RFID card.
"""
print("Starting the RFID Card Reader...")
print("Hold any RFID card near the reader.")
print("Press CTRL+C to exit.")
reader = None
try:
# Initialize the library
reader = RC522SPILibrary(rst_pin=22)
# Stores the UID of the last seen card to prevent constant repetition
last_uid = None
while True:
# 1. Scan for a card
status, _ = reader.request()
if status == StatusCodes.OK:
# 2. If a card is in the field, get its UID (Anti-collision)
status, uid = reader.anticoll()
if status == StatusCodes.OK:
# Only act if it's a new card
if uid != last_uid:
last_uid = uid
# Convert UID to a readable format
uid_str = ":".join([f"{i:02X}" for i in uid])
print("\n================================")
print(f"Card detected!")
print(f" UID: {uid_str}")
print("================================")
print("INFO: You can now use this UID in your own code.")
# If the UID could not be read, but a card is present,
# nothing is done until the card is removed.
else:
# 3. If no card is in the field anymore, reset `last_uid`
if last_uid is not None:
print("\nCard removed. The reader is ready for the next card.")
last_uid = None
# Short pause to reduce CPU load
time.sleep(0.1)
except Exception as e:
logging.error(f"An unexpected error occurred: {e}")
except KeyboardInterrupt:
print("\nExiting program.")
finally:
# Make sure to release the resources at the end
if reader:
reader.cleanup()
print("RC522 resources released successfully.")
if __name__ == '__main__':
main()
Usage and Testing
Now everything is ready to test the RFID reader.
- Navigate your terminal to the directory where you saved the two Python files (
rc522_spi_library.py
andexample.py
). - Run the example script:Bash
python3 example.py
- The program will start and prompt you to hold a card near the reader.
- When you bring an RFID card close to the reader, its UID should appear on the screen.
Example Output:
Starting the RFID Card Reader...
Hold any RFID card near the reader.
Press CTRL+C to exit.
================================
Card detected!
UID: 4A:F3:8B:1E
================================
INFO: You can now use this UID in your own code.
You can now copy this UID and use it in your own projects for access control, object identification, or triggering actions.