الرئيسية قاعدة المعرفة البرمجة والمنطق البرمجة المدمجة: برمجة المتحكمات الدقيقة
البرمجة والمنطق

البرمجة المدمجة: برمجة المتحكمات الدقيقة

ما هي البرمجة المدمجة؟

في كل مصنع حولك، هناك عشرات المتحكمات الصغيرة التي لا تراها: متحكم يراقب حرارة الفرن، آخر يتحكم بسرعة المحرك، ثالث يقرأ مستوى السائل في الخزان. هذه كلها أنظمة مدمجة (Embedded Systems) — حواسيب صغيرة مخصصة لمهمة واحدة محددة.

البرمجة المدمجة هي كتابة البرنامج (Firmware) الذي يعمل مباشرة على هذه الرقاقات — بدون نظام تشغيل كامل، بدون شاشة، بدون لوحة مفاتيح. أنت تتحدث مباشرة مع العتاد: منافذ، مسجلات، مقاطعات.

المتحكمات الدقيقة: الأنواع والاختيار

Arduino (ATmega328P)

المنصة الأسهل للمبتدئين. المتحكم ATmega328P يعمل بتردد 16 MHz، ذاكرة 32 KB، وله 14 منفذ رقمي و6 منافذ تمثيلية.

نقاط القوة: سهل التعلم، مكتبات ضخمة، مجتمع كبير نقاط الضعف: بطيء، ذاكرة محدودة، لا يناسب المهام المعقدة

متى تستخدمه: نماذج أولية سريعة، قراءة حساسات بسيطة، مشاريع تعليمية

ARM Cortex-M (STM32, ESP32)

للمشاريع الصناعية الجدية. STM32F4 مثلاً يعمل بتردد 168 MHz، ذاكرة 1 MB، ويدعم DMA ومقاطعات متعددة المستويات.

نقاط القوة: سريع، منافذ كثيرة، يدعم RTOS نقاط الضعف: أعقد في البرمجة، يحتاج فهم أعمق للعتاد

متى تستخدمه: تحكم بمحركات، أنظمة مراقبة صناعية، IoT متقدم

ESP32

مع WiFi وBluetooth مدمجين، مناسب لمشاريع IoT. ثنائي النواة بتردد 240 MHz.

متى تستخدمه: ربط الحساسات بالسحابة، مراقبة عن بعد، لوحات تحكم لاسلكية

GPIO: بوابتك للعالم المادي

GPIO (General Purpose Input/Output) هي المنافذ التي تربط المتحكم بالعالم الخارجي. كل منفذ يمكن أن يكون:

  • إدخال (Input): قراءة حالة زر، حساس، مفتاح نهاية حد
  • إخراج (Output): تشغيل LED، مرحّل (Relay)، صمام

مثال: قراءة حساس حرارة والتحكم بمروحة

// Arduino: قراءة حساس حرارة NTC والتحكم بمروحة تبريد
const int TEMP_SENSOR = A0;    // حساس حرارة على المنفذ التمثيلي 0
const int FAN_RELAY = 7;       // مرحّل المروحة على المنفذ الرقمي 7
const float TEMP_THRESHOLD = 60.0;  // درجة الحرارة الحدّية

