Rohan Barnwal
Published © GPL3+

Arduino Uno R4 Wi-Fi Based RFID Attendance System

An Arduino Uno R4 Wi-Fi-powered intelligent, automated RFID attendance system that tracks attendance, timing and summary in real-time!

IntermediateFull instructions provided1 hour112
Arduino Uno R4 Wi-Fi Based RFID Attendance System

Things used in this project

Hardware components

UNO R4 WiFi
Arduino UNO R4 WiFi
×1
Jumper wires (generic)
Jumper wires (generic)
×1
MFRC522 RFID Module and Tag
×1
20x4 I2C LCD
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

Connections

Code

Code

Arduino
/* Beginner-friendly RFID Attendance (RC522 + 20x4 I2C LCD)
   For Arduino Uno R4 Wi-Fi (pinout same as UNO-compatible boards)
   Wiring (Arduino UNO/R4):
     RC522: SDA(SS)=D10, SCK=D13, MOSI=D11, MISO=D12, RST=D9, 3.3V, GND
     I2C LCD: SDA=A4, SCL=A5, VCC=5V, GND=GND

   Changes in this version:
    - Removed duplicate UID entry.
    - Updated student names list to new names.
*/

#include <Wire.h>
#include <SPI.h>
#include <MFRC522.h>
#include <LiquidCrystal_I2C.h>

#define SS_PIN 10   // SDA/SS for RC522
#define RST_PIN 9   // RST for RC522

#define LCD_ADDR 0x27
#define LCD_COLS 20
#define LCD_ROWS 4

MFRC522 rfid(SS_PIN, RST_PIN);
LiquidCrystal_I2C lcd(LCD_ADDR, LCD_COLS, LCD_ROWS);

// teacher UID (uppercase, no spaces)
const char TEACHER_UID[] = "2A7E17B1";

// 10 minutes = 10 * 60 * 1000 milliseconds
const unsigned long ATTENDANCE_LENGTH = 10UL * 60UL * 1000UL;

// debounce time to avoid reading same card many times quickly
const unsigned long DEBOUNCE = 1000UL;

// how long to show a student's info (ms)
const unsigned long SHOW_MS = 1500UL;

// small refresh rate for live timer (ms)
const unsigned long REFRESH_MS = 500UL;

// --- Student UID list and matching names ---
// Duplicate UID removed — ensure all UIDs here are unique (uppercase, no spaces)
const char* uids[] = {
  "E2D2D500",
  "49F1DF00",
  "5C55D500",
  "827ED600",
  "310AF400",
  "D7CFE000",
  "23370EAA",
  "7EB6F300",
  "D784D500"
};

const char* names[] = {
  "Amanjeet Kaur",
  "Siddharth Verma",
  "Meera Patel",
  "Arjun Khanna",
  "Neha Rathi",
  "Vikram Singh",
  "Simran Kaur",
  "Ritik Sharma",
  "Kavya Menon"
};

const int NUM = sizeof(uids) / sizeof(uids[0]);

// attendance state: 0=absent, 1=on time, 2=late
uint8_t attendance[NUM];

// timing vars
unsigned long startTime = 0;
bool windowStarted = false;

// helpers for debounce and display refresh
String lastUID = "";
unsigned long lastUIDTime = 0;
unsigned long lastRefresh = 0;

void setup() {
  Serial.begin(9600);
  while (!Serial) { } // waits for Serial on some boards, harmless on UNO/R4

  SPI.begin();
  rfid.PCD_Init();

  lcd.init();
  lcd.backlight();
  lcd.clear();

  // welcome message
  lcd.setCursor(0, 0);
  lcd.print("Welcome to the");
  lcd.setCursor(0, 1);
  lcd.print("RFID attendance");
  lcd.setCursor(0, 2);
  lcd.print("system");
  delay(2000);

  // start attendance window now
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Ready for attendance");
  startTime = millis();
  windowStarted = true;

  // mark everyone absent initially
  for (int i = 0; i < NUM; i++) attendance[i] = 0;

  // show initial timer immediately
  updateTimerOnLCD();
  Serial.println("Attendance started.");
}

