مشروعات تطبيقية · الدرس 4.7

تسجيل الحضور بالبطاقات RFID

العودة إلى الدروس

فكرة المشروع والتوصيل

يعمق مشروع تسجيل الحضور فهم LCD1602 وقارئ RC522 والطنان، ويجمعها في تطبيق عملي.

المكونات المطلوبة

توصيل قارئ البطاقات (RC522 RF Card Module) والشاشة والساعة والطنان
توصيل قارئ البطاقات (RC522 RF Card Module) والشاشة والساعة والطنان

إدخال الموظفين وفتح الاتصال

افتح ملف البرنامج Clock_Cards.ino في بيئة Arduino IDE، وأدخل أسماء الموظفين وأرقامهم التعريفية. وصّل لوحة أردوينو (UNO R3 Board) بالحاسوب بكابل توصيل (USB Cable)، ثم اضغط Upload (رفع) لنقل البرنامج إلى اللوحة وتشغيله عليها. لا تفصل USB بعد الرفع. افتح البرنامج المساعد المرفق واضغط Open Serial.

واجهة البرنامج المساعد وزر Open Serial
واجهة البرنامج المساعد وزر Open Serial

تسجيل البطاقات وقراءة الحضور

اضغط زر الواجهة الأولية لدخول وضع الإدخال. مرر البطاقات لتسجيل معلومات الموظفين بالتتابع، وانتظر نحو ثانيتين عند التسجيل؛ تؤكد رسالة الشاشة نجاحه.

بعد الانتهاء اضغط الزر مجددًا للخروج. تمرير بطاقة مسجلة يعرض اسم الموظف ورقمه ووقت المرور في البرنامج المساعد.

بيانات مرور البطاقات في البرنامج
بيانات مرور البطاقات في البرنامج

حفظ النتائج

اضغط Save File لحفظ المعلومات المعروضة بصيغة CSV.

نافذة حفظ البيانات
نافذة حفظ البيانات
جدول بيانات الحضور المحفوظ
جدول بيانات الحضور المحفوظ

مخطط التنفيذ والمكتبات

يبدأ المخطط بواجهة الإقلاع. يبدل الزر بين وضع الإدخال ووضع العرض. في الإدخال تسجل معلومات الموظف وتفحص وجود رقم البطاقة ثم تدخل البيانات؛ وفي العرض تفحص البطاقة وتعرض بياناتها.

مخطط تنفيذ تسجيل الحضور
مخطط تنفيذ تسجيل الحضور

الخطوة الأولى

أضف المكتبات والكائنات المطلوبة في Time_clocks.h:‏ Wire و DS3231 و LiquidCrystal و SPI و MFRC522 و pitches.h. يعرف RST_PIN = A1 و SS_PIN = 10، والكائنين clock و dt، والشاشة LiquidCrystal lcd(A3, 2, 4, 7, 8, A2)، والقارئ MFRC522 mfrc522(SS_PIN, RST_PIN).

واجهة الإقلاع

الخطوة الثانية

في Clock_Cards يدرج Time_clocks.h و PinChangeInt.h. تهيئ setup() شاشة العرض (LCD1602)، والاتصال 9600، والساعة، وتضبط التاريخ والوقت من __DATE__ و __TIME__. تبدأ SPI وتستدعي mfrc522.PCD_Init(). يهيأ A0 بـ INPUT_PULLUP ويربط button_choose_interrupt بالحافة FALLING.

تستدعي loop() الدالة Start_interface(). إذا كان flag_start = 1، تعرض Starting up.. عند (3,0)، وتنتظر ثانيتين، ثم تصفر متغير الحالة وتمسح الشاشة.

الزر وإزالة الارتداد

تأكد من تثبيت مكتبة مقاطعات تغير المنفذ التي توفر PinChangeInt.h قبل رفع البرنامج.

الخطوة الثالثة

يعرف Key.h توقيت الزر الحالي والسابق. يتحقق ELIMINAT_THE_JITTER_OF_BUTTON_CHOOSE من أن الفارق المطلق أكبر من 200ms.

تقرأ button_choose_interrupt() الوقت من millis(). إذا اجتاز الاختبار، تحفظ التوقيت، وتبدل mode من DISPLAY_MODE إلى INPUT_MODE أو العكس، وتمسح الشاشة عند كل تغيير.

اختيار الوضع وحفظ الموظفين

الخطوات 4–7

تستخدم Mode_Choose(int mode) جملة switch للتنقل بين DISPLAY_MODE و INPUT_MODE.

تنشأ مصفوفتان name و number لحفظ أسماء الموظفين وأرقامهم. يعرض المثال xiaoou و xiaoou2 و xiaoou3 و xiaoou4 و elegoo3 و elegoo4، مع الأرقام 1–6.

