الرئيسية قاعدة المعرفة الكهرباء والإلكترون المقاطعات ونظام التشغيل الحقيقي RTOS: استجابة فورية للأحداث
الكهرباء والإلكترون

المقاطعات ونظام التشغيل الحقيقي RTOS: استجابة فورية للأحداث

ما هي المقاطعة ولماذا تحتاجها؟

المقاطعة (Interrupt) هي آلية تسمح للمتحكم بإيقاف البرنامج الحالي مؤقتاً للاستجابة لحدث طارئ، ثم العودة لإكمال ما كان يفعله. بدون المقاطعات، يجب على البرنامج فحص كل حدث باستمرار (Polling)، وهذا يضيّع وقت المعالج ويبطئ الاستجابة.

مثال من الواقع الصناعي

تخيل خط إنتاج يعمل فيه المتحكم على:

  • قراءة 8 مستشعرات كل 100 ميلي ثانية
  • التحكم بـ 3 محركات
  • إرسال بيانات عبر UART

إذا ضغط المشغّل زر التوقف الطارئ، لا يمكن الانتظار حتى تنتهي حلقة القراءة. المقاطعة تضمن استجابة فورية خلال ميكروثوانٍ.

مفهوم NVIC

في معالجات ARM Cortex-M، يدير NVIC (Nested Vectored Interrupt Controller) جميع المقاطعات. يدعم حتى 240 مصدر مقاطعة مع نظام أولويات متعدد المستويات. المقاطعة ذات الأولوية الأعلى (رقم أقل) يمكنها مقاطعة مقاطعة أخرى.

المقاطعات الخارجية: استجابة فورية للأحداث

المقاطعة الخارجية تُفعَّل عند تغيّر حالة طرف GPIO. يمكن ضبطها للاستجابة عند:

  • الحافة الصاعدة (Rising Edge): الانتقال من LOW إلى HIGH
  • الحافة الهابطة (Falling Edge): الانتقال من HIGH إلى LOW
  • كلا الحافتين (Both Edges): أي تغيير
// مقاطعة خارجية لزر التوقف الطارئ على PB0
void emergency_stop_init(void) {
    __HAL_RCC_GPIOB_CLK_ENABLE();

    GPIO_InitTypeDef gpio = {0};
    gpio.Pin = GPIO_PIN_0;
    gpio.Mode = GPIO_MODE_IT_FALLING;  // عند الضغط (حافة هابطة)
    gpio.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOB, &gpio);

    HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);  // أعلى أولوية
    HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}

volatile uint8_t emergency_flag = 0;

void EXTI0_IRQHandler(void) {
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
    if (GPIO_Pin == GPIO_PIN_0) {
        emergency_flag = 1;
        // إيقاف جميع المحركات فوراً
        HAL_GPIO_WritePin(MOTOR1_PORT, MOTOR1_PIN, GPIO_PIN_RESET);
        HAL_GPIO_WritePin(MOTOR2_PORT, MOTOR2_PIN, GPIO_PIN_RESET);
    }
}

مقاطعات المؤقت: مهام دورية بدقة

مقاطعة المؤقت تُنفَّذ بشكل دوري بدقة عالية. مثالية لحلقات التحكم PID وقراءة المستشعرات:

// قراءة مستشعرات كل 10 ميلي ثانية
TIM_HandleTypeDef htim6;

void sensor_timer_init(void) {
    __HAL_RCC_TIM6_CLK_ENABLE();
    htim6.Instance = TIM6;
    htim6.Init.Prescaler = (SystemCoreClock / 100000) - 1;
    htim6.Init.Period = 1000 - 1;  // 10 ms
    HAL_TIM_Base_Init(&htim6);

    HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 2, 0);
    HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);
    HAL_TIM_Base_Start_IT(&htim6);
}

volatile SensorReadings latest_readings;

void TIM6_DAC_IRQHandler(void) {
    HAL_TIM_IRQHandler(&htim6);
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM6) {
        latest_readings = read_all_sensors();
    }
}

مشاكل المقاطعات: التسابق والـ Volatile

كلمة volatile المفتاحية

أي متغير يُشارَك بين المقاطعة والبرنامج الرئيسي يجب أن يُعلَن volatile. بدونها، قد يُخبّئ المترجم القيمة في سجل ولا يرى التحديثات:

// خطأ شائع: بدون volatile
uint8_t flag = 0;  // المترجم قد يحسّن الحلقة ويزيل الفحص

// الصواب
volatile uint8_t flag = 0;  // يُقرأ من الذاكرة كل مرة

التسابق (Race Condition)

عند الوصول لبيانات مشتركة أكبر من كلمة واحدة (32-bit)، قد تحدث المقاطعة أثناء القراءة/الكتابة. الحل: تعطيل المقاطعات مؤقتاً:

// قراءة آمنة لمتغير 64-bit مشترك
uint64_t safe_read_counter(void) {
    __disable_irq();
    uint64_t val = shared_counter;
    __enable_irq();
    return val;
}

