RFID Tag UID Reading
RFID Tag UID Reading
Overview
Section titled “Overview”This section covers reading and validating RFID tag UIDs (Unique Identifiers) from the RC522 module, including tag differentiation, UID string formatting, and implementing tag whitelist logic. Learning this section will enable you to:
- Read and parse RFID tag UIDs as hexadecimal strings
- Differentiate between multiple tags by their UID
- Implement tag validation with whitelist/blacklist logic
- Handle common UID read edge cases
Prerequisites
Section titled “Prerequisites”Before starting this section, ensure you have:
- RC522 module wired and verified (05-02, 05-03)
- MFRC522 library installed
- At least two different RFID tags/cards for testing
- Basic sketch template ready
Key Concepts
Section titled “Key Concepts”What is a UID?
Section titled “What is a UID?”Every RFID tag has a unique identifier (UID) burned into its chip during manufacturing:
UID Structure for MIFARE Classic 1K:┌─────────┬─────────┬─────────┬─────────┐│ Byte 0 │ Byte 1 │ Byte 2 │ Byte 3 │├─────────┼─────────┼─────────┼─────────┤│ 0x04 │ 0xA3 │ 0xB2 │ 0xC1 │└─────────┴─────────┴─────────┴─────────┘
→ Full UID as string: "04A3B2C1" (8 hex chars for 4-byte UID)→ Full UID as string: "045A3B2C1D" (10 hex chars for 5-byte UID)Important Facts about UIDs:
- UIDs are not encrypted — they are read-only identifiers
- Most MIFARE Classic tags have 4-byte UIDs
- Newer tags (NTAG, MIFARE Ultralight) may have 7-byte UIDs
- UIDs are unique per tag, but clones exist (security limitation)
- UIDs can be printed on the physical tag body for identification
Tag vs Card Types
Section titled “Tag vs Card Types”The RC522 supports several tag/card types:
| Type | UID Size | Typical Use | Cost |
|---|---|---|---|
| MIFARE Classic 1K (S50) | 4 bytes | Access control, transit | ~$0.50 |
| MIFARE Classic 4K (S70) | 4 bytes | Complex applications | ~$1.00 |
| MIFARE Ultralight | 7 bytes | Single-use, event tickets | ~$0.20 |
| NTAG213/215/216 | 7 bytes | NFC tags, product tagging | ~$0.30 |
| MIFARE DESFire | 7 bytes | High security | ~$3.00 |
Implementation Steps
Section titled “Implementation Steps”Step 1: Read UID as Hexadecimal String
Section titled “Step 1: Read UID as Hexadecimal String”Create a function to read the tag UID and convert it to a hex string:
#include <SPI.h>#include <MFRC522.h>
#define RST_PIN 4#define SS_PIN 5MFRC522 rfid(SS_PIN, RST_PIN);
/** * Read RFID tag UID and return as hex string * Returns empty string if no tag detected */String readTagUID() { // Check if a new card is present if (!rfid.PICC_IsNewCardPresent()) { return ""; }
// Read the card serial number if (!rfid.PICC_ReadCardSerial()) { return ""; }
// Build UID string String uid = ""; for (byte i = 0; i < rfid.uid.size; i++) { if (rfid.uid.uidByte[i] < 0x10) { uid += "0"; // Pad single hex digits with leading zero } uid += String(rfid.uid.uidByte[i], HEX); } uid.toUpperCase();
// Halt communication rfid.PICC_HaltA(); rfid.PCD_StopCrypto1();
return uid;}
void setup() { Serial.begin(115200); SPI.begin(); rfid.PCD_Init();
Serial.println("RFID Tag Reader Ready"); Serial.println("Hold a tag near the reader..."); Serial.println();}
void loop() { String uid = readTagUID();
if (uid.length() > 0) { Serial.print("Tag detected! UID: "); Serial.print(uid); Serial.print(" | Length: "); Serial.print(uid.length()); Serial.println(" hex characters");
delay(2000); // Prevent rapid re-reads }}Expected Output:
RFID Tag Reader ReadyHold a tag near the reader...
Tag detected! UID: 04A3B2C1 | Length: 8 hex charactersTag detected! UID: E5F8123456 | Length: 10 hex charactersStep 2: Implement Tag Whitelist
Section titled “Step 2: Implement Tag Whitelist”For the asset tracking system, only known/registered tags should be accepted:
// Define known tags (whitelist)// Replace these with your actual tag UIDsconst String KNOWN_TAGS[] = { "04A3B2C1", // Employee A / Asset 1 "E5F8123456", // Employee B / Asset 2 "1A2B3C4D" // Employee C / Asset 3};const int TAG_COUNT = sizeof(KNOWN_TAGS) / sizeof(KNOWN_TAGS[0]);
// Tag owner names (for identification)const String TAG_NAMES[] = { "ESP32_TEAM", // Employee A "BLANK_TAG", // Employee B "DEVICE_C" // Employee C};
bool isKnownTag(String uid) { for (int i = 0; i < TAG_COUNT; i++) { if (uid == KNOWN_TAGS[i]) { return true; } } return false;}
String getTagName(String uid) { for (int i = 0; i < TAG_COUNT; i++) { if (uid == KNOWN_TAGS[i]) { return TAG_NAMES[i]; } } return "UNKNOWN";}
void loop() { String uid = readTagUID();
if (uid.length() > 0) { if (isKnownTag(uid)) { String name = getTagName(uid); Serial.print("✅ Welcome, "); Serial.print(name); Serial.print("! (UID: "); Serial.print(uid); Serial.println(")"); } else { Serial.print("❌ Unknown tag: "); Serial.println(uid); }
delay(2000); }}Step 3: Tag Initialization and Registration
Section titled “Step 3: Tag Initialization and Registration”For first-time setup, you may want to register unknown tags:
void loop() { String uid = readTagUID();
if (uid.length() > 0) { if (isKnownTag(uid)) { String name = getTagName(uid); Serial.print("Known user: "); Serial.println(name); } else { // Register new tag Serial.print("New tag detected! UID: "); Serial.println(uid); Serial.println("Type 'register <name>' to register this tag"); // In a real system, this would publish to MQTT // for Node-RED to handle registration }
delay(2000); }}Step 4: Multi-Tag Differentiation
Section titled “Step 4: Multi-Tag Differentiation”When using the system with multiple tags, it’s important to handle each tag differently:
// Extended tag handling with rolesenum TagAction { ACTION_CHECK_IN, ACTION_CHECK_OUT, ACTION_ACCESS_DENIED};
struct TagInfo { String uid; String name; bool isActive; // Currently checked in};
TagInfo tags[] = { {"04A3B2C1", "ESP32_TEAM", false}, {"E5F8123456", "BLANK_TAG", false}};const int TAG_INFO_COUNT = sizeof(tags) / sizeof(tags[0]);
TagAction processTag(String uid) { for (int i = 0; i < TAG_INFO_COUNT; i++) { if (uid == tags[i].uid) { if (!tags[i].isActive) { // Currently checked out → perform check-in tags[i].isActive = true; Serial.print(tags[i].name); Serial.println(" checked IN"); return ACTION_CHECK_IN; } else { // Currently checked in → perform check-out tags[i].isActive = false; Serial.print(tags[i].name); Serial.println(" checked OUT"); return ACTION_CHECK_OUT; } } } return ACTION_ACCESS_DENIED;}Verification
Section titled “Verification”Test the UID reading functionality:
// Test Script// 1. Upload the basic UID reader sketch// 2. Open Serial Monitor at 115200 baud// 3. Hold Tag #1 near reader
// Expected:// Tag detected! UID: 04A3B2C1 | Length: 8 hex characters// ✅ Welcome, ESP32_TEAM! (UID: 04A3B2C1)
// 4. Hold Tag #2 near reader
// Expected:// Tag detected! UID: E5F8123456 | Length: 10 hex characters// ✅ Welcome, BLANK_TAG! (UID: E5F8123456)
// 5. Hold an unregistered tag
// Expected:// Tag detected! UID: 9B8C7D6E | Length: 8 hex characters// ❌ Unknown tag: 9B8C7D6EVerification Checklist:
- Each tag returns a unique UID
- Known tags are correctly identified by name
- Unknown tags are rejected with error message
- UID string formatting is consistent (uppercase, no spaces)
- Tag action toggles between check-in and check-out on repeated scans
Troubleshooting
Section titled “Troubleshooting”Issue 1: UID Contains Unexpected Characters
Section titled “Issue 1: UID Contains Unexpected Characters”Symptom: UID string shows values like FFFFFFA3B2C1
Cause: Negative byte values from signed byte interpretation in Arduino
Solution:
// Use proper hex formatting to avoid sign extensionString uid = "";for (byte i = 0; i < rfid.uid.size; i++) { char hex[3]; sprintf(hex, "%02X", rfid.uid.uidByte[i]); // Always 2 chars uid += hex;}Issue 2: Same Tag Shows Different UIDs
Section titled “Issue 2: Same Tag Shows Different UIDs”Symptom: The same tag sometimes returns different UID strings
Cause: Read error or CRC mismatch
Solution:
- Check antenna connection
- Reduce read distance
- Add read verification:
bool readVerifiedUID(String &outUid) { String firstRead = readTagUID(); delay(100); String secondRead = readTagUID();
if (firstRead == secondRead && firstRead.length() > 0) { outUid = firstRead; return true; } return false;}Issue 3: Tags Not Differentiated Correctly
Section titled “Issue 3: Tags Not Differentiated Correctly”Symptom: Two different tags are identified as the same
Cause: Using only last 4 bytes of a 7-byte UID (truncation)
Solution:
// Always use the full UIDString uid = "";for (byte i = 0; i < rfid.uid.size; i++) { uid += String(rfid.uid.uidByte[i], HEX);}// DO NOT truncate to last 4 bytesBest Practices
Section titled “Best Practices”- ✅ Recommended: Store UIDs as uppercase hex strings for consistency
- ✅ Recommended: Use a whitelist approach for security (reject unknown tags)
- ✅ Recommended: Always halt card communication after reading (
PICC_HaltA()) - ❌ Avoid: Using UIDs for security-critical authentication (UIDs can be cloned)
- ❌ Avoid: Assuming all tags have 4-byte UIDs — handle both 4 and 7 byte variants
Summary
Section titled “Summary”- UID Reading: Use
PICC_IsNewCardPresent()+PICC_ReadCardSerial()in sequence - UID Format: 4-byte (8 hex chars) or 7-byte (14 hex chars), always use full UID
- Tag Validation: Implement whitelist for known tags, reject unknowns
- State Tracking: Track check-in/check-out state per tag for toggle logic
References
Section titled “References”Writing Date: 2026-05-17
Based on Source File: 校正版/10 Time recording witht RFID und TimeTagger.md
Target Audience: Alibaba.com IoT Pre-sales Engineer
Status: ✅ Completed