🧑🎓 Project: RFID-Based Attendance System Using Raspberry Pi Pico | RFID Based Attendance System Using Raspberry Pi Pico (Easy Guide)
🧾 Introduction
In this fun and useful project, we’ll build an RFID-based attendance system using:
- Raspberry Pi Pico
- MFRC522 RFID Reader
- RFID cards or keychains
- Green and Red LEDs
When a student taps their card, their name is displayed, and they are marked as present. A green LED will blink as confirmation.
🎯 What You Will Learn
- How RFID works
- How to connect MFRC522 with Raspberry Pi Pico
- How to read UID from an RFID card
- How to identify students using UID
- How to use LEDs for feedback
- How to write simple Python code for attendance
🧰 Components Required
Component | Quantity |
---|---|
Raspberry Pi Pico | 1 |
MFRC522 RFID Reader | 1 |
RFID Tags or Cards | 2+ |
Green LED | 1 |
Red LED | 1 |
Resistors (220Ω) | 2 |
Breadboard & Jumpers | 1 set |
USB cable (Micro B) | 1 |
🔌 Circuit Connections
📟 MFRC522 to Raspberry Pi Pico
RFID Pin | Pico Pin |
---|---|
RST | GP0 |
SDA | GP1 |
SCK | GP2 |
MOSI | GP3 |
MISO | GP4 |
GND | GND |
VCC | 3.3V |
🔴🟢 LEDs
LED Color | Pico Pin | Notes |
---|---|---|
Green | GP14 | Success LED |
Red | GP15 | Idle Blink |
Use a 220Ω resistor with each LED.
💻 Install MicroPython & Thonny
- Download Thonny IDE
- Flash MicroPython firmware to your Pico (Thonny: Tools > Options > Interpreter)
- Select
MicroPython (Raspberry Pi Pico)
as interpreter.
📦 Upload mfrc522.py
Library
Before using RFID, create a file named mfrc522.py
and copy-paste the working library code into it.
Save this file to Raspberry Pi Pico using Thonny.
the full working mfrc522.py
code :
from machine import Pin, SPI
from os import uname
class MFRC522:
DEBUG = False
OK = 0
NOTAGERR = 1
ERR = 2
NTAG_213 = 213
NTAG_215 = 215
NTAG_216 = 216
NTAG_NONE = 0
REQIDL = 0x26
REQALL = 0x52
AUTHENT1A = 0x60
AUTHENT1B = 0x61
PICC_ANTICOLL1 = 0x93
PICC_ANTICOLL2 = 0x95
PICC_ANTICOLL3 = 0x97
def __init__(self, sck, mosi, miso, rst, cs,baudrate=1000000,spi_id=0):
self.sck = Pin(sck, Pin.OUT)
self.mosi = Pin(mosi, Pin.OUT)
self.miso = Pin(miso)
self.rst = Pin(rst, Pin.OUT)
self.cs = Pin(cs, Pin.OUT)
self.rst.value(0)
self.cs.value(1)
self.NTAG = 0
self.NTAG_MaxPage = 0
board = uname()[0]
if board == 'WiPy' or board == 'LoPy' or board == 'FiPy':
self.spi = SPI(0)
self.spi.init(SPI.MASTER, baudrate=1000000, pins=(self.sck, self.mosi, self.miso))
elif (board == 'esp8266') or (board == 'esp32'):
self.spi = SPI(baudrate=100000, polarity=0, phase=0, sck=self.sck, mosi=self.mosi, miso=self.miso)
self.spi.init()
elif board == 'rp2':
self.spi = SPI(spi_id,baudrate=baudrate,sck=self.sck, mosi= self.mosi, miso= self.miso)
else:
raise RuntimeError("Unsupported platform")
self.rst.value(1)
self.init()
def _wreg(self, reg, val):
self.cs.value(0)
self.spi.write(b'%c' % int(0xff & ((reg << 1) & 0x7e)))
self.spi.write(b'%c' % int(0xff & val))
self.cs.value(1)
def _rreg(self, reg):
self.cs.value(0)
self.spi.write(b'%c' % int(0xff & (((reg << 1) & 0x7e) | 0x80)))
val = self.spi.read(1)
self.cs.value(1)
return val[0]
def _sflags(self, reg, mask):
self._wreg(reg, self._rreg(reg) | mask)
def _cflags(self, reg, mask):
self._wreg(reg, self._rreg(reg) & (~mask))
def _tocard(self, cmd, send):
recv = []
bits = irq_en = wait_irq = n = 0
stat = self.ERR
if cmd == 0x0E:
irq_en = 0x12
wait_irq = 0x10
elif cmd == 0x0C:
irq_en = 0x77
wait_irq = 0x30
self._wreg(0x02, irq_en | 0x80)
self._cflags(0x04, 0x80)
self._sflags(0x0A, 0x80)
self._wreg(0x01, 0x00)
for c in send:
self._wreg(0x09, c)
self._wreg(0x01, cmd)
if cmd == 0x0C:
self._sflags(0x0D, 0x80)
i = 2000
while True:
n = self._rreg(0x04)
i -= 1
if ~((i != 0) and ~(n & 0x01) and ~(n & wait_irq)):
break
self._cflags(0x0D, 0x80)
if i:
if (self._rreg(0x06) & 0x1B) == 0x00:
stat = self.OK
if n & irq_en & 0x01:
stat = self.NOTAGERR
elif cmd == 0x0C:
n = self._rreg(0x0A)
lbits = self._rreg(0x0C) & 0x07
if lbits != 0:
bits = (n - 1) * 8 + lbits
else:
bits = n * 8
if n == 0:
n = 1
elif n > 16:
n = 16
for _ in range(n):
recv.append(self._rreg(0x09))
else:
stat = self.ERR
return stat, recv, bits
def _crc(self, data):
self._cflags(0x05, 0x04)
self._sflags(0x0A, 0x80)
for c in data:
self._wreg(0x09, c)
self._wreg(0x01, 0x03)
i = 0xFF
while True:
n = self._rreg(0x05)
i -= 1
if not ((i != 0) and not (n & 0x04)):
break
return [self._rreg(0x22), self._rreg(0x21)]
def init(self):
self.reset()
self._wreg(0x2A, 0x8D)
self._wreg(0x2B, 0x3E)
self._wreg(0x2D, 30)
self._wreg(0x2C, 0)
self._wreg(0x15, 0x40)
self._wreg(0x11, 0x3D)
self.antenna_on()
def reset(self):
self._wreg(0x01, 0x0F)
def antenna_on(self, on=True):
if on and ~(self._rreg(0x14) & 0x03):
self._sflags(0x14, 0x03)
else:
self._cflags(0x14, 0x03)
def request(self, mode):
self._wreg(0x0D, 0x07)
(stat, recv, bits) = self._tocard(0x0C, [mode])
if (stat != self.OK) | (bits != 0x10):
stat = self.ERR
return stat, bits
def anticoll(self,anticolN):
ser_chk = 0
ser = [anticolN, 0x20]
self._wreg(0x0D, 0x00)
(stat, recv, bits) = self._tocard(0x0C, ser)
if stat == self.OK:
if len(recv) == 5:
for i in range(4):
ser_chk = ser_chk ^ recv[i]
if ser_chk != recv[4]:
stat = self.ERR
else:
stat = self.ERR
return stat, recv
def PcdSelect(self, serNum,anticolN):
backData = []
buf = []
buf.append(anticolN)
buf.append(0x70)
#i = 0
###xorsum=0;
for i in serNum:
buf.append(i)
#while i<5:
# buf.append(serNum[i])
# i = i + 1
pOut = self._crc(buf)
buf.append(pOut[0])
buf.append(pOut[1])
(status, backData, backLen) = self._tocard( 0x0C, buf)
if (status == self.OK) and (backLen == 0x18):
return 1
else:
return 0
def SelectTag(self, uid):
byte5 = 0
#(status,puid)= self.anticoll(self.PICC_ANTICOLL1)
#print("uid",uid,"puid",puid)
for i in uid:
byte5 = byte5 ^ i
puid = uid + [byte5]
if self.PcdSelect(puid,self.PICC_ANTICOLL1) == 0:
return (self.ERR,[])
return (self.OK , uid)
def tohexstring(self,v):
s="["
for i in v:
if i != v[0]:
s = s+ ", "
s=s+ "0x{:02X}".format(i)
s= s+ "]"
return s
def SelectTagSN(self):
valid_uid=[]
(status,uid)= self.anticoll(self.PICC_ANTICOLL1)
#print("Select Tag 1:",self.tohexstring(uid))
if status != self.OK:
return (self.ERR,[])
if self.DEBUG: print("anticol(1) {}".format(uid))
if self.PcdSelect(uid,self.PICC_ANTICOLL1) == 0:
return (self.ERR,[])
if self.DEBUG: print("pcdSelect(1) {}".format(uid))
#check if first byte is 0x88
if uid[0] == 0x88 :
#ok we have another type of card
valid_uid.extend(uid[1:4])
(status,uid)=self.anticoll(self.PICC_ANTICOLL2)
#print("Select Tag 2:",self.tohexstring(uid))
if status != self.OK:
return (self.ERR,[])
if self.DEBUG: print("Anticol(2) {}".format(uid))
rtn = self.PcdSelect(uid,self.PICC_ANTICOLL2)
if self.DEBUG: print("pcdSelect(2) return={} uid={}".format(rtn,uid))
if rtn == 0:
return (self.ERR,[])
if self.DEBUG: print("PcdSelect2() {}".format(uid))
#now check again if uid[0] is 0x88
if uid[0] == 0x88 :
valid_uid.extend(uid[1:4])
(status , uid) = self.anticoll(self.PICC_ANTICOLL3)
#print("Select Tag 3:",self.tohexstring(uid))
if status != self.OK:
return (self.ERR,[])
if self.DEBUG: print("Anticol(3) {}".format(uid))
if self.PcdSelect(uid,self.PICC_ANTICOLL3) == 0:
return (self.ERR,[])
if self.DEBUG: print("PcdSelect(3) {}".format(uid))
valid_uid.extend(uid[0:5])
# if we are here than the uid is ok
# let's remove the last BYTE whic is the XOR sum
return (self.OK , valid_uid[:len(valid_uid)-1])
#return (self.OK , valid_uid)
def auth(self, mode, addr, sect, ser):
return self._tocard(0x0E, [mode, addr] + sect + ser[:4])[0]
def authKeys(self,uid,addr,keyA=None, keyB=None):
status = self.ERR
if keyA is not None:
status = self.auth(self.AUTHENT1A, addr, keyA, uid)
elif keyB is not None:
status = self.auth(self.AUTHENT1B, addr, keyB, uid)
return status
def stop_crypto1(self):
self._cflags(0x08, 0x08)
def read(self, addr):
data = [0x30, addr]
data += self._crc(data)
(stat, recv, _) = self._tocard(0x0C, data)
return stat, recv
def write(self, addr, data):
buf = [0xA0, addr]
buf += self._crc(buf)
(stat, recv, bits) = self._tocard(0x0C, buf)
if not (stat == self.OK) or not (bits == 4) or not ((recv[0] & 0x0F) == 0x0A):
stat = self.ERR
else:
buf = []
for i in range(16):
buf.append(data[i])
buf += self._crc(buf)
(stat, recv, bits) = self._tocard(0x0C, buf)
if not (stat == self.OK) or not (bits == 4) or not ((recv[0] & 0x0F) == 0x0A):
stat = self.ERR
return stat
def writeSectorBlock(self,uid, sector, block, data, keyA=None, keyB = None):
absoluteBlock = sector * 4 + (block % 4)
if absoluteBlock > 63 :
return self.ERR
if len(data) != 16:
return self.ERR
if self.authKeys(uid,absoluteBlock,keyA,keyB) != self.ERR :
return self.write(absoluteBlock, data)
return self.ERR
def readSectorBlock(self,uid ,sector, block, keyA=None, keyB = None):
absoluteBlock = sector * 4 + (block % 4)
if absoluteBlock > 63 :
return self.ERR, None
if self.authKeys(uid,absoluteBlock,keyA,keyB) != self.ERR :
return self.read(absoluteBlock)
return self.ERR, None
def MFRC522_DumpClassic1K(self,uid, Start=0, End=64, keyA=None, keyB=None):
for absoluteBlock in range(Start,End):
status = self.authKeys(uid,absoluteBlock,keyA,keyB)
# Check if authenticated
print("{:02d} S{:02d} B{:1d}: ".format(absoluteBlock, absoluteBlock//4 , absoluteBlock % 4),end="")
if status == self.OK:
status, block = self.read(absoluteBlock)
if status == self.ERR:
break
else:
for value in block:
print("{:02X} ".format(value),end="")
print(" ",end="")
for value in block:
if (value > 0x20) and (value < 0x7f):
print(chr(value),end="")
else:
print('.',end="")
print("")
else:
break
if status == self.ERR:
print("Authentication error")
return self.ERR
return self.OK
def MFRC522_Dump_NTAG(self,Start=0, End=135):
for absoluteBlock in range(Start,End,4):
MaxIndex = 4 * 135
status = self.OK
print("Page {:02d}: ".format(absoluteBlock),end="")
if status == self.OK:
status, block = self.read(absoluteBlock)
if status == self.ERR:
break
else:
Index = absoluteBlock*4
for i in range(len(block)):
if Index < MaxIndex :
print("{:02X} ".format(block[i]),end="")
else:
print(" ",end="")
if (i%4)==3:
print(" ",end="")
Index+=1
print(" ",end="")
Index = absoluteBlock*4
for value in block:
if Index < MaxIndex:
if (value > 0x20) and (value < 0x7f):
print(chr(value),end="")
else:
print('.',end="")
Index+=1
print("")
else:
break
if status == self.ERR:
print("Authentication error")
return self.ERR
return self.OK
def writeNTAGPage(self,page,data):
if page>self.NTAG_MaxPage:
return self.ERR
if page < 4:
return self.ERR
if len(data) != 4:
return self.ERR
return self.write(page,data+[0]*12)
def getNTAGVersion(self):
buf = [0x60]
buf += self._crc(buf)
stat, recv,_ = self._tocard(0x0C, buf)
return stat, recv
#Version NTAG213 = [0x0 ,0x4, 0x4, 0x2, 0x1, 0x0,0x0f, 0x3]
#Version NTAG215 = [0x0 ,0x4, 0x4, 0x2, 0x1, 0x0,0x11, 0x3]
#Version NTAG216 = [0x0 ,0x4, 0x4, 0x2, 0x1, 0x0,0x13, 0x3]
def IsNTAG(self):
self.NTAG = self.NTAG_NONE
self.NTAG_MaxPage=0
(stat , rcv) = self.getNTAGVersion()
if stat == self.OK:
if len(rcv) < 8:
return False #do we have at least 8 bytes
if rcv[0] != 0:
return False #check header
if rcv[1] != 4:
return False #check Vendor ID
if rcv[2] != 4:
return False #check product type
if rcv[3] != 2:
return False #check subtype
if rcv[7] != 3:
return False #check protocol
if rcv[6] == 0xf:
self.NTAG= self.NTAG_213
self.NTAG_MaxPage = 44
return True
if rcv[6] == 0x11:
self.NTAG= self.NTAG_215
self.NTAG_MaxPage = 134
return True
if rcv[7] == 0x13:
self.NTAG= self.NTAG_216
self.NTAG_MaxPage = 230
return True
return False
save above code as mfrc522.py in your pico, not your computer.
🐍 Final Working Code (Attendance System)
Save this as main.py
on your Pico:
from mfrc522 import MFRC522
from machine import Pin
import utime
# Initialize RFID
rdr = MFRC522(
sck=2,
mosi=3,
miso=4,
rst=0,
cs=1,
baudrate=100000
)
# LED setup
green_led = Pin(14, Pin.OUT)
red_led = Pin(15, Pin.OUT)
green_led.off()
red_led.off()
# Registered UID database (replace with your own UID lists)
students = {
"C345B514": "PULKIT KUMAR SINHA",
"11223344": "Bob",
"DEADBEEF": "Charlie"
}
marked_present = []
print("📡 RFID Attendance System Ready!")
while True:
(status, tag_type) = rdr.request(rdr.REQIDL)
if status == rdr.OK:
(status, uid_bytes) = rdr.SelectTagSN()
if status == rdr.OK:
uid_str = ''.join(['%02X' % b for b in uid_bytes])
print("Card UID:", uid_str)
if uid_str in students:
name = students[uid_str]
if name not in marked_present:
marked_present.append(name)
time_str = "%02d:%02d:%02d" % utime.localtime()[3:6]
print(f"✅ {name} marked present at {time_str}")
# Green LED blink
for _ in range(2):
green_led.on()
utime.sleep(0.3)
green_led.off()
utime.sleep(0.3)
else:
print(f"ℹ️ {name} already marked present.")
else:
print("❌ Unknown UID")
else:
print("Error reading UID")
else:
# Blink red LED to show system is waiting
red_led.on()
utime.sleep(0.1)
red_led.off()
utime.sleep(0.1)
✅ FULL CODE to Find UID of RFID Card with Raspberry Pi Pico + MFRC522
Use this when you want to scan a card and find its UID so you can add it to your attendance system.
🐍 uid_reader.py
(Save and Run in Thonny)
from mfrc522 import MFRC522
from machine import Pin
import utime
# Initialize the MFRC522 with your working setup
rdr = MFRC522(
sck=2, # SCK → GP2
mosi=3, # MOSI → GP3
miso=4, # MISO → GP4
rst=0, # RST → GP0
cs=1, # SDA/CS → GP1
baudrate=100000
)
print("📡 RFID Reader Ready. Place a card near the reader.")
while True:
(status, tag_type) = rdr.request(rdr.REQIDL)
if status == rdr.OK:
print("🟠 Card detected!")
(status, uid) = rdr.SelectTagSN()
if status == rdr.OK:
uid_str = ''.join(['%02X' % b for b in uid])
print("✅ Card UID:", uid_str)
print("👉 Copy this UID and paste it into your attendance system.")
else:
print("❌ Failed to read UID.")
utime.sleep(2) # Avoid multiple detections
📌 How to Use:
- Upload
mfrc522.py
to your Raspberry Pi Pico if not already uploaded. - Save the above code as
uid_reader.py
on your Pico using Thonny. - Run the script in Thonny.
- Tap your RFID card near the reader.
- You’ll see the UID printed like:
✅ Card UID: C345B514
✂️ What to Do with This UID?
Copy it and paste into your students
dictionary like this:
students = {
"C345B514": "Alice",
"12345678": "Bob"
}
📝 Add Your Students
Replace the UIDs in the students
dictionary:
students = {
"C345B514": "Alice",
"11223344": "Bob"
}
Use the UID printed by your card.
🧠 How It Works
request()
checks if a card is present.SelectTagSN()
reads the UID.- UID is matched to a name in the
students
dictionary. - If matched → marked as present.
- Green LED blinks as confirmation.
- Red LED keeps blinking while idle.
🎁 Extra Ideas
- Save attendance to a file on the Pico
- Show names on an OLED screen
- Add a buzzer beep on card tap
- Store attendance per date
Let me know in comment if you want help with any of these upgrades!