Arduino 演練 - 利用LCD顯示RFID讀寫互動

這次主要是練習操作RFID讀卡器對電子標籤進行讀取、寫入的操作,並且應用上一次演練的LCD裝置來進行顯示,而input的部分是利用到Serial Monitor,不過若是有小鍵盤搭配的話應該會比較好玩一點!

這次使用的素材如下:
1. Arduino Uno R3
2. LiquidCrystal_I2C*1
3. Mifare RFID-RC522模組*1
4. 杜邦線(公對母)*4 (LCD使用)
5. 杜邦線(公對母)*7 (RFID模組使用)
6. 麵包板
7. 220Ω*1
8. 蜂鳴器*1
9. LED燈(藍)*1
10. 麵包線*4
11. 電子標籤*2
實際電路接法

一、演練效果說明
1. 板子接上電源後,初始化MFRC522、LCD等裝置
2. 當電子標籤放在MFRC522上面,此時透過LCD顯示並進行金鑰、UID驗證,驗證都符合就會進入選單,看是要操作read or write的動作!
3. 此時搭配Serial Monitor進行選項輸入,輸入1表示進行讀取動作;輸入2表示進行寫入動作!
4. 讀取程式會固定讀取block 4的16 bytes內容後進行顯示!
5. 寫入程式會固定寫入資料到block 4進行覆蓋!
PS. 關於電子標籤的資料區段介紹(如共有幾個block)請自行查找囉~

二、簡單說明一下電路接法
#Mifare RFID-RC522 → Arduino UNO
VCC → V3.3
RST → 數位9
GND → GND
MISO → 數位12
MOSI → 數位11
SCK → 數位13
NSS → 數位10
IRQ → X(不用接)

#LiquidCrystal_I2C → Arduino UNO
VCC → V5
GND → GND
SDA → A4
SCL → A5

#其他
蜂鳴器(+) → 數位8
LED燈 → 數位7

三、程式說明
這支程式主要需使用到兩個外部Library,分別為
Arduino-LiquidCrystal-I2C-library
Arduino-MFRC522
依序載入及宣告的global變數如下:
#include <SPI.h>
#include <MFRC522.h>
#include <LiquidCrystal_I2C.h>

#define LED_PIN 7
#define BUZZER_PIN  8
#define RST_PIN 9     // Configurable, see typical pin layout above
#define SS_PIN  10    // Configurable, see typical pin layout above

MFRC522 mfrc522(SS_PIN, RST_PIN);   // Create MFRC522 instance
MFRC522::MIFARE_Key key;

const byte existUID[] = {0x12,0x67,0x96,0xBB};
const byte blockIndex = 4;
const byte len = 18;
byte blockInfo[len]; //1 block: 16 bytes + external 2 bytes

// Set the LCD address to 0x27 for a 16 chars and 2 line display
LiquidCrystal_I2C lcd(0x27, 16, 2);

3.1、初始化
void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);                                          // Initialize serial communications with the PC
  SPI.begin();                                                 // Init SPI bus
  mfrc522.PCD_Init();                                          // Init MFRC522 card
  
  // Prepare key - all keys are set to FFFFFFFFFFFFh at chip delivery from the factory.
  for (byte i = 0; i < 6; i++) key.keyByte[i] = 0xFF;

  randomSeed(analogRead(0)); //set random seed
  
  lcd.begin();
  lcd.noBacklight();
  
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(LED_PIN, OUTPUT);
}
電子標籤要讀取或寫入都需要先經過金鑰驗證,在此金鑰預設值皆為0xFF

3.2、標籤偵測
void loop() {
   // Look for new cards
   if ( ! mfrc522.PICC_IsNewCardPresent()) {
     return;
   }
  
   // Select one of the cards
   if ( ! mfrc522.PICC_ReadCardSerial()) {
     return;
   }
   
   lcd.backlight();
   
   printLCD("**Card Detected:**");

   //...
}

void printLCD(String msg){
  printLCD(msg, 1000);
}

void printLCD(String msg, int milis){
  lcd.clear();
  int msgLen = msg.length();
  int lcdLineLimit = 16;
  lcd.setCursor(0, 0);
  if(msgLen > lcdLineLimit){
    lcd.print(msg.substring(0, lcdLineLimit));
    lcd.setCursor(0, 1);
    lcd.print(msg.substring(lcdLineLimit, lcdLineLimit * 2));
  }else{
    lcd.print(msg);
  }
  delay(milis);
}

打開MFRC522提供的example都會有前面這兩個判斷,一個是針對是否有新的標籤,另一個則是若同時有多個標籤時一次只會read一個! 當這兩個判斷式通過後LCD就會亮起來! 而printLCD函式預設在印出字串時維持1秒鐘的時間,若是指定的message長度超過16會自動排到第二行去!
PS. LCD最多只有兩行列印空間

3.3、金鑰、UID驗證
void loop() {
  //...
  if(keyVerify(blockIndex, key, mfrc522.uid)) {
    if(uidVerify()){
       Serial.setTimeout(5000L) ;     // wait until 5 seconds for input from serial
       //Enter option and input by Serial Console
       printLCD("Select Option:  (1)Read(2)Write");
       
       char option = getOptionInput();
       if(option == '1')
        readCard();
       else if(option == '2')
        writeCard();
       else
        printLCD("No option!!");
     }
  }
  //...
}

