MicroPython RFID on a Pico: write a card and read it back
MicroPython on a Raspberry Pi Pico: text typed in the Serial Monitor is written to block 8 of a MIFARE card on an RC522 reader and read back. 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
- Writing a 16 byte block on a MIFARE card
- Reading a block back to check a write
- Waiting in a loop until a card appears
- Taking typed input with
input - Using SoftSPI on any pins of a Pico
- 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. The reader runs on 3.3 V.
The script asks for text in the Serial Monitor, up to 16 characters. Then it prints 'Lay a card on the reader' and keeps looking until a card answers.
It logs in to block 8 with the factory key, writes your text there, reads the block back and prints both. If they match, the card really stored it. Then it asks for new text.
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.
# Write to a card and read it back: type up to 16 characters in the Serial
# Monitor, lay a card on the reader, and the text goes into block 8; it is
# then read back to check it.
from machine import Pin, SoftSPI
import time
from mfrc522 import MFRC522, OK
Pin(6, Pin.OUT, value=1)
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
def card():
"""The UID of a card on the reader, selected, or None."""
if reader.request() != OK:
return None
status, uid = reader.anticoll()
if status != OK or reader.select(uid) != OK:
return None
return uid
while True:
text = input("Text for block 8: ")[:16]
print("Lay a card on the reader")
uid = None
while uid is None:
uid = card()
time.sleep_ms(100)
if reader.auth(8, KEY, uid) == OK and reader.write_block(8, text.encode()) == OK:
back = bytes(reader.read_block(8)).rstrip(b"\x00").decode()
print("wrote", repr(text), "read back", repr(back))
else:
print("could not write the card")
reader.stop_crypto()
# 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
Code walkthrough
The key functions of the sketch, and what each one does.
card main.py
Looks for a card and returns its UID, or None. It sends a request, reads the UID with anticoll, and selects the card. If any step fails, it returns None, so the main loop just tries again a tenth of a second later.
def card():
"""The UID of a card on the reader, selected, or None."""
if reader.request() != OK:
return None
status, uid = reader.anticoll()
if status != OK or reader.select(uid) != OK:
return None
return uidTroubleshooting
The usual mistakes with this circuit, and the fix for each.
- It prints could not write the card. This project
Login or the write failed. Keep the card steady on the reader, and check the SPI wires, especially MOSI and MISO. - Only part of your text is stored. This project
A block holds 16 bytes, and the script cuts your text to 16 characters. - The script waits forever after Lay a card on the reader. This project
No card is being detected. Check the wiring and that RST is on GP6. - 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.