أساسيات لغة C++ للمتحكمات (3): الدوال (Functions) وكتابة كود نظيف


ماذا سنتعلم اليوم؟ سنتعمق في الدوال (Functions) في C++ للمتحكمات الدقيقة، وكيفية استخدامها لكتابة كود منظم، قابل لإعادة الاستخدام، وسهل الصيانة. سنركز على مبادئ الكود النظيف لبرمجة المتحكمات.

1. مفهوم الدالة الأساسي واستدعائها

الدوال هي كتل من الكود تؤدي مهمة محددة. إنها تساعد على تقسيم البرنامج إلى أجزاء أصغر وأكثر قابلية للإدارة. لنبدأ بدالة بسيطة تجعل مؤشر LED يومض.

ملاحظة تقنية: استخدام الدوال يقلل من تكرار الكود (DRY - Don't Repeat Yourself) ويحسن من وضوح البرنامج.

// تعريف دالة لجعل مؤشر LED يومض
void blinkLED() {
  digitalWrite(LED_BUILTIN, HIGH); // تشغيل مؤشر LED المدمج
  delay(500);                     // الانتظار لمدة 500 ملي ثانية
  digitalWrite(LED_BUILTIN, LOW);  // إيقاف تشغيل مؤشر LED المدمج
  delay(500);                     // الانتظار لمدة 500 ملي ثانية
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT); // تهيئة مؤشر LED المدمج كخرج
}

void loop() {
  blinkLED(); // استدعاء الدالة لجعل مؤشر LED يومض
}

في هذا الجزء، قمنا بتعريف دالة blinkLED() لا تأخذ أي معاملات ولا تعيد أي قيمة (لذلك نوع الإرجاع void). تقوم هذه الدالة بتشغيل وإيقاف مؤشر LED المدمج. ثم قمنا باستدعائها بشكل متكرر داخل دالة loop().

2. الدوال ذات المعاملات والقيم المعادة

يمكن للدوال أن تأخذ "معاملات" (parameters) لجعلها أكثر مرونة، ويمكنها أيضاً "إعادة" (return) قيمة بعد إكمال مهمتها. لنعدّل دالة الوميض لتأخذ مدة التأخير كمعامل، وننشئ دالة أخرى لحساب متوسط قراءات مستشعر.

ملاحظة تقنية: المعاملات تسمح للدوال بالعمل على بيانات مختلفة دون الحاجة لإعادة كتابة الدالة لكل حالة.

// تعريف دالة لجعل مؤشر LED يومض بمدة تأخير محددة
void blinkLEDWithDelay(int delayTime) {
  digitalWrite(LED_BUILTIN, HIGH); // تشغيل مؤشر LED المدمج
  delay(delayTime);                // الانتظار لمدة delayTime
  digitalWrite(LED_BUILTIN, LOW);  // إيقاف تشغيل مؤشر LED المدمج
  delay(delayTime);                // الانتظار لمدة delayTime
}

// تعريف دالة لحساب متوسط 3 قراءات لمستشعر
float calculateSensorAverage(int sensorPin) {
  int sum = 0; // متغير لتخزين مجموع القراءات
  for (int i = 0; i < 3; i++) {
    sum += analogRead(sensorPin); // قراءة المستشعر وإضافتها للمجموع
    delay(10);                    // تأخير قصير بين القراءات
  }
  return (float)sum / 3.0; // إعادة المتوسط كقيمة عشرية
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT); // تهيئة مؤشر LED المدمج كخرج
  Serial.begin(9600);           // تهيئة الاتصال التسلسلي
}

void loop() {
  blinkLEDWithDelay(200); // وميض سريع (200 ملي ثانية)
  blinkLEDWithDelay(1000); // وميض بطيء (1000 ملي ثانية)

  float avg = calculateSensorAverage(A0); // حساب متوسط قراءة المستشعر على المنفذ A0
  Serial.print("Sensor Average: ");
  Serial.println(avg); // طباعة المتوسط على الشاشة التسلسلية
  delay(5000); // تأخير قبل قراءة جديدة
}