قواعد ذهبية لدوال المقاطعة

  • اجعلها قصيرة قدر الإمكان
  • لا تستخدم HAL_Delay() أو أي انتظار
  • لا تستدعِ printf() أو دوال ثقيلة
  • ارفع علماً (flag) فقط واعالجه في الحلقة الرئيسية

FreeRTOS: نظام تشغيل حقيقي مصغّر

عندما يصبح النظام معقداً (عدة مهام بأولويات مختلفة)، تصبح المقاطعات وحدها غير كافية. FreeRTOS هو نظام تشغيل حقيقي مفتوح المصدر يعمل على معظم المتحكمات ويوفر:

  • تعدد المهام (Multitasking): تشغيل عدة مهام "بالتوازي"
  • الأولويات: المهام المهمة تعمل أولاً
  • التزامن: صفوف ومتغيرات إشارة لتبادل البيانات بأمان
  • التوقيت: تأخيرات دقيقة بدون حجب المهام الأخرى
#include "FreeRTOS.h"
#include "task.h"

void sensor_task(void *params) {
    (void)params;
    while (1) {
        read_all_sensors();
        vTaskDelay(pdMS_TO_TICKS(100));  // كل 100 ms
    }
}

void motor_task(void *params) {
    (void)params;
    while (1) {
        update_motor_control();
        vTaskDelay(pdMS_TO_TICKS(10));  // كل 10 ms
    }
}

المهام والأولويات والصفوف في FreeRTOS

إنشاء المهام

int main(void) {
    HAL_Init();
    SystemClock_Config();

    xTaskCreate(sensor_task,  "Sensors", 256, NULL, 2, NULL);
    xTaskCreate(motor_task,   "Motors",  256, NULL, 3, NULL);
    xTaskCreate(comms_task,   "Comms",   512, NULL, 1, NULL);

    vTaskStartScheduler();  // لا يعود أبداً
    while (1);
}

الأولوية الأعلى (رقم أكبر في FreeRTOS) تعمل أولاً. motor_task بأولوية 3 ستقاطع sensor_task بأولوية 2.

الصفوف (Queues): تبادل بيانات آمن

#include "queue.h"

QueueHandle_t sensor_queue;

void sensor_task(void *params) {
    sensor_queue = xQueueCreate(10, sizeof(SensorReadings));
    while (1) {
        SensorReadings data = read_all_sensors();
        xQueueSend(sensor_queue, &data, portMAX_DELAY);
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

void comms_task(void *params) {
    SensorReadings data;
    while (1) {
        if (xQueueReceive(sensor_queue, &data, portMAX_DELAY) == pdTRUE) {
            send_uart_data(&data);
        }
    }
}

مثال عملي: نظام متعدد المهام يقرأ مستشعرات ويتحكم بمحركات

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"

QueueHandle_t      cmd_queue;
SemaphoreHandle_t  uart_mutex;
volatile uint8_t   emergency = 0;

void sensor_task(void *p) {
    (void)p;
    TickType_t last_wake = xTaskGetTickCount();
    while (1) {
        SensorReadings s = read_all_sensors();
        if (s.motor_temp_c > 80.0f) {
            MotorCmd cmd = {.id = 0, .speed = 0};
            xQueueSend(cmd_queue, &cmd, 0);
        }
        xSemaphoreTake(uart_mutex, portMAX_DELAY);
        printf("T=%.1f I=%.2f\r\n", s.motor_temp_c, s.motor_current_a);
        xSemaphoreGive(uart_mutex);
        vTaskDelayUntil(&last_wake, pdMS_TO_TICKS(100));
    }
}

typedef struct { uint8_t id; int8_t speed; } MotorCmd;

void motor_task(void *p) {
    (void)p;
    cmd_queue = xQueueCreate(10, sizeof(MotorCmd));
    MotorCmd cmd;
    while (1) {
        if (emergency) {
            motor_set_speed(0);
        } else if (xQueueReceive(cmd_queue, &cmd, pdMS_TO_TICKS(10))) {
            motor_set_speed(cmd.speed);
        }
    }
}

int main(void) {
    HAL_Init();
    SystemClock_Config();
    peripherals_init();
    uart_mutex = xSemaphoreCreateMutex();

    xTaskCreate(sensor_task, "Sens", 512, NULL, 2, NULL);
    xTaskCreate(motor_task,  "Moto", 256, NULL, 3, NULL);
    vTaskStartScheduler();
    while (1);
}

الخلاصة

المقاطعات توفر استجابة فورية للأحداث الحرجة، و FreeRTOS ينظّم المهام المتعددة بأمان وكفاءة. الجمع بينهما يُنتج أنظمة صناعية متينة وموثوقة. في الدرس القادم سنتعلم ESP32 واتصال WiFi و MQTT لربط أنظمتنا المدمجة بالسحابة.

interrupts RTOS FreeRTOS real-time ISR multitasking المقاطعات نظام التشغيل الحقيقي الاستجابة الفورية المهام المتعددة الأولويات التزامن