☰
基于51单片机与MFRC522的Mifare One公交刷卡系统
2026/11/2 9:09:08 网站建设 项目流程

简介:面向电子信息工程、自动化等专业课程设计与毕业设计场景的公交RFID刷卡系统方案文档,围绕城市公交一卡通中的非接触式刷卡实现展开,适合需要参考硬件选型、软件流程与总体架构的学生及初级开发者。压缩包内仅含1个doc文件,整体约649KB,以电子系统设计说明书的形式组织内容,目录结构清晰,便于直接查阅和二次整理。文档以Mifare One射频卡和AT89S52单片机为核心,依次说明晶振电路、蜂鸣器提示音、LCD1602显示、DS1302时钟模块等硬件组成,并给出系统初始化、射频卡与读写器通信、软件设计总流程图等关键环节,能够帮助读者理解从器件选型、模块连接到程序流程设计的完整思路。资源还讨论了系统可靠性与成本之间的权衡,并提到该方案可向物流管理、身份识别等场景延伸。目前已有4299人学习,适合作为公交刷卡类课题的方案模板与知识点对照材料。

1. 一块 51 单片机和一张 M1 卡,撑起一次“滴”声

早高峰挤上车,卡往读卡区一贴,“滴”一声,两块钱没了。这套 RFID 公交车刷卡系统拆开看没有那么玄:一颗 AT89S52 单片机、一片 MFRC522 读卡芯片、一张 Mifare One 卡,外加 LCD1602 和 DS1302 时钟芯片。这份电子系统设计说明书把射频卡选型、11.0592MHz 晶振、天线匹配、寻卡防冲突、扇区密钥认证、钱包扣费一路写到流程图,三千多字的正文配了完整的硬件框图和软件时序,适合刚学完单片机、想找个真能上手的综合案例的人练手。它不是点灯级别的玩具,从卡片进入磁场到余额真正落到 EEPROM 里,中间每一步都有坑,把它焊出来跑到稳定读卡,非接触式 IC 卡这扇门基本就算推开了。

2. 硬件架构:从 Mifare One 卡到 AT89S52 的模块分工

2.1 射频卡与主控的选型账

选型这部分最容易被跳过,但恰恰决定了后面所有代码的形状。说明书里把市面上常见的卡列了一遍:条码、磁卡、接触式 IC 卡、非接触式 IC 卡。条码便宜但怕脏怕折,公交车上一天几百次摩擦,几个月就糊了;磁卡要刷磁条,读写头得定期清,磁条还怕磁场;接触式 IC 卡芯片外露,插拔次数一多,触点磨损、卡片折断都是常见故障。非接触式 IC 卡把芯片和线圈封在 PVC 里,没有裸露触点,不怕水不怕弯,刷卡也不用对准,隔着两三厘米就能读。

最终落在 Mifare One(也就是常说的 M1、S50)上,理由很实在:符合 ISO/IEC 14443 Type A 协议,13.56MHz 载波,1KB EEPROM 分成 16 个扇区、每扇区 4 块、每块 16 字节,出货量大、单价低、资料满地都是。更关键的是它带密钥认证,每个扇区有独立的 KeyA 和 KeyB,刷卡扣费这件事必须要有这个门槛,否则谁拿个读卡器都能改余额。

卡类型成本寿命抗污损可读写安全性
条码极低短差只读无
磁卡低中差读写弱
接触式 IC 卡中中(触点磨损)中读写强
Mifare One 非接触卡中低长好读写强(密钥认证)

主控选 AT89S52 而不是 DSP 或 ARM,逻辑同样朴素。这个系统要做的事:驱动 SPI 读卡、刷 LCD1602、读 DS1302、响蜂鸣器、走一路串口。这些任务对算力的要求在几十 MIPS 以下,ARM 的余量全浪费了,价格还翻好几倍。AT89S52 有 8KB Flash、256 字节 RAM、32 个 I/O、3 个定时器和 1 个全双工串口,兼容标准 MCS-51 指令集,支持 ISP 在线下载,改一次程序不用拔芯片。说明书里有一处写“18k bytes Flash”,应该是笔误,8KB 才是这颗片子的真实规格,做这个项目刚好塞得下。

提示:引脚数和 RAM 是这类项目的硬约束。256 字节 RAM 意味着不能开大缓冲区,读回来的卡片数据要立刻处理掉,别想着先攒着——这点在后面写代码时会反复咬人。

