/*
* 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;
}
本文链接:https://blog.nnwk.net/article/1550
有问题请留言。版权所有,转载请在显眼位置处保留文章出处,并留下原文连接
Leave your question and I'll get back to you as soon as I see it. All rights reserved. Please keep the source and links
友情链接:
子卿全栈
全部评论