使用RC522 实现发卡设备(1)-PCD程序

6/17/2024 9:10:10 PM
224
0
/*
 
 * Typical pin layout used:
 * -----------------------------------------------------------------------------------------
 *             MFRC522      Arduino       Arduino   Arduino    Arduino          Arduino
 *             Reader/PCD   Uno/101       Mega      Nano v3    Leonardo/Micro   Pro Micro
 * Signal      Pin          Pin           Pin       Pin        Pin              Pin
 * -----------------------------------------------------------------------------------------
 * RST/Reset   RST          9             5         D9         RESET/ICSP-5     RST
 * SPI SS      SDA(SS)      10            53        D10        10               10
 * SPI MOSI    MOSI         11 / ICSP-4   51        D11        ICSP-4           16
 * SPI MISO    MISO         12 / ICSP-1   50        D12        ICSP-1           14
 * SPI SCK     SCK          13 / ICSP-3   52        D13        ICSP-3           15
 *
 * More pin layouts for other boards can be found here: https://github.com/miguelbalboa/rfid#pin-layout
 */

#include <SPI.h>
#include <MFRC522.h>

#define SS_PIN 10
#define RST_PIN 9

typedef enum {
  msg_uid=0x01,
  msg_w=0x02,     //写数据消息
  msg_R=0x03,     //读数据消息
  msg_Info=0x04,  //提示消息
  msg_Err=0x05,   //错误消息
  msg_Alt=0x06,   //警告消息

} MsgType;


MFRC522 rfid(SS_PIN, RST_PIN);  // Instance of the class

bool locked = false;
byte keyA[6];
byte keyB[6];

void setup() {
  Serial.begin(9600);
  SPI.begin();      // Init SPI bus
  rfid.PCD_Init();  // Init MFRC522
  delay(4);

  // Clear the information stored about locked cards.
  rfid.uid.size = 0;

  Serial.println(F("This code scan the MIFARE Classsic NUID and reports removal."));
}

void loop() {
  // Wake up all cards present within the sensor/reader range.
  bool cardPresent = PICC_IsAnyCardPresent();  //唤醒HALT/IDLF中的卡片,进入到READY 状态,返回结果true标识唤醒或者发生碰撞,简单的来说就是唤醒所有卡片

  // Reset the loop if no card was locked an no card is present.
  // This saves the select process when no card is found.
  if (!locked && !cardPresent)  //如果没有锁定的卡或者没有唤醒卡片,继续下次循环
  {
    Serial.print(F("没有唤醒到卡片"));
    Serial.print(locked);
    //spiCommand();
    delay(1000);
    return;
  }
  // When a card is present (locked) the rest ahead is intensive (constantly checking if still present).
  // Consider including code for checking only at time intervals.

  // Ask for the locked card (if rfid.uid.size > 0) or for any card if none was locked.
  // (Even if there was some error in the wake up procedure, attempt to contact the locked card.
  // This serves as a double-check to confirm removals.)
  // If a card was locked and now is removed, other cards will not be selected until next loop,
  // after rfid.uid.size has been set to 0.
  MFRC522::StatusCode result = rfid.PICC_Select(&rfid.uid, 8 * rfid.uid.size);  //选择卡片,传入卡片id

  if (!locked && result == MFRC522::STATUS_OK) {
    //原来没有卡片,但是现在有唤醒卡片,说明是新卡(新移入的卡片)
    locked = true;
    // Action on card detection.
    Serial.print(F("locked! NUID tag: "));
    Send_Cmd_Data(msg_uid,rfid.uid.uidByte,rfid.uid.size);  //将卡片id发送到上位机中打印
    Serial.println();
  } else if (locked && result != MFRC522::STATUS_OK) {
    //在之前选中卡片,但是现在无法唤醒卡片,说明卡片离开
    locked = false;
    rfid.uid.size = 0;
    // Action on card removal.
    Serial.print(F("unlocked! Reason for unlocking: "));
    Serial.println(rfid.GetStatusCodeName(result));
  } else if (!locked && result != MFRC522::STATUS_OK) {
    // Clear locked card data just in case some data was retrieved in the select procedure
    // but an error prevented locking.  没有卡片的状态
    rfid.uid.size = 0;
  } else {
    //上位机命令解析
    spiCommand();
  }

  for (int i = 0; i < 6; i++) {
    keyA[i] = 0x00;
    keyB[i] = 0x00;
  }

  rfid.PICC_HaltA();       // Halt PICC
  rfid.PCD_StopCrypto1();  // Stop encryption on PCD
  delay(1000);
}