2.2 最小系统与三个外设模块的电路细节

晶振选 11.0592MHz 而不是更常见的 12MHz,是这套电路里少数几个“选错了后面全是玄学”的地方。11.0592MHz 经过 12 分频后是 921.6kHz,再除以 96/48/32 这些整数分频系数,正好能得到 9600、19200、57600 的标准波特率,误差为零。换成 12MHz 算一下,9600 波特率下的实际值是 9615,误差 0.16%,短帧还能忍,连续收发就开始丢字节,串口助手上一片乱码,新手往往要去怀疑线序、怀疑 USB 转串口芯片,其实根子在晶振上。晶振两端各接一只 30pF 的负载电容到地,这两只电容影响谐振频率和起振幅度,取值别乱改,少了不起振,大了频率偏移。

复位电路是经典的 10μF 电解电容加 10kΩ 下拉电阻,上电瞬间电容相当于短路,RST 脚被拉到高电平,随着电容充电,RST 电压降下来,完成上电复位。EA 脚必须接高电平,告诉单片机从片内 Flash 取指令,这个脚悬空的话程序跑不起来,而且现象很迷惑——什么都不发生。

蜂鸣器这一路用的是 PNP 三极管做开关。单片机 I/O 口的灌电流和拉电流都有限,直接驱动蜂鸣器会烧口或者声音小得听不见。P2.3 输出高电平时三极管截止,蜂鸣器回路断开,不响;输出低电平时三极管导通,蜂鸣器得电发声。这个逻辑和很多人的直觉相反,写代码的时候别把电平写反了,否则会变成“刷卡不响、不刷卡一直响”。

LCD1602 是 16×2 的字符型液晶,控制器兼容 HD44780,指令集到处都能查到。8 位并口模式占用 P0 口做数据线,RS、RW、E 三根控制线接 P2 口。P0 口内部没有上拉电阻,作为输出口驱动 LCD 时必须外接 10kΩ 排阻,否则电平拉不上去,屏幕会出现一半字符正常一半发虚的怪现象。对比度脚 V0 接 10kΩ 电位器的中间抽头,上电后调到字符清晰、背景不过黑的位置。

DS1302 走三线同步串行:CE(复位/片选)、SCLK(时钟)、I/O(双向数据)。它能对年月日周时分秒计时,带闰年补偿,工作电压 2.5V~5.5V,还内置 31 字节的 RAM。主电源掉电后靠备用纽扣电池维持走时,这是公交刷卡机必须有的能力——总不能每次断电重启时间就归零。它支持 Burst 模式,一次把 8 个时间字节连续读出来,比逐字节读省事,也不容易读到“秒变了但分没变”的撕裂数据。

MFRC522 这一片是整个系统的核心。它分模拟和数字两部分:模拟部分负责驱动天线、产生 13.56MHz 能量载波、对发送数据做调制、对卡片返回的信号做检测和解调;数字部分通过 SPI 或并口跟单片机通信。载波由外接的 13.56MHz 晶振提供,天线部分由 LC 低通滤波器和 LC 谐振电路组成,这两组电容电感的取值直接决定读卡距离,后文排查章节会专门讲。

2.3 总体框图与管脚分配

按功能切,整个系统分四块:控制模块(AT89S52 + 晶振 + 复位)、收发数据模块(MFRC522 + 13.56MHz 晶振 + 天线)、串行通信模块(跟 PC 或上位机对接)、显示模块(LCD1602 + 蜂鸣器)。控制模块是大脑,负责调度;收发模块是手,负责跟卡片对话;串口是嘴,把读到的卡号和时间吐给上位机;显示和蜂鸣器是脸,告诉乘客这一下刷成功没有。

具体管脚我一般这么分,改板子的时候照着这张表挪就行:

功能单片机引脚对接器件引脚备注
MFRC522 片选 NSSP1.0SDA/NSS低电平有效
MFRC522 时钟 SCKP1.1SCKSPI 时钟
MFRC522 主出 MOSIP1.2MOSI加电平转换
MFRC522 主入 MISOP1.3MISO加电平转换
MFRC522 复位 RSTP1.4RST低电平复位
LCD1602 数据P0.0~P0.7D0~D7外接 10k 排阻上拉
LCD1602 控制P2.0/P2.1/P2.2RS/RW/ERW 可接地只写
蜂鸣器P2.3三极管基极低电平响
DS1302P2.4/P2.5/P2.6CE/SCLK/I-O三线
串口P3.0/P3.1RXD/TXD接 USB-TTL

