أساسيات لغة C++ للمتحكمات (1): المتغيرات وأنواع البيانات الإلكترونية


أساسيات لغة C++ للمتحكمات (1): المتغيرات وأنواع البيانات الإلكترونية

ماذا سنتعلم؟

في هذا الدرس، سنتعمق في أساسيات المتغيرات وأنواع البيانات في لغة C++، وكيفية استخدامها بفعالية في برمجة المتحكمات الدقيقة مثل الأردوينو، مع التركيز على الكفاءة والاستخدام الأمثل للموارد.

1. المتغيرات: المخازن الرقمية الصغيرة

المتغيرات هي أسماء تُعطى لمواقع في ذاكرة المتحكم لتخزين البيانات. تخيلها كصناديق مرقمة، كل صندوق له اسم ونوع محدد لما يمكن أن يحتويه. في المتحكمات، نحتاج أن نكون دقيقين جداً في اختيار نوع وحجم هذه الصناديق للحفاظ على موارد الذاكرة المحدودة.

مثال: تعريف متغير بسيط

// تعريف متغير من نوع عدد صحيح (integer) لتخزين قراءة من حساس
int sensorValue = 0; // 'int' هو نوع البيانات، 'sensorValue' هو اسم المتغير، '0' هي القيمة الأولية
ملاحظة تقنية: تهيئة المتغيرات بقيمة أولية (Initialization) هي ممارسة جيدة جداً في برمجة المتحكمات لتجنب القيم العشوائية غير المتوقعة التي قد تؤدي إلى سلوك غير مستقر للمتحكم.

2. أنواع البيانات الإلكترونية: اختيار الحجم المناسب

تختلف أنواع البيانات في C++ بناءً على نوع وحجم القيمة التي ستخزنها. اختيار النوع المناسب يوفر الذاكرة ويزيد من كفاءة البرنامج. إليك بعض الأنواع الشائعة في بيئة المتحكمات:

  • int: عدد صحيح (عادةً 2 بايت أو 4 بايت حسب المعالج)، يستخدم لتخزين أرقام مثل أرقام المنافذ، عدادات بسيطة، أو قراءات حساسات ضمن نطاق معين.
  • long: عدد صحيح طويل (4 بايت)، يستخدم لتخزين قيم أكبر مثل أوقات التشغيل بالمللي ثانية (millis()).
  • float: عدد عشري (4 بايت)، يستخدم لتخزين أرقام بفاصلة عشرية مثل قراءات الجهد أو درجة الحرارة الدقيقة.
  • bool: قيمة منطقية (1 بايت)، لتخزين قيمتين فقط: true (صحيح) أو false (خطأ)، مثالية لحالة تشغيل/إيقاف.
  • char: حرف واحد (1 بايت)، لتخزين حرف ASCII.

مثال: استخدام أنواع بيانات مختلفة

// أمثلة على تعريف أنواع بيانات مختلفة
int temperature = 25;       // درجة الحرارة كعدد صحيح
long uptimeMillis = 123456789L; // وقت التشغيل بالمللي ثانية (اللاحقة 'L' تشير إلى long)
float voltage = 3.3;        // قيمة الجهد كعدد عشري
bool ledState = true;      // حالة الـ LED (true للتشغيل، false للإيقاف)
char commandChar = 'R';     // حرف يمثل أمر (مثل 'R' للقراءة)

// استخدام الكلمة المفتاحية 'const' لتعريف قيم ثابتة لا تتغير أثناء تشغيل البرنامج
const int ledPin = 13;      // رقم منفذ الـ LED هو 13، ولا يمكن تغييره لاحقاً
const int buttonPin = 2;    // رقم منفذ الزر هو 2، ولا يمكن تغييره لاحقاً
ملاحظة تقنية: استخدام الكلمة المفتاحية const للمتغيرات التي لن تتغير قيمتها (مثل أرقام المنافذ) يساعد المترجم على تحسين الكود ويوفر الذاكرة، لأنه يعلم أن هذه القيمة ثابتة ولا تحتاج إلى مكان في ذاكرة الوصول العشوائي (RAM) يمكن تعديله.

3. استخدام المتغيرات مع المدخلات والمخرجات الرقمية (GPIO)

تتفاعل المتغيرات بشكل مباشر مع الأجهزة الخارجية عبر منافذ الإدخال/الإخراج الرقمية (GPIO). يمكننا استخدام المتغيرات لتحديد أرقام المنافذ، تخزين حالات الأزرار، أو التحكم في حالة مصابيح الـ LED.

مثال: التحكم بـ LED باستخدام متغير وحالة زر

// تعريف أرقام المنافذ كمتغيرات ثابتة لسهولة التعديل والقراءة
const int ledPin = 13;   // المنفذ المتصل بـ LED
const int buttonPin = 2; // المنفذ المتصل بالزر

// متغير لتخزين حالة الزر
int buttonState = 0;     // 0 أو LOW يعني الزر غير مضغوط، 1 أو HIGH يعني الزر مضغوط

void setup() {
  // تهيئة المنافذ
  pinMode(ledPin, OUTPUT);   // تعريف منفذ الـ LED كمخرج
  pinMode(buttonPin, INPUT_PULLUP); // تعريف منفذ الزر كمدخل مع تفعيل مقاومة السحب الداخلية
                                  // (هذا يعني أن المنفذ سيكون HIGH افتراضياً، و LOW عند الضغط على الزر المتصل بالأرضي)
}