void spiCommand() {
  if (Serial.available()) {
    char c = (char)Serial.read();
    if (c == 'R') {
      Serial.println(F("进入R命令"));
      // 读命令
      byte block = Serial.read();
      Serial.print(F("读取到地址块"));
      Serial.println(block);
      getPasswordFromSerial(keyA);  // 从串口读取 A 密钥
      Serial.print(F("读取到KeyA "));
      printHex(keyA, 6);
      Serial.println();
      getPasswordFromSerial(keyB);  // 从串口读取 A 密钥
      Serial.print(F("读取到KeyB "));
      printHex(keyB, 6);
      Serial.println();
      byte buffer[18];
      byte accessBits[4];
      if (readBlock(block, buffer, keyA, accessBits)) {
        Serial.print("R:");    // 发送读命令响应前缀
        printHex(buffer, 16);  // 发送读取的数据
        Serial.println();
      } else {
        Serial.println("读块失败");
      }

    } else if (c == 'W') {
      Serial.println(F("W:写命令模式"));
      byte block = Serial.read();
      getPasswordFromSerial(keyA);  // 从串口读取 A 密钥
      getPasswordFromSerial(keyB);  // 从串口读取 B 密钥
      byte buffer[16];
      for (byte i = 0; i < 16; i++) {
        buffer[i] = Serial.read();
      }
      byte accessBits[4];
      if (writeBlock(block, buffer, keyA, accessBits)) {
        Serial.println(F("W:Write successful"));
      }

    } else if (c == 'P') {
      // 设置密码和控制位命令
      byte block = Serial.read();
      Serial.println(F("P:设置密码模式"));
    } else {
      Serial.print(F("未知命令"));
      Serial.print(c < 0x10 ? "0" : "");
      Serial.print(c, HEX);
      Serial.print(" ");
      Serial.println();
    }
  }
}
/**
 * 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]) >> 4) & 0x0F, HEX);
    Serial.print(buffer[i] & 0x0F, HEX);
    Serial.print(" ");
  }
}

// This convenience function could be added to the library in the future

/**
 * Returns true if a PICC responds to PICC_CMD_WUPA.
 * All cards in state IDLE or HALT are invited.
 * 
 * @return bool
 */
bool PICC_IsAnyCardPresent() {
  byte bufferATQA[2];
  byte bufferSize = sizeof(bufferATQA);

  // Reset baud rates
  rfid.PCD_WriteRegister(rfid.TxModeReg, 0x00);  //定义传输数据速率和帧格式
  rfid.PCD_WriteRegister(rfid.RxModeReg, 0x00);  //定义接收数据速率和帧格式
  // Reset ModWidthReg
  rfid.PCD_WriteRegister(rfid.ModWidthReg, 0x26);  //控制调制宽度设置,0x26=REQA可以唤醒 idle 状态的卡片

  MFRC522::StatusCode result = rfid.PICC_WakeupA(bufferATQA, &bufferSize);
  return (result == MFRC522::STATUS_OK || result == MFRC522::STATUS_COLLISION);
}  // End PICC_IsAnyCardPresent()


// 读取串口密码
void getPasswordFromSerial(byte *key) {
  for (byte i = 0; i < 6; i++) {
    key[i] = Serial.read();
  }
}


// 读取控制位
bool readAccessBits(byte trailerBlock, byte *accessBits, byte *keyA) {
  if (authenticateBlock(trailerBlock, keyA, MFRC522::PICC_CMD_MF_AUTH_KEY_A)) {
    byte buffer[18];
    byte size = sizeof(buffer);
    MFRC522::StatusCode status = (MFRC522::StatusCode)rfid.MIFARE_Read(trailerBlock, buffer, &size);
    if (status == MFRC522::STATUS_OK) {
      accessBits[0] = buffer[6];
      accessBits[1] = buffer[7];
      accessBits[2] = buffer[8];
      accessBits[3] = buffer[9];
      return true;
    }
  }
  return false;
}