注意:AT89S52 是 5V 器件,MFRC522 是 3.3V 器件,两边直接对接是这份设计里最容易翻车的地方。5V 的 MOSI、SCK、NSS 灌进 3.3V 的输入脚,短时间能用,时间长了会把芯片打坏;反过来 3.3V 的 MISO 输出高电平只有 3.3V,AT89S52 的高电平判决门限大约在 0.7×VCC = 3.5V,勉强够不着,读回来全是 0xFF。稳妥做法是加一片双向电平转换,或者最省事的电阻分压加 MISO 上拉。

3. 软件时序:从寻卡到钱包扣费的完整指令链

3.1 上电初始化:串口、定时器与 MFRC522 复位

初始化的顺序有讲究,先让单片机自己能跑起来,再让外设一个个醒过来。串口初始化放最前面,因为后面所有调试信息都要靠它吐出来,串口没通等于闭着眼睛修车。

/* 系统时钟 11.0592MHz,串口 9600-8-N-1 */ void UartInit(void) { SCON = 0x50; /* 方式1:8位UART,允许接收 */ TMOD &= 0x0F; /* 清掉定时器1的控制位,不动定时器0 */ TMOD |= 0x20; /* 定时器1工作在方式2:8位自动重装 */ TH1 = 0xFD; /* 11.0592MHz 下 9600bps 的重装值 */ TL1 = 0xFD; TR1 = 1; /* 启动定时器1,波特率发生器开始工作 */ ES = 1; /* 开串口中断 */ EA = 1; /* 开总中断 */ }

TH1/TL1 = 0xFD这个值不是拍脑袋来的:11.0592MHz ÷ 12 = 921.6kHz 是定时器的计数频率,方式2 是 8 位重装,256 - 921600/(32×9600) = 256 - 3 = 253 = 0xFD。换成 12MHz 晶振,算出来是 256 - 3.125 = 252.875,取整 253,误差就出来了。SCON = 0x50里,高四位 0101 表示方式1,bit4 是 REN 允许接收。

MFRC522 的初始化分硬复位和软复位两步。硬复位拉低 RST 脚至少 100ns 再拉高,然后等内部振荡器起振;软复位是往 CommandReg 写 0x0F,芯片自己回到初始状态。复位完成后必须做一件事——打开天线。很多板子焊完读不到卡,查半天 SPI 通信正常,最后发现是 TxControlReg 的低两位没置 1,天线根本没通电。

void RC522_Init(void) { unsigned char i; RST = 0; /* 硬复位拉低 */ DelayUs(10); RST = 1; /* 释放,芯片开始起振 */ DelayMs(50); /* 等内部振荡器稳定,别省这一步 */ RC522_WriteReg(CommandReg, 0x0F); /* 软复位 */ while (RC522_ReadReg(CommandReg) & 0x10); /* 等 PowerDown 位清零 */ RC522_WriteReg(TModeReg, 0x8D); /* 定时器自动启动,周期 25us */ RC522_WriteReg(TPrescalerReg, 0x3E); RC522_WriteReg(TReloadRegH, 0x00); RC522_WriteReg(TReloadRegL, 0x1E); /* 超时约 15ms */ RC522_WriteReg(TxASKReg, 0x40); /* 100% ASK 调制,ISO14443A 要求 */ RC522_WriteReg(ModeReg, 0x3D); /* CRC 初值 0x6363 */ RC522_SetBitMask(TxControlReg, 0x03); /* 关键:打开天线驱动 */ for (i = 0; i < 10; i++) /* 天线起振需要一点时间 */ DelayMs(10); }

TModeReg和两个 Reload 寄存器配的是 MFRC522 内部的超时计数器。读卡的时候如果卡片一直不回应,SPI 会一直等下去,主循环卡死,屏幕和蜂鸣器全没反应。设一个 15ms 左右的超时,超时就返回错误,主循环继续转,体验上才不会“死机”。TxASKReg = 0x40是强制 100% 幅度键控调制,Mifare 卡只认这个调制深度。ModeReg = 0x3D把 CRC 预设值设成 0x6363,这是 ISO14443A 规定的多项式初值,认证和读写命令都要用它。

