Unit E - College Practice

30. MFRC522 RFID Access Log (VMA202)

Allow listed cards to unlock a solenoid while every scan is logged to the Velleman VMA202 SD card.

Estimated time 5-6 hours

Learning outcomes

  • Wire MFRC522 on SPI without conflicting with the VMA202 SD CS pin
  • Compare a card UID with an allow list
  • Drive green unlock and red deny feedback
  • Append timestamped access events to an SD log file

Parts and preparation

Uno, MFRC522 RFID reader + tags, Velleman VMA202 data-logging shield with FAT16/FAT32 SD card, green LED, red LED, passive buzzer, MOSFET/transistor solenoid driver with flyback diode, and a suitable solenoid supply.

Before power: inspect wiring, confirm supply voltage and ensure all connected circuits share GND.

Libraries for this lesson

In Arduino IDE 2 open Tools → Manage Libraries…. Search by the Library Manager name and install the package by the exact author below. Similar names from other authors can use different APIs and will break the example.

IncludeLibrary Manager nameAuthorInstall note
SPI.hSPIArduinoBuilt into the Arduino core. No Library Manager install required.
MFRC522.hMFRC522GithubCommunityInstall via Library Manager (Miguel Balboa MFRC522).
SD.hSDArduinoBuilt-in Arduino library. No Library Manager install required.
Wire.hWireArduinoBuilt into the Arduino core. No Library Manager install required.
RTClib.hRTClibAdafruitInstall via Library Manager for VMA202 RTC timestamps.
MFRC522 RFID Access Log (VMA202) instructional connection diagram
Open the diagram for a larger view. Trace every connection before building.

VMA202 pins

The Velleman VMA202 logging shield uses SPI for the SD card: CS = D10, MOSI = D11, MISO = D12, SCK = D13. Its DS1307 RTC uses I2C on A4/SDA and A5/SCL. Do not move the SD CS away from D10 for this shield.

Shared SPI

MFRC522 also uses SPI. Share MOSI/MISO/SCK, but give the RFID module its own chip-select (SDA/SS) on D7 and RST on D9. Keep D10 free for the VMA202 SD CS.

Access logic

Read the card UID, compare it with allowed UIDs, then choose allow or deny. Allowed cards light the green LED and energise the solenoid driver. Denied cards light the red LED and produce three quick buzzer beeps.

Logging

Every scan appends one line to ACCESS.LOG on the SD card with date/time, UID and ALLOW/DENY. Format the card FAT16 or FAT32 before first use.

Solenoid safety

Drive the lock coil with a MOSFET/transistor and flyback diode from an external supply sized for the lock. Join grounds with the Uno. Never power a solenoid from an Uno pin.

Wiring and safe build sequence

  1. Stack VMA202 on the Uno (SD CS D10, MOSI D11, MISO D12, SCK D13, RTC A4/A5)
  2. MFRC522 SDA/SS -> D7
  3. MFRC522 RST -> D9
  4. MFRC522 MOSI -> D11, MISO -> D12, SCK -> D13, 3.3 V -> 3.3 V, GND -> GND
  5. Green LED D3 -> 330 ohm -> LED -> GND
  6. Red LED D4 -> 330 ohm -> LED -> GND
  7. Passive buzzer -> D6
  8. Solenoid driver gate/base -> D5; coil on external supply with diode; commons joined
Power rule: switch off before moving wires. Arduino I/O pins are control signals; high-current loads require a driver and suitable external supply.
#include <SPI.h>
#include <MFRC522.h>
#include <SD.h>
#include <Wire.h>
#include <RTClib.h>

const byte sdCsPin = 10;      // VMA202 SD chip select
const byte rfidSsPin = 7;     // MFRC522 SDA/SS (not D10)
const byte rfidRstPin = 9;
const byte greenLed = 3;
const byte redLed = 4;
const byte solenoidPin = 5;
const byte buzzerPin = 6;

MFRC522 rfid(rfidSsPin, rfidRstPin);
RTC_DS1307 rtc;

// Replace these UIDs with cards scanned from Serial during setup/testing.
byte allowedUid1[] = {0xDE, 0xAD, 0xBE, 0xEF};
byte allowedUid2[] = {0x01, 0x23, 0x45, 0x67};

unsigned long unlockUntil = 0;
unsigned long denyUntil = 0;
unsigned long nextBeepAt = 0;
byte beepsLeft = 0;

bool uidMatches(byte *uid, byte *allowed, byte length) {
  for (byte i = 0; i < length; i++) {
    if (uid[i] != allowed[i]) return false;
  }
  return true;
}

bool isAllowed(byte *uid, byte length) {
  if (length == 4 && uidMatches(uid, allowedUid1, 4)) return true;
  if (length == 4 && uidMatches(uid, allowedUid2, 4)) return true;
  return false;
}

