DIY ELECTRONICSTR

← All projects

RFID Attendance (ESP32 + LCD)

Intermediate40 min

The name of whoever scans their card appears on the LCD and is logged as "in" or "out"; the screen counts how many people are inside. For a club, class or workshop register: it shows the number of a card it does not know, and you add that number with a name to the list in the code. No breadboard needed; the RC522 and the LCD connect straight to the ESP32 with female-female wires.

Build it step by step in the app

What you need

Wiring steps

  1. 1

    Connect the LCD's power (GND and VCC)

    Connect the LCD's power (GND and VCC)

    Do not put the ESP32 on the breadboard: a 30-pin board covers nearly all of it. The board lies on the table and every connection is made straight to its pins with female-female jumper wires. Hold the board with its USB socket facing you; the pin names are printed on the board next to the pins. On the 4-pin header of the I2C module on the back of the LCD, connect GND to the GND pin on the ESP32's left edge, just above VIN, and VCC to the VIN pin at the bottom. VIN gives 5V while the board is plugged into USB; the LCD's text is clear at 5V.

    • ESP32 GND (next to VIN) → LCD GND (black wire)
    • ESP32 VIN (the USB's 5V) → LCD VCC (red wire)
  2. 2

    Connect the LCD's I2C wires (SDA and SCL)

    Connect the LCD's I2C wires (SDA and SCL)

    Connect the LCD's SDA pin to the D21 pin on the ESP32's right edge and its SCL pin to D22. These are the pins the ESP32 uses for I2C; if you use other pins, change SDA_PIN and SCL_PIN in the code too.

    • ESP32 D21 → LCD SDA (yellow wire)
    • ESP32 D22 → LCD SCL (green wire)
  3. 3

    Connect the RC522's power (3.3V and GND)

    Connect the RC522's power (3.3V and GND)

    Most kits ship the reader's 8-pin header loose; you may have to solder it to the board first. Push female-female wires onto the pins: connect 3.3V to the 3V3 pin at the bottom of the ESP32's right edge and GND to the GND pin just above it. CAREFUL: do not connect the RC522 to VIN (5V), it can break the reader; use the 3V3 pin only.

    • ESP32 3V3 → RC522 3.3V (orange wire)
    • ESP32 GND (next to 3V3) → RC522 GND (black wire)
  4. 4

    Connect the RC522's SPI wires

    Connect the RC522's SPI wires

    Connect the reader's SDA pin to the ESP32's D5 pin, SCK to D18, MOSI to D23, MISO to D19 and RST to D4; all of them are on the right edge. IRQ stays empty. D18, D19 and D23 are the ESP32's SPI pins, do not change them; if you use another pin for SDA or RST, change SS_PIN or RST_PIN in the code too.

    • ESP32 D5 → RC522 SDA (white wire)
    • ESP32 D18 → RC522 SCK (brown wire)
    • ESP32 D23 → RC522 MOSI (blue wire)
    • ESP32 D19 → RC522 MISO (purple wire)
    • ESP32 D4 → RC522 RST (grey wire)

Arduino sketch

// Install from the Library Manager:
// "MFRC522" (GithubCommunity) and
// "LiquidCrystal I2C"
// Board: "ESP32 Dev Module"
#include <SPI.h>
#include <MFRC522.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// The RC522's SDA and RST pins; SCK,
// MISO, MOSI are 18, 19, 23 on the ESP32
const int SS_PIN = 5;
const int RST_PIN = 4;
// The LCD's I2C pins
const int SDA_PIN = 21;
const int SCL_PIN = 22;
// The same card again within this (ms)
const unsigned long REPEAT_GUARD = 3000;
// A message stays on screen this long
const unsigned long MESSAGE_TIME = 3000;

// The known cards: number and name.
// A new card's number shows in the Serial
// Monitor; add it here (a name is at most
// 16 letters).
struct Person {
  const char* uid;
  const char* name;
  bool inside;
};
Person people[] = {
  {"01 02 03 04", "Ayse", false},
  {"11 22 33 44", "Burak", false},
  {"55 66 77 88", "Elif", false},
};
const int PEOPLE = sizeof(people) / sizeof(people[0]);

MFRC522 rfid(SS_PIN, RST_PIN);
// I2C address: 0x27 on most, 0x3F on some
LiquidCrystal_I2C lcd(0x27, 16, 2);

String lastUid = "";
unsigned long lastReadAt = 0;
unsigned long messageAt = 0;
bool showingMessage = false;

int insideCount() {
  int count = 0;
  for (int i = 0; i < PEOPLE; i++) {
    if (people[i].inside) count++;
  }
  return count;
}

// The card's number: "01 02 03 04"
String readUid() {
  String uid = "";
  for (byte i = 0; i < rfid.uid.size; i++) {
    if (i > 0) uid += " ";
    if (rfid.uid.uidByte[i] < 0x10) uid += "0";
    uid += String(rfid.uid.uidByte[i], HEX);
  }
  uid.toUpperCase();
  return uid;
}

// The main screen: waiting for a card
void showIdle() {
  lcd.clear();
  lcd.print("Scan your card");
  lcd.setCursor(0, 1);
  lcd.print("Inside: ");
  lcd.print(insideCount());
}

void setup() {
  Serial.begin(115200);
  Wire.begin(SDA_PIN, SCL_PIN);
  lcd.init();
  lcd.backlight();
  SPI.begin();
  rfid.PCD_Init();

  // A reader that does not answer reads
  // as version 0x00 or 0xFF
  byte version = rfid.PCD_ReadRegister(MFRC522::VersionReg);
  if (version == 0x00 || version == 0xFF) {
    Serial.println("RC522 not found, check the wires.");
    lcd.print("No RC522!");
    lcd.setCursor(0, 1);
    lcd.print("Check the wires");
    while (true) {
      delay(1000);
    }
  }
  Serial.println("Attendance ready: scan your card.");
  showIdle();
}

void loop() {
  // When the message is over, main screen
  if (showingMessage && millis() - messageAt > MESSAGE_TIME) {
    showingMessage = false;
    showIdle();
  }
  if (!rfid.PICC_IsNewCardPresent()) return;
  if (!rfid.PICC_ReadCardSerial()) return;
  String uid = readUid();
  rfid.PICC_HaltA();

  // A card left on the reader counts once
  if (uid == lastUid && millis() - lastReadAt < REPEAT_GUARD) {
    return;
  }
  lastUid = uid;
  lastReadAt = millis();
  lcd.clear();
  showingMessage = true;
  messageAt = millis();

  int found = -1;
  for (int i = 0; i < PEOPLE; i++) {
    if (uid == people[i].uid) found = i;
  }
  if (found < 0) {
    lcd.print("Unknown card");
    lcd.setCursor(0, 1);
    lcd.print(uid.substring(0, 16));
    Serial.print("Unknown card: ");
    Serial.println(uid);
    return;
  }

  // A known card: out if it was inside,
  // in if not
  Person& person = people[found];
  person.inside = !person.inside;
  lcd.print(person.name);
  lcd.setCursor(0, 1);
  lcd.print(person.inside ? "IN    " : "OUT   ");
  lcd.print("Inside:");
  lcd.print(insideCount());

  Serial.print(person.name);
  Serial.print(person.inside ? " came in" : " left");
  Serial.print(", now ");
  Serial.print(insideCount());
  Serial.println(" inside");
}

What the Serial Monitor shows

SERIAL MONITOR · 9600 BAUD
Attendance ready: scan your card.
Unknown card: 3A F2 11 9C
Mert came in, now 1 inside
Ayse came in, now 2 inside
Mert left, now 1 inside

If a wire is loose

SERIAL MONITOR · 9600 BAUD
RC522 not found, check the wires.

What you will see

If the ESP32 is not set up in the Arduino IDE yet, once: in File → Preferences write https://espressif.github.io/arduino-esp32/package_esp32_index.json into "Additional boards manager URLs", install "esp32 by Espressif Systems" from Tools → Board → Boards Manager and pick "ESP32 Dev Module" as the board. Install "MFRC522" (GithubCommunity) and "LiquidCrystal I2C" from the Library Manager (a warning about the "AVR architecture" does not matter). Upload the code and open the Serial Monitor at 115200 baud. The LCD says "Scan your card" and "Inside: 0". Hold the kit's white card 1-2 cm over the reader: the code does not know it yet, so the LCD shows "Unknown card" and the card's number, and the Serial Monitor prints a line like "Unknown card: 3A F2 11 9C". Add that number with a name to the people list in the code, for example {"3A F2 11 9C", "Mert", false}, (a name is at most 16 letters) and upload the code again. Now scanning the card shows your name and "IN" on the LCD and the number of people inside goes up by one; scanning the same card again shows "OUT" and the number goes down. After 3 seconds the screen waits for a card again. A card left on the reader is counted once. Ayse, Burak and Elif in the code are examples, you can delete them. When the ESP32 restarts everyone counts as "out"; the ins and outs are not stored. If it says "RC522 not found" and the LCD shows "No RC522!", check the reader's wires, especially 3.3V, SDA (D5), SCK (D18), MOSI (D23) and MISO (D19); if the pin header is not soldered or is loose, the reader does not answer. If the LCD's light is on but there is no text, turn the blue trimmer on the I2C module; if it still shows nothing, change 0x27 in the code to 0x3F. If a card is never read, hold it over the middle of the reader and closer. If uploading says "Connecting..." and fails, hold the ESP32's BOOT button down while it uploads.

In the app, tick off every wire, match the parts to your inventory and pick up where you left off.

Build it step by step in the app