MicroPython RFID on a Pico: read a card’s UID, RC522
MicroPython on a Raspberry Pi Pico: an RC522 reader on SoftSPI (GP2 to GP5) prints a MIFARE card’s UID and reads block 8 with the factory key. The whole build, a Raspberry Pi Pico and 1 more part, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Advanced About 20 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $7. On Mokxi they're free.
What you'll learn
- Using SoftSPI on any pins of a Pico
- Waking and selecting a MIFARE card
- What a card's UID is
- Logging in to a block with a key
- Splitting a driver into its own
mfrc522.pyfile
- Difficulty: Advanced
- Time: About 20 minutes
- Board: Raspberry Pi Pico
- Parts: 3
How it works
The RC522 reader is wired to the Pico with software SPI: SCK on GP2, MOSI on GP3, MISO on GP4 and chip select (the SDA pin) on GP5. GP6 goes to RST and is held high so the reader runs. It is powered from 3.3 V.
The script asks the reader every tenth of a second whether a card is in range. When one answers, anticoll returns its UID, and select picks that card out. The four UID bytes print as hex, like 'card UID: 1A 2B 3C 4D'.
It then logs in to block 8 with the factory key KEY, which is six 0xFF bytes, and prints the 16 bytes stored there. After that it waits one second before looking for a card again.
Parts list
3 parts, plus the jumper wires. They retail for about $7; on Mokxi they're free.
- 1 × Raspberry Pi PicoIn Fight Stick Kit.
- 1 × RFID reader, MFRC522In Escape Box Kit.
- 1 × Power, 3.3 V
Kits that can build it
This starter kit has every part in the circuit. Each kit's page lists what else it can build.
Wiring table
Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.
| Part and pin | Connects to |
|---|---|
| RFID reader, MFRC522 pin SCK | Raspberry Pi Pico pin GP2 |
| RFID reader, MFRC522 pin MOSI | Raspberry Pi Pico pin GP3 |
| RFID reader, MFRC522 pin MISO | Raspberry Pi Pico pin GP4 |
| RFID reader, MFRC522 pin SDA | Raspberry Pi Pico pin GP5 |
| RFID reader, MFRC522 pin RST | Raspberry Pi Pico pin GP6 |
| RFID reader, MFRC522 pin GND | Ground |
| RFID reader, MFRC522 pin 3.3V | 3.3 V |
The code
The MicroPython code exactly as the editor opens it. Change a line there and press Run: the board runs it on the official MicroPython firmware, with nothing to compile.
# An RC522 RFID reader on SoftSPI (SCK GP2, MOSI GP3, MISO GP4, CS GP5).
# When a card is laid on it: its UID, then block 8 read with the factory
# key. mfrc522.py, in the next tab, speaks to the reader.
from machine import Pin, SoftSPI
import time
from mfrc522 import MFRC522, OK
Pin(6, Pin.OUT, value=1) # RST high: the reader runs
spi = SoftSPI(baudrate=100000, polarity=0, phase=0, sck=Pin(2), mosi=Pin(3), miso=Pin(4))
reader = MFRC522(spi, cs=5)
KEY = [0xFF] * 6
print("Lay a card on the reader")
while True:
if reader.request() == OK:
status, uid = reader.anticoll()
if status == OK and reader.select(uid) == OK:
print("card UID:", " ".join("%02X" % b for b in uid[:4]))
if reader.auth(8, KEY, uid) == OK:
print("block 8:", reader.read_block(8))
reader.stop_crypto()
time.sleep(1)
time.sleep_ms(100)
# mfrc522.py: an MFRC522 RFID reader on SPI, and MIFARE Classic cards.
# The reader is a set of registers: a byte with the register's address in
# bits 6 to 1 (bit 7 set to read), then the data. Talking to a card is
# filling the reader's FIFO, starting a command and reading the answer back.
from machine import Pin
import time
OK, NOTAG, ERR = 0, 1, 2
class MFRC522:
def __init__(self, spi, cs):
self.spi = spi
self.cs = Pin(cs, Pin.OUT, value=1)
self.write_reg(0x01, 0x0F) # soft reset
time.sleep_ms(50)
self.write_reg(0x2A, 0x8D) # timer: give up on a silent card
self.write_reg(0x2B, 0x3E)
self.write_reg(0x2D, 30)
self.write_reg(0x2C, 0)
self.write_reg(0x15, 0x40) # 100 % ASK modulation
self.write_reg(0x11, 0x3D) # CRC preset 0x6363
self.set_bits(0x14, 0x03) # antenna on
def write_reg(self, reg, value):
self.cs.value(0)
self.spi.write(bytes([(reg << 1) & 0x7E, value]))
self.cs.value(1)
def read_reg(self, reg):
self.cs.value(0)
self.spi.write(bytes([((reg << 1) & 0x7E) | 0x80]))
value = self.spi.read(1)[0]
self.cs.value(1)
return value
def set_bits(self, reg, mask):
self.write_reg(reg, self.read_reg(reg) | mask)
def clear_bits(self, reg, mask):
self.write_reg(reg, self.read_reg(reg) & ~mask)
def command(self, cmd, data):
"""Run Transceive (0x0C) or MFAuthent (0x0E) on data; (status, bytes back, bits back)."""
wait = 0x30 if cmd == 0x0C else 0x10
self.write_reg(0x04, 0x7F) # clear every interrupt flag
self.set_bits(0x0A, 0x80) # empty the FIFO
self.write_reg(0x01, 0x00) # idle
for b in data:
self.write_reg(0x09, b)
self.write_reg(0x01, cmd)
if cmd == 0x0C:
self.set_bits(0x0D, 0x80) # start sending
for _ in range(2000):
irq = self.read_reg(0x04)
if irq & wait or irq & 0x01:
break
self.clear_bits(0x0D, 0x80)
if not irq & wait:
return NOTAG, [], 0
if self.read_reg(0x06) & 0x1B:
return ERR, [], 0
back = []
bits = 0
if cmd == 0x0C:
n = self.read_reg(0x0A)
last = self.read_reg(0x0C) & 0x07
bits = (n - 1) * 8 + last if last else n * 8
back = [self.read_reg(0x09) for _ in range(n)]
return OK, back, bits
def crc(self, data):
self.clear_bits(0x05, 0x04)
self.set_bits(0x0A, 0x80)
for b in data:
self.write_reg(0x09, b)
self.write_reg(0x01, 0x03) # CalcCRC
for _ in range(255):
if self.read_reg(0x05) & 0x04:
break
return [self.read_reg(0x22), self.read_reg(0x21)]
def request(self):
"""Wake a card in the field: OK when one answers."""
self.write_reg(0x0D, 0x07) # REQA is a 7-bit frame
status, back, bits = self.command(0x0C, [0x26])
return OK if status == OK and bits == 0x10 else NOTAG
def anticoll(self):
"""The card's four UID bytes, checked against their XOR byte."""
self.write_reg(0x0D, 0x00)
status, back, _ = self.command(0x0C, [0x93, 0x20])
if status != OK or len(back) != 5 or back[0] ^ back[1] ^ back[2] ^ back[3] != back[4]:
return ERR, []
return OK, back
def select(self, uid5):
frame = [0x93, 0x70] + uid5
status, back, bits = self.command(0x0C, frame + self.crc(frame))
return OK if status == OK and bits == 0x18 else ERR
def auth(self, block, key, uid5):
status, _, _ = self.command(0x0E, [0x60, block] + key + uid5[:4])
if status != OK or not self.read_reg(0x08) & 0x08:
return ERR
return OK
def stop_crypto(self):
self.clear_bits(0x08, 0x08)
def read_block(self, block):
frame = [0x30, block]
status, back, _ = self.command(0x0C, frame + self.crc(frame))
return back[:16] if status == OK and len(back) >= 16 else None
def write_block(self, block, data):
frame = [0xA0, block]
status, back, bits = self.command(0x0C, frame + self.crc(frame))
if status != OK or bits != 4 or back[0] & 0x0F != 0x0A:
return ERR
data = list(data[:16]) + [0] * (16 - len(data))
status, back, bits = self.command(0x0C, data + self.crc(data))
if status != OK or bits != 4 or back[0] & 0x0F != 0x0A:
return ERR
return OK
Troubleshooting
The usual mistakes with this circuit, and the fix for each.
- No card is ever detected. This project
Check SCK on GP2, MOSI on GP3, MISO on GP4 and SDA on GP5. MOSI and MISO are easy to swap. - The UID prints but block 8 does not. This project
The login with the factory key failed. The card may use a different key, so the script skips the read. - The reader seems dead. This project
Check that RST goes to GP6 and the reader has 3.3 V and ground. - The reader is damaged or behaves oddly on 5 V power. RFID reader
The MFRC522 runs on 3.3 V only. Power it from the 3.3 V pin, never 5 V. Ideally divide 5 V signals from an Uno down for SCK, MOSI and SS. - No card is detected. RFID reader
Check SDA (SS), RST, SCK, MOSI and MISO against your board's SPI pins and the constructor in your code. It reads 13.56 MHz cards only, not 125 kHz key fobs. - The Pico does not show up as a drive, or the program will not load. Raspberry Pi Pico
Hold the BOOTSEL button while you plug in USB. A drive named RPI-RP2 appears. Then copy the UF2 file onto it. - A 5 V signal or sensor is wired to a GPIO pin. Raspberry Pi Pico
Pico GPIO pins are 3.3 V only and are not 5 V tolerant. Use a level shifter or a voltage divider for any 5 V output going into the board.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.