void loop() {
  unsigned long now = millis();

  // update the live countdown at intervals
  if (now - lastRefresh >= REFRESH_MS) {
    updateTimerOnLCD();
    lastRefresh = now;
  }

  // check for a new card
  if (!rfid.PICC_IsNewCardPresent()) return;
  if (!rfid.PICC_ReadCardSerial()) return;

  // build UID string (HEX uppercase, no spaces)
  String uid = "";
  for (byte i = 0; i < rfid.uid.size; i++) {
    if (rfid.uid.uidByte[i] < 0x10) uid += "0";
    uid += String(rfid.uid.uidByte[i], HEX);
  }
  uid.toUpperCase();

  // simple debounce: ignore if same card scanned within DEBOUNCE ms
  if (uid == lastUID && (now - lastUIDTime) < DEBOUNCE) {
    rfid.PICC_HaltA();
    rfid.PCD_StopCrypto1();
    return;
  }
  lastUID = uid;
  lastUIDTime = now;

  Serial.print("Card scanned: ");
  Serial.println(uid);

  // teacher card -> show summary of all students
  if (uid == String(TEACHER_UID)) {
    showSummary();
    rfid.PICC_HaltA();
    rfid.PCD_StopCrypto1();
    updateTimerOnLCD();
    return;
  }

  // find student index
  int idx = findIndex(uid.c_str());
  if (idx < 0) {
    // unknown card
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("Unknown card:");
    lcd.setCursor(0,1);
    lcd.print(uid);
    lcd.setCursor(0,3);
    lcd.print("Not registered");
    Serial.println("Unknown UID - not registered.");
    delay(SHOW_MS);
    updateTimerOnLCD();
    rfid.PICC_HaltA();
    rfid.PCD_StopCrypto1();
    return;
  }

  // if already marked, just display status again
  if (attendance[idx] != 0) {
    showStudent(idx);
    delay(SHOW_MS);
    updateTimerOnLCD();
    rfid.PICC_HaltA();
    rfid.PCD_StopCrypto1();
    return;
  }

  // determine if scan is inside the attendance window
  bool onTime = windowStarted && ((now - startTime) <= ATTENDANCE_LENGTH);
  if (onTime) attendance[idx] = 1; // on time
  else attendance[idx] = 2;        // late

  // show student info
  showStudent(idx);
  delay(SHOW_MS);
  updateTimerOnLCD();

  rfid.PICC_HaltA();
  rfid.PCD_StopCrypto1();
}

// find index of uid in uids[]; return -1 if not found
int findIndex(const char* uid) {
  for (int i = 0; i < NUM; i++) {
    if (strcmp(uids[i], uid) == 0) return i;
  }
  return -1;
}

// show ready screen and live remaining time
void updateTimerOnLCD() {
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Ready for attendance");

  if (windowStarted) {
    long elapsed = (long)(millis() - startTime);
    long remaining = (long)ATTENDANCE_LENGTH - elapsed;
    if (remaining > 0) {
      unsigned long seconds = remaining / 1000UL;
      unsigned int mins = seconds / 60UL;
      unsigned int secs = seconds % 60UL;

      lcd.setCursor(0,1);
      // print "Time left: mm:ss" with leading zero for seconds
      lcd.print("Time left: ");
      if (mins < 10) lcd.print('0');
      lcd.print(mins);
      lcd.print(':');
      if (secs < 10) lcd.print('0');
      lcd.print(secs);
    } else {
      lcd.setCursor(0,1);
      lcd.print("Attendance closed");
    }
  } else {
    lcd.setCursor(0,1);
    lcd.print("Window not set");
  }

  // clear lower lines for neatness
  lcd.setCursor(0,2); lcd.print("                    ");
  lcd.setCursor(0,3); lcd.print("                    ");
}

// show one student's info on LCD
void showStudent(int i) {
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print(names[i]);    // name
  lcd.setCursor(0,1);
  lcd.print("Present");
  lcd.setCursor(0,2);
  if (attendance[i] == 1) {
    lcd.print("ON time");
    Serial.print(names[i]); Serial.println(" -> ON time");
  } else {
    lcd.print("Came late");
    Serial.print(names[i]); Serial.println(" -> Came late");
  }
}

// show one-by-one summary for teacher (Name + Present/Absent + On time/ Late)
void showSummary() {
  for (int i = 0; i < NUM; i++) {
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print(names[i]);
    if (attendance[i] == 0) {
      lcd.setCursor(0,1);
      lcd.print("Absent");
      Serial.print(names[i]); Serial.println(": Absent");
    } else if (attendance[i] == 1) {
      lcd.setCursor(0,1);
      lcd.print("Present");
      lcd.setCursor(0,2);
      lcd.print("ON time");
      Serial.print(names[i]); Serial.println(": Present (ON time)");
    } else {
      lcd.setCursor(0,1);
      lcd.print("Present");
      lcd.setCursor(0,2);
      lcd.print("Late");
      Serial.print(names[i]); Serial.println(": Present (Late)");
    }

    delay(2500); // pause 2.5 seconds per student
  }
}

Credits

Rohan Barnwal
37 projects • 35 followers
Rohan Barnwal - maker, hacker, tech enthusiast. I explore new tech & find innovative solutions. See my projects on hackster.io!

Comments