// 认证块
bool authenticateBlock(byte block, byte *key, MFRC522::PICC_Command command) {
  Serial.print("进入认证块函数:");
  Serial.println(block);
  Serial.print("当前卡片id");
  printHex(rfid.uid.uidByte, rfid.uid.size);
  Serial.println();

  MFRC522::MIFARE_Key authKey;
  memcpy(authKey.keyByte, key, 6);
  MFRC522::StatusCode status = (MFRC522::StatusCode)rfid.PCD_Authenticate(command, block, &authKey, &(rfid.uid));
  if (status == MFRC522::STATUS_OK) {
    return true;
  } else {
    if (command == MFRC522::PICC_CMD_MF_AUTH_KEY_A) {
      Serial.println(F("E:Key A authentication failed"));
    } else {
      Serial.println(F("E:Key B authentication failed"));
    }
  }

  return false;
}

// 检查访问权限
bool checkAccessRights(byte block, byte *accessBits, bool isRead) {
  /**
  根据控制位计算要读写的块需要验证的密码
  C1:第二个字节的高4位,根据块号向右移位取出控制位的值(&0xF0将保留高4位 >>4将高4位移位到低4位。再次根据块号(0、1、2、3)移位,则最低位就是需要的位(C10/C11/C12/C13))
  C2:第三个自己的第四位,根据块号向右移位取出控制位的值
  C3:第三个字节的高四位,根据块号向右移位取出控制位的值
  参考:https://blog.nnwk.net/article/113#tagpwtable
  **/
  byte c1 = (accessBits[1] & 0xF0) >> 4;  // 控制 C1,将控制位放到低位供后面提取
  Serial.print(F("控制位C1:"));
  Serial.print(c1, HEX);
  Serial.println();
  byte c2 = accessBits[2] & 0x0F;  // 控制 C2
  Serial.print(F("控制位C2:"));
  Serial.print(c2, HEX);
  Serial.println();
  byte c3 = (accessBits[2] & 0xF0) >> 4;  // 控制 C3
  Serial.print(F("控制位C3:"));
  Serial.print(c3, HEX);
  Serial.println();

  byte c1x = (c1 >> (block % 4)) & 0x01;  //右移动指定位数  然后&0x01 提取最右侧的bit位
  byte c2x = (c2 >> (block % 4)) & 0x01;
  byte c3x = (c3 >> (block % 4)) & 0x01;

  Serial.print(block);
  Serial.print(F("块的控制位Cn:"));
  Serial.print(c1x, HEX);
  Serial.print(c2x, HEX);
  Serial.print(c3x, HEX);
  Serial.println();

  byte accessBitsCombination = (c1x << 3) & (c2x << 2) & c3x;
  Serial.print("得到控制位:");
  Serial.println(accessBitsCombination, HEX);
  if (isRead) {  // 检查访问控制位组合(读取权限验证)
    switch (accessBitsCombination) {
      case 0x00:  // 000  a|b
      case 0x02:  // 010  a|b
      case 0x01:  // 001  a|b
      case 0x04:  // 100  a|b
      case 0x06:  // 110  a|b
        if (authenticateBlock(block, keyA, MFRC522::PICC_CMD_MF_AUTH_KEY_A) || authenticateBlock(block, keyB, MFRC522::PICC_CMD_MF_AUTH_KEY_B)) {
          Serial.println("授权a|b验证通过");
          return true;
        }
        break;
      case 0x03:  // 011  b
      case 0x05:  // 101  b
        if (authenticateBlock(block, keyB, MFRC522::PICC_CMD_MF_AUTH_KEY_B)) {
          Serial.println("授权b验证通过");
          return true;
        }
        break;         // 验证 B 密钥
      case 0x07:       // 111
        return false;  // 无法读取
    }
    return false;  // 验证失败
  } else {
    return (c1x == 0x0 && c2x == 0x0 && c3x == 0x0);  // 写入权限检查
  }
}