في وضع الإدخال يستخدم عدد العناصر sizeof(name)/sizeof(String) لتنظيم التسجيل واستدعاء Data_Input(name[cnt], number[cnt]). يوضح الشرح إنشاء قائمة مترابطة بالحجم الموافق. تفحص Data_Input(String name, String number) وجود رقم البطاقة؛ تحفظ المعلومات في القائمة إذا كان موجودًا، وتتجاوز التسجيل إذا لم يوجد.

المصفوفات والقوائم المترابطة

المصفوفة سهلة الاستخدام وسريعة الاستعلام نسبيًا، وتشغل منطقة ذاكرة متصلة. لكن حجمها ثابت ولا تلائم الإضافة والتخزين الديناميكي بسهولة.

القائمة المترابطة تتيح الإضافة والحذف وحجمًا متغيرًا، لكن الوصول عبر المؤشرات بالتتابع يجعل الاستعلام أبطأ. يشرح الرسم عقدة بها حقل بيانات data ومؤشر next، وتشير first إلى العقدة الأولى، ويحفظ كل مؤشر عنوان العقدة التالية حتى NULL.

بنية العقد ومؤشرات القائمة المترابطة
بنية العقد ومؤشرات القائمة المترابطة

الخطوة الثامنة

تنفذ Data_Display() وضع العرض بمرور على القائمة لقراءة معلومات البطاقة حسب رقمها. استخدم MySerial.exe لعرض بيانات الحضور وحفظها على الحاسوب.

الكود

RFID Time Clock/Clock_Cards/Clock_Cards.ino

#include "Time_clocks.h"
#include "PinChangeInt.h"

String name[] = {"xiaoou", "xiaoou2", "xiaoou3", "xiaoou4", "elegoo3", "elegoo4"};
String number[] = {"1", "2", "3", "4", "5", "6"};

void setup() {
  lcd.begin(16, 2);
  Serial.begin(9600);
  clock.begin();
  clock.setDateTime(__DATE__, __TIME__);
  SPI.begin();         // Init SPI bus
  mfrc522.PCD_Init();  // Init MFRC522 card
  pinMode(A0, INPUT_PULLUP);
  attachPinChangeInterrupt(A0, button_choose_interrupt, FALLING );
}

void loop() {
  Start_interface();
  Mode_Choose(mode);
}

void Mode_Choose(int mode)
{
  switch (mode)
  {
    case DISPLAY_MODE:
      LCD_ShowDate(clock.getDateTime());
      Data_Display();
      delay(100);
      break;
    case INPUT_MODE:
      lcd.setCursor(3, 0);
      lcd.print("Input Mode");
      lcd.setCursor(3, 1);
      lcd.print("Swipe card.");
      if (cnt < (sizeof(name) / sizeof(String)))
      {
        Data_Input(name[cnt], number[cnt]);
      }
      delay(100); break;
    default: break;
  }
}

void Start_interface()
{
  if (flag_start == 1)
  {
    lcd.setCursor(3, 0);
    lcd.print("Starting up..");
    delay(2000);
    flag_start = 0;
    lcd.clear();
  }
}


void Data_Input(String name, String number)
{
  if (UID_Check())
  {
    Staff** tmp = &staff;

    if (*tmp == NULL)
    {
      *tmp = new Staff;
      head = *tmp;
    }
    else
    {
      if (UID_Compare(*tmp))
      { //Serial.print("ssssssssss:");
        return;
      }

      if ((*tmp)->next == NULL)
      {
        (*tmp)->next = new Staff;
        (*tmp) = (*tmp)->next;
      }
      else
      {
        while ((*tmp)->next != NULL)
        {
          (*tmp) = (*tmp)->next;
        }
        (*tmp)->next = new Staff;
        (*tmp) = (*tmp)->next;
      }
    }

    for (int i = 0; i < mfrc522.uid.size; i++)
    {
      (*tmp)->m_uidData[i] = mfrc522.uid.uidByte[i];


    }
    Serial.println();
    (*tmp)->m_name = name;
    (*tmp)->m_number = number;
    //Serial.print("staff:"); Serial.println(name);
    (*tmp)->next = NULL;

    lcd.clear();
    lcd.setCursor(3, 0);
    lcd.print((*tmp)->m_name);
    lcd.setCursor(3, 1);
    lcd.print("OK !!");
    delay(1000);
    lcd.clear();
    //    lcd.setCursor(3, 0);
    //    lcd.print("Input Mode");
    //    lcd.setCursor(3, 1);
    //    lcd.print("Swipe card.");
    tone(3, NOTE_F5, duration);
    cnt++;
  }
}