判断芯片到底活没活,有个一步到位的办法:读 VersionReg(地址 0x37),正常的 MFRC522 会返回 0x91 或 0x92。返回 0x00 或者 0xFF,说明 SPI 根本没通,别再往下调协议了,先回去查线。

/* 上电自检:放在 main 开头,一句话定位 SPI 通不通 */ unsigned char RC522_SelfTest(void) { unsigned char ver = RC522_ReadReg(VersionReg); if (ver == 0x91 || ver == 0x92) Uart_SendStr("RC522 OK\r\n"); else Uart_SendStr("RC522 NOT FOUND, check SPI\r\n"); return ver; }

3.2 寻卡、防冲突与选卡的三段式指令链

读写器和卡片打交道,协议上规定了固定顺序,跳步必翻车。第一步寻卡(Request),读写器发 0x52(WUPA,唤醒天线区内所有卡)或 0x26(REQA,只唤醒空闲卡),卡片回一个 2 字节的 ATQA,用来告诉读写器“我是 Type A,我的 UID 长度是多少”。第二步防冲突(Anticollision),多发 0x93、0x20,卡片回 4 字节 UID 和一个 BCC 校验字节。第三步选卡(Select),发 0x93、0x70 加 UID 加 BCC,卡片回 SAK,表示“选我成功了,接下来可以认证了”。

多人同时上车的时候,天线区里可能有两三张卡,防冲突机制就是在这时候从里面挑一张操作,其余卡回到空闲态等下一轮。跳过这一步直接发选卡命令,现象是读卡成功率飘忽不定,人少的时候好好的,一挤就乱。

/* 底层收发:所有 PICC 命令都从这里走 */ char PcdComMF522(unsigned char command, unsigned char *inData, unsigned char inLen, unsigned char *outData, unsigned char *outLen) { unsigned char irqEn = 0x00, waitFor = 0x00, lastBits, n; unsigned int i; if (command == PCD_TRANSCEIVE) { irqEn = 0x77; /* 发送/接收/空闲/错误/定时器中断全开 */ waitFor = 0x30; /* 等 RxIRq(0x20) 或 IdleIRq(0x10) */ } RC522_WriteReg(ComIEnReg, irqEn | 0x80); RC522_ClearBitMask(ComIrqReg, 0x80); /* 清所有中断标志 */ RC522_SetBitMask(FIFOLevelReg, 0x80); /* FlushBuffer,清空 FIFO */ RC522_WriteReg(CommandReg, PCD_IDLE); for (i = 0; i < inLen; i++) RC522_WriteReg(FIFODataReg, inData[i]); /* 命令字依次压进 FIFO */ RC522_WriteReg(CommandReg, command); if (command == PCD_TRANSCEIVE) RC522_SetBitMask(BitFramingReg, 0x80); /* StartSend,真正发出去 */ i = 2000; /* 软件超时,约 25ms */ do { n = RC522_ReadReg(ComIrqReg); } while ((i--) && !(n & 0x01) && !(n & waitFor)); RC522_ClearBitMask(BitFramingReg, 0x80); if (i == 0) return MI_ERR; /* 超时,卡片没回应 */ if (RC522_ReadReg(ErrorReg) & 0x1B) return MI_ERR; /* 冲突/CRC/协议错 */ n = RC522_ReadReg(FIFOLevelReg); /* 收到几个字节 */ lastBits = RC522_ReadReg(ControlReg) & 0x07; /* 最后一字节有效几位 */ *outLen = lastBits ? (n - 1) * 8 + lastBits : n * 8; if (n == 0) n = 1; if (n > 16) n = 16; /* 防溢出,缓冲区只有 16 字节 */ for (i = 0; i < n; i++) outData[i] = RC522_ReadReg(FIFODataReg); return MI_OK; }

FIFOLevelReg的 bit7 是 FlushBuffer,写 1 清空 64 字节的 FIFO,每次发命令前必须清,否则上一次的残留数据会混进来。BitFramingReg的 bit7 是 StartSend,写 1 触发发送,发送完成后芯片自动清零,所以后面要显式清一次。lastBits处理的是“最后一字节只发了几位”的情况,寻卡命令只有 7 位,不是整字节,这个值不对会导致命令字被卡片当成噪声丢掉。

