学习完自动加减1的相关知识后,开始着手做实验。本次实验的目的有以下几个:
1、验证自动加减1的处理能否实现
2、自动加减1 的处理是否只跟着卡走,不挑读卡器。
3、一个卡中同时设置多个可自动加减的块,在执行加减处理时是否同时起作用。
为了执行这个实验,需要做两个装置。
1、一个装置用于为M1卡配置自动加减1 的值块;
2、一个用于测试划卡时是否自动执行了加减1的处理。
当然这两个处理也可以合并在一个处理程序中,通过专门处理(比如通过按钮改变状态值触发不同的工作方式)。
在之前的学习中,我产生了M1卡其实也是一个小的单片机系统,这是专门用于小型存储管理的系统的印象,因此以为它的电子钱包功能,是在M1卡上做好配置,后面只要这个M1卡只要靠近读卡器,就可以在M1卡上自动实现加减1的处理。事实上,这种理解是错误的,虽然M1卡在靠近读卡器的时候获得了工作电源,虽然它本身带有射频接口、数字控制单元及EEPROM存储器,但它没有中央处理单元,不能处理复杂逻辑的运算和任务,因此不能看做是单片机系统,它只是一种逻辑加密卡。因此自动加减1的处理任务,只能依靠配有读卡器的单片机系统来实施。
为了更快速的进行验证,本次实验中的设置值块的处理任务,由Arduino实现,使用合宙ESP32C3开发板;自动加减一的处理依旧由STC的擎天柱开发板实现,在前次读写处理的程序的基础上,进行简单修改。
值块的数据按照以下方式组织:
字节偏移 | 内容 | 长度 | 说明与制作方法 |
0~3 | FFFFFFFF | 4字节 | 存放你想要的数值。必须使用小端模式存储的有符号整数。 |
4~7 | 00000000 | 4字节 | 将值的每个位(bit)取反(0变1,1变0)。 |
8~11 | FFFFFFFF | 4字节 | 将值再原样复制一遍。 |
12~15 | 块的物理地址 | 4字节 | 通常存放该块的块号(如块5则填 0x00000005)。 |
值块被配置到第2扇区的0、1、2块上,也就是物理上的第8、9、10块。
Arduino下的电路连接可参照一下帖子:
Arduino下使用ESP32C3开发板测试RFID-RC522
代码如下:
/**
* 本程序为M1卡配置值块,可供加减一处理的值块
* 目标块为物理上的第8、9、10块
*/
#include <MFRC522v2.h>
#include <MFRC522DriverSPI.h>
#include <MFRC522DriverPinSimple.h>
#include <MFRC522Debug.h>
// 定义引脚 (与硬件连接一致)
#define SCK_PIN 4
#define MISO_PIN 5
#define MOSI_PIN 6
#define SS_PIN 7 // SDA
#define RST_PIN 8
// 初始化 SPI 驱动
MFRC522DriverPinSimple rst_pin(RST_PIN);
MFRC522DriverPinSimple ss_pin(SS_PIN);
MFRC522DriverSPI driver{ss_pin};
MFRC522 mfrc522{driver};
// 准备密钥(出厂默认为 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF)
MFRC522::MIFARE_Key key;
// 要读取的块地址(第1扇区的块0, 1, 2)
byte buffer[18]; // 读取缓冲区,至少18字节以容纳CRC
byte block= 4;
byte bufferSize = sizeof(buffer);
// 操作对象块的范围,必须是同一个扇区内的
byte blockList[3] = {8, 9, 10};
void setup() {
Serial.begin(115200);
SPI.begin(SCK_PIN, MISO_PIN, MOSI_PIN, SS_PIN); // 明确指定 SPI 引脚
mfrc522.PCD_Init();
MFRC522Debug::PCD_DumpVersionToSerial(mfrc522, Serial);
Serial.println(F("将卡片靠近读卡器..."));
// 将密钥设置为出厂默认值
for (byte i = 0; i < 6; i++) {
key.keyByte[i] = 0xFF;
}
delay(1000);
//Serial.println("Ready to read card...");
}
void loop() {
byte i = 0;
byte valueblock[17] = {0x0F, 0x0F, 0x0F, 0x0F, 0xF0, 0xF0, 0xF0, 0xF0, 0x0F, 0x0F, 0x0F, 0x0F, 4, 251, 4, 251, 0}; // 值块数据
// 检测新卡并读取序列号
if (!mfrc522.PICC_IsNewCardPresent() || !mfrc522.PICC_ReadCardSerial()) {
return;
}
// 显示卡片UID
Serial.print(F("卡片 UID: "));
MFRC522Debug::PrintUID(Serial, mfrc522.uid);
Serial.println();
block = blockList[0];
// 读取块8(对应第2扇区的0块:所在扇区的第一个块的地址 = 扇区编号 * 4)
// 使用Key A (0x60) 对当前块所在的扇区进行认证
// 注意:认证是针对整个扇区的,认证一个块后,同一扇区的其他块无需再次认证。
// 但为了代码清晰,这里每次读取前都进行认证。
byte status = mfrc522.PCD_Authenticate(0x60, block, &key, &mfrc522.uid);
if (status != 0) {
Serial.print(F("认证块 "));
Serial.print(block);
Serial.println(F(" 失败。"));
} else {
// 读取块数据
for (i=0; i <= 2; i++) {
block = blockList[i];
Serial.println(F("目标块:"));
Serial.println(block);
status = mfrc522.MIFARE_Read(block, buffer, &bufferSize);
if (status == 0) {
// 在串口监视器中打印数据(16字节)
Serial.print(F(" 原始数据 "));
printBlockData(); // 输出数据到串口
if ((block%4) != 3) {
// 避开每个扇区的最后一个控制块,避免物理锁死不能用
// 修改12 - 15的数据为 : 块地址,块地址取反,块地址,块地址取反
valueblock[12] = block;
valueblock[14] = block;
valueblock[13] = ~block;
valueblock[15] = ~block;
// 写入值块数据
status = mfrc522.MIFARE_Write(block, valueblock, 16);
if (status == 0) {
Serial.println(F(" 数据更新成功。"));
} else {
Serial.println(F(" 数据更新失败"));
}
// 再读
status = mfrc522.MIFARE_Read(block, buffer, &bufferSize);
if (status == 0) {
Serial.print(F(" 更新后数据 "));
printBlockData(); // 输出数据到串口
}
}
}
Serial.println(F("==============================================================="));
}
delay(2000);
}
// 停止与当前卡的通信
mfrc522.PICC_HaltA();
mfrc522.PCD_StopCrypto1();
delay(500);
}
void printBlockData(void) {
for (byte i = 0; i < 16; i++) {
// 以十六进制格式打印,不足两位补0
if (buffer[i] < 0x10) Serial.print("0");
Serial.print(buffer[i], HEX);
Serial.print(" ");
}
Serial.println();
}测试效果:

