认识RC522模块
RC522是一种常用的无线射频识别(RFID)模块,广泛用于门禁系统、身份识别、支付系统等领域。它基于13.56MHz的射频技术,与NFC(近场通信)技术兼容,能够读取和写入符合ISO/IEC 14443 A/MIFARE协议的标签和卡片。
ic卡结构
ic卡,比如我们日常使用的校园卡,以最常见的MIFARE Classic IC卡为例:包含16个扇区,每个扇区由4个块组成,每个块可以存储16个字节的数据。每个扇区的最后一个块是尾块,包含了两个密钥(Key A和Key B)以及访问条件。这些密钥和访问条件用于控制对相应扇区的读写权限。
其中扇区0:第一个扇区(扇区0)的第一个块(块0)被称为制造商块,它包含了卡片的唯一标识符(UID)、制造商数据等信息,是出厂时就写入的,而且在大多数情况下无法更改。
其他扇区可用于存放个人信息:姓名、性别、出生日期等;账户信息:余额、最近交易记录等……
因此当你给校园卡充值后,需要通过某种形式的刷卡操作,将新的金额信息写入到卡内,这样卡内保存的金额信息才会更新。
例子:扇区1的布局
- **块0 (扇区1的第1块)**:学生ID号
- 例如:
01 23 45 67
(假设学生ID为1234567,前面的0用于填充,因为每个块是16字节,剩余的字节可能留空或用于其他目的)
- **块1 (扇区1的第2块)**:学生基本信息
- 例如:
4A 6F 68 6E 20 44 6F 65
(ASCII编码的”John Doe”作为学生的姓名)
- 接着可能是出生年份:
19 99
(假设1999年出生)
- 剩余的字节可能用于填充或其他简单的信息
- **块2 (扇区1的第3块)**:图书馆借阅状态
- 例如:
00 03
(表示当前借了3本书)
- 后面跟着最后一次借书的日期:
20 21 04 15
(假设是2021年4月15日)
- 剩余的字节可以存储书的ID或留空
- **块3 (扇区1的第4块,控制块)**:控制块设置访问权限和存储密钥
- 这个块不用于存储普通数据,而是定义了如何访问这个扇区的数据。包括两个密钥(Key A和Key B)及其对应的访问条件。
示例数据
假设数据如下:
- 块0:
01 23 45 67 00 00 00 00 00 00 00 00 00 00 00 00
- 块1:
4A 6F 68 6E 20 44 6F 65 19 99 00 00 00 00 00 00
- 块2:
00 03 20 21 04 15 00 00 00 00 00 00 00 00 00 00
- 块3:密钥A、访问条件、密钥B(具体值依赖于系统安全策略)
硬件连接
首先,你需要将RC522模块和ESP8266连接起来。RC522模块的通信是通过SPI接口进行的。以下是一个典型的连接方式:
- SDA(SS): 连接到ESP8266的GPIO4(或其他可用的CS引脚)
- SCK: 连接到ESP8266的GPIO14(SPI的SCK)
- MOSI: 连接到ESP8266的GPIO13(SPI的MOSI)
- MISO: 连接到ESP8266的GPIO12(SPI的MISO)
- IRQ: 不连接
- GND: 连接到ESP8266的GND
- RST: 连接到ESP8266的GPIO5(或其他可控制的引脚)
- 3.3V: 连接到ESP8266的3.3V
代码
读取卡的内容,每个扇区的两组密码(Key A和Key B)用于不同的安全访问控制。这些密码可以独立设置,以控制对扇区内数据的读写权限。默认的密钥FF FF FF FF FF FF
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| #include <SPI.h> #include <MFRC522.h> #define RST_PIN 5 #define SS_PIN 4 MFRC522 mfrc522(SS_PIN, RST_PIN); MFRC522::MIFARE_Key key;
void setup() { Serial.begin(9600); while (!Serial); SPI.begin(); mfrc522.PCD_Init(); for (byte i = 0; i < 6; i++) { 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));
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; }
byte dataBlock[] = { }; byte trailerBlock = 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)); if (status != MFRC522::STATUS_OK) { Serial.print(F("身份验证失败?或者是卡链接失败")); Serial.println(mfrc522.GetStatusCodeName(status)); return; }
for (byte sector = 0; sector < 16; sector++) { Serial.println(F("显示所有扇区的数据")); mfrc522.PICC_DumpMifareClassicSectorToSerial(&(mfrc522.uid), &key, sector); Serial.println(); } }
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); } }
|
修改卡的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
| #include <SPI.h> #include <MFRC522.h> #define RST_PIN 5 #define SS_PIN 4 MFRC522 mfrc522(SS_PIN, RST_PIN); MFRC522::MIFARE_Key key; void setup() { Serial.begin(9600); while (!Serial); SPI.begin(); mfrc522.PCD_Init(); for (byte i = 0; i < 6; i++) { 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));
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; }
byte sector = 1; byte blockAddr = 4; byte dataBlock[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; byte trailerBlock = 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)); if (status != MFRC522::STATUS_OK) { Serial.print(F("身份验证失败?或者是卡链接失败")); Serial.println(mfrc522.GetStatusCodeName(status)); return; }
Serial.println(F("显示所有扇区的数据")); mfrc522.PICC_DumpMifareClassicSectorToSerial(&(mfrc522.uid), &key, sector); 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();
Serial.println(F("开始进行写入的准备...")); status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_B, trailerBlock, &key, &(mfrc522.uid)); if (status != MFRC522::STATUS_OK) { Serial.print(F("写入失败,没有连接上或者没有权限 ")); Serial.println(mfrc522.GetStatusCodeName(status)); return; }
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();
mfrc522.PICC_HaltA();
mfrc522.PCD_StopCrypto1(); }
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); } }
|
当使用SPI通信时,MOSI(主设备输出/从设备输入)和MISO(主设备输入/从设备输出)引脚通常是固定的,不需要在软件中显式配置它们。这是因为SPI接口是由硬件控制的,而不是软件模拟,所以MOSI和MISO引脚已经在微控制器的硬件设计中预定义好了。
对于ESP8266来说,这些固定的SPI引脚通常是:
- MOSI:D7 (GPIO13)
- MISO:D6 (GPIO12)
- SCK(时钟信号):D5 (GPIO14)
而SS(片选)引脚和RST(复位)引脚可以是任何可用的GPIO引脚,所以需要在软件中指定以正确初始化SPI设备。