874  RFID Hands-On Labs and Assessment

874.1 Learning Objectives

By the end of this chapter, you will be able to:

  • Build Access Control Systems: Create complete RFID door lock systems with LCD display, LEDs, and logging
  • Develop Inventory Dashboards: Implement web-based inventory management with real-time RFID scanning
  • Apply Security Knowledge: Evaluate RFID system security and select appropriate tag types
  • Select Frequency Bands: Choose LF, HF, or UHF based on application requirements
  • Integrate Transit Systems: Understand NFC payment integration for public transportation
  • Troubleshoot RFID Issues: Diagnose tag detection problems, interference, and read failures

What is this chapter? Complete hands-on projects and knowledge assessment for RFID systems.

Before starting labs: 1. Review RFID Hardware Integration 2. Gather required hardware components 3. Set up development environment (Arduino IDE or Python)

Hardware for Labs:

Lab Hardware Required Estimated Cost
Lab 1 ESP32 + RC522 + LCD + LEDs $15-20
Lab 2 Raspberry Pi + RC522 $40-50

Assessment Focus: - RFID frequency selection - Security vulnerabilities - Payment system design

874.2 Prerequisites

Before diving into this chapter, you should be familiar with:

874.3 Lab 1: ESP32 RFID Access Control System

Build a complete access control system with LCD display, buzzer feedback, and access logging.

Hardware Required: - ESP32 Development Board - RC522 RFID Reader Module (13.56 MHz) - I2C LCD Display (16x2 or 20x4) - Buzzer - Green and Red LEDs - RFID cards/tags - Resistors (220Ω for LEDs, 1kΩ for buzzer)

Wiring:

Component ESP32 Pin
RC522 SDA GPIO 5
RC522 SCK GPIO 18
RC522 MOSI GPIO 23
RC522 MISO GPIO 19
RC522 RST GPIO 4
RC522 3.3V 3.3V
RC522 GND GND
LCD SDA GPIO 21
LCD SCL GPIO 22
Green LED GPIO 25 (via 220Ω resistor)
Red LED GPIO 26 (via 220Ω resistor)
Buzzer GPIO 27 (via 1kΩ resistor)

Complete Code:

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

// Pin definitions
#define SS_PIN 5
#define RST_PIN 4
#define GREEN_LED 25
#define RED_LED 26
#define BUZZER 27

// Initialize components
MFRC522 rfid(SS_PIN, RST_PIN);
LiquidCrystal_I2C lcd(0x27, 16, 2);  // I2C address 0x27, 16 cols, 2 rows

// Authorized UIDs (add your card UIDs here)
String authorizedUIDs[] = {
    "04521AB2",        // Employee card 1
    "04A1B2C3D4E5F6",   // Employee card 2
    "D3A512C4"          // Admin card
};

int numAuthorized = sizeof(authorizedUIDs) / sizeof(authorizedUIDs[0]);

// Access log structure
struct AccessLog {
    String uid;
    String timestamp;
    bool granted;
};

AccessLog accessLog[100];  // Store last 100 access attempts
int logIndex = 0;

void setup() {
    Serial.begin(115200);

    // Initialize SPI and RFID
    SPI.begin();
    rfid.PCD_Init();

    // Initialize LCD
    lcd.init();
    lcd.backlight();
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("RFID Access");
    lcd.setCursor(0, 1);
    lcd.print("Control Ready");

    // Initialize LEDs and buzzer
    pinMode(GREEN_LED, OUTPUT);
    pinMode(RED_LED, OUTPUT);
    pinMode(BUZZER, OUTPUT);

    // Test sequence
    digitalWrite(GREEN_LED, HIGH);
    delay(200);
    digitalWrite(GREEN_LED, LOW);
    digitalWrite(RED_LED, HIGH);
    delay(200);
    digitalWrite(RED_LED, LOW);
    tone(BUZZER, 1000, 100);

    Serial.println("\n=== RFID Access Control System ===");
    Serial.println("System initialized. Waiting for cards...\n");

    delay(2000);
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Scan Your Card");
}

