RFID
射频识别模块
- 什么是RFID
- MFRC522
- S50-M1卡
- 1、主要指标
- 2、存储结构
- 3、AB密码一些问题
- RC522与Arduino UNO的接线
- MFRC522库的使用
- examples
- 1、ReadUID 读取卡的UID
- 2、ReadAndWrite 数据读写
什么是RFID
无线射频识别即射频识别技术(Radio Frequency Identification,RFID),是自动识别技术的一种,通过无线射频方式进行非接触双向数据通信,利用无线射频方式对记录媒体(电子标签或射频卡)进行读写,从而达到识别目标和数据交换的目的。
MFRC522
MF RC522 利用了先进的调制和解调概念,完全集成了在13.56MHz 下所有类型的被动非接触式通信方式和协议。支持 ISO14443A 的多层应用。其内部发送器部分可驱动读写器天线与ISO 14443A/MIFARE卡和应答机的通信,无需其它的电路。接收器部分提供一个坚固而有效的解调和解码电路,用于处理ISO14443A 兼容的应答器信号。数字部分处理ISO14443A 帧和错误检测(奇偶 &CRC)。此外,它还支持快速CRYPTO1 加密算法,用于验证MIFARE 系列产品。MFRC522 支持MIFARE?更高速的非接触式通信,双向数据传输速率高达424kbit/s。
S50-M1卡
1、主要指标
- 容量为8K位(1KByte)EEPROM
- 分为16个扇区,每个扇区为4块,每块16个字节,以块为存取单位
- 每个扇区有独立的一组密码及访问控制
- 每张卡有唯一序列号,为32位
- 数据保存期为10年,可改写10万次,读无限次
- 工作温度:-20℃~50℃(湿度为90%)
- 工作频率:13.56MHZ
- 通信速率:106 KBPS
- 读写距离:10 cm以内(与读写器有关)
2、存储结构
1、M1卡分为16个扇区,每个扇区由4块(块0、块1、块2、块3)组成,(我们也将16个扇区的64个块按绝对地址编号为0~63,存贮结构如下图所示:
2、第0扇区的块0(即绝对地址0块),它用于存放厂商代码,已经固化,不可更改。
3、每个扇区的块0、块1、块2为数据块,可用于存贮数据。
数据块 可作两种应用:
★用作一般的数据保存,可以进行读、写操作。
★用作数据值,可以进行初始化值、加值、减值、读值操作。
4、每个扇区的块3为控制块,包括了密码A、存取控制、密码B。具体结构如下:
A0 A1 A2 A3 A4 A5 | FF 07 80 69 | B0 B1 B2 B3 B4 B5 |
---|---|---|
密码A(6字节) | 存取控制(4字节) | 密码B(6字节) |
5、每个扇区的密码和存取控制都是独立的,可以根据实际需要设定各自的密码及存取控制。存取控制为4个字节,共32位,扇区中的每个块(包括数据块和控制块)的存取条件是由密码和存取控制共同决定的,在存取控制中每个块都有相应的三个控制位,定义如下:
块 | 控制位1 | 控制位2 | 控制位3 |
---|---|---|---|
块0: | C10 | C20 | C30 |
块1: | C11 | C21 | C31 |
块2: | C12 | C22 | C32 |
块3: | C13 | C23 | C33 |
三个控制位以正和反两种形式存在于存取控制字节中,决定了该块的访问权限(如
进行减值操作必须验证KEY A,进行加值操作必须验证KEY B,等等)。
存取控制(4字节,其中字节9为备用字节)结构如下所示:
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|---|---|---|---|---|---|---|---|
字节6 | C23_b | C22_b | C21_b | C20_b | C13_b | C12_b | C11_b | C10_b |
字节7 | C13 | C12 | C11 | C10 | C33_b | C32_b | C31_b | C30_b |
字节8 | C33 | C32 | C31 | C30 | C23 | C22 | C21 | C20 |
字节9 |
( 注: _b表示取反 )
6、数据块(块0、块1、块2)的存取控制如下:
控制位1 | 控制位2 | 控制位3 | 访 问 条 件 | – | – | – |
---|---|---|---|---|---|---|
C1X | C2X | C3X | 读 | 写 | 增加 | 减少,传输,存储 |
0 | 0 | 0 | KeyA/B | KeyA/B | KeyA/B | KeyA/B |
0 | 1 | 0 | KeyA/B | Never | Never | Never |
1 | 0 | 0 | KeyA/B | KeyB | Never | Never |
1 | 1 | 0 | KeyA/B | KeyB | KeyB | KeyA/B |
0 | 0 | 1 | KeyA/B | Never | Never | KeyA/B |
0 | 1 | 1 | KeyB | KeyB | Never | Never |
1 | 0 | 1 | KeyB | Never | Never | Never |
1 | 1 | 1 | Never | Never | Never | Never |
KeyA/|B 表示密码A或密码B,Never表示任何条件下不能实现,x=0,1,2
例如:当块0的存取控制位C10 C20 C30=1 0 0时,验证密码A或密码B正确后可读;验证密码B正确后可写;不能进行加值、减值操作。
7、控制块-块3的存取控制与数据块(块0、1、2)不同,它的存取控制如下:
控制位 | 密码A | 存取控制 | 密码B
C13 | C23 | C33 | Read | Write | Read | Write | Read | Write |
---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | Never | KeyA/B | KeyA/B | Never | KeyA/B | KeyA/B |
0 | 1 | 0 | Never | Never | KeyA/B | Never | KeyA/B | Never |
1 | 0 | 0 | Never | KeyB | KeyA/B | Never | Never | KeyB |
1 | 1 | 0 | Never | Never | KeyA/B | Never | Never | Never |
0 | 0 | 1 | Never | KeyA/B | KeyA/B | KeyA/B | KeyA/B | KeyA/B |
0 | 1 | 1 | Never | KeyB | KeyA/B | KeyB | Never | KeyB |
1 | 0 | 1 | Never | Never | KeyA/B | KeyB | Never | Never |
1 | 1 | 1 | Never | Never | KeyA/B | Never | Never | Never |
例如:当块3的存取控制位C13 C23 C33=001时,表示:
密码A:不可读,验证KEYA或KEYB正确后,可写(更改)。
存取控制:验证KEYA或KEYB正确后,可读、可写。
密码B:验证KEYA或KEYB正确后,可读、可写。
3、AB密码一些问题
控制字的默认值是“FF078069”,此时
A密钥:不可被读出,有全部权限
B密钥:可被读出,没有任何权限
在大多数使用B密钥的系统中,控制字 = 08778F00, 此时
A密钥:不可被读出,有读取数据可扣款权限
B密钥:不可被读出,有全部权限
原装的Philps S50芯片在出厂时设置每个分区的的第四块A密钥是“FFFFFFFFFFFF”,控制字是:“FF078069”,B密钥是:“FFFFFFFFFFFF”,A密钥是供用户读写操作的,利用A密钥可对对除0区外其它所有扇区块进行读写操作。B密钥不可操作,这些用的都是逻逻加密算法加密,而且密钥都是不可见,我们在读时能看到的A密钥都是显示为“000000000000”,B密钥显示:“FFFFFFFFFFFF”, 这些都是出厂时厂家设定的默认值。
如果用户要使用B密钥,如“公交一卡通的公交卡”,那先要把中间控制改了,如果改错那所改的那个分区就被加密没用了。比如先把控制改成“08778F00”,A密钥改成“111111111111”,B密钥改成“222222222222”,改完之后再用我们的测试DEMO对块三进行写,写操作成功后,这样您就可以利用B密钥对您所改的扇区进行读写操作了,这时A密钥也就不起作用。
RC522与Arduino UNO的接线
一般库文件中有接线定义
RC522 | Arduino |
---|---|
SDA | 10 |
SCK | 13 |
MOSI | 11 |
MISO | 12 |
IRQ | 空置 |
GND | GND |
RST | 9 |
3.3 | 3.3 |
MFRC522库的使用
首先要在ArduinoIDE或者vscode platformIO中下载MFRC522库
examples
1、ReadUID 读取卡的UID
#include <SPI.h>#include <MFRC522.h>#define SS_PIN 10#define RST_PIN 9 MFRC522 rfid(SS_PIN, RST_PIN); // Instance of the classMFRC522::MIFARE_Key key; // Init array that will store new NUID byte nuidPICC[4];void setup() { Serial.begin(9600); SPI.begin(); // Init SPI bus rfid.PCD_Init(); // Init MFRC522 for (byte i = 0; i < 6; i++) { key.keyByte[i] = 0xFF; } Serial.println(F("This code scan the MIFARE Classsic NUID.")); Serial.print(F("Using the following key:")); printHex(key.keyByte, MFRC522::MF_KEY_SIZE);} void loop() { // Reset the loop if no new card present on the sensor/reader. This saves the entire process when idle. if ( ! rfid.PICC_IsNewCardPresent()) return; // Verify if the NUID has been readed if ( ! rfid.PICC_ReadCardSerial()) return; Serial.print(F("PICC type: ")); MFRC522::PICC_Type piccType = rfid.PICC_GetType(rfid.uid.sak); Serial.println(rfid.PICC_GetTypeName(piccType)); // Check is the PICC of Classic MIFARE type if (piccType != MFRC522::PICC_TYPE_MIFARE_MINI && piccType != MFRC522::PICC_TYPE_MIFARE_1K && piccType != MFRC522::PICC_TYPE_MIFARE_4K) { Serial.println(F("Your tag is not of type MIFARE Classic.")); return; } if (rfid.uid.uidByte[0] != nuidPICC[0] || rfid.uid.uidByte[1] != nuidPICC[1] || rfid.uid.uidByte[2] != nuidPICC[2] || rfid.uid.uidByte[3] != nuidPICC[3] ) { Serial.println(F("A new card has been detected.")); // Store NUID into nuidPICC array for (byte i = 0; i < 4; i++) { nuidPICC[i] = rfid.uid.uidByte[i]; } Serial.println(F("The NUID tag is:")); Serial.print(F("In hex: ")); printHex(rfid.uid.uidByte, rfid.uid.size); Serial.println(); Serial.print(F("In dec: ")); printDec(rfid.uid.uidByte, rfid.uid.size); Serial.println(); } else Serial.println(F("Card read previously.")); // Halt PICC rfid.PICC_HaltA(); // Stop encryption on PCD rfid.PCD_StopCrypto1();}/** * Helper routine to dump a byte array as hex values to Serial. */void printHex(byte *buffer, byte bufferSize) { for (byte i = 0; i < bufferSize; i++) { Serial.print(buffer[i] < 0x10 ? " 0" : " "); Serial.print(buffer[i], HEX); }}/** * Helper routine to dump a byte array as dec values to Serial. */void printDec(byte *buffer, byte bufferSize) { for (byte i = 0; i < bufferSize; i++) { Serial.print(buffer[i] < 0x10 ? " 0" : " "); Serial.print(buffer[i], DEC); }}
2、ReadAndWrite 数据读写
#include <SPI.h>#include <MFRC522.h>#define RST_PIN 9 // 配置针脚#define SS_PIN 10 MFRC522 mfrc522(SS_PIN, RST_PIN); // 创建新的RFID实例MFRC522::MIFARE_Key key; //6字节的密码void dump_byte_array(byte *buffer, byte bufferSize); //声明dump_byte_array函数void setup() { Serial.begin(9600); // 设置串口波特率为9600 while (!Serial); // 如果串口没有打开,则死循环下去不进行下面的操作 SPI.begin(); // SPI开始 mfrc522.PCD_Init(); // Init MFRC522 card for (byte i = 0; i < 6; i++) {//设置key为:FF FF FF FF FF FF key.keyByte[i] = 0xFF; } Serial.println(F("扫描卡开始进行读或者写")); Serial.print(F("使用A和B作为键")); dump_byte_array(key.keyByte, MFRC522::MF_KEY_SIZE); Serial.println(); Serial.println(F("注意,会把数据写入到卡在#1"));} void loop() { // 寻找新卡 if ( ! mfrc522.PICC_IsNewCardPresent()) return; // 选择一张卡 if ( ! mfrc522.PICC_ReadCardSerial()) return; // 显示卡片的详细信息 Serial.print(F("卡片 UID:")); dump_byte_array(mfrc522.uid.uidByte, mfrc522.uid.size); Serial.println(); Serial.print(F("卡片类型: ")); MFRC522::PICC_Type piccType = mfrc522.PICC_GetType(mfrc522.uid.sak);//获取卡片类型码 Serial.println(mfrc522.PICC_GetTypeName(piccType));//转换类型码为类型名称 // 检查兼容性,只有MIFARE类型的卡才能读写 if ( piccType != MFRC522::PICC_TYPE_MIFARE_MINI && piccType != MFRC522::PICC_TYPE_MIFARE_1K && piccType != MFRC522::PICC_TYPE_MIFARE_4K) { Serial.println(F("仅仅适合Mifare Classic卡的读写")); return; } // 我们只使用第二个扇区 // 覆盖扇区4 byte sector = 1; byte blockAddr = 4;//第4个块为第二个扇区第一个数据块 byte dataBlock[] = { 0x01, 0x02, 0x03, 0x04, // 1, 2, 3, 4, 0x05, 0x06, 0x07, 0x08, // 5, 6, 7, 8, 0x00, 0x00, 0x00, 0x00, // 0,0,0,0 0x00, 0x00, 0x00, 0x00 // 0,0,0,0 };//写入的数据定义 byte trailerBlock = 7;//第7个块为第二个扇区的控制块 MFRC522::StatusCode status; byte buffer[18]; byte size = sizeof(buffer); // 原来的数据 Serial.println(F("显示原本的数据...")); status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid));//在uid为mfrc522.uid的卡的trailerBlock块(此块为扇区控制块)验证key是否与A密码相同 if (status != MFRC522::STATUS_OK) { Serial.print(F("身份验证失败?或者是卡链接失败")); Serial.println(mfrc522.GetStatusCodeName(status)); return; } // 显示整个扇区 Serial.println(F("显示所有扇区的数据")); mfrc522.PICC_DumpMifareClassicSectorToSerial(&(mfrc522.uid), &key, sector);//串行输出uid卡,第sector扇区的数据 Serial.println(); // 从块儿读取数据 Serial.print(F("读取块儿的数据在:")); Serial.print(blockAddr); Serial.println(F("块 ...")); status = (MFRC522::StatusCode) mfrc522.MIFARE_Read(blockAddr, buffer, &size);//读取size个第blockAddr块的数据到buffer if (status != MFRC522::STATUS_OK) { Serial.print(F("读卡失败,没有连接上 ")); Serial.println(mfrc522.GetStatusCodeName(status)); } Serial.print(F("数据内容在第 ")); Serial.print(blockAddr); Serial.println(F(" 块:")); dump_byte_array(buffer, 16); Serial.println();//输出第4块的数据 Serial.println(); //开始进行写入准备 Serial.println(F("开始进行写入的准备...")); status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_B, trailerBlock, &key, &(mfrc522.uid));//验证密码B if (status != MFRC522::STATUS_OK) { Serial.print(F("写入失败,没有连接上或者没有权限 ")); Serial.println(mfrc522.GetStatusCodeName(status)); return; } // Write data to the block Serial.print(F("在第: ")); Serial.print(blockAddr); Serial.println(F(" 块中写入数据...")); dump_byte_array(dataBlock, 16); Serial.println();//显示要写入的数据 status = (MFRC522::StatusCode) mfrc522.MIFARE_Write(blockAddr, dataBlock, 16);//写入数据 if (status != MFRC522::STATUS_OK) { Serial.print(F("写入失败... ")); Serial.println(mfrc522.GetStatusCodeName(status)); } Serial.println(); // 再次读取卡中数据,这次是写入之后的数据 Serial.print(F("读取写入后第")); Serial.print(blockAddr); Serial.println(F(" 块的数据 ...")); status = (MFRC522::StatusCode) mfrc522.MIFARE_Read(blockAddr, buffer, &size); if (status != MFRC522::STATUS_OK) { Serial.print(F("读取失败... ")); Serial.println(mfrc522.GetStatusCodeName(status)); } Serial.print(F("块 ")); Serial.print(blockAddr); Serial.println(F("数据为 :")); dump_byte_array(buffer, 16); Serial.println(); // 验证一下数据,要保证写入前后数据是相等的 // 通过计算块中的字节数量 Serial.println(F("等待验证结果...")); byte count = 0; for (byte i = 0; i < 16; i++) { // 比较一下缓存中的数据(我们读出来的数据) = (我们刚刚写的数据) if (buffer[i] == dataBlock[i]) count++; } Serial.print(F("匹配的字节数量 = ")); Serial.println(count); if (count == 16) { Serial.println(F("验证成功 :")); } else { Serial.println(F("失败,数据不匹配")); Serial.println(F("也许写入的内容不恰当")); } Serial.println(); // 转储扇区数据 Serial.println(F("写入后的数据内容为::")); mfrc522.PICC_DumpMifareClassicSectorToSerial(&(mfrc522.uid), &key, sector); Serial.println(); // 停止 PICC mfrc522.PICC_HaltA(); //停止加密PCD mfrc522.PCD_StopCrypto1();} /** * 将字节数组串行输出为16进制字符 */void dump_byte_array(byte *buffer, byte bufferSize) { for (byte i = 0; i < bufferSize; i++) { Serial.print(buffer[i] < 0x10 ? " 0" : " "); Serial.print(buffer[i], HEX); }}