void loop() {
  // قراءة حالة الزر وتخزينها في المتغير 'buttonState'
  buttonState = digitalRead(buttonPin);

  // التحقق من حالة الزر وتحديث الـ LED بناءً عليها
  if (buttonState == LOW) { // إذا كان الزر مضغوطاً (LOW بسبب INPUT_PULLUP)
    digitalWrite(ledPin, HIGH); // تشغيل الـ LED
  } else {
    digitalWrite(ledPin, LOW);  // إيقاف الـ LED
  }
}

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

هذا الكود يجمع كل المفاهيم التي تعلمناها، ويستخدم المتغيرات وأنواع البيانات للتحكم بـ LED عبر زر وطباعة معلومات على الشاشة التسلسلية.

// أساسيات لغة C++ للمتحكمات (1): المتغيرات وأنواع البيانات الإلكترونية

// 1. المتغيرات: المخازن الرقمية الصغيرة
// تعريف متغير لتخزين قيمة من حساس، مثل حساس حرارة أو ضوء
int sensorValue = 0; // متغير من نوع عدد صحيح لتخزين قراءة الحساس، تم تهيئته بقيمة 0

// 2. أنواع البيانات الإلكترونية: اختيار الحجم المناسب
// أمثلة على أنواع بيانات مختلفة شائعة في المتحكمات
int temperature = 25;       // متغير من نوع عدد صحيح لتخزين درجة الحرارة
long uptimeMillis = 0;      // متغير من نوع عدد صحيح طويل لتخزين وقت تشغيل المتحكم بالمللي ثانية
float voltage = 0.0;        // متغير من نوع عدد عشري لتخزين قيمة الجهد
bool ledState = false;      // متغير منطقي (صحيح/خطأ) لتخزين حالة الـ LED (مطفأ في البداية)
char commandChar = 'N';     // متغير حرفي لتخزين حرف يمثل أمر معين

// استخدام "const" لتعريف قيم ثابتة لا تتغير
const int ledPin = 13;      // تعريف رقم منفذ الـ LED كمتغير ثابت، لا يمكن تغيير قيمته لاحقاً
const int buttonPin = 2;    // تعريف رقم منفذ الزر كمتغير ثابت

void setup() {
  Serial.begin(9600); // بدء الاتصال التسلسلي بسرعة 9600 بت في الثانية لمراقبة المخرجات
  Serial.println("بدء تشغيل المتحكم...");

  // تهيئة المنافذ الرقمية (GPIO)
  pinMode(ledPin, OUTPUT); // تهيئة منفذ الـ LED كمخرج
  pinMode(buttonPin, INPUT_PULLUP); // تهيئة منفذ الزر كمدخل مع مقاومة سحب داخلية
                                    // (الزر متصل بالأرضي، يكون HIGH عند عدم الضغط و LOW عند الضغط)

  // عرض القيم الأولية للمتغيرات
  Serial.print("القيمة الأولية لـ sensorValue: ");
  Serial.println(sensorValue);
  Serial.print("القيمة الأولية لـ temperature: ");
  Serial.println(temperature);
  Serial.print("القيمة الأولية لـ voltage: ");
  Serial.println(voltage);
  Serial.print("القيمة الأولية لـ ledState: ");
  Serial.println(ledState ? "HIGH" : "LOW"); // تحويل القيمة المنطقية إلى نص
  Serial.print("القيمة الأولية لـ commandChar: ");
  Serial.println(commandChar);
}

void loop() {
  // تحديث قيمة المتغير uptimeMillis في كل تكرار
  uptimeMillis = millis(); // دالة millis() تعيد عدد المللي ثواني منذ بدء تشغيل المتحكم

  // قراءة حالة الزر وتخزينها في متغير مؤقت
  // بما أننا استخدمنا INPUT_PULLUP، الزر يكون LOW عند الضغط عليه
  int currentButtonState = digitalRead(buttonPin);

  // تحديث حالة الـ LED بناءً على الزر
  if (currentButtonState == LOW) { // إذا كان الزر مضغوطاً (LOW بسبب INPUT_PULLUP)
    ledState = true; // تشغيل الـ LED
  } else {
    ledState = false; // إيقاف الـ LED
  }

  digitalWrite(ledPin, ledState ? HIGH : LOW); // تطبيق حالة الـ LED على المنفذ

  // طباعة القيم المتغيرة على الشاشة التسلسلية كل ثانية
  if (millis() % 1000 < 10) { // طباعة كل 1000 مللي ثانية تقريباً
    Serial.print("وقت التشغيل (مللي ثانية): ");
    Serial.println(uptimeMillis);
    Serial.print("حالة الـ LED: ");
    Serial.println(ledState ? "مضاء" : "مطفأ");
    Serial.print("حالة الزر (منفذ ");
    Serial.print(buttonPin);
    Serial.print("): ");
    Serial.println(currentButtonState == LOW ? "مضغوط" : "غير مضغوط");
  }

  delay(10); // تأخير بسيط لتجنب القراءات المتكررة جداً ولتوفير طاقة المعالج
}

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

عند تحميل الكود على متحكم الأردوينو (أو أي متحكم متوافق):

  1. ستقوم الشاشة التسلسلية (Serial Monitor) في بيئة الأردوينو IDE بطباعة رسالة "بدء تشغيل المتحكم..."، ثم تعرض القيم الأولية للمتغيرات sensorValue، temperature، voltage، ledState، و commandChar.
  2. بعد ذلك، ستبدأ الشاشة التسلسلية في طباعة وقت تشغيل المتحكم بالمللي ثانية (uptimeMillis)، وحالة الـ LED، وحالة الزر كل ثانية تقريباً.
  3. الـ LED المتصل بالمنفذ 13 سيُضيء عندما يتم الضغط على الزر المتصل بالمنفذ 2 (مع توصيل الزر بالأرضي). وعند تحرير الزر، سينطفئ الـ LED.