الرئيسية قاعدة المعرفة الكهرباء والإلكترون GPIO والإشارات الرقمية: التحكم بالعالم الخارجي
الكهرباء والإلكترون

GPIO والإشارات الرقمية: التحكم بالعالم الخارجي

ما هو GPIO؟ أطراف الأغراض العامة

GPIO (General Purpose Input/Output) هي الأطراف التي تربط المتحكم الدقيق بالعالم الخارجي. كل طرف يمكن ضبطه كمدخل لقراءة حالة حساس أو زر، أو كمخرج لتشغيل LED أو مرحل (Relay) يتحكم بمعدات صناعية.

في المتحكمات الصناعية مثل STM32، تُنظَّم الأطراف في مجموعات (Ports): GPIOA، GPIOB، وهكذا. كل مجموعة تحتوي حتى 16 طرفاً. قبل استخدام أي طرف، يجب تفعيل ساعة المجموعة التابع لها.

مستويات الجهد

  • مستوى منخفض (0): 0V إلى 0.8V
  • مستوى مرتفع (1): 2.0V إلى 3.3V
  • منطقة غير محددة: 0.8V إلى 2.0V (يجب تجنبها)

تعمل معظم المتحكمات الحديثة بجهد 3.3V. عند التعامل مع أجهزة 5V صناعية، تحتاج محوّل مستوى (Level Shifter).

إعداد طرف كمخرج: تشغيل LED ومرحل

عند ضبط طرف كمخرج، يمكنك التحكم بجهده: مرتفع (3.3V) أو منخفض (0V). هذا يكفي لتشغيل LED مباشرة أو قيادة ترانزستور يشغّل مرحلاً.

// إعداد طرف PA5 كمخرج وتشغيل مرحل
#include "stm32f4xx_hal.h"

#define RELAY_PIN    GPIO_PIN_5
#define RELAY_PORT   GPIOA

void relay_init(void) {
    __HAL_RCC_GPIOA_CLK_ENABLE();
    GPIO_InitTypeDef gpio = {0};
    gpio.Pin = RELAY_PIN;
    gpio.Mode = GPIO_MODE_OUTPUT_PP;  // مخرج Push-Pull
    gpio.Pull = GPIO_NOPULL;
    gpio.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(RELAY_PORT, &gpio);
}

void relay_on(void) {
    HAL_GPIO_WritePin(RELAY_PORT, RELAY_PIN, GPIO_PIN_SET);
}

void relay_off(void) {
    HAL_GPIO_WritePin(RELAY_PORT, RELAY_PIN, GPIO_PIN_RESET);
}

أنماط المخرج

  • Push-Pull: يقود المخرج بفاعلية نحو HIGH أو LOW. الأكثر استخداماً.
  • Open-Drain: يسحب نحو LOW فقط. يحتاج مقاومة رافعة خارجية. يُستخدم مع I2C وبعض الدوائر الصناعية.

إعداد طرف كمدخل: قراءة زر وحساس

عند ضبط طرف كمدخل، يقرأ المتحكم حالة الجهد عليه: مرتفع أو منخفض. هذا يُستخدم لقراءة أزرار التشغيل، حساسات القرب، ونهايات المشوار (Limit Switches).

// قراءة حساس قرب صناعي على PB0
#define SENSOR_PIN    GPIO_PIN_0
#define SENSOR_PORT   GPIOB

void sensor_init(void) {
    __HAL_RCC_GPIOB_CLK_ENABLE();
    GPIO_InitTypeDef gpio = {0};
    gpio.Pin = SENSOR_PIN;
    gpio.Mode = GPIO_MODE_INPUT;
    gpio.Pull = GPIO_PULLUP;  // مقاومة رافعة داخلية
    HAL_GPIO_Init(SENSOR_PORT, &gpio);
}

uint8_t is_object_detected(void) {
    // الحساس يعطي LOW عند اكتشاف جسم (NPN)
    return (HAL_GPIO_ReadPin(SENSOR_PORT, SENSOR_PIN) == GPIO_PIN_RESET);
}

المقاومات الرافعة والساحبة

عندما لا يكون هناك شيء متصل بطرف المدخل، يكون في حالة "عائمة" (Floating) ويقرأ قيماً عشوائية. المقاومات الرافعة (Pull-up) والساحبة (Pull-down) تحل هذه المشكلة.

المقاومة الرافعة (Pull-up)

تربط الطرف بـ VCC عبر مقاومة (عادة 10KΩ). الحالة الافتراضية HIGH. عند الضغط على الزر، يصبح LOW. هذا هو النمط الشائع في الأزرار الصناعية.

المقاومة الساحبة (Pull-down)

