Skip to content

RFID Tag UID Reading

RFID Tag UID Reading

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

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

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

The RC522 supports several tag/card types:

TypeUID SizeTypical UseCost
MIFARE Classic 1K (S50)4 bytesAccess control, transit~$0.50
MIFARE Classic 4K (S70)4 bytesComplex applications~$1.00
MIFARE Ultralight7 bytesSingle-use, event tickets~$0.20
NTAG213/215/2167 bytesNFC tags, product tagging~$0.30
MIFARE DESFire7 bytesHigh security~$3.00

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 5
MFRC522 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 Ready
Hold a tag near the reader...
Tag detected! UID: 04A3B2C1 | Length: 8 hex characters
Tag detected! UID: E5F8123456 | Length: 10 hex characters

For the asset tracking system, only known/registered tags should be accepted:

// Define known tags (whitelist)
// Replace these with your actual tag UIDs
const 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);
}
}

When using the system with multiple tags, it’s important to handle each tag differently:

// Extended tag handling with roles
enum 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;
}

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: 9B8C7D6E

Verification 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

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 extension
String 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;
}

Symptom: The same tag sometimes returns different UID strings

Cause: Read error or CRC mismatch

Solution:

  1. Check antenna connection
  2. Reduce read distance
  3. 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 UID
String uid = "";
for (byte i = 0; i < rfid.uid.size; i++) {
uid += String(rfid.uid.uidByte[i], HEX);
}
// DO NOT truncate to last 4 bytes
  • 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
  1. UID Reading: Use PICC_IsNewCardPresent() + PICC_ReadCardSerial() in sequence
  2. UID Format: 4-byte (8 hex chars) or 7-byte (14 hex chars), always use full UID
  3. Tag Validation: Implement whitelist for known tags, reject unknowns
  4. State Tracking: Track check-in/check-out state per tag for toggle logic

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