char getOptionInput(){
  byte buffer[len];
  Serial.readBytes(buffer, len);
  char inputOption = char(buffer[0]);
  printLCD("You input option:"+(String(inputOption)));
  return inputOption;
}

bool keyVerify(byte blockAddr, MFRC522::MIFARE_Key key, MFRC522::Uid uid){
  MFRC522::StatusCode status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, blockAddr, &key, &(uid)); //line 834 of MFRC522.cpp file
  if(status == MFRC522::STATUS_OK){
    printLCD("Auth. Success!");
  }else{
    digitalWrite(BUZZER_PIN, HIGH);
    enableBuzzer(255);
    printLCD("Auth. Failed!");
    return false;
  }
  return true;
}

bool uidVerify(){
   byte *currUID = mfrc522.uid.uidByte;
   byte idSize = mfrc522.uid.size;
   if(memcmp(existUID, currUID, idSize) == 0){
      enableBuzzer(50);
      return true;
   }else{
      enableBuzzer(255);
      printLCD("Failed!!Card does not exist!!", 2000);
      return false;
   }
}

void enableBuzzer(int count){
  unsigned char i = 0;
  for(i = 0 ; i < count ; i++){
    digitalWrite(BUZZER_PIN, HIGH);
    delay(1);
    digitalWrite(BUZZER_PIN, LOW);
    delay(1);
  }
}
標籤被偵測到後,隨即進行金鑰驗證呼叫keyVerify(...),傳入的參數分別為哪個block、key值、還有這個標籤的UID,若驗證成功才可以對這個block的區塊進行資料讀取寫入的操作!
而uidVerify這個函式是檢查標籤的uid是否有match變數existUID內紀錄的值,若有的話才可以進行讀取或寫入,算是多一層過濾的動作!
通過兩道驗證之後,接著LCD會印出選單供您進行操作,這邊需搭配Arduino IDE內的Serial Monitor進行input動作,在此設定輸入的timeout時間為5秒!
PS. 若有小鍵盤就不用如此麻煩

3.4、Read data from tag
void readCard(){  
  digitalWrite(LED_PIN, HIGH);
  if(readData(blockIndex, blockInfo, len)) {
    int i = 0;
    String str;
    for(i = 0 ; i < len - 2 ; i++){
      str += char(blockInfo[i]);
    }
    printLCD("Welcome!!"+str, 3000);
  }
  digitalWrite(LED_PIN, LOW);
}

bool readData(byte blockAddr, byte bufferArr[], byte len){
  MFRC522::StatusCode status = mfrc522.MIFARE_Read(blockAddr, bufferArr, &len);
  if(status != MFRC522::STATUS_OK){
    enableBuzzer(255);
    printLCD("Read Failed!");
    return false;
  }
  return true;
}
在此需指定如前面驗證的block編號及buffer array(長度為18)進行讀取動作,接著再把read出來的資料轉成字串印在LCD上面!

3.5、Write data to tag
void writeCard(){
  digitalWrite(LED_PIN, HIGH);
  Serial.setTimeout(10000L) ;     // wait until 10 seconds for input from serial
  printLCD("Type name, ending with # ");
  byte currLen = Serial.readBytesUntil('#', (char *) blockInfo, len - 2) ; // read name from serial
  //filter '\n' character
  if(currLen > 0 && blockInfo[currLen - 1] == '\n'){
    currLen--;
  }
  for (byte i = currLen; i < len - 2; i++) blockInfo[i] = ' ';     // pad with spaces

  if(currLen == 0){
    printLCD("Wait Timeout...");
    digitalWrite(LED_PIN, LOW);
    return;
  }
  delay(1000);
  if(writeData(blockIndex, blockInfo, len - 2)) {
    String strData;
    for (byte c = 0 ; c < len - 2 ; c++)
      strData += char(blockInfo[c]);
    printLCD("Write Success:"+strData, 3000);
  }
  digitalWrite(LED_PIN, LOW);
}

bool writeData(byte blockAddr, byte bufferArr[], byte len){
  MFRC522::StatusCode status = mfrc522.MIFARE_Write(blockAddr, bufferArr, len);
  if(status != MFRC522::STATUS_OK){
    enableBuzzer(255);
    printLCD("Write Failed!");
    return false;
  }
  return true;
}
寫入的部分與讀取大同小異,只不過多了等待10秒的輸入畫面在Serial Monitor!

最後在進行完讀取或寫入的操作之後,除了呼叫PICC_HaltA()外,若需要再次重複偵測到這個標籤(拿開再靠近),還要呼叫PCD_StopCrypto1(),如此才可以重新感應到這個標籤!
"Remember to call PCD_StopCrypto1() after communicating with the authenticated PICC - otherwise no new communications can start."
void loop() {
   //...
   lcd.clear();
   lcd.noBacklight();
   
   mfrc522.PICC_HaltA(); //stop reading
   mfrc522.PCD_StopCrypto1();

   //filter serial available data, avoid input option to get
   while (Serial.available() > 0) {
    Serial.println("filter...");
    Serial.println(Serial.read());
   }
   delay(3000);
}

最後DEMO影片如下:

參考資料
趙英傑著 超圖解 arduino 互動設計入門第3版第十七章節

留言

  1. 嗨您好 方便私訊嗎? 想了解程式內容 感恩!

    回覆刪除
    回覆
    1. 你直接將私訊的方式寄到我的信箱?

      刪除

張貼留言