مشروعات تطبيقية · الدرس 4.7
تسجيل الحضور بالبطاقات RFID
العودة إلى الدروسفكرة المشروع والتوصيل
يعمق مشروع تسجيل الحضور فهم LCD1602 وقارئ RC522 والطنان، ويجمعها في تطبيق عملي.
المكونات المطلوبة
- لوحة أردوينو (Arduino Uno) وقارئ البطاقات (RC522 RF Card Module).
- طنان غير نشط (Passive Buzzer) ولوحة الحساسات (ALL IN ONE Sensor Shield).
- شاشة العرض (LCD1602) ووحدة ساعة (Clock Module).

إدخال الموظفين وفتح الاتصال
افتح ملف البرنامج Clock_Cards.ino في بيئة Arduino IDE، وأدخل أسماء الموظفين وأرقامهم التعريفية. وصّل لوحة أردوينو (UNO R3 Board) بالحاسوب بكابل توصيل (USB Cable)، ثم اضغط Upload (رفع) لنقل البرنامج إلى اللوحة وتشغيله عليها. لا تفصل USB بعد الرفع. افتح البرنامج المساعد المرفق واضغط 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
ملفات الدرس والمكتبات
المكتبات المرفقة
- libraries/DS3231.zip
- libraries/LiquidCrystal.zip
- libraries/pitches.zip
- libraries/rfid.zip
- libraries/SPI.zip
- libraries/Wire.zip