هنا، أصبحت دالة blinkLEDWithDelay() أكثر مرونة بقبول معامل delayTime. كما قدمنا دالة calculateSensorAverage() التي تقرأ مستشعرًا على منفذ معين (A0 هنا) ثلاث مرات وتحسب متوسطها ثم تعيد القيمة كـ float. لاحظ استخدام Serial.begin() و Serial.println() لطباعة النتائج.

3. تنظيم الكود باستخدام النماذج الأولية للدوال (Function Prototypes)

عندما تقوم بتعريف دالة بعد دالة loop()، ستحتاج إلى "نموذج أولي" (prototype) لها قبل دالة setup() حتى يعرف المترجم بوجودها. هذا يساعد في تنظيم الكود، خاصة في المشاريع الكبيرة.

ملاحظة تقنية: النموذج الأولي للدالة هو إعلان عن توقيع الدالة (اسمها، نوع الإرجاع، أنواع المعاملات) دون تفاصيل تنفيذها.

// النماذج الأولية للدوال (Function Prototypes)
// هذا يسمح لنا بتعريف الدوال بعد دالة loop()
void blinkLEDWithDelay(int delayTime);
float calculateSensorAverage(int sensorPin);
void printWelcomeMessage(); // دالة جديدة لطباعة رسالة ترحيب

void setup() {
  pinMode(LED_BUILTIN, OUTPUT); // تهيئة مؤشر LED المدمج كخرج
  Serial.begin(9600);           // تهيئة الاتصال التسلسلي
  printWelcomeMessage();        // استدعاء دالة رسالة الترحيب مرة واحدة عند بدء التشغيل
}

void loop() {
  blinkLEDWithDelay(300); // وميض متوسط
  delay(1000); // تأخير بين الأنماط

  float avg = calculateSensorAverage(A0); // حساب متوسط قراءة المستشعر
  Serial.print("Current Sensor Average: ");
  Serial.println(avg);
  delay(2000);
}

// تعريف الدوال بعد دالة loop() لتحسين القراءة
void blinkLEDWithDelay(int delayTime) {
  digitalWrite(LED_BUILTIN, HIGH);
  delay(delayTime);
  digitalWrite(LED_BUILTIN, LOW);
  delay(delayTime);
}

float calculateSensorAverage(int sensorPin) {
  int sum = 0;
  for (int i = 0; i < 5; i++) { // زيادة عدد القراءات للمتوسط
    sum += analogRead(sensorPin);
    delay(5);
  }
  return (float)sum / 5.0;
}

void printWelcomeMessage() {
  Serial.println("--- Welcome to C++ Functions Lesson ---");
  Serial.println("Monitoring LED and Sensor A0...");
}

في هذا المثال، قمنا بالإعلان عن النماذج الأولية للدوال blinkLEDWithDelay، calculateSensorAverage، و printWelcomeMessage قبل دالة setup(). هذا يسمح لنا بتعريف التنفيذ الفعلي لهذه الدوال في أي مكان آخر في الملف (عادةً بعد دالة loop()) مما يجعل دالتي setup() و loop() أقصر وأسهل في القراءة، ويعزز مبدأ الكود النظيف.

الكود النهائي الكامل

هذا هو الكود المجمع الذي يوضح جميع المفاهيم التي تعلمناها اليوم. يمكنك نسخ هذا الكود ولصقه في بيئة تطوير الأردوينو (Arduino IDE) لتجربته.

// أساسيات لغة C++ للمتحكمات (3): الدوال (Functions) وكتابة كود نظيف

// النماذج الأولية للدوال (Function Prototypes)
// الإعلان عن الدوال قبل دالتي setup() و loop()
void blinkLEDWithDelay(int delayTime);
float calculateSensorAverage(int sensorPin);
void printWelcomeMessage();

