🧑‍🎓 Project: RFID-Based Attendance System Using Raspberry Pi Pico | RFID Based Attendance System Using Raspberry Pi Pico (Easy Guide)

🧑‍🎓 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

ComponentQuantity
Raspberry Pi Pico1
MFRC522 RFID Reader1
RFID Tags or Cards2+
Green LED1
Red LED1
Resistors (220Ω)2
Breadboard & Jumpers1 set
USB cable (Micro B)1

🔌 Circuit Connections

📟 MFRC522 to Raspberry Pi Pico

RFID PinPico Pin
RSTGP0
SDAGP1
SCKGP2
MOSIGP3
MISOGP4
GNDGND
VCC3.3V

🔴🟢 LEDs

LED ColorPico PinNotes
GreenGP14Success LED
RedGP15Idle Blink

Use a 220Ω resistor with each LED.


💻 Install MicroPython & Thonny

  1. Download Thonny IDE
  2. Flash MicroPython firmware to your Pico (Thonny: Tools > Options > Interpreter)
  3. 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:

  1. Upload mfrc522.py to your Raspberry Pi Pico if not already uploaded.
  2. Save the above code as uid_reader.py on your Pico using Thonny.
  3. Run the script in Thonny.
  4. Tap your RFID card near the reader.
  5. 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!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *