深入RFID:从读卡到写卡,我用RC522踩过的那些坑
说起RFID,大家肯定不陌生——门禁卡、公交卡、校园卡、银行卡……每天都在用。但你有没有好奇过,那张小小的卡片里到底存了什么?读卡器是怎么跟它通信的?数据是怎么读出来、写进去的?
最近做了两个RFID实验,一个读、一个写,用的是经典的MFRC522模块和STM32L4xx。从SPI通信到寻卡防冲撞,从密码验证到扇区读写,一路踩了不少坑。今天把整个过程整理出来,带你从原理到代码彻底搞懂RFID。
一、RFID到底是怎么工作的?
先从最基础的说起。RFID(Radio Frequency Identification,射频识别)是一种非接触式的自动识别技术,通过射频信号来识别目标对象并获取数据。
我们常用的Mifare卡(也就是S50卡、M1卡)工作在13.56MHz频率下,属于近场通信的一种。它的原理说穿了就三步:
第一步:读卡器给卡片供电。卡片本身是没有电池的,完全靠"无线充电"。读卡器的天线发出13.56MHz的电磁波,卡片里的线圈感应出电流,给卡片上的芯片供电。这也是为什么要把卡"靠近"读卡器才能用——远了感应不到足够的能量。
第二步:读卡器和卡片通过调制信号通信。读卡器要发数据给卡片时,就对载波进行ASK(幅移键控)调制;卡片要回数据时,通过改变自身负载阻抗来影响读卡器天线的电压,这种方式叫"负载调制"。所以通信是半双工的,你一句我一句。
第三步:数据交互遵循ISO14443A协议。这是一套国际标准,规定了寻卡、防冲撞、选卡、认证、读写等操作的具体命令格式和时序。RC522芯片就是专门用来处理这套协议的,把底层的射频调制解调都封装好了,MCU通过SPI跟RC522打交道就行。
二、硬件连接:SPI就够了
RC522模块跟STM32的连接很简单,核心就是SPI接口加两个控制脚:
RC522引脚 | STM32引脚 | 功能 |
|---|---|---|
SDA/CS | PA15 | SPI片选,低电平有效 |
SCK | SPI3_SCK | SPI时钟 |
MOSI | SPI3_MOSI | SPI主机发从机收 |
MISO | SPI3_MISO | SPI主机收从机发 |
RST | PD0 | 复位/休眠控制 |
RST脚的用法挺有意思的——拉低就是休眠,拉高就是唤醒。初始化的时候先拉低再拉高,相当于硬件复位一下。
SPI的配置也没什么特殊的,速率不用太高,实验里用了64分频。读写寄存器的格式有讲究:
读寄存器:先发送一个字节,最高位是1(表示读),中间6位是寄存器地址(左移一位),最低位不用管。然后再发一个空字节,同时接收回来的就是寄存器值。
写寄存器:最高位是0(表示写),后面跟寄存器地址,再后面跟要写的数据。
// 读寄存器 ucAddr = ((address<<1)&0x7E)|0x80; // bit7=1表示读 RF_SPI_WriteByte(ucAddr); ucResult = RF_SPI_WriteByte(0); // 发空字节,收数据 // 写寄存器 ucAddr = ((address<<1)&0x7E); // bit7=0表示写 RF_SPI_WriteByte(ucAddr); RF_SPI_WriteByte(dat);就这么简单的SPI读写,是所有上层功能的基础。
三、读卡的完整流程:寻卡→防冲撞→选卡→读数据
很多人以为读RFID卡就是"滴一下读出ID",其实中间经过了好几个步骤,一步都不能少。
第一步:寻卡(Request)
读卡器先发一个寻卡命令(0x26或0x52),问"附近有没有卡啊?"。0x26只寻没休眠的卡,0x52寻所有卡。
如果有卡在场,卡片会返回自己的类型码(2字节)。比如S50卡返回0x0400,Ultralight返回0x4400。这样读卡器就知道"哦,附近有一张S50卡"。
第二步:防冲撞(Anticollision)
如果有好几张卡同时在场怎么办?总不能一起回答吧,那不就乱了。这就需要防冲撞机制。
简单说,防冲撞就是"按位筛选"。读卡器说"我要ID第N位是0的卡回答",如果有多张,就继续往下分,直到只剩一张。整个过程是协议里规定好的,不用自己实现,调用RC522_Anticoll函数就行。
防冲撞成功后,你就能拿到卡片的4字节唯一ID(UID)了。每张卡的UID都是唯一的,这就是我们常说的"卡号"。
第三步:选卡(Select)
拿到UID之后,还要发选卡命令,告诉那张卡"就你了,准备好跟我通信吧"。卡片返回一个字节的ATS(应答选择),确认选卡成功。
第四步:验证密码(Authentication)
Mifare卡不是谁想读就能读的,每个扇区都有密码保护。读之前必须先验证密码——用A密钥(0x60命令)或者B密钥(0x61命令)。
出厂默认密码一般是6个0xFF,也就是{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}。如果密码不对,后面的读写操作都会失败。
第五步:读/写数据
密码验证通过之后,就可以读写数据了。一次读或写都是16个字节(一个块的大小)。
整个流程串起来就是这样:
status = RC522_Search_Cards(PICC_REQALL, card_type); // 寻卡 if(status == MI_OK) { status = RC522_Anticoll(card_id); // 防冲撞,拿ID } if (status == MI_OK) { status = RC522_Select_Cards(card_id); // 选卡 } if(status == MI_OK) { status = RC522_Verification_Password( // 验证密码 PICC_AUTHENT1A, block_addr, key, card_id); } if(status == MI_OK) { status = RC522_Read_Date(block_addr, data); // 读数据 // 或者 RC522_Write_Date(block_addr, data); // 写数据 }一步一步来,缺了任何一步都不行。
四、Mifare卡的存储结构:扇区、块、密码区
要往卡里写数据,必须先搞清楚卡的存储空间是怎么组织的。
S50卡(也就是Mifare 1K)总共有1KB的EEPROM存储空间,分成了16个扇区(Sector 0~15),每个扇区又分成4个块(Block 0~3),每个块16字节。加起来就是16×4×16 = 1024字节。
这64个块按绝对地址编号就是0~63。每个扇区的4个块分工不同:
块0、块1、块2:数据块,随便读随便写,存你自己的数据
块3:控制块/尾块,存的是密码和访问控制,千万不要乱写!
控制块的16个字节是有固定格式的:
字节0-5: 密钥A (Key A) 字节6-9: 访问控制位 (Access Bits) 字节10-15: 密钥B (Key B)如果你不小心往控制块里写了乱七八糟的数据,密钥就变了,这个扇区可能就再也打不开了——俗称"扇区锁死"或者"把卡写废了"。实验代码里反复强调不要往块3写数据,就是这个原因。
还有一个特殊的地方:第0扇区的块0(绝对地址0)是厂商代码区,出厂时就固化了UID和厂商信息,是只读的,改不了。这也是每张卡UID唯一的原因。
实验里用的是第1扇区(RC522_SECTOR = 1),对应绝对地址块4、5、6、7。其中块4、5、6是数据块,可以放心写;块7是控制块,别碰。
五、写入实验:按键选择,刷卡写入
写入实验比读取实验多了一个交互——用4个按键来选择要做什么操作:
KEY1:往数据块0写"123456"
KEY2:往数据块1写"this is a test"
KEY3:往数据块2写十六进制数据{0x30,0x31,0x32,0x33}
KEY4:清除数据块0、1、2的数据(全写0)
操作流程是:先按键选择功能,然后把卡放到感应区,程序检测到卡后就执行对应的写入操作,最后再把三个数据块的内容读出来显示验证。
这里有个细节:每次写完之后都会再读回来验证。这是个好习惯——因为RFID是非接触通信,受距离、干扰、卡片位置等因素影响,写入有可能失败。写了再读一遍确认,心里才踏实。
还有个容易踩的坑:写入操作其实是分两步的。先发写命令+块地址,卡片返回ACK之后,再发16字节数据+CRC。不是一次就能写完的。RC522_Write_Date函数内部已经把这两步封装好了,但你得知道有这么回事。
六、那些年我踩过的坑
做这两个实验的过程中,踩了不少坑,说几个印象最深的:
坑1:SPI通信死活不通。一开始怎么都读不到RC522的寄存器值,查了半天才发现是SPI的CPOL和CPHA没配对。RC522要求SPI模式是CPOL=0、CPHA=0(也就是模式0),上升沿采样。配错了读出来全是0xFF或者0x00。
坑2:寻卡成功但防冲撞失败。有时候能寻到卡,但防冲撞那步老是返回错误。后来发现是天线的问题——RC522_Init之后一定要记得开天线(RC522_AntennaOn),而且开关天线之间至少要有1ms的间隔。天线没调好,信号强度不够,防冲撞就容易失败。
坑3:密码验证通不过。一开始以为是代码问题,查了半天,结果是那张卡的密码被之前的实验改过了,不是默认的6个0xFF了。换了张新卡就好了。所以玩RFID卡的时候,最好在卡上贴个标签记一下密码,不然哪天忘了就只能当一次性卡用了。
坑4:往控制块写了数据,扇区废了。这个是真的坑……刚开始不了解存储结构,随手往块3写了点测试数据,结果那个扇区就再也打不开了。后来才知道控制块不能乱写,数据只能往块0、1、2里存。
坑5:卡片放的位置不对。RFID读卡对位置还挺敏感的,不是随便一放就能行。有时候卡稍微偏一点或者离远一点,寻卡就失败。实验里做了个感应区的标记,把卡放在标记正上方,成功率就高多了。
七、从实验到应用:RFID能做什么?
搞懂了原理和读写操作,能玩的东西就多了。说几个我觉得比较有意思的方向:
门禁系统:这是最经典的应用。把授权的卡号存在数据库里,刷卡的时候比对一下,合法就开门。再加点功能,比如记录刷卡时间、支持临时卡、黑名单管理等等。
消费系统:校园卡、公交卡其实就是把余额存在卡里的某个扇区(值块),刷卡的时候扣钱。值块有专门的格式,还支持增减操作和备份,RC522_Deduction_Recharge函数就是干这个的。
资产管理:给设备贴个RFID标签,盘点的时候一扫就知道哪些设备在、哪些不在。比人工扫码快多了。
NFC手机交互:现在的手机基本都支持NFC,可以跟RFID卡交互。比如把网址、文本存在卡里,手机一靠就打开网页或者显示内容。
往深了做还能研究安全——Mifare卡的加密其实早就被破解了,有很多关于嗅探、克隆、破解的技术。当然这个得在合法合规的范围内玩。
八、写在最后
两个实验做下来,最大的感受是:RFID这东西看似简单(不就是"滴"一下嘛),但里面的门道真不少。从射频通信到协议栈,从存储结构到安全机制,每一层都值得深入研究。
RC522是个很好的入门芯片,资料多、价格便宜、社区活跃。把它搞懂了,再去接触其他RFID芯片甚至NFC控制器,就容易多了。
如果你也对RFID感兴趣,我的建议是:买个RC522模块和几张白卡,跟着代码一步步调,从读ID开始,到读数据、写数据、改密码,慢慢往上加。动手调过几个bug,很多原理自然就懂了。
好了,这篇就写到这里。后面打算再研究一下值块操作和Mifare的安全机制,到时候再来分享。