void setup() {
  pinMode(FAN_RELAY, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  int raw = analogRead(TEMP_SENSOR);  // قراءة 0-1023
  float voltage = raw * (5.0 / 1023.0);
  float temperature = (voltage - 0.5) * 100.0;  // تحويل لدرجة مئوية

  Serial.print("Temperature: ");
  Serial.print(temperature);
  Serial.println(" C");

  if (temperature > TEMP_THRESHOLD) {
    digitalWrite(FAN_RELAY, HIGH);   // شغّل المروحة
    Serial.println("Fan ON - Cooling active");
  } else {
    digitalWrite(FAN_RELAY, LOW);    // أطفئ المروحة
  }

  delay(1000);  // قراءة كل ثانية
}

المقاطعات: الاستجابة الفورية

المشكلة مع delay() و loop() هي أنك قد تفوّت حدثاً مهماً أثناء الانتظار. ماذا لو ضُغط زر الطوارئ بينما المتحكم في منتصف delay(1000)؟

المقاطعات (Interrupts) تحل هذه المشكلة: عندما يحدث حدث على منفذ معين، يتوقف البرنامج الرئيسي فوراً ويُنفَّذ كود المقاطعة، ثم يعود.

// Arduino: زر طوارئ بمقاطعة
const int EMERGENCY_BTN = 2;    // المنفذ 2 يدعم مقاطعات خارجية
const int MOTOR_RELAY = 8;
volatile bool emergencyStop = false;

void setup() {
  pinMode(EMERGENCY_BTN, INPUT_PULLUP);
  pinMode(MOTOR_RELAY, OUTPUT);
  // ربط المقاطعة: عند الضغط (هبوط الإشارة) نفّذ الدالة
  attachInterrupt(
    digitalPinToInterrupt(EMERGENCY_BTN),
    onEmergencyPress,
    FALLING
  );
}

// دالة المقاطعة — يجب أن تكون قصيرة وسريعة
void onEmergencyPress() {
  emergencyStop = true;
}

void loop() {
  if (emergencyStop) {
    digitalWrite(MOTOR_RELAY, LOW);  // أوقف المحرك فوراً
    Serial.println("EMERGENCY STOP ACTIVATED");
    while (true) {
      // ابقَ متوقفاً حتى إعادة تشغيل يدوية
    }
  }
  // العمل الطبيعي للمحرك
  digitalWrite(MOTOR_RELAY, HIGH);
}

قواعد مهمة للمقاطعات:

  • اجعل دالة المقاطعة (ISR) قصيرة قدر الإمكان
  • استخدم volatile للمتغيرات المشتركة بين ISR والبرنامج الرئيسي
  • لا تستخدم delay() أو Serial.print() داخل ISR

PWM: التحكم بالسرعة والشدة

PWM (Pulse Width Modulation) يُمكّنك من التحكم بسرعة محرك أو شدة إضاءة LED بتغيير نسبة زمن التشغيل للإشارة. بدلاً من تشغيل/إطفاء بسيط، تتحكم بالنسبة المئوية.

Duty Cycle 25%:  ████____________████____________
Duty Cycle 50%:  ████████________████████________
Duty Cycle 75%:  ████████████____████████████____
Duty Cycle 100%: ████████████████████████████████
// التحكم بسرعة محرك DC عبر PWM
const int MOTOR_PWM = 9;          // منفذ يدعم PWM
const int SPEED_POT = A0;         // مقاومة متغيرة للتحكم بالسرعة

void setup() {
  pinMode(MOTOR_PWM, OUTPUT);
}

void loop() {
  int potValue = analogRead(SPEED_POT);    // 0-1023
  int motorSpeed = map(potValue, 0, 1023, 0, 255);  // تحويل لـ 0-255
  analogWrite(MOTOR_PWM, motorSpeed);      // كتابة PWM
  delay(50);
}

الاتصالات التسلسلية: UART و SPI و I2C

المتحكم يحتاج للتواصل مع الحساسات والشاشات والمتحكمات الأخرى. ثلاثة بروتوكولات أساسية:

UART (Serial)

اتصال نقطة-لنقطة بسيط بسلكين (TX و RX). يُستخدم للتواصل مع الحاسوب أو وحدات GPS/GSM.

// إرسال قراءات للحاسوب عبر UART
Serial.begin(115200);
Serial.println("Temp: 67.5C | Pressure: 4.8 bar");

I2C

ناقل بسلكين (SDA و SCL) يربط حتى 127 جهازاً على نفس الناقل. كل جهاز له عنوان فريد. مثالي لحساسات الحرارة والرطوبة والشاشات الصغيرة.

#include <Wire.h>
// قراءة حساس حرارة I2C (عنوان 0x48)
Wire.begin();
Wire.requestFrom(0x48, 2);
int msb = Wire.read();
int lsb = Wire.read();
float temp = ((msb << 8) | lsb) / 256.0;

SPI

أسرع بكثير من I2C لكن يحتاج أسلاك أكثر (MOSI, MISO, SCK, CS). يُستخدم لشاشات TFT، بطاقات SD، محولات ADC سريعة.

البروتوكول الأسلاك السرعة الأجهزة الاستخدام
UART 2 بطيء 1:1 تواصل مع الحاسوب
I2C 2 متوسط حتى 127 حساسات، شاشات OLED
SPI 4+ سريع محدود شاشات TFT، بطاقات SD

Firmware: البرنامج الثابت

الفرق بين كود عادي وFirmware:

  • لا يوجد نظام تشغيل يحميك — إذا تعطل كودك، المتحكم يتجمد
  • الموارد محدودة — كل بايت من الذاكرة مهم
  • الزمن الحقيقي مطلوب — تأخر ميلي ثانية قد يعني خطراً
  • لا إعادة تشغيل سهلة — الجهاز في الحقل، بعيد عنك

Watchdog Timer: شبكة الأمان

#include <avr/wdt.h>

void setup() {
  wdt_enable(WDTO_2S);  // إذا لم يُعاد ضبط المؤقت خلال 2 ثانية
                          // يُعاد تشغيل المتحكم تلقائياً
}

void loop() {
  wdt_reset();           // أنا حي — أعد ضبط المؤقت
  readSensors();
  processData();
  updateOutputs();
  // إذا تعلّق البرنامج هنا ولم يصل لـ wdt_reset()
  // سيُعاد تشغيل المتحكم بعد ثانيتين
}

مثال تكاملي: وحدة مراقبة صناعية

لنبني وحدة تقرأ 3 حساسات وترسل البيانات عبر Serial وتُشغّل إنذاراً:

// وحدة مراقبة صناعية مصغرة
#include <Wire.h>

struct SensorData {
  float temperature;
  float pressure;
  float vibration;
  bool alarm;
};

const float TEMP_MAX = 85.0;
const float VIB_MAX = 10.0;
const int ALARM_PIN = 13;
const int BUZZER_PIN = 6;

SensorData data;

void setup() {
  Serial.begin(115200);
  Wire.begin();
  pinMode(ALARM_PIN, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);
  Serial.println("Industrial Monitor v1.0 Ready");
}

void loop() {
  data.temperature = readTemperature();
  data.pressure = readPressure();
  data.vibration = readVibration();

  data.alarm = (data.temperature > TEMP_MAX) ||
               (data.vibration > VIB_MAX);

  if (data.alarm) {
    digitalWrite(ALARM_PIN, HIGH);
    tone(BUZZER_PIN, 2000, 500);
  } else {
    digitalWrite(ALARM_PIN, LOW);
  }

  // إرسال البيانات كـ JSON عبر Serial
  Serial.print("{\"temp\":");
  Serial.print(data.temperature, 1);
  Serial.print(",\"pres\":");
  Serial.print(data.pressure, 1);
  Serial.print(",\"vib\":");
  Serial.print(data.vibration, 1);
  Serial.print(",\"alarm\":");
  Serial.print(data.alarm ? "true" : "false");
  Serial.println("}");

  delay(500);
}

خلاصة

المفهوم الوظيفة
GPIO قراءة حساسات والتحكم بمخرجات
المقاطعات استجابة فورية للأحداث الحرجة
PWM تحكم تدريجي بالسرعة والشدة
UART/I2C/SPI تواصل مع الأجهزة الخارجية
Watchdog حماية من تجمّد البرنامج
Firmware برنامج يعمل مباشرة على العتاد

البرمجة المدمجة هي أقرب ما يكون المهندس فيه للعتاد. كل سطر كود يتحول مباشرة إلى إشارة كهربائية تُحرّك محركاً أو تقرأ حساساً. هذا هو الجسر بين عالم البرمجيات وعالم الآلات.

embedded microcontroller Arduino ARM firmware GPIO البرمجة المدمجة المتحكم الدقيق اردوينو البرنامج الثابت المنافذ الرقمية المقاطعات