1. 项目拆解:一次“嘀卡”背后的完整握手过程
先问一个问题:当你把一张IC卡贴近读卡器,听到“嘀”的一声,屏幕跳出卡号或者门禁打开,这短短几百毫秒里到底发生了什么?如果你做过嵌入式或者物联网开发,一定知道答案是“ISO14443A读卡流程”。但知道归知道,真到自己写代码调驱动的时候,很多细节就藏不住了——ATQA什么含义、防碰撞的级联怎么处理、SAK怎么解析、为什么有的卡能读有的卡读不出来。这篇文章我就把整个ISO14443A读卡流程从头到尾拆开揉碎,从协议原理到实际代码,从硬件注意到坑点排查,一次讲清楚。
ISO14443是接触式智能卡领域应用极广的国际标准,工作频率13.56MHz,其中Type A(即ISO14443A)被用在大量门禁卡、公交卡、校园卡和身份证件读取场景中。NFC手机模拟卡、很多读卡器模块(RC522、PN532、FM17550)走的也是这套协议。可以说,搞懂了ISO14443A读卡流程,你就掌握了射频卡读写的底层功底。
这篇文章适合刚接触射频读卡方案的嵌入式工程师、正在做门禁或支付终端相关项目的开发者,也适合自学NFC协议、被各种术语绕晕的初学者。我会用实际的寄存器操作和代码示例来讲解,而不是只停留在概念层。
1.1 ISO14443A是什么:读卡领域的“普通话”
ISO14443A和ISO14443B是ISO14443标准下的两种通信方式,Type A用的是米勒编码和负载调制,Type B用的是NRZ编码和副载波调制。从信号层面看,两者差异不小,但对上层应用来说,读卡流程核心是一样的:PCD(Proximity Coupling Device,也就是读卡器)负责发送能量和指令,PICC(Proximity Integrated Circuit Card,也就是卡片)通过调制负载从射频场中取能并回传数据。
所以读卡器要比我们想象中做得更多,它不仅要发出13.56MHz的载波,还要解调卡片回传的信号。PICC本身没有电池,它全靠读卡器发出的电磁场供电,这就意味着PICC的回传信号功率非常微弱,这对读卡器的模拟前端和协议时序都有要求。Type A能成为主流,很大原因是它的抗干扰能力和功耗表现比较均衡,加上MIFARE系列卡片的普及,市面上大多数非接触卡场景都被它承包了。
1.2 完整读卡流程的阶段划分
一次ISO14443A读卡,从上电到卡片数据可以被上层应用读取,大致要经历这几个阶段:
- 读卡器建立射频场,等待卡片进入。
- 读卡器持续发送REQA(Request Type A,请求A型卡),探测场内是否有卡片。
- 卡片以ATQA(Answer To Request Type A,对请求的应答)回应。
- 读卡器执行防碰撞流程,从多张卡中唯一选择一张,拿到完整UID。
- 读卡器发送SELECT(选择指令),卡片回SAK(Select Acknowledge,选择确认)。
- 如果卡片支持ISO14443-4协议层,再走ATS(Answer To Select)和PPS(Protocol Parameter Selection)协商通信参数。
- 如果涉及MIFARE等专有认证,在此之后再执行认证和读写扇区操作。
听起来不复杂,但每一步背后都有协议规定好的时序和状态机。比如卡片进入场内后,读卡器如果发送的是REQA,卡片只响应一次;如果发送的是WUPA(Wake Up Type A),卡片会从HALT状态被唤醒并响应。这个细节很多人第一次写驱动时会忽略,导致卡片“叫不醒”。
1.3 为什么要把读卡流程单独拆出来讲
很多开发者在拿到RC522模块后第一个Demo都是直接调用库函数,然后读一个UID出来就算“跑通了”。这是一种幸运,因为示例代码把防碰撞、选卡这些环节都封装好了,你根本看不到底层发生了什么。但一旦遇到多卡在场、UID长度变化、卡片进入HALT后不响应这类真实场景,只会在高层调库的人就会非常被动。
拆解读卡流程的第二个原因在于,ISO14443A是分层的协议栈。物理层管信号编码,协议层管指令交互,应用层管数据格式(比如NFC Forum定义的数据交换格式)。这三层相互独立,出问题时如果只会看应用层,根本定位不到原因。我见过好几个人把“读不到卡”归结为天线硬件问题,实际上只是防碰撞循环没有正确关闭,卡片状态已经飘掉了。
所以这篇文章不只讲“流程是什么”,还要讲“每一步为什么要这么设计”“读卡器芯片里对应的寄存器在做什么”以及“真出问题时该从哪里查”。
2. 核心细节解析:从REQA到SAK的每一步,都别想糊弄过去
有人觉得读卡流程无非就是“发指令—收响应”,但ISO14443A里每条指令都有非常具体的格式和时序要求,任何一个字节不对、任何一段等待时间不够,都会直接导致链路失败。这一节我们跟着协议栈一层层往下看,把关键细节抠明白。
2.1 第一步“叫醒”卡片:REQA和WUPA,到底该发哪个
读卡器上电后,先要建立一个稳定的13.56MHz射频场,脉冲宽度、上升时间、场强都有规格要求。场建立之后,PCD会周期性地发送REQA命令。REQA是一个7字节的短帧,具体内容是0x26,它要求PICC在接收到命令后的约5ms内回ATQA。
注意REQA和WUPA(0x52)的区别:REQA只会让处于IDLE状态的卡片响应,而WUPA可以唤醒HALT状态下的卡片。在实际轮询设计中,如果只想探测场内有卡,应该发REQA;如果发现卡片已经休眠(比如执行完某条指令后卡片自动进入HALT),就必须发WUPA才能再次激活它。很多协议栈的做法是先用REQA扫描,失败后再补发WUPA,这样既能快速发现新卡,又能把休眠的旧卡“捞”回来。
读卡器芯片一般会提供一个寄存器控制发送的帧类型。以MFRC522为例,发送REQA之前要配置CommandReg寄存器进入Transmit模式,通过写入FIFO数据0x26并调用Transceive命令发出。接收端收到的ATQA则是两个字节,比如MIFARE Classic卡通常回0x0400。
2.2 ATQA里藏的信息:不仅仅是“我在这里”
ATQA(Answer To Request Type A)虽然只有两个字节,但里面的信息很关键。第一个字节的低4位表示的是卡的UID size(UID长度),比如值为0时代表UID为4字节,值为1时代表UID为7字节,值为2时代表UID为10字节。这在后面的防碰撞阶段非常有用,因为你只有在知道UID长度的情况下,才能决定这次防碰撞要跑几轮级联。
ATQA的其他bit还包含了数据速率、是否支持bit-oriented防碰撞等能力信息。虽然很多现成库不解析这些东西,直接进防碰撞流程也能工作,但如果你想在驱动层做一些卡片能力判断、动态调整后续流程,ATQA就是第一手的参考依据。
这里特别提醒:实际项目里不要假设ATQA永远等于0x0400。我遇到过某些国产兼容卡回的是0x0004,甚至某些特殊应用卡会把bit位置反。所以读卡程序最好对ATQA做“白名单+默认容忍”的双重处理,避免因为一个字节不符合预期就放弃整张卡。
2.3 防碰撞流程:当三张卡同时进场的“点名系统”
防碰撞(Anti-Collision)是ISO14443A读卡流程中最有意思的一段。你可以把它想象成老师点名:老师喊“想来上课的同学举个手”,结果三个人同时举手,老师听不清谁是谁,于是开始逐个问“1号是你吗”“2号是你吗”。读卡器做防碰撞也是同样的思路,只不过它并不是一个个问的,而是用二进制搜索算法把不同UID的卡分开。
具体过程是这样的:PCD先发送一个带级联级别的防碰撞命令,每个PICC收到后把自己的UID片段逐位放在应答里。如果只有一张卡,那就直接返回完整UID;如果多张卡同时在场,它们会在某些bit位上发生碰撞,读卡器收到的信号既不是0也不是1,而是一个“冲突位”。PCD根据冲突位置作决策:先假设该位为0,让所有在该位为1的卡进入静默,再次发送防碰撞命令,第二轮继续处理。如此逐层缩小范围,直到只剩一张卡能完整应答。
这个流程对应到ISO14443A标准里叫“二进制搜索算法”,每一轮都要传输完整的UID二进制数。对4字节UID的卡来说最多跑4轮就能挑出一张,7字节UID则需要拆成两个级联级别分别处理。MFRC522这类芯片提供了标准的防碰撞指令,但协议栈如果做得不严谨,很容易在多卡环境下死循环。实际开发中建议设置最大碰撞轮次,比如某个级联级别超过32轮还没收敛,就要主动报错并重新发REQA复位。
2.4 选择与确认:SELECT + SAK,建立“一对一”连接
防碰撞成功后,PCD拿到了唯一的UID,接着要发送SELECT命令,把这一张卡正式激活。SELECT命令里包含了完整的UID和CRC校验,卡片收到后会返回SAK。
SAK的高位bit非常关键:第bit5(即0x20位)为1时,代表当前UID还没发完,还有下一个级联级别要继续;如果为0,则说明这就是完整的UID,防碰撞流程可以结束。此外,SAK还携带了卡片的通信协议能力,比如bit6为1表示支持ISO14443-4。你可以根据SAK判断这张卡是MIFARE Classic(通常SAK=0x08)还是支持更高级协议的卡片(如SAK=0x20,进一步走ATS流程)。
读卡流程走到这一步,“选出一张卡”已经完成,后续能不能做数据传输,取决于这张卡走的是MIFARE专有协议还是ISO14443-4标准协议。MIFARE Classic卡不会响应ATS,它需要直接执行MIFARE认证指令(如AuthKeyA/B)才能访问扇区;而符合ISO14443-4的卡片,比如公交卡、银行IC卡,则会响应ATS,并提供更完整的传输层能力。
2.5 进入更深一层:ATS与PPS,协商传输参数
如果你选的卡支持ISO14443-4,读卡器就可以发送ATS请求。ATS是一条“请求卡片能力清单”的指令,卡片会返回自己支持的帧大小、发送速率、接收速率、最大等待时间等参数。PCD解析ATS后,决定要不要通过PPS把通信速度从默认的106kbps提升到更高值(比如212kbps、424kbps或848kbps)。
PPS协商的初衷非常务实:默认速率比较慢,对大数据量的非接触交易来说拖时间,双方先商量好一个更快的速率,后续传输就能提速。但要注意,PPS协商必须双方都同意才行,强行提高速率而对方不支持,链路立刻就会断开。所以协议栈里要对PPS失败做回退处理,回到默认速率重试,而不是直接报错。
对大多数嵌入式项目来说,如果不做NFC Forum相关业务,只读一下UID,ATS和PPS这一步可以跳过。但如果你的项目要传输复杂数据(比如读取银行卡片信息、做安全模块交互),这层就绕不开。这也是区分“会读卡”和“精通读卡”的分水岭。
3. 实操过程:用MFRC522从零跑通一张Type A卡
原理讲再多,不落地都是空的。这一节我以MFRC522为例,从硬件连接开始,到寄存器操作、指令收发,把完整读卡流程走一遍。MFRC522算是最常见的低成本读卡芯片,很多门禁模块都在用,用它演示最有代表性。
3.1 硬件连接与初始化
MFRC522支持SPI、I2C和UART三种接口,开发板最常用的是SPI,连接方式如下表:
| 模块引脚 | 开发板连接 | 说明 |
|---|---|---|
| SDA/CS | 任意GPIO(拉低选通) | SPI片选 |
| SCK | SPI时钟 | 速率建议不超过10MHz |
| MOSI | SPI主发从收 | 命令与数据写入 |
| MISO | SPI主收从发 | 读回寄存器和FIFO |
| RST | 任意GPIO(复位) | 低电平复位,高电平运行 |
| VCC | 3.3V | 注意不要接5V,多数模块稳压能力一般 |
| GND | GND | 共地必须保证 |
初始化代码里要做几件事:配置SPI、将RST引脚拉低再拉高,执行软件复位(CommandReg写0x0F),然后通过写寄存器配置定时器、波特率、天线开关。一个典型配置是:
void rc522_init(void) { // 复位 digitalWrite(RST_PIN, LOW); delay(50); digitalWrite(RST_PIN, HIGH); delay(50); // 软复位 rc522_write_reg(CommandReg, 0x0F); while ((rc522_read_reg(CommandReg) & 0x0F) != 0x0A) ; // 关闭天线,配置后再打开 rc522_write_reg(TxControlReg, 0x00); // 配置波特率、调制深度等参数 rc522_write_reg(TxModeReg, 0x00); rc522_write_reg(RxModeReg, 0x00); rc522_write_reg(ModWidthReg, 0x26); // 打开天线 rc522_write_reg(TxControlReg, 0x83); }这里的ModWidthReg设置为0x26对应106kbps下标准定义的调制脉冲宽度。如果后续要做高速率通信,这个寄存器值也要跟着改。初始化完成后,RF场就建立起来了,此时进入轮询读卡流程。
3.2 发送REQA并接收ATQA:卡的第一步响应
这一步的核心是使用RC522的Transceive命令发送0x26,然后从FIFO里读回ATQA。RC522在发送之后会自动等待接收,所以代码逻辑比较简单。
uint8_t buffer[2] = {0x26}; uint8_t len = 1; uint8_t status = rc522_transceive(buffer, &len); if (status == STATUS_OK) { // len此时为2,buffer[0]和buffer[1]就是ATQA uint16_t atqa = (buffer[0] << 8) | buffer[1]; printf("ATQA = 0x%04X\n", atqa); } else { printf("No card in field\n"); }rc522_transceive内部做的事比较多:清空FIFO,把要发送的数据写进FIFO,设置CommandReg为Transceive,然后打开BitFramingReg(因为REQA是短帧,只发7位),处理中断标志,最后读取FIFO内容。多张卡同时在场时,这一步一般还是能返回ATQA,因为ATQA是卡对REQA的公共应答,冲突概率比后面防碰撞要低。
3.3 执行防碰撞流程:拿到完整UID
防碰撞是协议栈里最容易“多卡出错”的一环。RC522芯片内置了防碰撞指令,标准流程是先发送级联级别1的防碰撞命令(0x93),带上UID的CL1部分,然后读取PICC返回的UID数据。
uint8_t uid[10] = {0}; uint8_t uid_len = 0; uint8_t cascade_level = 1; uint8_t is_finished = 0; while (!is_finished && cascade_level <= 2) { uint8_t cmd = (cascade_level == 1) ? 0x93 : 0x95; uint8_t buffer[10] = {cmd}; uint8_t len = 1; uint8_t status = rc522_transceive(buffer, &len); if (status != STATUS_OK) { printf("Anti-collision failed\n"); return 0; } // 这轮返回的buffer里包含完整的UID片段和BCC校验 memcpy(uid + (cascade_level - 1) * 4, buffer, 4); // 根据SAK判断是否还有级联 uint8_t sak_cmd[] = {0x93, buffer[0], buffer[1], buffer[2], buffer[3], buffer[0] ^ buffer[1] ^ buffer[2] ^ buffer[3]}; len = 6; status = rc522_transceive(sak_cmd, &len); if (status == STATUS_OK) { uint8_t sak = buffer[0]; if (sak & 0x04) { // 还有下一级联 cascade_level++; } else { is_finished = 1; if (uid_len == 0) uid_len = cascade_level * 4; } } }这里有个要点:SELECT命令的格式是“命令字节 + 4字节UID数据 + BCC校验字节”,其中BCC是前4个字节的异或值。很多人写到这里容易漏掉BCC,然后卡片就永远不回SAK。等踩过几次坑你就会记住:ISO14443A是一个对格式极度敏感的标准,多一个字节、少一个字节都可能导致静默失败。
3.4 选卡后的认证与扇区读取
拿到UID只是“入场券”,MIFARE Classic卡里最重要的数据都被扇区保护着。认证指令要使用UID作为认证参数之一,所以流程上必须先选卡,才能认证。以读取第1扇区为例:
// 认证第1扇区,使用KeyA,密钥6字节全为0xFF uint8_t auth_key[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; uint8_t status = mifare_auth(1, auth_key, uid, uid_len); if (status == STATUS_OK) { printf("Auth OK\n"); // 读取第1扇区第0块(扇区首块) uint8_t block_data[16]; status = mifare_read_block(1 * 4 + 0, block_data); if (status == STATUS_OK) { for (int i = 0; i < 16; i++) { printf("%02X ", block_data[i]); } printf("\n"); } }mifare_auth内部要做的核心操作是发送0x60(用KeyA认证)命令,跟着扇区块号和6字节密钥,再把UID作为参数传给RC522的密钥寄存区。RC522会负责计算加密握手,如果认证成功,后续的读块指令就会在加密信道上进行,不再明文传输。
这里要特别强调一个常见误解:MIFARE Classic的加密算法(Crypto-1)在安全强度上已经是上世纪的技术,破解成本极低。这篇文章把流程写清楚是为了让你理解协议栈和排错思路,不是鼓励你去破解任何人的门禁卡。做技术研究、开发自己的产品,建议用正规模拟卡或自研卡,别碰别人的系统。
3.5 调试利器:逻辑分析仪看协议波形
软件调不通的时候,最有效的办法不是盯着printf看状态码,而是用逻辑分析仪直接抓SPI和RC522的中断信号,观察指令时序。ISO14443A是一种半双工协议,读卡器芯片只是把数据从SPI搬到了射频链路,所以SPI层的读写时序能直接反映上层流程是否正确。
我在实际项目里抓到过一种很典型的错误:轮询线程和数据处理线程共用同一个RC522,且没有加锁,导致一个线程正在收卡返回数据,另一个线程却发起了新的Transceive命令,直接把FIFO冲掉,结果就是偶发读卡失败、成功率99%但永远卡出那1%。加一个简单的互斥锁,问题立刻消失。这类问题如果只靠“代码走查”很难发现,但逻辑分析仪上波形一目了然。
4. 常见问题与排查技巧实录
协议流程写完了,接下来这部分是我这几年积累的最实用经验,全是真金白银踩坑踩出来的。每一项我都先写现象,再讲原理,再给解决方案。
4.1 卡片完全无响应:从“场”开始查
现象是发REQA后永远没有ATQA,或者偶尔能读卡、拿手持接近时才能读到。优先检查射频场:用示波器或者简单的场强检测卡看13.56MHz载波有没有建立。RC522有一个专门的寄存器能读回天线驱动状态,但最简单的方法是看TxControlReg的天线开关位有没有设为1。很多人初始化时忘记打开天线,读卡器其实一直在“装死”,写再多上层代码都没用。
另一个容易被忽略的因素是天线的Q值和匹配网络。RC522模块自带天线一般没问题,但如果你把天线换成自制的线圈,匹配不对会导致场强不足。这种情况下卡片需要贴得非常近才能取到足够能量,表现为读卡距离很短、响应不稳定。注意观察天线匹配电路的电容值,RC522的典型匹配参考值在电路图里有,照抄问题不大。
4.2 防碰撞流程死循环或读UID出错
如果在多张卡同时靠近时程序卡死在防碰撞里,多半是碰撞位处理有bug。二进制搜索算法要求每轮根据碰撞位置构造掩码,有些实现贪图简单直接逐bit重试,导致某种UID组合下永远无法收敛。解决方法是给防碰撞循环加最大轮次上限(建议32轮),到上限就强制失败并重新从REQA开始。这算是一个“安全护栏”,它不会修复算法本身的bug,但至少不会让系统卡死。
另外,UID不是4字节时最容易出错。7字节UID需要跑两轮防碰撞,第二轮的级联级别是0x95而不是0x93。如果你在代码里把0x93硬编码写死,7字节UID的卡永远选不出来。拿到SAK后,记得检查bit2(0x04)是否为1。如果为1,说明还有级联级别要处理,要继续走下一轮防碰撞。
4.3 SAK解析不当导致选卡失败
SAK的某些bit还受到卡片厂商和卡种的影响。比如MIFARE Classic 1K的SAK通常是0x08,MIFARE Plus的SAK可能是0x20。有些开发者的代码里写死了“只认0x08”,遇到其他卡就直接拒绝,这在产品上会变成“某些卡能用、某些卡不能用”的兼容性问题。更好的做法是屏蔽掉无关位,只关注你关心的能力位,而不是做全等比较。
4.4 MIFARE Classic卡没有ATS,怎么处理
一句话:不要对不支持ISO14443-4的卡发送ATS。检测方法是看SAK的bit6(0x20)。如果该位为0,直接进入MIFARE Classic的认证流程;如果为1,再尝试发ATS。顺序反了,卡片不会响应ATS,串口打印看起来就像“卡死了”,实际上卡片只是在等待它支持的指令。这个问题在兼容多卡型的项目里非常常见,我建议把卡型判断做成一个独立函数,先用SAK分流,再走对应分支。
4.5 常见问题速查表
| 现象 | 可能原因 | 排查与解决 |
|---|---|---|
| 发REQA后无ATQA | 天线未打开、场强太弱、卡片太远 | 检查TxControlReg、天线匹配、增大发射功率 |
| 偶尔能读卡但不稳定 | 电源纹波大、天线附近有金属干扰 | 给模块单独供电,远离金属物体 |
| 多卡同时在场时卡死 | 防碰撞算法未收敛 | 加轮次上限,检查碰撞位处理逻辑 |
| 7字节UID卡读不出来 | 级联级别2没有正确执行 | 检查0x95命令和SAK的级联标志 |
| ATS发送后无响应 | 卡不支持ISO14443-4 | 看SAK bit6,不支持的卡不要发ATS |
| 认证失败 | 密钥错误、UID传入有误 | 确认选卡成功后再认证,UID字节顺序要一致 |
这个表格建议直接存一份。我平时调试新项目时就是靠它快速缩小范围,大部分读卡问题都逃不出这几类。
5. 结尾:读懂流程之后,你还能做什么
如果你完整跟着这篇文章读到这里,应该已经对ISO14443A读卡流程有了一个从物理层到应用层的整体认知。在我看来,很多人觉得射频读卡难,不是难在硬件或者编码,而是难在协议栈的状态切换和那些“看不见的时序”。一旦你把REQA到SAK的每一步在逻辑分析仪上跑一遍,就会发现它其实是一个非常精巧但逻辑清晰的状态机。
我的个人经验是:不要一上来就抄现成的库函数,先用寄存器操作把最基础的REQA和防碰撞跑通一遍。这个过程虽然慢,但它会逼着你把每个字节的意义搞清楚。等你亲手感受过“给0x93打上BCC,卡片终于回了一个SAK”的那种瞬间,你对整个协议栈的理解会有一个质的提升。
后续如果还有时间,可以在这个基础上继续扩展,比如自己实现一个精简的NFC读卡器、把读卡流程做成一个状态机来跑多卡轮询、或者在低功耗设备上做定时唤醒扫描。ISO14443A的流程是很多NFC功能的地基,地基打牢了,上面建什么都顺手。