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變數如下:
  1. #include <SPI.h>
  2. #include <MFRC522.h>
  3. #include <LiquidCrystal_I2C.h>
  4. #define LED_PIN 7
  5. #define BUZZER_PIN 8
  6. #define RST_PIN 9 // Configurable, see typical pin layout above
  7. #define SS_PIN 10 // Configurable, see typical pin layout above
  8. MFRC522 mfrc522(SS_PIN, RST_PIN); // Create MFRC522 instance
  9. MFRC522::MIFARE_Key key;
  10. const byte existUID[] = {0x12,0x67,0x96,0xBB};
  11. const byte blockIndex = 4;
  12. const byte len = 18;
  13. byte blockInfo[len]; //1 block: 16 bytes + external 2 bytes
  14. // Set the LCD address to 0x27 for a 16 chars and 2 line display
  15. LiquidCrystal_I2C lcd(0x27, 16, 2);

3.1、初始化
  1. void setup() {
  2. // put your setup code here, to run once:
  3. Serial.begin(9600); // Initialize serial communications with the PC
  4. SPI.begin(); // Init SPI bus
  5. mfrc522.PCD_Init(); // Init MFRC522 card
  6. // Prepare key - all keys are set to FFFFFFFFFFFFh at chip delivery from the factory.
  7. for (byte i = 0; i < 6; i++) key.keyByte[i] = 0xFF;
  8. randomSeed(analogRead(0)); //set random seed
  9. lcd.begin();
  10. lcd.noBacklight();
  11. pinMode(BUZZER_PIN, OUTPUT);
  12. pinMode(LED_PIN, OUTPUT);
  13. }
電子標籤要讀取或寫入都需要先經過金鑰驗證,在此金鑰預設值皆為0xFF

3.2、標籤偵測
  1. void loop() {
  2. // Look for new cards
  3. if ( ! mfrc522.PICC_IsNewCardPresent()) {
  4. return;
  5. }
  6. // Select one of the cards
  7. if ( ! mfrc522.PICC_ReadCardSerial()) {
  8. return;
  9. }
  10. lcd.backlight();
  11. printLCD("**Card Detected:**");
  12. //...
  13. }
  14. void printLCD(String msg){
  15. printLCD(msg, 1000);
  16. }
  17. void printLCD(String msg, int milis){
  18. lcd.clear();
  19. int msgLen = msg.length();
  20. int lcdLineLimit = 16;
  21. lcd.setCursor(0, 0);
  22. if(msgLen > lcdLineLimit){
  23. lcd.print(msg.substring(0, lcdLineLimit));
  24. lcd.setCursor(0, 1);
  25. lcd.print(msg.substring(lcdLineLimit, lcdLineLimit * 2));
  26. }else{
  27. lcd.print(msg);
  28. }
  29. delay(milis);
  30. }
打開MFRC522提供的example都會有前面這兩個判斷,一個是針對是否有新的標籤,另一個則是若同時有多個標籤時一次只會read一個! 當這兩個判斷式通過後LCD就會亮起來! 而printLCD函式預設在印出字串時維持1秒鐘的時間,若是指定的message長度超過16會自動排到第二行去!
PS. LCD最多只有兩行列印空間

3.3、金鑰、UID驗證
  1. void loop() {
  2. //...
  3. if(keyVerify(blockIndex, key, mfrc522.uid)) {
  4. if(uidVerify()){
  5. Serial.setTimeout(5000L) ; // wait until 5 seconds for input from serial
  6. //Enter option and input by Serial Console
  7. printLCD("Select Option: (1)Read(2)Write");
  8. char option = getOptionInput();
  9. if(option == '1')
  10. readCard();
  11. else if(option == '2')
  12. writeCard();
  13. else
  14. printLCD("No option!!");
  15. }
  16. }
  17. //...
  18. }
  19. char getOptionInput(){
  20. byte buffer[len];
  21. Serial.readBytes(buffer, len);
  22. char inputOption = char(buffer[0]);
  23. printLCD("You input option:"+(String(inputOption)));
  24. return inputOption;
  25. }
  26. bool keyVerify(byte blockAddr, MFRC522::MIFARE_Key key, MFRC522::Uid uid){
  27. MFRC522::StatusCode status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, blockAddr, &key, &(uid)); //line 834 of MFRC522.cpp file
  28. if(status == MFRC522::STATUS_OK){
  29. printLCD("Auth. Success!");
  30. }else{
  31. digitalWrite(BUZZER_PIN, HIGH);
  32. enableBuzzer(255);
  33. printLCD("Auth. Failed!");
  34. return false;
  35. }
  36. return true;
  37. }
  38. bool uidVerify(){
  39. byte *currUID = mfrc522.uid.uidByte;
  40. byte idSize = mfrc522.uid.size;
  41. if(memcmp(existUID, currUID, idSize) == 0){
  42. enableBuzzer(50);
  43. return true;
  44. }else{
  45. enableBuzzer(255);
  46. printLCD("Failed!!Card does not exist!!", 2000);
  47. return false;
  48. }
  49. }
  50. void enableBuzzer(int count){
  51. unsigned char i = 0;
  52. for(i = 0 ; i < count ; i++){
  53. digitalWrite(BUZZER_PIN, HIGH);
  54. delay(1);
  55. digitalWrite(BUZZER_PIN, LOW);
  56. delay(1);
  57. }
  58. }
