简介:中南大学RFID课程设计配套的代码与实验报告合辑,面向物联网、自动化及计算机相关专业学生,用于完成基于射频识别的签到/考勤系统课程设计。压缩包共320个文件、约8.72MB,包含21个cpp源文件与17个h头文件、5个ui界面文件、1个pro工程配置及db数据库文件,另有png截图、obj/o编译中间文件、dll/so动态库和exe可执行文件,覆盖Qt工程结构、数据库操作、用户管理、签到界面与串口通信等模块。已有1317人学习下载,说明其工程结构与实验材料具有一定参考价值。读者可从中对照Attendance项目的界面组织、DBControl数据存取、ManageWidget与ClockInWidget功能实现,并结合实验报告梳理系统架构、软硬件选型、功能测试和问题分析,适合作为课程设计复现、答辩准备和RFID应用开发的参考。
1. RFID课程设计资料包拆解:先别急着改报告,先确认读卡链路是否闭环
这份RFID课程设计资料包把代码和实验报告放在同一套工程里,适合课程设计周期紧、又想把寻卡、读块、写块链路真正跑通的人。反直觉的是,很多人拿到资料先改报告模板,最后卡在SPI时序和密钥认证上,代码跑不起来,实验报告里的数据也只能靠猜。这套资料的价值不在复制粘贴,而在它把读写器初始化、寻卡、防碰撞、扇区读写和实验记录放在一条可验证的链路上。你要做的是先按手头硬件对齐引脚和供电,再用最小程序确认版本寄存器可读,最后才进入块操作和报告数据整理。适合单片机基础一般、但需要交付代码和实验报告的学生,也适合带课的助教做快速核验。
2. 实验台架与代码骨架:MFRC522的SPI链路先读到VersionReg再谈寻卡
RFID课程设计里最容易翻车的地方,不是防碰撞算法写得多漂亮,而是读卡链路根本没有闭环。代码能编译、串口有打印、报告里有截图,这三件事不等于读卡器真的在工作。我一般先让主控通过SPI读到MFRC522的VersionReg,再打开天线、发寻卡命令,最后才谈UID和扇区。这个顺序看起来慢,实际能省掉大量“寻卡时有时无”的排查时间。
2.1 读写器、标签与主控的选型边界
课程设计资料包里常见的硬件组合是13.56MHz读写器模块加MIFARE Classic卡,主控用一块Cortex-M开发板或常见开源单片机框架。选型时先看三个边界:工作频率、协议族、供电电压。MFRC522面向13.56MHz的ISO/IEC 14443-A,能处理寻卡、防冲突和部分MIFARE认证流程;它不能读125kHz低频ID卡,也不能读ISO/IEC 15693 vicinity卡。拿错卡去调代码,串口只会反复打印“no card”,这不是代码问题。
标签类型也要在实验报告里写清楚。MIFARE Classic 1K有16个扇区,每扇区4块,每块16字节;MIFARE Classic 4K前32个扇区每扇区4块,后8个扇区每扇区16块。不同容量的块地址不一样,代码里写死块号,换卡就会写失败。常见做法是先读块0的厂商信息,确认UID和容量,再按卡类型分支处理。不要一上来就写块3,块3是扇区尾块,存密钥和访问控制位,写错会把整张卡锁成只读。
| 项目 | 常见参数 | 课程设计里的注意点 |
|---|---|---|
| 读写器 | MFRC522,13.56MHz | 只覆盖ISO/IEC 14443-A,不兼容低频卡 |
| 标签 | MIFARE Classic 1K/4K | 块地址和尾块位置不同,代码要分支 |
| 主控接口 | SPI,3.3V逻辑 | 不要用5V直连,先确认电平转换 |
| 天线 | 板载PCB天线 | 附近金属会明显缩短读距 |
| 供电 | 3.3V,纹波尽量小 | 杜邦线过长会引入复位和误读 |
注意:资料包里的代码可能默认某一种开发板引脚,直接烧录前先核对片选、复位和SPI引脚,不要只看宏定义名字。
2.2 SPI引脚与初始化时序:第一步先读VersionReg
SPI链路要先跑通,再谈协议。MFRC522的SPI地址字节有固定格式:写寄存器时地址左移一位并清最低位,读寄存器时地址左移一位并置最高位。很多“读出来全是0xFF”的问题,不是芯片坏了,而是地址转换写反或者片选没有拉低。下面这段C代码用常见HAL库初始化SPI,并给出读寄存器的底层函数。时钟先降到1MHz到2MHz,等链路稳定后再提高。
// 常见Cortex-M HAL初始化:SPI模式0,软件片选 hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // 空闲低电平 hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // 第一个边沿采样 hspi1.Init.NSS = SPI_NSS_SOFT; // 片选手动控制 hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; HAL_SPI_Init(&hspi1); // MFRC522寄存器地址转换:读地址最高位置1,写地址最低位清零 static uint8_t MFRC522_ReadReg(uint8_t reg) { uint8_t addr = ((reg << 1) & 0x7E) | 0x80; uint8_t value = 0; CS_LOW(); HAL_SPI_Transmit(&hspi1, &addr, 1, 10); HAL_SPI_Receive(&hspi1, &value, 1, 10); CS_HIGH(); return value; } // 上电后先读VersionReg,常见返回0x91或0x92;0x00/0xFF先查线 uint8_t version = MFRC522_ReadReg(0x37); printf("MFRC522 VersionReg = 0x%02X\r\n", version);这段代码的关键不是把SPI配置得多复杂,而是把片选和地址转换固定下来。CS_LOW()和CS_HIGH()必须包住一次完整事务,不能在发送地址和接收数据之间被中断打断。BaudRatePrescaler先设大一点,读VersionReg稳定后再降到4MHz以内;线长超过10厘米时,SPI时钟越高越容易读到错位数据。VersionReg返回0x91或0x92只能说明SPI基本通了,不代表天线已经打开,下一步还要看TxControlReg的天线使能位。
如果VersionReg返回0x00,先查MISO是否接对、片选是否真的拉低、模块是否供电;返回0xFF,常见原因是MISO被拉高、SPI模式不对或者主控引脚复用没配置。血泪经验是:不要在同一块板上同时怀疑芯片和代码,先拿万用表量VCC和GND,再用示波器看SCK有没有波形,排查顺序比反复烧录有效。
2.3 最小验证链:寻卡、防冲突、选卡
VersionReg通过后,最小验证链是“打开天线、发REQA、防冲突、选卡、读UID”。下面这段C++代码基于常见MFRC522库,适合快速验证硬件;如果你手里的资料包是寄存器级代码,逻辑也一样,只是函数名不同。重点看每一步的返回值,不要只打印“寻卡成功”。
// 常见MFRC522库的最小验证链:寻卡、防冲突、选卡、打印UID #include <SPI.h> #include <MFRC522.h> MFRC522 rfid(SS_PIN, RST_PIN); void setup() { Serial.begin(115200); SPI.begin(); rfid.PCD_Init(); rfid.PCD_AntennaOn(); // 打开天线 Serial.print("VersionReg=0x"); Serial.println(rfid.PCD_ReadRegister(MFRC522::VersionReg), HEX); } void loop() { // 500ms超时内没有新卡就返回,避免死等 if (!rfid.PICC_IsNewCardPresent()) { delay(50); return; } // 防冲突并读取UID;失败时打印状态码 if (!rfid.PICC_ReadCardSerial()) { Serial.println("anticoll/select failed"); delay(50); return; } Serial.print("UID:"); for (byte i = 0; i < rfid.uid.size; i++) { if (rfid.uid.uidByte[i] < 0x10) Serial.print("0"); Serial.print(rfid.uid.uidByte[i], HEX); } Serial.println(); rfid.PICC_HaltA(); // 让卡进入HALT,准备下一轮 delay(200); }这段代码里,PCD_AntennaOn()决定天线场是否建立,PICC_IsNewCardPresent()内部会发REQA并检查ATQA,PICC_ReadCardSerial()完成防冲突和选卡。PICC_HaltA()不能省,否则同一张卡在下一轮可能不响应新的REQA,表现为“只能读一次”。UID长度可能是4字节、7字节或10字节,打印时按uid.size循环,不要固定写4。实验报告里要记录ATQA、SAK和UID长度,这些数据能解释为什么有的卡能读、有的卡认证失败。
提示:寻卡距离突然变短,先换一根短杜邦线、把模块远离金属桌面,再检查供电。天线附近的金属和人体电容对13.56MHz场强影响很大,这类问题在代码里找不到答案。
3. 扇区读写与密钥认证:Mifare Classic的块地址、密钥和失败分支怎么对齐
寻卡成功只说明读写器看到了卡,真正做课程设计还要读块、写块、验证数据。MIFARE Classic的认证是“先认证扇区,再读写该扇区内的块”,认证失败后面所有读写都会返回失败。很多人把读块失败归因于SPI,实际是密钥类型或块地址错了。这一章把扇区结构、认证流程和参数设置拆开,方便你把代码里的失败分支补全。
3.1 存储结构:扇区、块、控制块与密钥
MIFARE Classic 1K的16个扇区各4块,块0是厂商块,通常只读;块1和块2是数据块;块3是扇区尾块,存放KeyA、访问控制位和KeyB。访问控制位决定数据块能否读写、KeyA能否读、KeyB能否写。课程设计里经常用默认密钥FFFFFFFFFFFF做实验,但这不是所有卡都成立,二手卡或已改密钥的卡会直接认证失败。代码里要允许配置KeyA和KeyB,不要把密钥写死在多个函数里。
| 卡类型 | 扇区数 | 每扇区块数 | 每块字节 | 尾块位置 | 常见用途 |
|---|---|---|---|---|---|
| MIFARE Classic 1K | 16 | 4 | 16 | 每扇区第3块 | 学生卡、门禁实验 |
| MIFARE Classic 4K | 32 | 前32扇区4块,后8扇区16块 | 16 | 每扇区最后一块 | 大容量数据实验 |
| 只读UID卡 | 视卡型 | 不适用 | 不适用 | 不适用 | 只能寻卡,不能写块 |
块地址不是“第几个扇区就写第几块”。1K卡里扇区1的块地址是4、5、6、7,扇区2是8到11,依次类推。写数据块前先确认目标块不是块0和尾块。尾块写错会改访问控制位,轻则该扇区只读,重则整张卡无法认证。实验报告里要写清楚测试卡是1K还是4K、目标块地址、密钥类型和认证返回值,否则数据无法复查。
3.2 认证、读块、写块三段代码
下面这段代码用常见库函数完成“认证扇区、读块、写块、回读校验”。代码里把每一步的状态码打印出来,方便对照实验报告。写块前先读一次原数据,写完后再次读回比较,避免“写命令返回成功但数据没落盘”的假成功。
// 认证、读块、写块、回读校验:以块4为例 byte keyA[6] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; byte keyB[6] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; byte blockAddr = 4; // 扇区1的第0块,不是块1 byte buffer[18]; byte size = sizeof(buffer); // 1) 认证:KeyA认证块4所在扇区 MFRC522::StatusCode status = rfid.PCD_Authenticate( MFRC522::PICC_CMD_MF_AUTH_KEY_A, blockAddr, &keyA, &(rfid.uid)); if (status != MFRC522::STATUS_OK) { Serial.print("auth failed: "); Serial.println(rfid.GetStatusCodeName(status)); return; } // 2) 读块:读16字节,size返回18(含CRC) status = rfid.MIFARE_Read(blockAddr, buffer, &size); if (status != MFRC522::STATUS_OK) { Serial.print("read failed: "); Serial.println(rfid.GetStatusCodeName(status)); return; } // 3) 写块:写入16字节,最后2字节是CRC,不要手填 byte data[16] = {0}; memcpy(data, "RFID-LAB-2024", 13); status = rfid.MIFARE_Write(blockAddr, data, 16); if (status != MFRC522::STATUS_OK) { Serial.print("write failed: "); Serial.println(rfid.GetStatusCodeName(status)); return; } // 4) 回读校验:写后读回,逐字节比较 size = sizeof(buffer); status = rfid.MIFARE_Read(blockAddr, buffer, &size); if (status == MFRC522::STATUS_OK && memcmp(buffer, data, 16) == 0) { Serial.println("write verify ok"); } else { Serial.println("write verify failed"); }PCD_Authenticate的第一个参数决定用KeyA还是KeyB,第二个参数是块地址,不是扇区号。认证通过后,该扇区内的读写才有效;换扇区要重新认证。MIFARE_Read的size既是输入也是输出,调用前设为缓冲区大小,返回后表示实际读到的字节数。MIFARE_Write固定写16字节,CRC由库计算,不要手工拼。回读比较用memcmp,不要只比较前几个字节。
失败分支要按状态码区分:STATUS_TIMEOUT先查天线和卡距离,STATUS_MIFARE_NACK多半是密钥错或块权限不允许,STATUS_CRC_WRONG查SPI时钟和线长,STATUS_COLLISION说明有多张卡同时在场。实验报告里不要只写“读写成功”,要把状态码、块地址、密钥类型和耗时一起记录,否则复现时无法判断是环境问题还是代码问题。
3.3 超时、重试和天线参数怎么设
课程设计不是生产环境,但参数也不能随手写。SPI时钟建议从1MHz开始,稳定后升到4MHz以内;寻卡超时设500ms左右,认证和读写超时设200ms到500ms;重试次数设3次,每次重试前重新寻卡和认证。天线增益在MFRC522上不是任意可调的,能调的是天线匹配、供电质量和软件里的天线使能位。不要指望改一个寄存器就把读距从1厘米拉到10厘米。
| 参数 | 建议值 | 调整依据 | 失败时看什么 |
|---|---|---|---|
| SPI时钟 | 1MHz起步,4MHz以内 | 线长、模块质量 | VersionReg是否稳定 |
| 寻卡超时 | 500ms | 卡靠近速度 | ATQA是否返回 |
| 认证超时 | 300ms | 密钥和卡类型 | 状态码是否NACK |
| 重试次数 | 3次 | 实验稳定性 | 重试后是否同一UID |
| 写后回读 | 每次写后都做 | 数据可靠性 | 比较结果和状态码 |
注意:写块前先读一次目标块,确认不是厂商块和尾块;写尾块会改访问控制位,课程设计里除非明确要求,不要动块3。
4. 防碰撞与实验数据:把寻卡时延、误读率做成能复查的表格
防碰撞是RFID课程设计里最容易写进报告、也最容易和数据脱节的部分。代码里调用一次防冲突函数,报告里就写“实现了防碰撞”,这种写法经不起追问。真正能复查的做法是:固定标签数量、距离和采样次数,记录每次寻卡的UID、耗时和状态,再用脚本算成功率和误读率。这样代码、日志和实验报告才能对上。
4.1 防碰撞算法在代码里的落点
ISO/IEC 14443-A的防冲突靠UID层级和位冲突检测完成。MFRC522库里的PICC_ReadCardSerial()已经封装了防冲突和选卡,课程设计如果要求“体现防碰撞”,可以在外层加多标签轮询:每轮寻卡后让当前卡进入HALT,再继续寻下一张,直到没有新卡。不要在同一轮里反复读同一张卡,否则日志里会出现重复UID,误读率被算高。
下面这段C++代码用固定次数采样记录寻卡结果,适合做多标签对比实验。micros()记录单次寻卡耗时,HaltA()让已读卡退出场,避免重复计数。标签数量从1张逐步加到5张,距离固定为1厘米、3厘米、5厘米三档,每档采样100次。
// 多标签轮询采样:每轮最多读5张,记录UID和耗时 const int SAMPLE_ROUNDS = 100; for (int round = 0; round < SAMPLE_ROUNDS; round++) { int cardCount = 0; unsigned long start = micros(); while (cardCount < 5) { if (!rfid.PICC_IsNewCardPresent()) break; if (!rfid.PICC_ReadCardSerial()) break; Serial.print("round="); Serial.print(round); Serial.print(",uid="); for (byte i = 0; i < rfid.uid.size; i++) { if (rfid.uid.uidByte[i] < 0x10) Serial.print("0"); Serial.print(rfid.uid.uidByte[i], HEX); } Serial.print(",elapsed_us="); Serial.println(micros() - start); rfid.PICC_HaltA(); cardCount++; } delay(100); // 给卡离开HALT状态留时间 }这段代码的SAMPLE_ROUNDS是采样轮数,cardCount限制单轮最多读5张,elapsed_us是单轮从开始到当前卡读完的累计耗时。delay(100)不是随便加的,卡进入HALT后需要场复位或短暂离开才能重新响应,间隔太短会漏卡。日志格式固定为round,uid,elapsed_us,后面用Python解析时不需要猜字段。
防碰撞实验的报告表格至少要有这些列:标签数量、测试距离、采样轮数、成功寻卡次数、重复UID次数、平均耗时、最大耗时、误读率。误读率可以定义为“UID与预期不符或重复计数的次数除以总寻卡次数”,不要只写“成功率”。如果同一轮里读到两张卡但UID顺序不稳定,要在分析里说明轮询顺序受场强和卡位置影响。
4.2 实验报告该记录哪些原始数据
实验报告最怕只有结论没有原始数据。课程设计资料包里的报告模板可以借鉴结构,但数据必须来自你自己的日志。建议把原始日志作为附录或单独文件提交,正文只放统计表和图表。每个测试项记录环境、代码版本、硬件连接和采样条件,否则换一块板子数据就不可比。
| 测试项 | 必记字段 | 采样建议 | 判定标准 |
|---|---|---|---|
| 寻卡 | 时间戳、UID、ATQA、耗时 | 每档100次 | 成功次数和重复UID次数 |
| 读块 | 块地址、密钥类型、状态码、耗时 | 每块30次 | 读到16字节且CRC正常 |
| 写块 | 块地址、写入内容、回读结果 | 每块30次 | 回读逐字节一致 |
| 多标签 | 标签数量、距离、轮询顺序 | 1/2/3/5张 | 不漏卡、不重复计数 |
| 异常 | 失败状态码、重试次数 | 全量记录 | 能定位到硬件或协议层 |
表格里的“状态码”不要只写OK/FAIL,要写库返回的具体名称。STATUS_TIMEOUT和STATUS_MIFARE_NACK对应的排查方向完全不同。写块测试要避开块0和尾块,选数据块做写入,写完后回读并记录比较结果。多标签测试要固定卡的位置,不要用手拿着晃,人体和角度会改变场强,数据会变得不可解释。
4.3 从串口日志到报告图表的整理流程
串口日志直接粘进报告会很长,也不方便算统计。常见做法是用Python按固定格式解析,输出CSV和汇总表。下面的脚本读取rfid_log.txt,按round,uid,elapsed_us提取记录,统计每轮成功数和耗时。脚本不依赖额外库,课程设计环境一般都能跑。
# 解析串口日志:round,uid,elapsed_us -> CSV + 汇总 import re import csv from collections import defaultdict pattern = re.compile(r"round=(\d+),uid=([0-9A-Fa-f]+),elapsed_us=(\d+)") rows = [] with open("rfid_log.txt", "r", encoding="utf-8") as f: for line in f: m = pattern.search(line) if m: rows.append({ "round": int(m.group(1)), "uid": m.group(2).upper(), "elapsed_us": int(m.group(3)) }) # 按轮统计:成功卡数和最大耗时 by_round = defaultdict(list) for r in rows: by_round[r["round"]].append(r) with open("rfid_summary.csv", "w", newline="", encoding="utf-8") as f: writer = csv.writer(f) writer.writerow(["round", "card_count", "max_elapsed_us", "uids"]) for rnd in sorted(by_round): items = by_round[rnd] writer.writerow([ rnd, len(items), max(i["elapsed_us"] for i in items), "|".join(i["uid"] for i in items) ]) print(f"records={len(rows)}, rounds={len(by_round)}")脚本里的正则要求日志格式固定,所以代码里的Serial.print字段顺序不要随意改。by_round按轮聚合,card_count是单轮读到的卡数,max_elapsed_us是单轮最大累计耗时。输出的CSV可以直接导入表格软件画柱状图或箱线图。报告里的图表建议用“标签数量-成功率”和“距离-平均耗时”两组,不要把所有数据塞进一张图。
实验报告的“结果与分析”部分要解释异常点:某一轮耗时突然变大,可能是卡进入HALT后没有及时离开;某一档误读率升高,可能是标签重叠或距离过近导致场强饱和。分析要回到原始日志,不要只写“受环境影响”。代码版本、日志文件和报告数据表放在同一目录,换人复查时能按文件名对应上。
5. 避坑与排查:寻卡时有时无、写块失败、报告数据对不上怎么定位
RFID实验的排查顺序比“改代码”重要。读卡链路从供电、SPI、天线、寻卡、认证到块操作,每一层都有独立现象。下面记录5条课程设计里高频出现的踩坑,每条按“现象、原因、解决”写。你不需要全部遇到,但遇到时能少烧几块板子。
5.1 硬件与供电类踩坑
现象一:VersionReg读出来是0xFF,换代码也没用。
原因通常是MISO被固定拉高、片选没有真正拉低、SPI模式不对,或者模块VCC和GND没有共地。还有一种情况是主控引脚复用了其他功能,代码里配置了SPI,但实际引脚没有切到SPI外设。解决时先断开MFRC522,只测主控SCK有没有波形;再接上模块,用万用表确认VCC在3.3V附近、GND导通;最后检查CS_LOW()是否真的把片选拉到地。不要在同一时间改SPI时钟和地址转换,两个变量一起动,出了问题无法定位。
现象二:寻卡距离只有几毫米,手一靠近就读不到。
原因多半是供电纹波大、杜邦线太长、天线附近有金属,或者模块被5V供电后工作异常。MFRC522模块对电源质量敏感,USB转串口板的3.3V输出带不动天线峰值电流时,VersionReg能读,但一开天线就复位。解决时用独立3.3V供电,VCC和GND之间加100nF和10uF电容,缩短SPI线到10厘米以内,把模块从金属桌面和手机旁边移开。实验报告里要记录供电方式和读距,不要只写“读卡正常”。
5.2 协议与参数类踩坑
现象三:寻卡成功,读块返回STATUS_MIFARE_NACK。
原因通常是密钥错、块地址落在未认证扇区、目标块是尾块且访问控制位不允许,或者卡片不是MIFARE Classic。解决时先确认卡类型和扇区号,再用默认密钥认证块4;认证通过后只读块4,不要直接写。如果读块4也NACK,换一张已知可读的卡对比。写块前先读一次原数据,确认块地址不是0和3的倍数尾块。密钥不要散落在多个函数里,统一从一个数组读取,改密钥时只改一处。
现象四:多标签实验只能读到一张,或者同一张卡重复计数。
原因是轮询循环里没有PICC_HaltA(),或者HALT后没有给卡离开的时间;也可能是两张卡重叠太近,防冲突阶段读到的UID不完整。解决时每读完一张卡就调用HaltA(),循环之间加100ms左右间隔;测试时把标签分开摆放,不要叠在一起。日志里如果出现同一UID连续多轮,先查HALT和场复位,不要先怀疑防碰撞算法。多标签实验的成功率要按“预期卡数”算,不要按“读到的记录数”算。
5.3 报告与版本类踩坑
现象五:实验报告里的数据和串口日志对不上。
原因是代码改过但日志没重录、报告引用了旧截图、采样时手动删除了失败记录,或者日志格式变了导致脚本漏解析。解决时给代码加一个版本字符串,启动时打印出来;日志每行带时间戳和UID;报告里的每个统计表标注对应的日志文件名和采样日期。失败记录不要删,单独统计到异常表里。带课的助教核验时,先看日志文件能不能被脚本重新算出同样的数字,再看报告结论。
| 排查顺序 | 先查什么 | 具体动作 | 判定通过 |
|---|---|---|---|
| 1 | 供电和共地 | 量VCC、GND,加滤波电容 | VersionReg稳定 |
| 2 | SPI链路 | 读VersionReg,看SCK波形 | 返回0x91/0x92 |
| 3 | 天线 | 调PCD_AntennaOn(),看TxControlReg | 能寻到单张卡 |
| 4 | 认证 | 用已知密钥认证块4 | 返回STATUS_OK |
| 5 | 块操作 | 读块、写块、回读比较 | 数据逐字节一致 |
| 6 | 日志与报告 | 用脚本重算统计 | 数字与报告一致 |
下面这段自检代码把关键寄存器一次性打印出来,适合在排查时快速判断链路状态。VersionReg看SPI,TxControlReg看天线使能,ErrorReg看最近一次通信错误,CollReg看冲突标志。打印前先确保串口初始化完成。
// 上电自检:打印关键寄存器,快速判断链路层状态 void rfidSelfCheck() { byte version = rfid.PCD_ReadRegister(MFRC522::VersionReg); byte txCtrl = rfid.PCD_ReadRegister(MFRC522::TxControlReg); byte error = rfid.PCD_ReadRegister(MFRC522::ErrorReg); byte coll = rfid.PCD_ReadRegister(MFRC522::CollReg); Serial.print("VersionReg=0x"); Serial.println(version, HEX); Serial.print("TxControlReg=0x"); Serial.println(txCtrl, HEX); Serial.print("ErrorReg=0x"); Serial.println(error, HEX); Serial.print("CollReg=0x"); Serial.println(coll, HEX); if ((txCtrl & 0x03) == 0) { Serial.println("antenna off, call PCD_AntennaOn()"); } }这段代码只读寄存器,不改变卡状态,适合在setup()里调用一次,也可以在每次寻卡失败后调用。TxControlReg最低两位控制天线引脚,读出0x03附近才说明天线使能;ErrorReg里的位能提示协议错误、CRC错误或缓冲区溢出;CollReg的冲突位能辅助判断多卡场景。注意不要在认证过程中频繁读寄存器,某些读操作会打断当前命令,排查完就回到正常流程。
6. 进阶用法:用一次最小闭环验证把代码和实验报告锁在同一版本
课程设计交上去之后,最怕被问“这个数据是哪版代码跑出来的”。我现在的习惯是:每次改完读卡逻辑,先跑一次最小闭环验证,把VersionReg、寻卡UID、块4读写结果和日志文件名记在同一张回归表里。回归表不复杂,但能把代码、日志、报告三样东西锁在同一版本上。下面这张表可以直接抄到实验记录里。
| 回归项 | 操作 | 通过标准 | 记录位置 |
|---|---|---|---|
| SPI链路 | 读VersionReg | 返回0x91或0x92 | 串口日志首行 |
| 单卡寻卡 | 放一张已知卡 | UID与卡面记录一致 | rfid_log.txt |
| 认证读块 | KeyA认证块4并读16字节 | 状态码STATUS_OK | 读块日志 |
| 写块回读 | 写块4后立即读回 | 16字节逐字节一致 | 写块日志 |
| 多标签轮询 | 2张卡各读3轮 | 不漏卡、不重复计数 | 多标签日志 |
| 报告对账 | 用脚本重算统计 | 与报告表格数字一致 | rfid_summary.csv |
这张表的关键是“每次只动一个变量”。换密钥就只换密钥,不要同时改SPI时钟和块地址;换标签就只换标签,不要顺手改采样轮数。回归跑完再把日志和报告一起归档,文件名带上日期和代码版本,例如rfid_log_v3_20240520.txt。报告里的每个图表下面标注数据来源文件,复查的人不用问你要原始数据。
还有一个实用技巧:把失败状态码也纳入回归表。很多人只记录成功样本,结果报告里成功率永远是100%,一问失败原因就说不清。我的做法是保留每次STATUS_TIMEOUT和STATUS_MIFARE_NACK的计数,失败样本单独放一张异常表,分析时说明是卡距离、密钥还是多卡冲突导致的。这样即使数据不完美,报告也可信。
从那以后我每次交这类课程设计前,都强制走一遍“VersionReg、寻卡、读块、写块回读、日志对账”五步,不再靠记忆判断代码有没有改乱。希望帮到你。
本文还有配套的精品资源,点击获取