pi-rc522 consists of two Python classes for controlling an SPI RFID module "RC522" using Raspberry Pi or Beaglebone Black. You can get this module on AliExpress or Ebay for $3.
This fork makes the library compatible with recent kernels (current Raspberry Pi OS based on Debian bookworm).
The original library uses RPi.GPIO, which has not been updated to work with /sys/class/gpio-less kernels yet.
The fork uses gpiozero, which works properly.
This fork therefore fixes the RuntimeError: Failed to add edge detection
crash when using the IRQ pin.
Based on lmurach's pi-rc522, which is based on MFRC522-python.
Install using pip:
pip install pi-rc522
Or get source code from Github:
git clone https://github.com/ondryaso/pi-rc522.git
cd pi-rc522
python setup.py install
You'll also need to install the gpiozero, spidev and RPi.GPIO libraries on Raspberry PI. Beaglebone Black is untested in this fork (might work via gpiozero?).
MIFARE datasheet can be useful.
Classic 1K MIFARE tag has 16 sectors, each contains 4 blocks. Each block has 16 bytes. All this stuff is indexed - you must count from zero. The library uses "block addresses", which are positions of blocks - so block address 5 is second block of second sector, thus it's block 1 of sector 1 (indexes). Block addresses 0, 1, 2, 3 are from the first sector - sector 0. Block addresses 4, 5, 6, 7 are from the second sector - sector 1, and so on. You should not write to first block - S0B0, because it contains manufacturer data. Each sector has it's sector trailer, which is located at it's last block - block 3. This block contains keys and access bits for corresponding sector. For more info, look at page 10 of the datasheet. You can use this useful utility to calculate access bits.
Connecting RC522 module to SPI is pretty easy. You can use this neat website for reference.
Board pin name | Board pin | Physical RPi pin | RPi pin name | Beaglebone Black pin name |
---|---|---|---|---|
SDA | 1 | 24 | GPIO8, CE0 | P9_17, SPI0_CS0 |
SCK | 2 | 23 | GPIO11, SCKL | P9_22, SPI0_SCLK |
MOSI | 3 | 19 | GPIO10, MOSI | P9_18, SPI0_D1 |
MISO | 4 | 21 | GPIO9, MISO | P9_21, SPI0_D0 |
IRQ | 5 | 18 | GPIO24 | P9_15, GPIO_48 |
GND | 6 | 6, 9, 20, 25 | Ground | Ground |
RST | 7 | 22 | GPIO25 | P9_23, GPIO_49 |
3.3V | 8 | 1,17 | 3V3 | VDD_3V3 |
You can also connect the SDA pin to CE1 (GPIO7, pin #26) and call the RFID constructor with bus=0, device=1 and you can connect RST pin to any other free GPIO pin and call the constructor with pin_rst=BOARD numbering pin. Furthermore, the IRQ pin is configurable by passing pin_irq=BOARD numbering pin.
NOTE: For RPi A+/B+/2/3 with 40 pin connector, SPI1/2 is available on top of SPI0. Kernel 4.4.x or higher and dtoverlay configuration is required. For SPI1/2, pin_ce=BOARD numbering pin is required.
NOTE: On Beaglebone Black, use pin names (e.g. "P9_17"
).
NOTE: On Beaglebone Black, generally you have to enable the SPI for the spidev device to show up; you can enable SPI0 by doing echo BB-SPIDEV0 > /sys/devices/bone_capemgr.9/slots
. SPI1 is available only if you disable HDMI.
NOTE: If you are not using IRQ, you can pass pin_irq = None
to the constructor.
You may change BOARD pinout to BCM py passing pin_mode=RPi.GPIO.BCM. Please note, that you then have to define all pins (irq+rst, ce if neccessary). Otherwise they would default to perhaps wrong pins (rst to pin 15/GPIO22, irq to pin 12/GPIO18).
The library is split to two classes - RFID and RFIDUtil. You can use only RFID, RFIDUtil just makes life a little bit better. You basically want to start with while True loop and "poll" the tag state. That's done using request method. Most of the methods return error state, which is simple boolean - True is error, False is not error. The request method returns True if tag is not present. If request is successful, you should call anticoll method. It runs anti-collision algorithms and returns used tag UID, which you'll use for select_tag method. Now you can do whatever you want. Important methods are documented. You can also look at the Read and KeyChange examples for RFIDUtil usage.
from pirc522 import RFID
rdr = RFID()
try:
while True:
rdr.wait_for_tag()
(error, tag_type) = rdr.request()
if not error:
print("Tag detected")
(error, uid) = rdr.anticoll()
if not error:
print("UID: " + str(uid))
# Select Tag is required before Auth
if not rdr.select_tag(uid):
# Auth for block 10 (block 2 of sector 2) using default shipping key A
if not rdr.card_auth(rdr.auth_a, 10, [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], uid):
# This will print something like (False, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
print("Reading block 10: " + str(rdr.read(10)))
# Always stop crypto1 when done working
rdr.stop_crypto()
except KeyboardInterrupt:
# Graceful shutdown
rdr.cleanup()
To retrieve tag UID in either 4 or 7 byte form, you can use:
from pirc522 import RFID
rdr = RFID()
while True:
rdr.wait_for_tag()
uid = rdr.read_id(as_number = True)
if uid is not None:
print(f'UID: {uid:X}')
# Graceful shutdown
reader.cleanup()
RFIDUtil contains a few useful methods for dealing with tags.
from pirc522 import RFID
import signal
import time
rdr = RFID()
util = rdr.util()
# Set util debug to true - it will print what's going on
util.debug = True
while True:
# Wait for tag
rdr.wait_for_tag()
# Request tag
(error, data) = rdr.request()
if not error:
print("\nDetected")
(error, uid) = rdr.anticoll()
if not error:
# Print UID
print("Card read UID: "+str(uid[0])+","+str(uid[1])+","+str(uid[2])+","+str(uid[3]))
# Set tag as used in util. This will call RFID.select_tag(uid)
util.set_tag(uid)
# Save authorization info (key B) to util. It doesn't call RFID.card_auth(), that's called when needed
util.auth(rdr.auth_b, [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
# Print contents of block 4 in format "S1B0: [contents in decimal]". RFID.card_auth() will be called now
util.read_out(4)
# Print it again - now auth won't be called, because it doesn't have to be
util.read_out(4)
# Print contents of different block - S1B2 - RFID.card_auth() will be called again
util.read_out(6)
# We can change authorization info if you have different key in other sector
util.auth(rdr.auth_a, [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
#If you want to use methods from RFID itself, you can use this for authorization
# This will authorize for block 1 of sector 2 -> block 9
# This is once more called only if it's not already authorized for this block
util.do_auth(util.block_addr(2, 1))
# Now we can do some "lower-level" stuff with block 9
rdr.write(9, [0x01, 0x23, 0x45, 0x67, 0x89, 0x98, 0x76, 0x54, 0x32, 0x10, 0x69, 0x27, 0x46, 0x66, 0x66, 0x64])
# We can rewrite specific bytes in block using this method. None means "don't change this byte"
# Note that this won't do authorization, because we've already called do_auth for block 9
util.rewrite(9, [None, None, 0xAB, 0xCD, 0xEF])
# This will write S2B1: [0x01, 0x23, 0xAB, 0xCD, 0xEF, 0x98, 0x76......] because we've rewritten third, fourth and fifth byte
util.read_out(9)
# Let's see what do we have in whole tag
util.dump()
# We must stop crypto
util.deauth()