拿到 UID 之后要做一次 BCC 校验:UID0 异或 UID1 异或 UID2 异或 UID3,结果应该等于第五个字节。这一步能挡掉一部分通信误码,尤其是在读卡距离边缘、信号弱的时候。

char PcdAnticoll(unsigned char *uid) { char status; unsigned char i, buf[5] = {0x93, 0x20, 0, 0, 0}; unsigned char len; RC522_ClearBitMask(Status2Reg, 0x08); /* 关掉 Crypto1 加密,明码通信 */ RC522_WriteReg(BitFramingReg, 0x00); /* 整字节发送 */ RC522_ClearBitMask(CollReg, 0x80); /* 清冲突标志 ValuesAfterColl */ status = PcdComMF522(PCD_TRANSCEIVE, buf, 2, buf, &len); if (status == MI_OK) { for (i = 0; i < 4; i++) uid[i] = buf[i]; if ((uid[0] ^ uid[1] ^ uid[2] ^ uid[3]) != buf[4]) status = MI_ERR; /* BCC 对不上,判定为误码 */ } return status; }

Status2Reg的 bit3 是 MFCrypto1On,认证成功后由硬件置位。防冲突阶段必须把它清掉,否则芯片会以为还在加密通道里,发出去的数据全被加密,卡片根本解不出来。这个坑很隐蔽——现象是第一次刷卡正常,第二次开始永远读不到,因为上一次认证留下的加密位没清。

3.3 扇区认证与钱包块扣费

Mifare One 的存储结构是 16 个扇区,每个扇区 4 块,每块 16 字节。第 0 扇区第 0 块存厂商代码和 UID,出厂只读,改不了。每个扇区的第 3 块是尾块,存 KeyA(6 字节)、存取控制位(4 字节)、KeyB(6 字节),也不能当数据块用。所以真正能拿来存余额的,是各扇区的前三块。

认证的命令字是 0x60(用 KeyA 认证)或 0x61(用 KeyB 认证),后面跟 1 字节绝对块号、6 字节密钥、4 字节 UID,一共 12 字节。认证通过后,硬件把 MFCrypto1On 置 1,后续所有读写都在加密通道里跑。

char PcdAuthState(unsigned char mode, unsigned char block, unsigned char *key, unsigned char *uid) { char status; unsigned char len, buf[12]; buf[0] = mode; /* 0x60 = KeyA,0x61 = KeyB */ buf[1] = block; /* 绝对块号 0~63 */ memcpy(&buf[2], key, 6); /* 6 字节密钥 */ memcpy(&buf[8], uid, 4); /* 4 字节卡号 */ status = PcdComMF522(PCD_AUTHENT, buf, 12, buf, &len); if (status != MI_OK || !(RC522_ReadReg(Status2Reg) & 0x08)) status = MI_ERR; /* 命令通了但加密位没置,仍算失败 */ return status; }

这里有个细节:PcdComMF522返回 MI_OK 只说明 SPI 层面收发完成了,不代表认证成功。真正的判据是Status2Reg的 bit3 有没有置起来。只看返回值不看这个位,会出现“认证失败但程序以为成功,接着去读数据读到一片 0”的情况。

扣费这块,Mifare 卡提供了一整套钱包指令,比直接读改写安全得多。直接写会有个致命问题:写一半断电,余额就废了。钱包指令的做法是,减值(0xC1)只在芯片内部的临时数据寄存器里算,不碰 EEPROM;确认无误后再发传输(0xB0),才真正写回块里。中间断电,卡片上的余额保持原样,不会变成乱码。