/*
  读取块数据
  读取命令示例:52 04  FF ff ff ff ff ff ff ff ff ff ff ff
*/
bool readBlock(byte block, byte *buffer, byte *keyA, byte *accessBits) {
  Serial.println(F("进入数据块读取"));
  byte trailerBlock = (block / 4) * 4 + 3;
  // 计算 trailer block 位置
  Serial.print(F("计算出权限控制块:"));
  Serial.println(trailerBlock);


  // 获取控制位
  if (!readAccessBits(trailerBlock, accessBits, keyA)) {
    Serial.println(F("E:Failed to read access bits"));
    return false;
  }
  Serial.print("读取控制位成功:");
  printHex(accessBits, 4);
  // 发送读取的数据
  Serial.println();
  // 检查控制位
  if (!checkAccessRights(block, accessBits, true)) {
    Serial.println(F("E:Read access denied"));
    return false;
  }


  byte size = 18;
  MFRC522::StatusCode status = (MFRC522::StatusCode)rfid.MIFARE_Read(block, buffer, &size);
  if (status == MFRC522::STATUS_OK) {
    return true;
  } else {
    Serial.println(F("E:Read failed"));
  }
  return false;
}



//bcc校验,求uid的 校验位
byte idChecksum(byte *buffer) {
  return buffer[0] ^ buffer[1] ^ buffer[2] ^ buffer[3];
}

/*
  写入块数据
  写入命令示例:57 04  FF ff ff ff ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*/
bool writeBlock(byte block, byte *buffer, byte *keyA, byte *accessBits) {

  if (block == 0) {
    //计算id校验码
    byte inputID[4] = { 0 };
    inputID[0] = buffer[0];
    inputID[1] = buffer[1];
    inputID[2] = buffer[2];
    inputID[3] = buffer[3];
    byte paritybit = idChecksum(inputID);  //计算校验位
    buffer[4] = paritybit;
  }

  byte trailerBlock = (block / 4) * 4 + 3;  // 计算 trailer block 位置

  // 获取控制位
  if (!readAccessBits(trailerBlock, accessBits, keyA)) {
    Serial.println(F("E:Failed to read access bits"));
    return false;
  }

  // 检查控制位
  if (!checkAccessRights(block, accessBits, false)) {
    Serial.println(F("E:Write access denied"));
    return false;
  }

  if (authenticateBlock(block, keyA, MFRC522::PICC_CMD_MF_AUTH_KEY_A)) {
    MFRC522::StatusCode status = (MFRC522::StatusCode)rfid.MIFARE_Write(block, buffer, 16);
    if (status == MFRC522::STATUS_OK) {
      return true;
    } else {
      Serial.println(F("E:Write failed"));
    }
  }
  return false;
}


//封包回应上位机
void packrespond(byte *buffer, uint8_t size, MsgType msgtype) {
  Serial.write(0xA5);  //包起始以A55A为开头
  Serial.write(0x5A);
  Serial.write(size);  //写入数据大小
  Serial.write(msgtype);
  Serial.write(size);

  for (uint8_t i = 0; i < size; ++i) {
    Serial.write(buffer[i]);  // 写入数据缓冲区中的每个字节
  }
}


void Send_Cmd_Data(uint8_t cmd, const uint8_t *datas, uint8_t len) {
  uint8_t buf[300], i, cnt = 0;
  uint16_t crc16;
  buf[cnt++] = 0x55;
  buf[cnt++] = 0xAA;
  buf[cnt++] = len;
  buf[cnt++] = cmd;
  for (i = 0; i < len; i++) {
    buf[cnt++] = datas[i];
  }
  crc16 = CRC16_Check(buf, len + 4);
  buf[cnt++] = crc16 & 0xff;
  buf[cnt++] = crc16 >> 8;
  buf[cnt++] = 0xFF;
  Send(buf, cnt);  //调用数据帧发送函数将打包好的数据帧发送出去
}


void Send(const uint8_t *data, uint8_t len) {
  for (uint8_t i = 0; i < len; i++) {
    Serial.write(data[i]);  //发送一个字节
  }
}



uint16_t CRC16_Check(const uint8_t *data, uint8_t len) {
  uint16_t CRC16 = 0xFFFF;
  uint8_t state, i, j;
  for (i = 0; i < len; i++) {
    CRC16 ^= data[i];
    for (j = 0; j < 8; j++) {
      state = CRC16 & 0x01;
      CRC16 >>= 1;
      if (state) {
        CRC16 ^= 0xA001;
      }
    }
  }
  return CRC16;
}

全部评论



提问