接下来进行测试,为了快速验证,这里我们依旧使用Arduino方式处理自动加减,接线方式与上面相同。处理代码:
/**
* 本程序测试M1卡自动减一处理
* 目标块为物理上的第4、5块
*/
#include <MFRC522v2.h>
#include <MFRC522DriverSPI.h>
#include <MFRC522DriverPinSimple.h>
#include <MFRC522Debug.h>
// 定义引脚 (与硬件连接一致)
#define SCK_PIN 4
#define MISO_PIN 5
#define MOSI_PIN 6
#define SS_PIN 7 // SDA
#define RST_PIN 8
// 初始化 SPI 驱动
MFRC522DriverPinSimple rst_pin(RST_PIN);
MFRC522DriverPinSimple ss_pin(SS_PIN);
MFRC522DriverSPI driver{ss_pin};
MFRC522 mfrc522{driver};
// 准备密钥(出厂默认为 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF)
MFRC522::MIFARE_Key key;
// 要读取的块地址(第1扇区的块0, 1, 2)
byte buffer[18]; // 读取缓冲区,至少18字节以容纳CRC
byte block= 8;
byte bufferSize = sizeof(buffer);
// 操作对象块的范围,必须是同一个扇区内的
byte blockList[3] = {8, 9, 10};
void setup() {
Serial.begin(115200);
SPI.begin(SCK_PIN, MISO_PIN, MOSI_PIN, SS_PIN); // 明确指定 SPI 引脚
mfrc522.PCD_Init();
MFRC522Debug::PCD_DumpVersionToSerial(mfrc522, Serial);
Serial.println(F("将卡片靠近读卡器..."));
// 将密钥设置为出厂默认值
for (byte i = 0; i < 6; i++) {
key.keyByte[i] = 0xFF;
}
delay(1000);
//Serial.println("Ready to read card...");
}
void loop() {
byte i=0;
byte valueblock[17] = {0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 4, 0};
// 检测新卡并读取序列号
if (!mfrc522.PICC_IsNewCardPresent() || !mfrc522.PICC_ReadCardSerial()) {
return;
}
// 显示卡片UID
Serial.print(F("卡片 UID: "));
MFRC522Debug::PrintUID(Serial, mfrc522.uid);
Serial.println();
block = blockList[0];
// 读取块4(对应第1扇区的0块:所在扇区的第一个块的地址 = 扇区编号 * 4)
// 使用Key A (0x60) 对当前块所在的扇区进行认证
// 注意:认证是针对整个扇区的,认证一个块后,同一扇区的其他块无需再次认证。
// 但为了代码清晰,这里每次读取前都进行认证。
byte status = mfrc522.PCD_Authenticate(0x60, block, &key, &mfrc522.uid);
if (status != 0) {
Serial.print(F("认证块 "));
Serial.print(block);
Serial.println(F(" 失败。"));
} else {
Serial.print(F("认证块 "));
Serial.print(block);
Serial.println(F(" 成功。"));
// 读取块数据
for (i=0; i <= 2; i++) {
block = blockList[i];
Serial.print(F("目标块:"));
Serial.println(block);
status = mfrc522.MIFARE_Read(block, buffer, &bufferSize);
if (status == 0) {
// 在串口监视器中打印数据(16字节)
Serial.print(F(" 原始数据 "));
printBlockData(); // 输出数据到串口
}
if ((block%4) != 3) {
// 避开每个扇区的最后一个控制块,避免物理锁死不能用
if ((block%4) == 2) {
Serial.println(F(" 启动递增处理,递增值2"));
addValue(4, 2);
} else if ((block%4) == 1) {
Serial.println(F(" 启动递减处理,递减值3"));
subtractValue(block, 3);
} else if ((block%4) == 0) {
Serial.println(F(" 启动递减处理,递减值1"));
subtractValue(block, 1);
}
Serial.println(F("==============================================================="));
// 再读
Serial.println(F(" 重新读取数据 "));
status = mfrc522.MIFARE_Read(block, buffer, &bufferSize);
if (status == 0) {
Serial.print(F(" 更新后数据 "));
printBlockData(); // 输出数据到串口
}
}
}
delay(3000);
}
// 停止与当前卡的通信
mfrc522.PICC_HaltA();
mfrc522.PCD_StopCrypto1();
delay(500);
}
void printBlockData(void) {
for (byte i = 0; i < 16; i++) {
// 以十六进制格式打印,不足两位补0
if (buffer[i] < 0x10) Serial.print("0");
Serial.print(buffer[i], HEX);
Serial.print(" ");
}
Serial.println();
}
// 对指定的值块进行加法操作
// blockAddr : 快地址
// delta : 增加的值
void addValue(byte blockAddr, long delta) {
MFRC522::StatusCode status;
// 执行递增指令
status = mfrc522.MIFARE_Increment(blockAddr, delta);
if (status != 0) {
Serial.print(F(" 执行递增指令失败: 返回值 = "));
Serial.println(status);
return;
}
// 关键步骤:将计算结果从读卡器缓存写回到卡片的值块中
status = mfrc522.MIFARE_Transfer(blockAddr);
if (status != 0) {
Serial.println(F(" 写回到卡片失败: "));
} else {
Serial.println(F(" 写回到卡片成功!"));
}
}
// 对指定的值块进行减法操作
// blockAddr : 快地址
// delta : 减少的值
void subtractValue(byte blockAddr, long delta) {
MFRC522::StatusCode status;
// 执行递减指令
status = mfrc522.MIFARE_Decrement(blockAddr, delta);
if (status != 0) {
Serial.print(F(" 执行递减指令失败: 返回值 = "));
Serial.println(status);
return;
}
// 将计算结果从读卡器缓存写回到卡片的值块中
status = mfrc522.MIFARE_Transfer(blockAddr);
if (status != 0) {
Serial.println(F(" 写回到卡片失败: "));
} else {
Serial.println(F(" 写回到卡片成功!"));
}
}运行效果:

失败了。不知道是不是设置为FFFFFFFF后,递增数据超出了范围造成的。于是重新修改初始化值块的数据值为:
byte valueblock[17] = {0x0F, 0x0F, 0x0F, 0x0F, 0xF0, 0xF0, 0xF0, 0xF0, 0x0F, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 4, 0};

测试结果:

还是有问题啊。
查看MifareClassisValueBlock例程,程序里面涉及值块的地方,最后四个字节是:块地址、块地址取反、块地址、块地址取反的形式。按照这个形式改了这块数据,结果测试依旧不正常。
看着递增递减处理的返回值不一致,我想看看把增减处理的顺序改一下是什么结果。把顺序改成递减的处理靠前,递增的处理靠后,结果测试效果变成了这样(注:因为之前操作高第一扇区搞死了,就换成了8、9、10三个扇区的处理):

看现象,第8块的递减1操作成功,第9块的递减3操作都成功了,但第10块的递增2操作依旧是失败,返回值还是255。
注意:测试图片中有一部分是在M1卡的第一扇区还没有被搞死之前的截图。后续处理中代码已经被改成了对8、9、10块的操作。
我要赚赏金