/* 钱包扣费:认证 -> 读余额 -> 判断 -> 减值 -> 传输落盘 */ char Wallet_Pay(unsigned char block, unsigned char *uid, unsigned long amount) { unsigned char buf[16], len; unsigned long balance; if (PcdAuthState(0x60, block, KEYA, uid) != MI_OK) return MI_ERR; /* 密钥不对,直接退出 */ buf[0] = 0x30; buf[1] = block; /* READ 命令 */ if (PcdComMF522(PCD_TRANSCEIVE, buf, 2, buf, &len) != MI_OK) return MI_ERR; balance = ((unsigned long)buf[0]) | ((unsigned long)buf[1] << 8) | ((unsigned long)buf[2] << 16) | ((unsigned long)buf[3] << 24); /* 小端序 */ if (balance < amount) return MI_NOTAUTH; /* 余额不足,在减法之前拦掉 */ buf[0] = 0xC1; buf[1] = block; /* DECREMENT */ buf[2] = (unsigned char)(amount); buf[3] = (unsigned char)(amount >> 8); buf[4] = (unsigned char)(amount >> 16); buf[5] = (unsigned char)(amount >> 24); if (PcdComMF522(PCD_TRANSCEIVE, buf, 6, buf, &len) != MI_OK) return MI_ERR; buf[0] = 0xB0; buf[1] = block; /* TRANSFER,真正写回 EEPROM */ if (PcdComMF522(PCD_TRANSCEIVE, buf, 2, buf, &len) != MI_OK) return MI_ERR; return MI_OK; /* 到这一步余额才真的变了 */ }

amount用 4 字节小端序存,和 Mifare 钱包块的数据格式对齐。MI_NOTAUTH是我自己加的一个返回码,用来跟通信错误区分开,界面上可以据此显示“余额不足”而不是笼统的“刷卡失败”。主循环里把寻卡、防冲突、选卡、扣费串起来,串口顺手把 UID 打出去,联调的时候能省很多事。

4. 跑通流程:从焊板子到串口打印出第一张卡号

4.1 供电与连线自检

这块板子有两个电源域,5V 给单片机和 LCD1602,3.3V 给 MFRC522。很多人图省事,直接从 5V 串一个电阻降压给读卡芯片,结果是待机电压 3.3V 看着正常,天线一发射,电流瞬间冲到 100mA 以上,电压塌到 2.8V,芯片反复复位。读卡距离短、时好时坏,根子就在这里。稳妥做法是用一片 AMS1117-3.3 单独稳压,输入端和输出端各挂 10μF 加 0.1μF,天线附近再补一只 100nF。

上电之前先用万用表量三件事:5V 和 3.3V 对地有没有短路;MFRC522 的 VCC 和 GND 之间是不是 3.3V;晶振两个脚对地的电压大概在 1.2V~2V 之间,说明起振了。第三点经常被忽略,晶振不起振的时候芯片看起来完全正常,SPI 也能读写寄存器,就是永远读不到卡。

提示:MFRC522 的天线是 PCB 走线做的电感,跟匹配电容一起构成 13.56MHz 谐振回路。天线附近别铺大面积地,别走其他信号线,否则谐振频率偏移,读卡距离从 5cm 掉到 5mm。

4.2 编译烧录与第一行串口输出

代码在 Keil C51 下编译,工程里要把启动文件STARTUP.A51加进去,输出格式勾选 HEX-80。AT89S52 支持 ISP 在线下载,接口就在 P1.5(MOSI)、P1.6(MISO)、P1.7(SCK)加 RST 这四根线上,板上留一个 5 针排针,用 USBASP 之类的下载器接上就能烧,不用把芯片拔下来插编程器。

烧录顺序建议分三段验证,别一次把全部代码烧进去然后对着不亮的屏幕发呆。第一段只烧串口打印,while(1)里每秒输出一句字符串,能稳定收到就说明最小系统、晶振、串口线全对。第二段加上 LCD 和 DS1302,屏幕上能看到走时,说明并口时序和三线时序都通了。第三段才是 MFRC522,先只读 VersionReg,读到 0x91 再加寻卡。

/* 三段式自检的主函数骨架 */ void main(void) { unsigned char uid[4], type[2]; SystemInit(); /* 串口 -> LCD -> DS1302 -> RC522 */ LCD_ShowStr(0, 0, " BUS CARD READY"); while (1) { if (PcdRequest(0x52, type) != MI_OK) continue; /* 寻卡 */ if (PcdAnticoll(uid) != MI_OK) continue; /* 防冲突,拿 UID */ if (PcdSelect(uid) != MI_OK) continue; /* 选卡 */ Uart_SendHex(uid, 4); /* 先把卡号打出来,联调最有用 */ if (Wallet_Pay(8, uid, 200) == MI_OK) { /* 块 8,扣 2.00 元 */ Beep(50); LCD_ShowStr(1, 0, "PAY OK -2.00 "); } else { Beep(200); LCD_ShowStr(1, 0, "PAY FAIL "); } DelayMs(800); /* 防止同一张卡连刷 */ } }

