RFID是許多人日常中不可或缺的裝置,舉凡大樓門禁、電梯權限控制,很多企業也用RFID來進行出勤管理。最常用到的RFID模組就是RC522了,我們用最簡潔的程式,來讀取卡片的UID,並且和已儲存過的UID進行比對。然後再配合OLED,讓它變成可以實用的系統!
購買套件:https://www.jmaker.com.tw/products/rfid-project
在寫程式前,先把線都接好,因為後續還會用到OLED,所以把OLED也一併接好吧!如果不需要OLED的朋友,就不用理會OLED的部份囉!
再來就是安裝函式庫了,傑森測試過最方便也真正可以用的,MFRC522函式庫一定是第一名,而且它可以在程式管理員中直接下載。
我們先來寫支程式來讀取RFID卡或是磁扣的UID,UID是每張卡獨有的、不重複的,所以大多數安全層級不高的門禁管控,都是直接從UID來識別每一張卡。
程式:讀取卡片UID,從監控視窗查看
#include <SPI.h>
#include <MFRC522.h>
#define RST_PIN 9
#define SS_PIN 10 //就是模組上的SDA接腳
MFRC522 mfrc522; // 建立MFRC522實體
void setup() {
Serial.begin(9600);
SPI.begin(); // 初始化SPI介面
mfrc522.PCD_Init(SS_PIN, RST_PIN); // 初始化MFRC522卡
Serial.print(F("Reader "));
Serial.print(F(": "));
mfrc522.PCD_DumpVersionToSerial(); // 顯示讀卡設備的版本
}
void loop() {
// 檢查是不是一張新的卡
if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
// 顯示卡片內容
Serial.print(F("Card UID:"));
dump_byte_array(mfrc522.uid.uidByte, mfrc522.uid.size); // 顯示卡片的UID
Serial.println();
Serial.print(F("PICC type: "));
MFRC522::PICC_Type piccType = mfrc522.PICC_GetType(mfrc522.uid.sak);
Serial.println(mfrc522.PICC_GetTypeName(piccType)); //顯示卡片的類型
mfrc522.PICC_HaltA(); // 卡片進入停止模式
}
}
/**
* 這個副程式把讀取到的UID,用16進位顯示出來
*/
void dump_byte_array(byte *buffer, byte bufferSize) {
for (byte i = 0; i < bufferSize; i++) {
Serial.print(buffer[i] < 0x10 ? " 0" : " ");
Serial.print(buffer[i], HEX);
}
}
這支程式是傑森簡化函式庫所附的範例改寫的,底下這行會檢測RFID設備的版本,基本上也算是偵測設是否運作正常。
mfrc522.PCD_DumpVersionToSerial();
讀完卡片後,其中最重要的的UID,是存在mfrc522.uid.uidByte中的,利用dump_byte_array這支副程式把UID顯示在監控視窗中。
程式並不難,大家應該都能完成。
我們已經得到卡片的UID了,把它記下來,接下來我們要寫一支程式,來判斷是不是偵測到已記錄的卡片。
程式:判斷卡片UID是否通過,從監控視窗查看
#include <SPI.h>
#include <Wire.h>
#include <MFRC522.h>
#define RST_PIN 9
#define SS_PIN 10 //RC522卡上的SDA
MFRC522 mfrc522; // 建立MFRC522實體
char *reference;
byte uid[]={0x49, 0xE5, 0xA0, 0xC1}; //這是我們指定的卡片UID,可由讀取UID的程式取得特定卡片的UID,再修改這行
void setup()
{
Serial.begin(9600);
SPI.begin();
mfrc522.PCD_Init(SS_PIN, RST_PIN); // 初始化MFRC522卡
Serial.print(F("Reader "));
Serial.print(F(": "));
mfrc522.PCD_DumpVersionToSerial(); // 顯示讀卡設備的版本
}
void loop() {
//Serial.print("reading...");
// 檢查是不是偵測到新的卡
if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
// 顯示卡片的UID
Serial.print(F("Card UID:"));
dump_byte_array(mfrc522.uid.uidByte, mfrc522.uid.size); // 顯示卡片的UID
Serial.println();
Serial.print(F("PICC type: "));
MFRC522::PICC_Type piccType = mfrc522.PICC_GetType(mfrc522.uid.sak);
Serial.println(mfrc522.PICC_GetTypeName(piccType)); //顯示卡片的類型
//把取得的UID,拿來比對我們指定好的UID
bool they_match = true; // 初始值是假設為真
for ( int i = 0; i < 4; i++ ) { // 卡片UID為4段,分別做比對
if ( uid[i] != mfrc522.uid.uidByte[i] ) {
they_match = false; // 如果任何一個比對不正確,they_match就為false,然後就結束比對
break;
}
}
//在監控視窗中顯示比對的結果
if(they_match){
Serial.print(F("Access Granted!"));
}else{
Serial.print(F("Access Denied!"));
}
mfrc522.PICC_HaltA(); // 卡片進入停止模式
}
}
/**
* 這個副程式把讀取到的UID,用16進位顯示出來
*/
void dump_byte_array(byte *buffer, byte bufferSize) {
for (byte i = 0; i < bufferSize; i++) {
Serial.print(buffer[i] < 0x10 ? " 0" : " ");
Serial.print(buffer[i], HEX);
}
}
程式大致上和上一支讀取UID差不多,底下這行最重要,請換上之前讀到的其中一張卡片的UID,這樣我們才能進行比對。
byte uid[]={0x49, 0xE5, 0xA0, 0xC1};
再來就是透過底兩段程式碼,來判斷放入的卡片,是不是和我們記錄的UID完全相同。
//把取得的UID,拿來比對我們指定好的UID
bool they_match = true; // 初始值是假設為真
for ( int i = 0; i < 4; i++ ) { // 卡片UID為4段,分別做比對
if ( uid[i] != mfrc522.uid.uidByte[i] ) {
they_match = false; // 如果任何一個比對不正確,they_match就為false,然後就結束比對
break;
}
}
//在監控視窗中顯示比對的結果
if(they_match){
Serial.print(F("Access Granted!"));
}else{
Serial.print(F("Access Denied!"));
}
接下來我們附上兩支結合了OLED的程式,這次傑森是用u8g2函式庫配合0.96吋的OLED。有關OLED部份,請參考傑森之前的文章。
程式:讀取卡片UID,從OLED查看
#include <Arduino.h>
#include <U8g2lib.h>
#include <SPI.h>
#include <Wire.h>
#include <MFRC522.h>
//MFRC522用到的兩個pin
#define RST_PIN 9
#define SS_PIN 10 //RC522卡上的SDA
MFRC522 mfrc522; // 建立MFRC522實體
char *reference; //顯示設備正常與否的文字
//這行是NodeOLED用的
//U8G2_SSD1306_128X64_NONAME_1_SW_I2C u8g2(U8G2_R0, /* clock=*/ D2, /* data=*/ D1, /* reset=*/ U8X8_PIN_NONE);
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); //Arduino Uno+0.96吋OLED用這行
//U8G2_SH1106_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); //如果用1.3吋OLED用這行
//OLED上方標題,用圖形表現
static const unsigned char PROGMEM title[256] = {
0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XBF,0XEF,0XFE,0XFF,0XFF,0XFF,0XFF,0XFF,
0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFD,0XE7,0XBF,0XEF,0XFA,0XFF,0XFF,0XFF,0XFD,0XFE,
0XE3,0X3F,0XF8,0XC1,0X1F,0XFE,0XFB,0XEE,0X7F,0X83,0XFA,0XFF,0XE3,0XFF,0X7E,0XFE,
0XDB,0XBF,0XFF,0XF7,0XDF,0XFD,0XE0,0X82,0XFF,0XFF,0XF6,0X1F,0XFC,0XFF,0X12,0XE0,
0XBB,0XBF,0XFF,0XF7,0XDF,0XFB,0XFF,0XFE,0X0F,0XB6,0XFE,0XE1,0XFE,0X7F,0XBB,0XFF,
0XBB,0XBF,0XFF,0XF7,0XDF,0XF7,0XA5,0XB6,0XFF,0X03,0XF0,0X3F,0XFF,0XBF,0XBB,0XFB,
0XBB,0XBF,0XFF,0XF7,0XDF,0XF7,0XB5,0XD6,0X1F,0XFE,0XFE,0XDF,0XF3,0X1F,0XDC,0XFB,
0XBB,0XBF,0XFF,0XF7,0XDF,0XF7,0XB7,0XDE,0XFF,0X83,0XF6,0X07,0XFC,0XFF,0XDE,0XF3,
0XDB,0X3F,0XF8,0XF7,0XDF,0XF7,0XA0,0X82,0X1F,0XBA,0XFA,0X7F,0XE6,0X7F,0X0B,0XF4,
0XE3,0XBF,0XFF,0XF7,0XDF,0XF7,0XFB,0XEE,0XFF,0XBB,0XFA,0X9F,0XDF,0XBF,0XB9,0XE5,
0XDB,0XBF,0XFF,0XF7,0XDF,0XF7,0XFB,0XEE,0X1F,0X82,0XFD,0X03,0X80,0X1F,0XBE,0XFD,
0XDB,0XBF,0XFF,0XF7,0XDF,0XF7,0XE0,0X82,0XDF,0XBA,0XFD,0XFB,0XBE,0XFF,0XB3,0XFD,
0XDB,0XBF,0XFF,0XF7,0XDF,0XFB,0XFB,0XEE,0XDF,0XBA,0XFE,0XCF,0XF6,0XBF,0XB6,0XFD,
0XBB,0XBF,0XFF,0XF7,0XDF,0XFD,0X7B,0XEF,0XDF,0X82,0XF4,0XE7,0XCE,0XBF,0XD5,0XED,
0XBB,0XBF,0XFF,0XC1,0X1F,0XFE,0X7D,0XEF,0X1F,0X7A,0XF5,0XF9,0XBE,0XBF,0XDD,0XED,
0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XBD,0XEF,0XDF,0XBF,0XFB,0X1F,0XFE,0XDF,0XE7,0XF3,
};
void setup()
{
Serial.begin(9600);
SPI.begin(); // 初始化SPI介面
u8g2.begin();
mfrc522.PCD_Init(SS_PIN, RST_PIN); // 初始化MFRC522卡
Serial.print(F("Reader "));
Serial.print(F(": "));
mfrc522.PCD_DumpVersionToSerial(); // 顯示讀卡設備的版本
//測試RFID設備是否正常
byte v = mfrc522.PCD_ReadRegister(mfrc522.VersionReg);
if((v == 0x00) || (v == 0xFF)){
reference = "Reader Error";
showText1();
}else{
reference = "Reader Ready";
showText1();
delay(1500);
showText2();
}
}
//顯示第一行文字
void showText1(){
u8g2.setFont(u8g2_font_samim_16_t_all); //字型
u8g2.firstPage();
do {
u8g2.drawXBMP(0,0, 128, 16, title);
u8g2.drawStr(0,40,"Reader Testing...");
u8g2.drawStr(10,60,reference);
} while ( u8g2.nextPage() );
}
//顯示第二行文字
void showText2(){
char *reference;
u8g2.setFont(u8g2_font_samim_16_t_all); //字型
u8g2.firstPage();
do {
u8g2.drawXBMP(0,0, 128, 16, title);
u8g2.drawStr(10,40,"Place RFID...");
} while ( u8g2.nextPage() );
}
void loop() {
//Serial.print("reading...");
// 檢查是不是偵測到新的卡
if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
// 顯示卡片內容
Serial.print(F("Card UID:"));
dump_byte_array(mfrc522.uid.uidByte, mfrc522.uid.size); // 顯示卡片的UID到監控視窗
//顯示卡片UID;mfrc522.uid.uidByte內容就是UID,資料是byte的陣列,我們要逐一取出後,顯示在OLED上
for(int i = 0; i < mfrc522.uid.size; i++)
{
u8g2.firstPage();
do {
u8g2.drawXBMP(0,0, 128, 16, title);
u8g2.setCursor(10,40);
u8g2.print("RFID UID:");
for (byte i = 0; i < mfrc522.uid.size; i++) {
if(mfrc522.uid.uidByte[i] < 0x10){ //讀到小於10的數字時,前面多顯示一個零
u8g2.setCursor(10+i*25,60);
u8g2.print("0");
u8g2.setCursor(10+i*25+10,60);
u8g2.print(mfrc522.uid.uidByte[i], HEX); //用HEX的方式顯示一段UID,如A0或C1等。
}else{
u8g2.setCursor(10+i*25,60);
u8g2.print(mfrc522.uid.uidByte[i], HEX);
}
}
} while ( u8g2.nextPage() );
}
Serial.println();
Serial.print(F("PICC type: "));
MFRC522::PICC_Type piccType = mfrc522.PICC_GetType(mfrc522.uid.sak);
Serial.println(mfrc522.PICC_GetTypeName(piccType)); //顯示卡片的類型
mfrc522.PICC_HaltA(); // 卡片進入停止模式
}
}
/**
* 這個副程式把讀取到的UID,用16進位顯示出來
*/
void dump_byte_array(byte *buffer, byte bufferSize) {
for (byte i = 0; i < bufferSize; i++) {
Serial.print(buffer[i] < 0x10 ? " 0" : " ");
Serial.print(buffer[i], HEX);
}
}
程式:判斷卡片UID是否通過,從OLED上查看
#include <Arduino.h>
#include <U8g2lib.h>
#include <SPI.h>
#include <Wire.h>
#include <MFRC522.h>
#define RST_PIN 9
#define SS_PIN 10 //RC522卡上的SDA
MFRC522 mfrc522; // 建立MFRC522實體
char *reference;
byte uid[]={0x49, 0xE5, 0xA0, 0xC1}; //這是我們指定的卡片UID,可由讀取UID的程式取得特定卡片的UID,再修改這行
//這行是NodeOLED用的
//U8G2_SSD1306_128X64_NONAME_1_SW_I2C u8g2(U8G2_R0, /* clock=*/ D2, /* data=*/ D1, /* reset=*/ U8X8_PIN_NONE);
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); //Arduino Uno+0.96吋OLED用這行
//U8G2_SH1106_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); //如果用1.3吋OLED用這行
//OLED上方標題,用圖形表現
static const unsigned char PROGMEM title[256] = {
0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XBF,0XEF,0XFE,0XFF,0XFF,0XFF,0XFF,0XFF,
0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFD,0XE7,0XBF,0XEF,0XFA,0XFF,0XFF,0XFF,0XFD,0XFE,
0XE3,0X3F,0XF8,0XC1,0X1F,0XFE,0XFB,0XEE,0X7F,0X83,0XFA,0XFF,0XE3,0XFF,0X7E,0XFE,
0XDB,0XBF,0XFF,0XF7,0XDF,0XFD,0XE0,0X82,0XFF,0XFF,0XF6,0X1F,0XFC,0XFF,0X12,0XE0,
0XBB,0XBF,0XFF,0XF7,0XDF,0XFB,0XFF,0XFE,0X0F,0XB6,0XFE,0XE1,0XFE,0X7F,0XBB,0XFF,
0XBB,0XBF,0XFF,0XF7,0XDF,0XF7,0XA5,0XB6,0XFF,0X03,0XF0,0X3F,0XFF,0XBF,0XBB,0XFB,
0XBB,0XBF,0XFF,0XF7,0XDF,0XF7,0XB5,0XD6,0X1F,0XFE,0XFE,0XDF,0XF3,0X1F,0XDC,0XFB,
0XBB,0XBF,0XFF,0XF7,0XDF,0XF7,0XB7,0XDE,0XFF,0X83,0XF6,0X07,0XFC,0XFF,0XDE,0XF3,
0XDB,0X3F,0XF8,0XF7,0XDF,0XF7,0XA0,0X82,0X1F,0XBA,0XFA,0X7F,0XE6,0X7F,0X0B,0XF4,
0XE3,0XBF,0XFF,0XF7,0XDF,0XF7,0XFB,0XEE,0XFF,0XBB,0XFA,0X9F,0XDF,0XBF,0XB9,0XE5,
0XDB,0XBF,0XFF,0XF7,0XDF,0XF7,0XFB,0XEE,0X1F,0X82,0XFD,0X03,0X80,0X1F,0XBE,0XFD,
0XDB,0XBF,0XFF,0XF7,0XDF,0XF7,0XE0,0X82,0XDF,0XBA,0XFD,0XFB,0XBE,0XFF,0XB3,0XFD,
0XDB,0XBF,0XFF,0XF7,0XDF,0XFB,0XFB,0XEE,0XDF,0XBA,0XFE,0XCF,0XF6,0XBF,0XB6,0XFD,
0XBB,0XBF,0XFF,0XF7,0XDF,0XFD,0X7B,0XEF,0XDF,0X82,0XF4,0XE7,0XCE,0XBF,0XD5,0XED,
0XBB,0XBF,0XFF,0XC1,0X1F,0XFE,0X7D,0XEF,0X1F,0X7A,0XF5,0XF9,0XBE,0XBF,0XDD,0XED,
0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XBD,0XEF,0XDF,0XBF,0XFB,0X1F,0XFE,0XDF,0XE7,0XF3,
};
void setup()
{
Serial.begin(9600);
SPI.begin(); // 初始化SPI介面
u8g2.begin();
mfrc522.PCD_Init(SS_PIN, RST_PIN); // 初始化MFRC522卡
Serial.print(F("Reader "));
Serial.print(F(": "));
mfrc522.PCD_DumpVersionToSerial(); // 顯示讀卡設備的版本
//測試RFID設備是否正常
byte v = mfrc522.PCD_ReadRegister(mfrc522.VersionReg);
if((v == 0x00) || (v == 0xFF)){
reference = "Reader Error";
showText1();
}else{
reference = "Reader Ready";
showText1();
delay(1500);
showText2();
}
}
void showText1(){
u8g2.setFont(u8g2_font_samim_16_t_all); //字型
u8g2.firstPage();
do {
u8g2.drawXBMP(0,0, 128, 16, title);
u8g2.drawStr(0,40,"Reader Testing...");
u8g2.drawStr(10,60,reference);
} while ( u8g2.nextPage() );
}
void showText2(){
char *reference;
u8g2.setFont(u8g2_font_samim_16_t_all); //字型
u8g2.firstPage();
do {
u8g2.drawXBMP(0,0, 128, 16, title);
u8g2.drawStr(10,40,"Place RFID...");
} while ( u8g2.nextPage() );
}
void loop() {
//Serial.print("reading...");
// 檢查是不是偵測到新的卡
if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
// 顯示卡片的UID
Serial.print(F("Card UID:"));
dump_byte_array(mfrc522.uid.uidByte, mfrc522.uid.size); // 顯示卡片的UID
for(int i = 0; i < mfrc522.uid.size; i++)
{
u8g2.firstPage();
do {
u8g2.drawXBMP(0,0, 128, 16, title);
u8g2.setCursor(10,40);
u8g2.print("RFID UID:");
for (byte i = 0; i < mfrc522.uid.size; i++) {
if(mfrc522.uid.uidByte[i] < 0x10){
u8g2.setCursor(10+i*25,60);
u8g2.print("0");
u8g2.setCursor(10+i*25+10,60);
u8g2.print(mfrc522.uid.uidByte[i], HEX);
}else{
u8g2.setCursor(10+i*25,60);
u8g2.print(mfrc522.uid.uidByte[i], HEX);
}
}
} while ( u8g2.nextPage() );
}
Serial.println();
Serial.print(F("PICC type: "));
MFRC522::PICC_Type piccType = mfrc522.PICC_GetType(mfrc522.uid.sak);
Serial.println(mfrc522.PICC_GetTypeName(piccType)); //顯示卡片的類型
//把取得的UID,拿來比對我們指定好的UID
bool they_match = true; // 初始值是假設為真
for ( int i = 0; i < 4; i++ ) { // 卡片UID為4段,分別做比對
if ( uid[i] != mfrc522.uid.uidByte[i] ) {
they_match = false; // 如果任何一個比對不正確,they_match就為false,然後就結束比對
break;
}
}
Serial.println(they_match);
//在OLED顯示比對的結果
u8g2.firstPage();
do {
u8g2.drawXBMP(0,0, 128, 16, title);
if(they_match){
u8g2.drawStr(10,40,"Access Granted");
}else{
u8g2.drawStr(10,40,"Access Denied");
}
} while ( u8g2.nextPage() );
delay(1500);
u8g2.firstPage();
do {
u8g2.drawXBMP(0,0, 128, 16, title);
u8g2.drawStr(10,40,"Place RFID...");
} while ( u8g2.nextPage() );
mfrc522.PICC_HaltA(); // 卡片進入停止模式
}
}
/**
* 這個副程式把讀取到的UID,用16進位顯示出來
*/
void dump_byte_array(byte *buffer, byte bufferSize) {
for (byte i = 0; i < bufferSize; i++) {
Serial.print(buffer[i] < 0x10 ? " 0" : " ");
Serial.print(buffer[i], HEX);
}
}