//Staff* Data_Modify(Staff* head, String name, String number)
//{
//  if (UID_Check())
//  {
//    Staff* tmp = head;
//    while (tmp != NULL)
//    {
//      if (UID_Compare(tmp))
//      {
//        tmp->m_name = name;
//        tmp->m_number = number;
//      }
//      tmp = tmp->next;
//    }
//    return head;
//  }
//}

void Data_Display()
{
  if (UID_Check())
  {
    Staff* tmp = head;

    while (tmp != NULL)
    {
      if (UID_Compare(tmp))
      {
        tone(3, NOTE_F5, duration);
        lcd.clear();
        lcd.setCursor(3, 0);
        lcd.print(tmp->m_name);
        lcd.setCursor(3, 1);
        lcd.print(tmp->m_number);
        dt = clock.getDateTime();
        Serial.print("$");
        Serial.print(tmp->m_number);
        Serial.print(",");
        Serial.print(tmp->m_name);
        Serial.print(",");
        Serial.print(dt.year);
        Serial.print(".");
        Serial.print(dt.month);
        Serial.print(".");
        Serial.print(dt.day);
        Serial.print(",");
        Serial.print(dt.hour);
        Serial.print(":");
        Serial.print(dt.minute);
        Serial.print(":");
        Serial.print(dt.second);
        Serial.print("%");
        delay(2000);
      }
      tmp = tmp->next;
    }
  }
}

bool UID_Compare(Staff* tmp)
{
  int sum = 0;
  for (int i = 0; i < mfrc522.uid.size; i++)
  {
    sum += tmp->m_uidData[i] - mfrc522.uid.uidByte[i];
  }
  if (0 == sum)
  {
    return true;
  }
  else
    return false;
}

void LCD_ShowDate(RTCDateTime dt)
{
  lcd.setCursor(3, 0);
  lcd.print(dt.year);   lcd.print(".");
  lcd.print(dt.month);  lcd.print(".");
  lcd.print(dt.day);

  lcd.setCursor(3, 1);
  lcd.print(dt.hour);   lcd.print(":");
  lcd.print(dt.minute); lcd.print(":");
  lcd.print(dt.second);
  if (dt.second == 0 || dt.second > 59)
  {
    lcd.setCursor(10, 1);
    lcd.print(" ");
  }
}

bool UID_Check()
{
  if ( ! mfrc522.PICC_IsNewCardPresent() || ! mfrc522.PICC_ReadCardSerial() ) {
    delay(50);
    return false;
  }

  return true;
}

RFID Time Clock/Clock_Cards/Key.h

#ifndef KEY_H
#define KEY_H

int time_button_choose=0;
int last_time_button_choose=0;

#define ELIMINAT_THE_JITTER_OF_BUTTON_CHOOSE (200 < (abs(time_button_choose - last_time_button_choose)))

#endif

RFID Time Clock/Clock_Cards/Key.ino

#include "Key.h"

void button_choose_interrupt ()
{
  time_button_choose = millis();

  if(ELIMINAT_THE_JITTER_OF_BUTTON_CHOOSE)
  {
    last_time_button_choose = time_button_choose;
    if(mode==DISPLAY_MODE)
    {
      mode=INPUT_MODE;
      lcd.clear();
     }
     else if(mode==INPUT_MODE)
     {
      mode=DISPLAY_MODE;
      lcd.clear();
      }
  }
}

RFID Time Clock/Clock_Cards/Time_clocks.h

#ifndef TIME_CLOCKS_H_
#define TIME_CLOCKS_H_

#include <Wire.h>
#include <DS3231.h>
#include <LiquidCrystal.h>
#include <SPI.h>
#include <MFRC522.h>
#include "pitches.h"

#define RST_PIN   A1    
#define SS_PIN    10

void LCD_ShowDate(RTCDateTime dt);

DS3231 clock;
RTCDateTime dt;
LiquidCrystal lcd(2,A3,8,7,A2,4);
MFRC522 mfrc522(SS_PIN, RST_PIN);

int cnt=0;
int flag_start=1;
int16_t duration = 500; 

enum Mode{
  DISPLAY_MODE,
  INPUT_MODE,
}mode=DISPLAY_MODE;


typedef struct Staff
{
  byte m_uidData[10];
  String m_name;
  String m_number; 
  Staff* next;
}; 
Staff* staff = NULL;
Staff* head = NULL;
#endif

ملفات الدرس والمكتبات

المكتبات المرفقة

ملفات الدرس

100%

عرض الصورة

استخدم + و− للتكبير والتصغير، أو اضغط Esc للإغلاق.