To connect an RFID reader to a Raspberry Pi, use the MFRC522 via SPI for cheap weekend prototyping, or the PN532 via I2C for reliable production deployments. The code and wiring guide below targets the Raspberry Pi 4 Model B using the ubiquitous MFRC522 breakout board over the primary SPI0 bus.
While the RC522 is the most popular hobbyist RFID module, pairing it with a Linux-based single-board computer introduces SPI timing and logic-level traps that don't exist on bare-metal microcontrollers like the Arduino. This guide cuts through the generic tutorials to give you exact pin mappings, production-ready Python code with hardware safeguards, and a definitive debugging path for the most common SPI failures.
Decision Tree: Which RFID Module to Pick
Not all RFID modules play nicely with Linux user-space SPI drivers. Use this decision matrix to select the right hardware for your specific build phase.
| Module | Protocol | Frequency | Pi Compatibility | Best For |
|---|---|---|---|---|
| MFRC522 | SPI / I2C / UART | 13.56 MHz | Moderate (SPI timing issues) | Quick prototypes, low-budget builds |
| PN532 | I2C / SPI / UART | 13.56 MHz | Excellent (Native I2C support) | Production, Home Assistant, reliable reads |
| RDM6300 | UART (Serial) | 125 kHz | Excellent (Simple serial stream) | Long-range access control, legacy tags |
Exact Parts List and Hardware Specs
Skip the generic 'RFID kit' bundles on Amazon; they often ship with 5V-only logic shifters that will fry your Pi's GPIO bank. Source these exact components:
- Compute Board: Raspberry Pi 4 Model B (2GB, 4GB, or 8GB). Note: If using a Pi 5, the RP1 chip changes GPIO handling; you must use
rpi-lgpioinstead ofRPi.GPIO. - RFID Module: MFRC522 Breakout (v2.0 preferred, identifiable by the 3.3V LDO regulator on the back).
- Logic Level Converter: BSS138-based Bidirectional Logic Level Converter (4-channel). Essential if your specific RC522 board lacks onboard 3.3V regulation on the MISO line.
- Tags: MIFARE Classic 1K (13.56 MHz). Do not buy 125 kHz EM4100 fobs; they will not trigger the RC522 antenna.
- Wiring: 24 AWG solid core jumper wires (Dupont connectors often cause high-resistance SPI clock failures).
Pin Mapping and the 3.3V Logic Trap
The Raspberry Pi's GPIO pins operate strictly at 3.3V. The MFRC522 chip itself is 3.3V native, but many cheap breakout boards route the 5V VCC pin directly to the logic pull-ups. Never connect the Pi's 3.3V pin to a 5V RFID module's VCC, and never feed 5V back into the Pi's MISO pin.
| RC522 Pin | Pi 4 GPIO (BCM) | Pi 4 Physical Pin | Function |
|---|---|---|---|
| SDA (SS) | 8 (CE0) | 24 | SPI Chip Select |
| SCK | 11 (SCLK) | 23 | SPI Clock |
| MOSI | 10 (MOSI) | 19 | Master Out Slave In |
| MISO | 9 (MISO) | 21 | Master In Slave Out |
| IRQ | None | None | Interrupt (Unused in polling) |
| GND | GND | 25 | Ground Reference |
| RST | 22 | 15 | Hardware Reset |
| 3.3V | 3V3 Power | 1 | Power (Max 150mA) |
Complete Python RFID Read Script
This script targets Raspberry Pi OS Bookworm on the Pi 4. It explicitly initializes the SPI bus at a safe 1MHz clock speed—a critical step that most copy-paste tutorials miss, leading to immediate bus crashes.
Prerequisites: Run sudo apt install python3-spidev python3-rpi.gpio and pip3 install mfrc522.
import spidev
import RPi.GPIO as GPIO
import time
import sys
from mfrc522 import SimpleMFRC522
# --- HARDWARE PIN DEFINITIONS (BCM) ---
SPI_BUS = 0
SPI_DEVICE = 0
RST_PIN = 22
# Target Board: Raspberry Pi 4 Model B
# Pi 5 users: Replace RPi.GPIO with rpi-lgpio due to the RP1 chip architecture.
def verify_and_init_spi():
"""Manually initialize SPI to enforce the 1MHz speed limit."""
try:
spi = spidev.SpiDev()
spi.open(SPI_BUS, SPI_DEVICE)
# CRITICAL: RC522 silicon fails to respond reliably above 1MHz on Linux
spi.max_speed_hz = 1000000
spi.mode = 0
return spi
except Exception as e:
print(f"[FATAL] SPI Initialization failed: {e}")
print("Check if SPI is enabled in raspi-config.")
sys.exit(1)
def setup_gpio():
"""Configure the hardware reset pin."""
GPIO.setmode(GPIO.BCM)
GPIO.setup(RST_PIN, GPIO.OUT, initial=GPIO.HIGH)
# Perform a hard reset pulse to clear any stale RC522 state
GPIO.output(RST_PIN, GPIO.LOW)
time.sleep(0.1)
GPIO.output(RST_PIN, GPIO.HIGH)
time.sleep(0.05)
def main():
setup_gpio()
verify_and_init_spi()
# SimpleMFRC522 inherits the spidev settings if initialized after
reader = SimpleMFRC522()
print("System Ready. Hold a MIFARE Classic tag near the antenna...")
print("Press Ctrl+C to exit.")
try:
while True:
try:
id, text = reader.read()
print(f"Tag Detected -> UID: {id} | Data: {text.strip()}")
time.sleep(2) # Debounce to prevent double-reads
except Exception as read_err:
# Catch transient SPI bus glitches without crashing the loop
print(f"[WARN] Read glitch: {read_err}")
time.sleep(0.5)
except KeyboardInterrupt:
print("\nScan interrupted by user.")
finally:
GPIO.cleanup()
print("GPIO cleaned up. Exiting.")
if __name__ == '__main__':
main()
Debugging: First Three Checks and Exact Error Strings
When working with Raspberry Pi SPI peripherals, user-space Python libraries often fail silently or throw cryptic OS-level errors. If your script crashes, follow this exact decision path.
The Exact Error: OSError: [Errno 121] Remote I/O error
This is the most common failure when running spi.xfer2() on the RC522. It means the Pi sent a clock signal, but the MISO line stayed high/low (no response). Ranked causes:
- SPI Clock Speed Too High: The NXP MFRC522 datasheet specifies a 10MHz max clock, but the silicon on cheap clone boards struggles above 1MHz due to parasitic capacitance on the breadboard. Fix: Ensure
spi.max_speed_hz = 1000000is explicitly set in your code. - SPI Interface Disabled: Bookworm OS disables SPI by default. Fix: Run
sudo raspi-config-> Interface Options -> SPI -> Enable. Alternatively, adddtparam=spi=onto/boot/firmware/config.txtand reboot. - Logic Level Mismatch: If your RC522 board has a 5V LDO but you are powering it from the Pi's 3.3V pin, the MISO output voltage will be too low for the Pi to register as a logic HIGH. Fix: Power the board from the Pi's 5V pin, but ensure you are using a bidirectional logic level converter on the MISO/MOSI/SCK lines.
- Floating Ground: The ground wire between the Pi and the RC522 has high resistance. Fix: Use a multimeter to verify < 1 ohm resistance between the RC522 GND pin and the Pi's GND pin.
The First Three Things to Check When It Fails
If you aren't seeing the Errno 121 error but the reader simply returns None or hangs:
- Verify the Tag Type: The RC522 only reads 13.56 MHz tags (MIFARE, NTAG215). It will physically ignore 125 kHz HID or EM4100 fobs. Test with the credit-card style tags that came with the module.
- Check the Antenna Solder Joints: The PCB trace antenna on clone boards often has micro-fractures at the solder pads. Inspect with a magnifying glass and reflow the two antenna pins if necessary.
- Measure VCC under Load: When the RC522 powers up its RF field, it draws up to 150mA. If your Pi's power supply is marginal, the 3.3V rail will brownout, resetting the chip mid-read. Measure the VCC pin with a multimeter while a tag is present; it should not drop below 3.1V.
Simplifying or Extending Your RFID Build
Once you have a stable read loop, you need to decide how this fits into your broader project architecture.
How to Simplify (The USB Alternative)
If you are tired of debugging SPI timing and just need to log tag UIDs into a Python script or database, abandon the GPIO entirely. Buy a 125kHz USB RFID Reader (often sold as 'HID ProxKey' emulators, ~$12). These plug into the Pi's USB port and emulate a standard HID keyboard. You can read tags using Python's evdev library or even a simple bash script reading from /dev/input/eventX. This eliminates all hardware wiring and logic-level risks.
How to Extend (MQTT and Home Assistant)
For a smart home access control system, polling a local Python script isn't enough. You need to push the UID to a central broker.
- Install the
paho-mqttPython library. - Inside the
while Trueloop, after a successful read, publish the UID to an MQTT topic:client.publish('home/rfid/front_door', id). - Configure the Home Assistant MQTT integration to listen to that topic and trigger an automation (e.g., unlock a relay, log the event, or send a Telegram notification).
By enforcing strict SPI clock limits and respecting the 3.3V logic boundary, your Raspberry Pi RFID reader will transition from a frustrating weekend debugging session into a reliable, always-on access control node.






