MFRC522 RFID课程设计:从VersionReg到扇区读写闭环 简介中南大学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是扇区尾块存密钥和访问控制位写错会把整张卡锁成只读。项目常见参数课程设计里的注意点读写器MFRC52213.56MHz只覆盖ISO/IEC 14443-A不兼容低频卡标签MIFARE Classic 1K/4K块地址和尾块位置不同代码要分支主控接口SPI3.3V逻辑不要用5V直连先确认电平转换天线板载PCB天线附近金属会明显缩短读距供电3.3V纹波尽量小杜邦线过长会引入复位和误读注意资料包里的代码可能默认某一种开发板引脚直接烧录前先核对片选、复位和SPI引脚不要只看宏定义名字。2.2 SPI引脚与初始化时序第一步先读VersionRegSPI链路要先跑通再谈协议。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或0x920x00/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(VersionReg0x); 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并检查ATQAPICC_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 1K16416每扇区第3块学生卡、门禁实验MIFARE Classic 4K32前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(rround(\d),uid([0-9A-Fa-f]),elapsed_us(\d)) rows [] with open(rfid_log.txt, r, encodingutf-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, encodingutf-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(frecords{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稳定2SPI链路读VersionReg看SCK波形返回0x91/0x923天线调PCD_AntennaOn()看TxControlReg能寻到单张卡4认证用已知密钥认证块4返回STATUS_OK5块操作读块、写块、回读比较数据逐字节一致6日志与报告用脚本重算统计数字与报告一致下面这段自检代码把关键寄存器一次性打印出来适合在排查时快速判断链路状态。VersionReg看SPITxControlReg看天线使能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(VersionReg0x); Serial.println(version, HEX); Serial.print(TxControlReg0x); Serial.println(txCtrl, HEX); Serial.print(ErrorReg0x); Serial.println(error, HEX); Serial.print(CollReg0x); 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、寻卡、读块、写块回读、日志对账”五步不再靠记忆判断代码有没有改乱。希望帮到你。本文还有配套的精品资源点击获取