RFID标签UID读取
RFID标签UID读取
本节介绍从 RC522 模块读取和验证 RFID 标签 UID(唯一标识符)的相关内容,包括标签区分、UID 字符串格式化以及实现标签白名单逻辑。学习完本节后,您将能够:
- 以十六进制字符串形式读取和解析 RFID 标签 UID
- 通过 UID 区分多个标签
- 使用白名单/黑名单逻辑实现标签验证
- 处理常见的 UID 读取边缘情况
开始本节前,请确保您已完成:
- RC522 模块已接线并验证(05-02、05-03)
- MFRC522 库已安装
- 至少两个不同的 RFID 标签/卡用于测试
- 基本草稿模板已就绪
什么是 UID?
Section titled “什么是 UID?”每个 RFID 标签在制造时都有一个唯一的标识符(UID)烧录在芯片中:
MIFARE Classic 1K 的 UID 结构:┌─────────┬─────────┬─────────┬─────────┐│ 字节 0 │ 字节 1 │ 字节 2 │ 字节 3 │├─────────┼─────────┼─────────┼─────────┤│ 0x04 │ 0xA3 │ 0xB2 │ 0xC1 │└─────────┴─────────┴─────────┴─────────┘
→ 完整 UID 字符串:"04A3B2C1"(4 字节 UID,8 个十六进制字符)→ 完整 UID 字符串:"045A3B2C1D"(5 字节 UID,10 个十六进制字符)关于 UID 的重要事实:
- UID 不加密——它们是只读标识符
- 大多数 MIFARE Classic 标签具有 4 字节 UID
- 较新的标签(NTAG、MIFARE Ultralight)可能具有 7 字节 UID
- UID 每个标签唯一,但存在克隆可能(安全性限制)
- UID 可以印在标签本体上以便识别
标签/卡类型
Section titled “标签/卡类型”RC522 支持多种标签/卡类型:
| 类型 | UID 大小 | 典型用途 | 成本 |
|---|---|---|---|
| MIFARE Classic 1K(S50) | 4 字节 | 门禁控制、交通 | 约 $0.50 |
| MIFARE Classic 4K(S70) | 4 字节 | 复杂应用 | 约 $1.00 |
| MIFARE Ultralight | 7 字节 | 一次性使用、活动票务 | 约 $0.20 |
| NTAG213/215/216 | 7 字节 | NFC 标签、产品标签 | 约 $0.30 |
| MIFARE DESFire | 7 字节 | 高安全性 | 约 $3.00 |
步骤 1:以十六进制字符串读取 UID
Section titled “步骤 1:以十六进制字符串读取 UID”创建一个读取标签 UID 并将其转换为十六进制字符串的函数:
#include <SPI.h>#include <MFRC522.h>
#define RST_PIN 4#define SS_PIN 5MFRC522 rfid(SS_PIN, RST_PIN);
/** * 读取 RFID 标签 UID 并返回十六进制字符串 * 如果未检测到标签则返回空字符串 */String readTagUID() { // 检查是否有新卡出现 if (!rfid.PICC_IsNewCardPresent()) { return ""; }
// 读取卡的序列号 if (!rfid.PICC_ReadCardSerial()) { return ""; }
// 构建 UID 字符串 String uid = ""; for (byte i = 0; i < rfid.uid.size; i++) { if (rfid.uid.uidByte[i] < 0x10) { uid += "0"; // 单位数十六进制数字前面补零 } uid += String(rfid.uid.uidByte[i], HEX); } uid.toUpperCase();
// 停止通信 rfid.PICC_HaltA(); rfid.PCD_StopCrypto1();
return uid;}
void setup() { Serial.begin(115200); SPI.begin(); rfid.PCD_Init();
Serial.println("RFID 标签读取器就绪"); Serial.println("将标签靠近读取器..."); Serial.println();}
void loop() { String uid = readTagUID();
if (uid.length() > 0) { Serial.print("检测到标签!UID:"); Serial.print(uid); Serial.print(" | 长度:"); Serial.print(uid.length()); Serial.println(" 个十六进制字符");
delay(2000); // 防止快速重复读取 }}预期输出:
RFID 标签读取器就绪将标签靠近读取器...
检测到标签!UID:04A3B2C1 | 长度:8 个十六进制字符检测到标签!UID:E5F8123456 | 长度:10 个十六进制字符步骤 2:实现标签白名单
Section titled “步骤 2:实现标签白名单”对于资产追踪系统,只应接受已知/已注册的标签:
// 定义已知标签(白名单)// 将这些替换为您的实际标签 UIDconst String KNOWN_TAGS[] = { "04A3B2C1", // 员工 A / 资产 1 "E5F8123456", // 员工 B / 资产 2 "1A2B3C4D" // 员工 C / 资产 3};const int TAG_COUNT = sizeof(KNOWN_TAGS) / sizeof(KNOWN_TAGS[0]);
// 标签所有者名称(用于识别)const String TAG_NAMES[] = { "ESP32_TEAM", // 员工 A "BLANK_TAG", // 员工 B "DEVICE_C" // 员工 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 "未知";}
void loop() { String uid = readTagUID();
if (uid.length() > 0) { if (isKnownTag(uid)) { String name = getTagName(uid); Serial.print("✅ 欢迎,"); Serial.print(name); Serial.print("!(UID:"); Serial.print(uid); Serial.println(")"); } else { Serial.print("❌ 未知标签:"); Serial.println(uid); }
delay(2000); }}步骤 3:标签初始化和注册
Section titled “步骤 3:标签初始化和注册”在首次设置时,您可能需要注册未知标签:
void loop() { String uid = readTagUID();
if (uid.length() > 0) { if (isKnownTag(uid)) { String name = getTagName(uid); Serial.print("已知用户:"); Serial.println(name); } else { // 注册新标签 Serial.print("检测到新标签!UID:"); Serial.println(uid); Serial.println("输入 'register <name>' 注册此标签"); // 在实际系统中,这将发布到 MQTT // 让 Node-RED 处理注册 }
delay(2000); }}步骤 4:多标签区分
Section titled “步骤 4:多标签区分”当系统使用多个标签时,以不同方式处理每个标签非常重要:
// 扩展标签处理,带角色enum TagAction { ACTION_CHECK_IN, ACTION_CHECK_OUT, ACTION_ACCESS_DENIED};
struct TagInfo { String uid; String name; bool isActive; // 当前是否已签到};
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) { // 当前已签退 → 执行签到 tags[i].isActive = true; Serial.print(tags[i].name); Serial.println(" 已签到"); return ACTION_CHECK_IN; } else { // 当前已签到 → 执行签退 tags[i].isActive = false; Serial.print(tags[i].name); Serial.println(" 已签退"); return ACTION_CHECK_OUT; } } } return ACTION_ACCESS_DENIED;}测试 UID 读取功能:
// 测试脚本// 1. 上传基础 UID 读取草稿// 2. 以 115200 波特率打开串口监视器// 3. 将标签 #1 靠近读取器
// 预期:// 检测到标签!UID:04A3B2C1 | 长度:8 个十六进制字符// ✅ 欢迎,ESP32_TEAM!(UID:04A3B2C1)
// 4. 将标签 #2 靠近读取器
// 预期:// 检测到标签!UID:E5F8123456 | 长度:10 个十六进制字符// ✅ 欢迎,BLANK_TAG!(UID:E5F8123456)
// 5. 放置一个未注册的标签
// 预期:// 检测到标签!UID:9B8C7D6E | 长度:8 个十六进制字符// ❌ 未知标签:9B8C7D6E验证清单:
- 每个标签返回唯一的 UID
- 已知标签被正确按名称识别
- 未知标签被拒绝并显示错误消息
- UID 字符串格式一致(大写,无空格)
- 重复扫描时标签操作在签到和签退之间切换
问题 1:UID 包含意外字符
Section titled “问题 1:UID 包含意外字符”症状:UID 字符串显示类似 FFFFFFA3B2C1 的值
原因:Arduino 中有符号字节解释导致的负字节值
解决方案:
// 使用正确的十六进制格式化以避免符号扩展String uid = "";for (byte i = 0; i < rfid.uid.size; i++) { char hex[3]; sprintf(hex, "%02X", rfid.uid.uidByte[i]); // 始终 2 个字符 uid += hex;}问题 2:同一标签显示不同的 UID
Section titled “问题 2:同一标签显示不同的 UID”症状:同一标签有时返回不同的 UID 字符串
原因:读取错误或 CRC 不匹配
解决方案:
- 检查天线连接
- 减少读取距离
- 添加读取验证:
bool readVerifiedUID(String &outUid) { String firstRead = readTagUID(); delay(100); String secondRead = readTagUID();
if (firstRead == secondRead && firstRead.length() > 0) { outUid = firstRead; return true; } return false;}问题 3:标签未能正确区分
Section titled “问题 3:标签未能正确区分”症状:两个不同的标签被识别为同一个
原因:只使用了 7 字节 UID 的最后 4 个字节(截断)
解决方案:
// 始终使用完整的 UIDString uid = "";for (byte i = 0; i < rfid.uid.size; i++) { uid += String(rfid.uid.uidByte[i], HEX);}// 不要截断为最后 4 个字节- ✅ 建议:将 UID 存储为大写十六进制字符串以确保一致性
- ✅ 建议:使用白名单方法以确保安全(拒绝未知标签)
- ✅ 建议:读取后始终停止卡片通信(
PICC_HaltA()) - ❌ 避免:将 UID 用于安全关键型认证(UID 可被克隆)
- ❌ 避免:假设所有标签都有 4 字节 UID——处理 4 字节和 7 字节两种变体
- UID 读取:按顺序使用
PICC_IsNewCardPresent()+PICC_ReadCardSerial() - UID 格式:4 字节(8 个十六进制字符)或 7 字节(14 个十六进制字符),始终使用完整 UID
- 标签验证:为已知标签实现白名单,拒绝未知标签
- 状态追踪:追踪每个标签的签到/签退状态以实现切换逻辑