標籤被偵測到後,隨即進行金鑰驗證呼叫keyVerify(...),傳入的參數分別為哪個block、key值、還有這個標籤的UID,若驗證成功才可以對這個block的區塊進行資料讀取寫入的操作!
而uidVerify這個函式是檢查標籤的uid是否有match變數existUID內紀錄的值,若有的話才可以進行讀取或寫入,算是多一層過濾的動作!
通過兩道驗證之後,接著LCD會印出選單供您進行操作,這邊需搭配Arduino IDE內的Serial Monitor進行input動作,在此設定輸入的timeout時間為5秒!
PS. 若有小鍵盤就不用如此麻煩

3.4、Read data from tag
  1. void readCard(){
  2. digitalWrite(LED_PIN, HIGH);
  3. if(readData(blockIndex, blockInfo, len)) {
  4. int i = 0;
  5. String str;
  6. for(i = 0 ; i < len - 2 ; i++){
  7. str += char(blockInfo[i]);
  8. }
  9. printLCD("Welcome!!"+str, 3000);
  10. }
  11. digitalWrite(LED_PIN, LOW);
  12. }
  13. bool readData(byte blockAddr, byte bufferArr[], byte len){
  14. MFRC522::StatusCode status = mfrc522.MIFARE_Read(blockAddr, bufferArr, &len);
  15. if(status != MFRC522::STATUS_OK){
  16. enableBuzzer(255);
  17. printLCD("Read Failed!");
  18. return false;
  19. }
  20. return true;
  21. }
在此需指定如前面驗證的block編號及buffer array(長度為18)進行讀取動作,接著再把read出來的資料轉成字串印在LCD上面!

3.5、Write data to tag
  1. void writeCard(){
  2. digitalWrite(LED_PIN, HIGH);
  3. Serial.setTimeout(10000L) ; // wait until 10 seconds for input from serial
  4. printLCD("Type name, ending with # ");
  5. byte currLen = Serial.readBytesUntil('#', (char *) blockInfo, len - 2) ; // read name from serial
  6. //filter '\n' character
  7. if(currLen > 0 && blockInfo[currLen - 1] == '\n'){
  8. currLen--;
  9. }
  10. for (byte i = currLen; i < len - 2; i++) blockInfo[i] = ' '; // pad with spaces
  11. if(currLen == 0){
  12. printLCD("Wait Timeout...");
  13. digitalWrite(LED_PIN, LOW);
  14. return;
  15. }
  16. delay(1000);
  17. if(writeData(blockIndex, blockInfo, len - 2)) {
  18. String strData;
  19. for (byte c = 0 ; c < len - 2 ; c++)
  20. strData += char(blockInfo[c]);
  21. printLCD("Write Success:"+strData, 3000);
  22. }
  23. digitalWrite(LED_PIN, LOW);
  24. }
  25. bool writeData(byte blockAddr, byte bufferArr[], byte len){
  26. MFRC522::StatusCode status = mfrc522.MIFARE_Write(blockAddr, bufferArr, len);
  27. if(status != MFRC522::STATUS_OK){
  28. enableBuzzer(255);
  29. printLCD("Write Failed!");
  30. return false;
  31. }
  32. return true;
  33. }
寫入的部分與讀取大同小異,只不過多了等待10秒的輸入畫面在Serial Monitor!

最後在進行完讀取或寫入的操作之後,除了呼叫PICC_HaltA()外,若需要再次重複偵測到這個標籤(拿開再靠近),還要呼叫PCD_StopCrypto1(),如此才可以重新感應到這個標籤!
"Remember to call PCD_StopCrypto1() after communicating with the authenticated PICC - otherwise no new communications can start."
  1. void loop() {
  2. //...
  3. lcd.clear();
  4. lcd.noBacklight();
  5. mfrc522.PICC_HaltA(); //stop reading
  6. mfrc522.PCD_StopCrypto1();
  7. //filter serial available data, avoid input option to get
  8. while (Serial.available() > 0) {
  9. Serial.println("filter...");
  10. Serial.println(Serial.read());
  11. }
  12. delay(3000);
  13. }

最後DEMO影片如下:

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

留言

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

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

      刪除

張貼留言