PcdRequest、PcdAnticoll、PcdSelect三个函数任何一个失败就continue回主循环,别在里面死等。有人写成while(PcdRequest(...) != MI_OK);,卡片不在场的时候整个程序卡死在这一行,屏幕不刷新、串口不输出,看起来像死机,其实是逻辑写死了。DelayMs(800)是防连刷,同一个人刷完卡手还没抬起来,下一轮又读到了,会连扣两次。

4.3 用逻辑分析仪抓 SPI 波形定位通信问题

代码全对但读不到卡的时候,别再改代码了,上逻辑分析仪。8 通道的便宜货就够,采样率设到 24MHz 以上,接 NSS、SCK、MOSI、MISO 四根线,触发条件设成 NSS 下降沿。

正常的一帧长这样:NSS 拉低,第一个字节是地址字节,读操作时最高位是 1,写操作时最高位是 0,bit0 固定 0,中间 6 位是寄存器地址。比如读 VersionReg(0x37),发出去的应该是0x37 << 1 | 0x80 = 0xEE。抓到 0xEE 之后跟着 8 个时钟,MISO 上应该出现 0x91 或 0x92。如果地址字节就是错的,说明移位方向或者位序搞反了;如果地址对但 MISO 全是高电平,说明从机没驱动总线,查片选和供电;如果 MISO 上有数据但跟在错误的时钟沿上,说明 CPOL/CPHA 配错了。MFRC522 用的是 SPI 模式 0,时钟空闲为低,上升沿采样。

还有个更省事的招:把 SCK 频率降到 100kHz 以下再试。线过长、上拉不足、电平转换芯片速度不够,这些问题在高速下才暴露,降速能读、全速不能读,基本可以锁定是信号完整性问题,去查线长和上拉,而不是继续怀疑软件。

5. 排查手册:读卡距离短、死机、乱码的五个高频坑

5.1 射频与卡片侧的三类现象

现象一:读卡距离只有几毫米,卡片必须贴到天线上才有反应。

原因通常有两个。一是天线匹配没调,MFRC522 的天线接口是差分输出,外面接 LC 低通滤波和 LC 谐振两节网络,谐振频率偏到 13.56MHz 之外,发射效率直线下降。二是TxControlReg的低两位没置 1,天线驱动只有一路开着,功率减半。

解决:先用示波器或者频谱仪看天线两端的载波幅度,正常应该在十几伏峰峰值(这是谐振升压的结果,别被吓到)。调匹配电容,让幅度最大。软件上确认RC522_SetBitMask(TxControlReg, 0x03)这一句真的执行到了,别放在某个提前 return 的分支后面。天线周围 5mm 内不要放金属,电池、螺丝、屏蔽罩都会吸走能量。

现象二:一个人刷卡正常,两三个人同时挤上来就乱,读到的卡号对不上。

原因是防冲突流程没走完,或者走了但没做 BCC 校验。多人场景下天线区里有多张卡同时回应,读写器收到的 UID 是几路信号叠加的结果,每一位都可能冲突。协议规定的防冲突机制会让卡片按位依次退让,最终只留一张,这个过程必须完整执行。

解决:确认PcdRequest → PcdAnticoll → PcdSelect三步都在,且CollReg的 bit7(ValuesAfterColl)在每次防冲突前清零。加上 BCC 校验,uid[0]^uid[1]^uid[2]^uid[3]必须等于返回的第五个字节,对不上就丢弃重来。实测下来,加了校验之后误读率基本归零。

现象三:某张卡昨天还能刷,今天怎么都读不出来,换张卡就行。

原因是那个扇区被锁死了。Mifare 卡有安全机制,同一个扇区连续认证失败若干次(不同批次芯片是 3 到 10 次不等),卡片会把这整个扇区永久锁死,之后连正确的密钥也认证不过去。新手调试的时候,密钥还没确认就往死里循环重试,几次就把卡废了。

解决:调试阶段把重试次数限制在 2 次以内,失败就停手,先确认密钥。KeyA 出厂默认是 6 个 0xFF,如果卡片被前一个项目改过密钥,就得用已知密钥的卡先跑通流程。写代码时给认证函数加一个失败计数器,超过阈值就打印告警并暂停,别让它无限重试。

5.2 主控、供电与接口侧的两类现象

**现象四:程序跑着跑

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询