这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 综合技术 » 基础知识 » 我的NFC学习之旅(9)---Arduino下实验自动增减处理1

共2条 1/1 1 跳转至

我的NFC学习之旅(9)---Arduino下实验自动增减处理1

专家
2026-07-16 14:32:05     打赏

学习完自动加减1的相关知识后,开始着手做实验。本次实验的目的有以下几个:

1、验证自动加减1的处理能否实现

2、自动加减1 的处理是否只跟着卡走,不挑读卡器。

3、一个卡中同时设置多个可自动加减的块,在执行加减处理时是否同时起作用。

为了执行这个实验,需要做两个装置。

1、一个装置用于为M1卡配置自动加减1 的值块;

2、一个用于测试划卡时是否自动执行了加减1的处理。

当然这两个处理也可以合并在一个处理程序中,通过专门处理(比如通过按钮改变状态值触发不同的工作方式)。

在之前的学习中,我产生了M1卡其实也是一个小的单片机系统,这是专门用于小型存储管理的系统的印象,因此以为它的电子钱包功能,是在M1卡上做好配置,后面只要这个M1卡只要靠近读卡器,就可以在M1卡上自动实现加减1的处理。事实上,这种理解是错误的,虽然M1卡在靠近读卡器的时候获得了工作电源,虽然它本身带有射频接口、数字控制单元及EEPROM存储器,但它没有中央处理单元,不能处理复杂逻辑的运算和任务,因此不能看做是单片机系统,它只是一种逻辑加密卡。因此自动加减1的处理任务,只能依靠配有读卡器的单片机系统来实施。

为了更快速的进行验证,本次实验中的设置值块的处理任务,由Arduino实现,使用合宙ESP32C3开发板;自动加减一的处理依旧由STC的擎天柱开发板实现,在前次读写处理的程序的基础上,进行简单修改。

值块的数据按照以下方式组织:

字节偏移

内容

长度

说明与制作方法

03

FFFFFFFF

4字节

存放你想要的数值。必须使用小端模式存储的有符号整数。

47

00000000

4字节

将值的每个位(bit)取反(0变1,1变0)。

811

FFFFFFFF

4字节

将值再原样复制一遍。

1215

块的物理地址

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();
}

测试效果: 

图片1.png

接下来进行测试,为了快速验证,这里我们依旧使用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("    写回到卡片成功!"));
  }
}


运行效果:

图片2.png

失败了。不知道是不是设置为FFFFFFFF后,递增数据超出了范围造成的。于是重新修改初始化值块的数据值为:

byte valueblock[17] = {0x0F, 0x0F, 0x0F, 0x0F, 0xF0, 0xF0, 0xF0, 0xF0, 0x0F, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 4, 0};

图片3.png

测试结果:

 

图片4.png

还是有问题啊。

查看MifareClassisValueBlock例程,程序里面涉及值块的地方,最后四个字节是:块地址、块地址取反、块地址、块地址取反的形式。按照这个形式改了这块数据,结果测试依旧不正常。

看着递增递减处理的返回值不一致,我想看看把增减处理的顺序改一下是什么结果。把顺序改成递减的处理靠前,递增的处理靠后,结果测试效果变成了这样(注:因为之前操作高第一扇区搞死了,就换成了8、9、10三个扇区的处理):

 

图片5.png

看现象,第8块的递减1操作成功,第9块的递减3操作都成功了,但第10块的递增2操作依旧是失败,返回值还是255。

注意:测试图片中有一部分是在M1卡的第一扇区还没有被搞死之前的截图。后续处理中代码已经被改成了对8、9、10块的操作。

 

 

 

 

 





关键词: 懒猫的学习笔记     NFC    

专家
2026-07-16 15:24:38     打赏
2楼

查明递增处理失败的原因了,在这里

addValue(4, 2);

传递了错误的参数,应该是

addValue(block, 2);


共2条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]