void loop() {
    // Look for new cards
    if (!rfid.PICC_IsNewCardPresent())
        return;

    // Select one of the cards
    if (!rfid.PICC_ReadCardSerial())
        return;

    // Read card UID
    String uid = "";
    for (byte i = 0; i < rfid.uid.size; i++) {
        uid += String(rfid.uid.uidByte[i] < 0x10 ? "0" : "");
        uid += String(rfid.uid.uidByte[i], HEX);
    }
    uid.toUpperCase();

    // Check authorization
    bool authorized = false;
    for (int i = 0; i < numAuthorized; i++) {
        if (uid.equals(authorizedUIDs[i])) {
            authorized = true;
            break;
        }
    }

    // Display result and log access
    if (authorized) {
        grantAccess(uid);
    } else {
        denyAccess(uid);
    }

    // Halt card and stop encryption
    rfid.PICC_HaltA();
    rfid.PCD_StopCrypto1();

    // Return to ready state after 3 seconds
    delay(3000);
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Scan Your Card");
    digitalWrite(GREEN_LED, LOW);
    digitalWrite(RED_LED, LOW);
}

void grantAccess(String uid) {
    Serial.println("✓ ACCESS GRANTED");
    Serial.println("  UID: " + uid);
    Serial.println("  Time: " + getTimestamp());

    // LCD display
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("ACCESS GRANTED");
    lcd.setCursor(0, 1);
    lcd.print("Welcome!");

    // Green LED and success beep
    digitalWrite(GREEN_LED, HIGH);
    tone(BUZZER, 2000, 100);
    delay(100);
    tone(BUZZER, 2500, 100);

    // Log access
    logAccess(uid, true);
}

void denyAccess(String uid) {
    Serial.println("✗ ACCESS DENIED");
    Serial.println("  UID: " + uid);
    Serial.println("  Time: " + getTimestamp());
    Serial.println("  Reason: Unauthorized card");

    // LCD display
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("ACCESS DENIED");
    lcd.setCursor(0, 1);
    lcd.print("Unauthorized!");

    // Red LED and error beep
    digitalWrite(RED_LED, HIGH);
    tone(BUZZER, 500, 200);
    delay(250);
    tone(BUZZER, 500, 200);
    delay(250);
    tone(BUZZER, 500, 200);

    // Log access attempt
    logAccess(uid, false);
}

void logAccess(String uid, bool granted) {
    accessLog[logIndex].uid = uid;
    accessLog[logIndex].timestamp = getTimestamp();
    accessLog[logIndex].granted = granted;

    logIndex = (logIndex + 1) % 100;  // Circular buffer
}

String getTimestamp() {
    // In real system, use RTC module for accurate timestamps
    // For now, return milliseconds since boot
    unsigned long ms = millis();
    unsigned long seconds = ms / 1000;
    unsigned long minutes = seconds / 60;
    unsigned long hours = minutes / 60;

    return String(hours % 24) + ":" +
           String(minutes % 60) + ":" +
           String(seconds % 60);
}

void printAccessLog() {
    // Call this function via Serial command or button press
    Serial.println("\n=== ACCESS LOG (Last 10 Entries) ===");

    int start = max(0, logIndex - 10);
    for (int i = start; i < logIndex; i++) {
        Serial.print(accessLog[i].timestamp);
        Serial.print(" | ");
        Serial.print(accessLog[i].uid);
        Serial.print(" | ");
        Serial.println(accessLog[i].granted ? "GRANTED" : "DENIED");
    }
    Serial.println();
}

Expected Output (Serial Monitor):

=== RFID Access Control System ===
System initialized. Waiting for cards...

✓ ACCESS GRANTED
  UID: 04521AB2
  Time: 0:5:12
✗ ACCESS DENIED
  UID: F7A9B2C1
  Time: 0:5:45
  Reason: Unauthorized card
✓ ACCESS GRANTED
  UID: E2801160600002A1
  Time: 0:6:03

Expected Output (LCD Display):

[When ready]
Scan Your Card

[On authorized card]
ACCESS GRANTED
Welcome!

[On unauthorized card]
ACCESS DENIED
Unauthorized!

874.4 Lab 2: Python RFID Inventory Dashboard with Real-Time Monitoring

Build a complete inventory management dashboard with web interface using Flask.

Requirements:

pip install Flask mfrc522 RPi.GPIO sqlite3