تربط الطرف بـ GND عبر مقاومة. الحالة الافتراضية LOW. عند الضغط، يصبح HIGH.

معظم المتحكمات توفر مقاومات داخلية يمكن تفعيلها برمجياً (كما في المثال السابق: GPIO_PULLUP)، لكن في البيئات الصناعية يُفضَّل استخدام مقاومات خارجية لمتانة أعلى.

مشكلة الارتداد Debouncing

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

حل برمجي بسيط

#define DEBOUNCE_MS 50

uint8_t read_button_debounced(GPIO_TypeDef *port, uint16_t pin) {
    if (HAL_GPIO_ReadPin(port, pin) == GPIO_PIN_RESET) {
        HAL_Delay(DEBOUNCE_MS);
        if (HAL_GPIO_ReadPin(port, pin) == GPIO_PIN_RESET) {
            return 1;  // ضغطة مؤكدة
        }
    }
    return 0;
}

حل بآلة حالة (أفضل للتطبيقات الصناعية)

typedef struct {
    uint8_t  state;
    uint8_t  last_raw;
    uint32_t last_change_tick;
} Debouncer;

uint8_t debounce_update(Debouncer *d, uint8_t raw, uint32_t now_ms) {
    if (raw != d->last_raw) {
        d->last_change_tick = now_ms;
        d->last_raw = raw;
    }
    if ((now_ms - d->last_change_tick) >= DEBOUNCE_MS) {
        d->state = d->last_raw;
    }
    return d->state;
}

مثال عملي: تشغيل مروحة تبريد بحساس حرارة ثنائي

في هذا المثال، نستخدم حساس حرارة ثنائي الحالة (Thermal Switch) يعطي إشارة رقمية عند تجاوز درجة محددة، ونشغّل مروحة تبريد عبر مرحل.

#include "stm32f4xx_hal.h"

#define TEMP_ALERT_PIN   GPIO_PIN_0   // PB0 - حساس الحرارة
#define TEMP_ALERT_PORT  GPIOB
#define FAN_RELAY_PIN    GPIO_PIN_5   // PA5 - مرحل المروحة
#define FAN_RELAY_PORT   GPIOA
#define LED_STATUS_PIN   GPIO_PIN_13  // PC13 - LED الحالة
#define LED_STATUS_PORT  GPIOC

void system_init(void) {
    HAL_Init();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
    __HAL_RCC_GPIOC_CLK_ENABLE();

    GPIO_InitTypeDef gpio_out = {
        .Mode = GPIO_MODE_OUTPUT_PP,
        .Pull = GPIO_NOPULL,
        .Speed = GPIO_SPEED_FREQ_LOW
    };
    gpio_out.Pin = FAN_RELAY_PIN;
    HAL_GPIO_Init(FAN_RELAY_PORT, &gpio_out);
    gpio_out.Pin = LED_STATUS_PIN;
    HAL_GPIO_Init(LED_STATUS_PORT, &gpio_out);

    GPIO_InitTypeDef gpio_in = {
        .Pin = TEMP_ALERT_PIN,
        .Mode = GPIO_MODE_INPUT,
        .Pull = GPIO_PULLUP
    };
    HAL_GPIO_Init(TEMP_ALERT_PORT, &gpio_in);
}

int main(void) {
    system_init();
    Debouncer temp_db = {0};

    while (1) {
        uint8_t raw = HAL_GPIO_ReadPin(TEMP_ALERT_PORT, TEMP_ALERT_PIN);
        uint8_t overtemp = debounce_update(&temp_db, !raw, HAL_GetTick());

        if (overtemp) {
            HAL_GPIO_WritePin(FAN_RELAY_PORT, FAN_RELAY_PIN, GPIO_PIN_SET);
            HAL_GPIO_WritePin(LED_STATUS_PORT, LED_STATUS_PIN, GPIO_PIN_SET);
        } else {
            HAL_GPIO_WritePin(FAN_RELAY_PORT, FAN_RELAY_PIN, GPIO_PIN_RESET);
            HAL_GPIO_WritePin(LED_STATUS_PORT, LED_STATUS_PIN, GPIO_PIN_RESET);
        }
        HAL_Delay(10);
    }
}

الخلاصة

أطراف GPIO هي الواجهة الأساسية بين المتحكم والعالم المادي. التعامل الصحيح مع المدخلات والمخرجات الرقمية، مع مراعاة مشاكل الارتداد ومستويات الجهد، هو أساس كل مشروع مدمج صناعي. في الدرس القادم سننتقل إلى عالم الإشارات التناظرية وقراءة المستشعرات عبر ADC.

GPIO digital-IO LED relay pull-up pull-down المداخل الرقمية المخارج الرقمية المرحل المقاومة الرافعة التحكم الأطراف