RFID at Raspberry Pi 5

Raspberry Pi 5 & RC522: Reading RFID Cards without RPi.GPIO

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 outdated RPi.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 PinPi 5 Pin (Physical)GPIO (BCM)Description
SDA/SSPin 24GPIO 8Chip Select (CS)
SCKPin 23GPIO 11SPI Clock
MOSIPin 19GPIO 10Master Out -> Slave In
MISOPin 21GPIO 9Master In <- Slave Out
RSTPin 22GPIO 25Reset
GNDPin 6, 9, etc.Ground
VCCPin 1 or 173.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.

  1. Open a terminal and enter sudo raspi-config.
  2. Navigate to 3 Interface Options -> I4 SPI.
  3. Confirm “Yes” to the prompt asking if you want to enable the SPI interface.
  4. 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.

  1. Navigate your terminal to the directory where you saved the two Python files (rc522_spi_library.py and example.py).
  2. Run the example script:Bashpython3 example.py
  3. The program will start and prompt you to hold a card near the reader.
  4. 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.

We don't track you. No ads, no unnecessary cookies, no Facebook pixel stuff. Consider becoming a member instead to support Pollux Labs.