Web Dashboard Template (templates/dashboard.html):

<!DOCTYPE html>
<html>
<head>
    <title>RFID Inventory Dashboard</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: Arial, sans-serif; background: #f5f5f5; padding: 20px; }
        .container { max-width: 1400px; margin: 0 auto; }
        h1 { color: #333; margin-bottom: 20px; }

        .stats {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }

        .stat-card {
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }

        .stat-card h3 { color: #666; font-size: 14px; margin-bottom: 10px; }
        .stat-card .value { font-size: 32px; font-weight: bold; color: #4CAF50; }

        .sections {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 20px;
        }

        .section {
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }

        .section h2 { margin-bottom: 15px; color: #333; }

        table { width: 100%; border-collapse: collapse; }
        th, td { padding: 12px; text-align: left; border-bottom: 1px solid #eee; }
        th { background: #f9f9f9; font-weight: 600; color: #666; }

        .live-scan {
            background: #E3F2FD;
            padding: 15px;
            border-radius: 8px;
            border-left: 4px solid #2196F3;
            margin-bottom: 20px;
        }

        .live-scan.active {
            background: #C8E6C9;
            border-left-color: #4CAF50;
        }

        .timestamp { color: #999; font-size: 12px; }
    </style>
</head>
<body>
    <div class="container">
        <h1>RFID Inventory Dashboard</h1>

        <div class="live-scan" id="liveScan">
            <strong>Last Scan:</strong> <span id="lastScanInfo">Waiting for tags...</span>
        </div>

        <div class="stats">
            <div class="stat-card">
                <h3>Total Items</h3>
                <div class="value" id="totalItems">0</div>
            </div>
            <div class="stat-card">
                <h3>Scans Today</h3>
                <div class="value" id="scansToday">0</div>
            </div>
            <div class="stat-card">
                <h3>Categories</h3>
                <div class="value" id="categories">0</div>
            </div>
        </div>

        <div class="sections">
            <div class="section">
                <h2>Inventory Items</h2>
                <table>
                    <thead>
                        <tr>
                            <th>Name</th>
                            <th>Category</th>
                            <th>Last Seen</th>
                        </tr>
                    </thead>
                    <tbody id="itemsTable">
                        <tr><td colspan="3">Loading...</td></tr>
                    </tbody>
                </table>
            </div>

            <div class="section">
                <h2>Recent Activity</h2>
                <table>
                    <thead>
                        <tr>
                            <th>Item</th>
                            <th>Location</th>
                            <th>Time</th>
                        </tr>
                    </thead>
                    <tbody id="activityTable">
                        <tr><td colspan="3">Loading...</td></tr>
                    </tbody>
                </table>
            </div>
        </div>
    </div>

    <script>
        // Update statistics
        function updateStats() {
            fetch('/api/statistics')
                .then(r => r.json())
                .then(data => {
                    document.getElementById('totalItems').textContent = data.total_items;
                    document.getElementById('scansToday').textContent = data.scans_today;
                    document.getElementById('categories').textContent =
                        Object.keys(data.by_category).length;
                });
        }

        // Update items table
        function updateItems() {
            fetch('/api/items')
                .then(r => r.json())
                .then(items => {
                    const tbody = document.getElementById('itemsTable');
                    tbody.innerHTML = items.map(item => `
                        <tr>
                            <td><strong>${item.name}</strong><br>
                                <span class="timestamp">${item.rfid_uid}</span></td>
                            <td>${item.category}</td>
                            <td class="timestamp">${item.last_seen || 'Never'}</td>
                        </tr>
                    `).join('');
                });
        }

        // Update recent scans
        function updateActivity() {
            fetch('/api/recent_scans')
                .then(r => r.json())
                .then(scans => {
                    const tbody = document.getElementById('activityTable');
                    tbody.innerHTML = scans.slice(0, 10).map(scan => `
                        <tr>
                            <td>${scan.item_name}</td>
                            <td>${scan.location}</td>
                            <td class="timestamp">${new Date(scan.timestamp).toLocaleTimeString()}</td>
                        </tr>
                    `).join('');
                });
        }

        // Check for new scans (live updates)
        let lastScanUID = null;
        function checkLastScan() {
            fetch('/api/last_scan')
                .then(r => r.json())
                .then(data => {
                    if (data.uid && data.uid !== lastScanUID) {
                        lastScanUID = data.uid;
                        const liveScan = document.getElementById('liveScan');
                        const scanInfo = document.getElementById('lastScanInfo');

                        liveScan.classList.add('active');
                        scanInfo.textContent = `${data.name} (${data.uid}) - ${new Date(data.timestamp).toLocaleTimeString()}`;

                        // Remove highlight after 3 seconds
                        setTimeout(() => liveScan.classList.remove('active'), 3000);

                        // Refresh data
                        updateStats();
                        updateItems();
                        updateActivity();
                    }
                });
        }

        // Initial load
        updateStats();
        updateItems();
        updateActivity();

        // Periodic updates
        setInterval(checkLastScan, 1000);  // Check for new scans every second
        setInterval(updateStats, 5000);     // Update stats every 5 seconds
        setInterval(updateItems, 10000);    // Update items every 10 seconds
        setInterval(updateActivity, 5000);  // Update activity every 5 seconds
    </script>
</body>
</html>

Running the Dashboard:

python3 rfid_dashboard.py

Expected Output:

Starting RFID Inventory Dashboard...
RFID scanner thread started...
Access dashboard at: http://localhost:5000
 * Running on http://0.0.0.0:5000

Tag detected: 1234567890
  Item: Arduino Uno R3
Tag detected: 9876543210
  Item: Raspberry Pi 4

874.5 Knowledge Check

Test your understanding of RFID technology and applications.

Question 1: A pet microchip implantation clinic needs to select an RFID system for animal identification. The chips must: (1) Work through tissue/fur without line-of-sight, (2) Be readable 5-10 years after implantation with no battery, (3) Have minimal power requirements for scanning (handheld scanners), (4) Comply with ISO 11784/11785 standards. Which RFID frequency band should they use?

Explanation: LF (Low Frequency) 125-134 kHz is the ONLY correct choice for pet microchips due to physics of electromagnetic wave propagation through biological tissue:

RFID Frequency Band Comparison:

Frequency Band Range Penetration Power Use Case ISO Standard
LF (125-134 kHz) 0.1-1 m Excellent (tissue/water) Low Animal ID, access ISO 11784/11785
HF (13.56 MHz) 0.1-1 m Good (paper/plastic) Medium Payments, transit ISO 14443, 15693
UHF (860-960 MHz) 1-12 m Poor (liquid/metal) High Warehouse, retail EPC Gen2
Microwave (2.45-5.8 GHz) 1-30 m Very poor (absorbed by water) Very high Active tracking Proprietary

Why LF is correct for pet microchips:

  1. Tissue Penetration Physics: LF wavelength (~2,200m) >> tissue thickness, resulting in minimal absorption (0.01-0.05 dB/cm). UHF at 915 MHz has high absorption (0.5-2.0 dB/cm) because water content absorbs the signal.

  2. ISO 11784/11785 Compliance: The global standard for animal identification specifies 134.2 kHz (LF band). Non-compliant chips cannot be read by standard veterinary scanners.

  3. Passive Tag Operation: LF chips harvest energy from the reader’s magnetic field via inductive coupling, lasting 25+ years without batteries.

Why other options are wrong: - A (HF): Not ISO 11784 compliant, poorer tissue penetration - B (UHF): Water in tissue absorbs 80-90% of signal, unreliable through tissue - D (Microwave): This is literally microwave oven frequency - completely absorbed by tissue

Question 1b: A warehouse needs to read dozens of tagged boxes on a pallet from several meters away as it passes through a dock door. Which RFID band is most commonly used for this type of high-throughput inventory scanning?

Explanation: UHF RFID (EPC Gen2) supports longer read ranges (meters) and high tag read rates, which is why it is widely used for supply chain and warehouse inventory scanning.

Question 2: A university library is upgrading from barcode-based book tracking to RFID. They need to: (1) Check out 30 books simultaneously in a stack (no individual scanning), (2) Detect unauthorized removal at exit gates, (3) Track 500,000 books in the collection, (4) Use ISO 28560 library standard tags. However, some library staff raise security concerns about MIFARE Classic 1K tags (currently proposed). What is the MOST critical security vulnerability of MIFARE Classic 1K, and what should be the recommended replacement?

Explanation: MIFARE Classic’s Crypto1 encryption was completely broken in 2008, making it trivially cloneable - this is a CRITICAL vulnerability for library security:

The Crypto1 Vulnerability: - 2007: Researchers reverse-engineer Crypto1 cipher (48-bit key, proprietary stream cipher) - 2008: Academic paper “Dismantling MIFARE Classic” published complete cryptanalysis - Attack complexity: ~5 seconds on a laptop - Result: MIFARE Classic tags can be cloned with commodity tools

Security Comparison:

Feature MIFARE Classic 1K MIFARE DESFire EV3
Encryption Crypto1 (48-bit, broken) AES-128
Key length 48 bits 128 bits
Broken? YES (2008) NO (secure as of 2025)
Cloning time ~60 seconds Impossible*
Mutual auth NO YES

Why other options are wrong: - A: “Any encrypted tag” is too broad - some have weak protection - B: Changing default keys doesn’t fix a broken cipher - C: UHF EPC Gen2 is for inventory, not authentication workflows

Question 3: A smart city project needs to implement a public transportation payment system for buses/metro. Commuters should be able to: (1) Tap their phone/card in <300ms for quick boarding, (2) Store encrypted balance/tickets on the card (works offline when cell network down), (3) Be compatible with 2+ billion existing smartphones, (4) Work within 4-10 cm range to avoid accidental charges. The project manager proposes UHF RFID (860-960 MHz) for its long range. Why is this the WRONG choice, and what should be used instead?

Explanation: NFC (Near Field Communication) based on HF 13.56 MHz is the ONLY technology used by ALL major contactless payment systems worldwide (Apple Pay, Google Pay, EMV contactless) because physics and business requirements align perfectly:

Why UHF is wrong for transit payments: | Requirement | UHF RFID (Wrong) | NFC HF 13.56 MHz (Correct) | |:————|:—————–|:—————————| | Range | 1-12 meters | 4-10 cm | | Phone support | 0 phones | 2+ billion phones | | Standard | EPC Gen2 (inventory) | ISO 14443 (payments) | | Accidental reads | YES - huge problem | NO - must be <10cm |

Problem: Accidental charges with UHF At 1-12m range, walking past a UHF reader could charge your card. Transit systems need intentional “tap” action within 4-10cm.

Actual Transit Systems Using NFC: - London Oyster/Contactless - New York OMNY - Singapore SimplyGo - Hong Kong Octopus - Tokyo Suica/Pasmo

None use UHF. All use HF 13.56 MHz (NFC).

Why other options are wrong: - A: LF doesn’t solve the range problem and isn’t in smartphones - C: QR codes are slower (500-1000ms) and require screen brightness - D: BLE automatic payment is even worse (10-50m range = charged when walking by)

Deep Dives: - RFID Fundamentals and Standards - Operating principles and ISO standards - RFID Security and Privacy - Cryptographic authentication and privacy protocols - NFC Architecture - Near-field communication as HF RFID extension

Comparisons: - NFC vs RFID - When to use NFC versus traditional RFID - Technology Comparison - RFID in context of IoT technology stack

Learning: - Quizzes Hub - Test your RFID knowledge - Knowledge Gaps - Identify areas for deeper study

874.6 Summary

This chapter provided hands-on labs and assessment for RFID systems:

  • Lab 1: ESP32 Access Control: Complete door lock system with RC522, LCD display, LED indicators, buzzer feedback, and access logging using circular buffer
  • Lab 2: Python Dashboard: Flask-based web interface with real-time RFID scanning, inventory tracking, and periodic AJAX updates
  • Security Assessment: MIFARE Classic Crypto1 vulnerability (broken 2008) vs DESFire EV3 AES-128 security
  • Frequency Selection: LF for tissue penetration (pet microchips), HF/NFC for payments (<10cm range), UHF for inventory (1-12m range)
  • Transit Systems: NFC is universal for payments (Apple Pay, Google Pay, EMV) - UHF’s long range causes accidental charges

874.7 What’s Next

Continue your RFID learning with RFID Security and Privacy for deeper coverage of cryptographic authentication, privacy protocols, and secure deployment practices.