大家好,我是Danny,今天我要记录一个使用Arduino对RFID(RC522)模块进行编程的实例,模仿了公交中的刷卡系统,将余额记录在卡片中,每次刷卡自动扣费500,如果余额不足则会自动显示“余额不足”。
本人第一次学习Arduino,所以有错误欢迎指正.。
Mifare卡的存储原理
RFID的作用,历史什么的,随便找网址一大堆,所以我就不多赘述了。这里主要讲一下我对RFID中的一种—RC522模块所使用的Mifare卡的存储原理的理解。
在Mifare卡中一共有16个扇区(sector),每个扇区有4个区块(Block),每个块有16个字节。按照编号,16个扇区分为0-15号,64个区块可以分为某个扇区的0-3号,或者直接分为0-63号。打个比方,5号扇区的2号区块,即是22号区块。
在如此多的块中,0号扇区的0号区块用于存放厂商代码,已经固化,不可更改。除此之外,每个扇区的块0、块1、块2为数据块,可用于存贮数据;而每个扇区的块3为控制块,包括了密码A、存取控制、密码B。
每个扇区的密码和存取控制都是独立的,可以根据实际需要设定各自的密码及存取控制。存取控制为4个字节,共32位,扇区中的每个块(包括数据块和控制块)的存取条件是由密码和存取控制共同决定的,在存取控制中每个块都有相应的三个控制位,定义如下:
三个控制位以正和反两种形式存在于存取控制字节中,决定了该块的访问权限(如进行减值操作必须验证KEY A,进行加值操作必须验证KEY B,等等)。存取控制(4字节,其中字节9为备用字节)结构如下所示:
数据块(块0、块1、块2)的存取控制如下:
控制块块 3 的存取控制与数据块(块 0、 1、 2)不同,它的存取控制如下:
以上即为Mifare卡的重要储存原理。
库,变量和setup函数
#include <SPI.h>
#include <MFRC522.h>
#include <LiquidCrystal.h>
#define RST_PIN 5
#define SS_PIN 53
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);//lcd显示器相关代码
MFRC522 mfrc522(SS_PIN, RST_PIN);
MFRC522::MIFARE_Key key; //这部分代码参考网站:https://github.com/miguelbalboa/rfid/blob/master/examples/MifareClassicValueBlock/MifareClassicValueBlock.ino#L66C1-L66C1
long int intvalue;
String strvalue="";
String me="";
String password="5E9941B"; //设置uid为“5E99041B”这个请根据自己的卡片自行定义
void setup()
{
Serial.begin(9600);
SPI.begin();
lcd.begin(16, 2);
mfrc522.PCD_Init();
Serial.println("Scan a MIFARE Classic card");
// 定义读写密钥
for (byte i = 0; i < 6; i++) {
key.keyByte[i] = 0xFF; //keyByte在结构体(struct)"MIFARE_Key"中已经被定义
}
}
此处说明一下,部分代码源于网站https://github.com/miguelbalboa/rfid/blob/master/examples/MifareClassicValueBlock/MifareClassicValueBlock.ino#L66C1-L66C1,各种头文件均可在Arduino UNO的官网中下载,密钥A、B均为“FFFFFFFFFFFF”。
Mifare卡的Uid识别函数
//识别uid是否对应的函数
void uidtest(byte *buffer){//在loop函数中对应mfrc522.uid.uidByte
for(byte i=0;i<4;i++){
me.concat(String(buffer[i], HEX)); //uid既是uidByte数组中的前四位,concat函数的作用为连接字符至字符串String中,HEX代表16进制
}
me.toUpperCase();//改为大写字母
if(me!=password){//识别失败
lcd.print("Unable to recognize");
me="";
//delay(100);
return;
}
else{
lcd.print("Danny Leo");//识别成功,在lcd屏幕上显示"Dannt Leo"
//delay(1000);
}
me="";
}
uidtest函数用于识别Mifare卡中的uid(即0扇区0区块的前4个字节)是否与设置的变量“password”一致。
Mifare卡数据读写函数代码
int writeBlock(int blockNumber, byte arrayAddress[]) {
String strv=String(intvalue);
String tocard="0000000000000000"+strv;
tocard=tocard.substring(tocard.length()-16);
tocard.getBytes(arrayAddress, 17);
//以上代码的作用为将intvalue里的值(10进制)转换为String,再转换为Byte[16]
int largestModulo4Number=blockNumber/4*4;//这里先除以4再乘以4的原理是将区块号变成当前扇区的0号,如17号区块,17/4*4的值为16,即4号扇区的0号区块
int trailerBlock=largestModulo4Number+3;//将区块号变成当前扇区的3号区块
byte status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_B, trailerBlock, &key, &(mfrc522.uid));
if (status != MFRC522::STATUS_OK) {
Serial.print("PCD_Authenticate() failed: ");
Serial.println(mfrc522.GetStatusCodeName(status));
return 3;//return "3" as error message
}
//核心代码,将arrayAddress[16]写入对应区块
status = mfrc522.MIFARE_Write(blockNumber, arrayAddress, 16);
if (status != MFRC522::STATUS_OK) {
Serial.print("MIFARE_Write() failed: ");
Serial.println(mfrc522.GetStatusCodeName(status));
return 4;
}
}
int readBlock(int blockNumber, byte arrayAddress[]) {
int largestModulo4Number=blockNumber/4*4;
int trailerBlock=largestModulo4Number+3;
char transform[16];
//核心代码:验证密钥
byte status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid));
if (status != MFRC522::STATUS_OK) {
Serial.print("PCD_Authenticate() failed (read): ");
Serial.println(mfrc522.GetStatusCodeName(status));
return 3;
}
byte buffersize = 18;
status = mfrc522.MIFARE_Read(blockNumber, arrayAddress, &buffersize);//核心代码:将对应的区块中的数值存入变量arrayAddress[18]中,数值存于前16位
if (status != MFRC522::STATUS_OK) {
Serial.print("MIFARE_read() failed: ");
Serial.println(mfrc522.GetStatusCodeName(status));
return 4;
}
for (int j=0 ; j<16 ; j++){
transform[j]=arrayAddress[j];
strvalue.concat(transform[j]);
}
intvalue=strvalue.toInt();
//以上代码的作用是将arrayAddress[18]中的16进制值转换为char[18],然后将前16位连接导入至String变量strvalue中,最后将其变为int变量存入intvalue变量
}
总而言之,writeBlock()的作用是将intvalue中存储的数值存入Mifare卡中,readBlock()的作用是读取Mifare卡中存储的数值至intvalue中。
loop函数
void loop(){
if ( ! mfrc522.PICC_IsNewCardPresent()) {
return;
}
if ( ! mfrc522.PICC_ReadCardSerial()) {
return;
}
lcd.clear();
int block=5;
byte blockcontent[17];
byte readbackblock[18];
uidtest(mfrc522.uid.uidByte);
readBlock(block, readbackblock);
Serial.print(intvalue);
Serial.println(" - 500");
intvalue-=500;//卡里的钱-500
if(intvalue<0){
lcd.setCursor(0, 1);
//判定为“余额不足”,在lcd第二行显示出来
lcd.print("Not Enough Money");
intvalue+=500;
return;
}
Serial.print("Money Left: ");
Serial.println(intvalue);
String lcdd=String(intvalue+500)+"-500="+String(intvalue);
lcd.setCursor(0, 1);
lcd.print(lcdd);在这里插入图片描述
writeBlock(block, blockcontent);
strvalue="";
me="";
mfrc522.PICC_HaltA();
mfrc522.PCD_StopCrypto1();
}
该代码呈现出的结果为每次刷卡,都会使得卡里的余额-500,如果余额不足则会显示“Not Enough Money”。
硬件部分
本次使用 Arduino Mega 2560 作为控制核心,接线方式为将下面两幅图叠加:
实物图如下所示:
效果呈现
如下视频所示:
Arduino mega 2560 RFID
第一次学习Arduino,有问题或者疑问请务必提出,这也会让我很开心,谢谢!