void setup() {
  // تهيئة المنفذ المدمج للمتحكم كمخرج (LED_BUILTIN هو رقم المنفذ الخاص بـ LED المدمج)
  pinMode(LED_BUILTIN, OUTPUT);
  // بدء الاتصال التسلسلي بسرعة 9600 بت في الثانية
  Serial.begin(9600);
  // استدعاء دالة طباعة رسالة الترحيب مرة واحدة عند بدء التشغيل
  printWelcomeMessage();
}

void loop() {
  // استدعاء دالة وميض LED بتأخير 300 ملي ثانية
  blinkLEDWithDelay(300);
  // تأخير لمدة ثانية واحدة قبل العملية التالية
  delay(1000);

  // حساب متوسط قراءة المستشعر المتصل بالمنفذ التناظري A0
  float currentAvg = calculateSensorAverage(A0);
  // طباعة رسالة توضيحية على الشاشة التسلسلية
  Serial.print("Current Sensor Average from A0: ");
  // طباعة قيمة المتوسط المحسوب
  Serial.println(currentAvg);
  // تأخير لمدة ثانيتين قبل تكرار حلقة loop
  delay(2000);
}

/**
 * @brief تجعل مؤشر LED المدمج يومض بمدة تأخير محددة.
 * @param delayTime المدة بالملي ثانية التي سيبقى فيها LED مضاءً ثم مطفأً.
 */
void blinkLEDWithDelay(int delayTime) {
  digitalWrite(LED_BUILTIN, HIGH); // تشغيل مؤشر LED
  delay(delayTime);                // الانتظار للمدة المحددة
  digitalWrite(LED_BUILTIN, LOW);  // إيقاف تشغيل مؤشر LED
  delay(delayTime);                // الانتظار للمدة المحددة مرة أخرى
}

/**
 * @brief تحسب متوسط عدة قراءات من منفذ مستشعر تناظري.
 * @param sensorPin رقم المنفذ التناظري الذي يتصل به المستشعر (مثلاً A0).
 * @return متوسط القراءات كقيمة عشرية (float).
 */
float calculateSensorAverage(int sensorPin) {
  int sum = 0; // متغير لتخزين مجموع القراءات
  const int numReadings = 5; // عدد القراءات التي سيتم أخذها للمتوسط
  for (int i = 0; i < numReadings; i++) {
    sum += analogRead(sensorPin); // قراءة المستشعر وإضافتها للمجموع
    delay(5);                     // تأخير قصير لتحسين استقرار القراءات
  }
  return (float)sum / numReadings; // إعادة المتوسط كقيمة عشرية
}

/**
 * @brief تطبع رسالة ترحيب ومعلومات بدء التشغيل على الشاشة التسلسلية.
 */
void printWelcomeMessage() {
  Serial.println("\n--- Welcome to C++ Functions Lesson for Microcontrollers ---");
  Serial.println("This sketch demonstrates LED blinking and sensor reading using functions.");
  Serial.println("Monitoring LED_BUILTIN and Analog Sensor on A0...");
  Serial.println("-----------------------------------------------------------\n");
}

النتيجة المتوقعة

عند تحميل هذا الكود على لوحة أردوينو، ستلاحظ ما يلي:

  1. سيومض مؤشر LED المدمج على اللوحة بشكل متكرر، حيث يظل مضاءً لمدة 300 ملي ثانية ثم مطفأً لمدة 300 ملي ثانية، مع فترة توقف ثانية واحدة بين كل دورة وميض.
  2. ستطبع لوحة الأردوينو رسالة ترحيب أولية على الشاشة التسلسلية (Serial Monitor) عند بدء التشغيل.
  3. بعد رسالة الترحيب، ستقوم اللوحة بشكل دوري (كل 3 ثوانٍ تقريباً) بطباعة متوسط قراءات المستشعر المتصل بالمنفذ A0 على الشاشة التسلسلية. إذا لم يكن هناك مستشعر متصل، فستظهر قيم عشوائية أو صفرية.