void uidToText(byte *uid, byte length, char *out, byte outSize) {
  byte pos = 0;
  out[0] = 0;
  for (byte i = 0; i < length; i++) {
    if ((pos + 3) >= outSize) break;
    sprintf(&out[pos], "%02X", uid[i]);
    pos += 2;
    if (i + 1 < length && (pos + 1) < outSize) {
      out[pos++] = ':';
      out[pos] = 0;
    }
  }
}

void logEvent(const char *uidText, bool allowed) {
  DateTime now = rtc.now();
  File logFile = SD.open("ACCESS.LOG", FILE_WRITE);
  if (!logFile) {
    Serial.println("SD log open failed");
    return;
  }
  char stamp[20];
  snprintf(stamp, sizeof(stamp), "%04d-%02d-%02d %02d:%02d:%02d",
           now.year(), now.month(), now.day(),
           now.hour(), now.minute(), now.second());
  logFile.print(stamp);
  logFile.print(",");
  logFile.print(uidText);
  logFile.print(",");
  logFile.println(allowed ? "ALLOW" : "DENY");
  logFile.close();
}

void startAllow() {
  digitalWrite(redLed, LOW);
  digitalWrite(greenLed, HIGH);
  digitalWrite(solenoidPin, HIGH);
  noTone(buzzerPin);
  unlockUntil = millis() + 3000;
  denyUntil = 0;
  beepsLeft = 0;
}

void startDeny() {
  digitalWrite(greenLed, LOW);
  digitalWrite(solenoidPin, LOW);
  digitalWrite(redLed, HIGH);
  denyUntil = millis() + 900;
  beepsLeft = 3;
  nextBeepAt = millis();
  unlockUntil = 0;
}

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

  if (unlockUntil != 0 && now >= unlockUntil) {
    digitalWrite(solenoidPin, LOW);
    digitalWrite(greenLed, LOW);
    unlockUntil = 0;
  }

  if (beepsLeft > 0 && now >= nextBeepAt) {
    tone(buzzerPin, 1800, 80);
    beepsLeft--;
    nextBeepAt = now + 180;
  }

  if (denyUntil != 0 && now >= denyUntil) {
    digitalWrite(redLed, LOW);
    noTone(buzzerPin);
    denyUntil = 0;
  }
}

void setup() {
  pinMode(greenLed, OUTPUT);
  pinMode(redLed, OUTPUT);
  pinMode(solenoidPin, OUTPUT);
  pinMode(buzzerPin, OUTPUT);
  digitalWrite(solenoidPin, LOW);

  Serial.begin(9600);
  SPI.begin();
  rfid.PCD_Init();
  Wire.begin();

  if (!rtc.begin()) {
    Serial.println("RTC missing on VMA202");
  }
  if (!rtc.isrunning()) {
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }

  if (!SD.begin(sdCsPin)) {
    Serial.println("VMA202 SD init failed (CS=D10)");
  } else {
    Serial.println("VMA202 SD ready");
  }
  Serial.println("Scan a card to learn its UID, then update allowedUid arrays.");
}

void loop() {
  serviceOutputs();

  if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) {
    return;
  }

  char uidText[24];
  uidToText(rfid.uid.uidByte, rfid.uid.size, uidText, sizeof(uidText));
  Serial.print("UID ");
  Serial.println(uidText);

  bool allowed = isAllowed(rfid.uid.uidByte, rfid.uid.size);
  logEvent(uidText, allowed);
  if (allowed) startAllow();
  else startDeny();

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

How the code works

  1. VMA202 SD chip select is D10; RFID SS must be a different pin (D7 here).
  2. Replace allowedUid1/allowedUid2 with UIDs printed on Serial from your own cards.
  3. Allow holds green LED + solenoid for 3 s using millis. Deny keeps red LED on while three short beeps play.
  4. Set the VMA202 RTC once, then comment out rtc.adjust if you do not want compile-time resets.

Test and record evidence

Expected result: An allowed card unlocks for three seconds with green LED and an ACCESS.LOG ALLOW line. A denied card shows red, beeps three times and logs DENY.

Practical evidence checklist

Common faults and checks
  • SD fails: format FAT16/FAT32, confirm CS=10 and the card is fully seated on the VMA202.
  • RFID silent: check 3.3 V supply to the reader and that SS is D7, not D10.
  • Everything denied: copy the Serial UID into allowedUid1 exactly.
  • Solenoid weak/resets board: use an external coil supply and common GND.
Extension challenge: Store allowed UIDs in EEPROM and add a master card that enrols a new card into the allow list.

Check your understanding

Q1. Which VMA202 pin is SD chip select?

Show answer

D10.

Q2. Why must MFRC522 SS avoid D10?

Show answer

D10 is already used by the VMA202 SD card CS on the shared SPI bus.