الحسّاسات والمشغّلات للمتحكّمات · الفصل 3 — المداخل الرقمية: توصيل حسّاس رقمي بالمتحكّمמבואות ספרתיים: חיבור חיישן ספרתי לבקר · 33.102⌂📚🏠
الفصل 3

المداخل الرقمية: توصيل حسّاس رقمي بالمتحكّم

מבואות ספרתיים: חיבור חיישן ספרתי לבקר
الصفّان الحادي عشر والثاني عشر · 4 ساعات تعليمية

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

في نهاية هذا الفصل سنعرف أن:
  • نحدّد مستويات الجهد التي يقرؤها Arduino Uno و ESP32 كـ HIGH وكـ LOW، والمنطقة غير المعرّفة بينهما.
  • نشرح مشكلة المدخل العائم ونحلّها بمقاومة رفع أو خفض، خارجية أو داخلية، ونحسب تيارها.
  • نعدّ الضغطات بكشف الحافة، ونزيل ارتداد التلامس برمجيا بالدالة millis().
  • نصمّم مُجزِّء جهد لقراءة حسّاس يعمل بـ 5 V بمتحكّم يعمل بـ 3.3 V.
  • نعزل إشارة صناعية 24 V بمقرن ضوئي ونحسب مقاومتي الدائرة.
  • نعدّ نبضات سريعة بمقاطعة خارجية attachInterrupt().

רמות לוגיות במבוא ספרתי – المستويات المنطقية في المدخل الرقمي (Logic Levels)

المدخل الرقمي (מבוא ספרתי) لا يقيس الجهد مثل الفولتمتر. هو يقارن جهد الطرف بحدّين، ويعيد إلى البرنامج قيمة واحدة من اثنتين: HIGH (1) أو LOW (0).

المستوى المنطقي (רמה לוגית): هو مجال الجهد الذي يقرؤه المدخل الرقمي كقيمة منطقية واحدة. عتبة المستوى العالي VIH هي أصغر جهد يُضمن أن يُقرأ 1، وعتبة المستوى المنخفض VIL هي أكبر جهد يُضمن أن يُقرأ 0. الجهد بين العتبتين يقع في المنطقة غير المعرّفة (תחום לא מוגדר): قد يُقرأ 0 وقد يُقرأ 1.

في المتحكّم ATmega328P الذي في لوحة Arduino Uno تُعطى العتبتان في ورقة البيانات ككسر من جهد التغذية VCC، وفي ESP32 ككسر من VDD:

عتبات المدخل الرقمي في ATmega328P (حسب ورقة البيانات) · ספי מבוא ספרתי
VIH = 0.6·VCC
VIL = 0.3·VCC
الرمزالمعنىوحدة القياس
VIHأصغر جهد يُقرأ بالتأكيد HIGH[V]
VILأكبر جهد يُقرأ بالتأكيد LOW[V]
VCCجهد تغذية المتحكّم، 5 V في Uno[V]

وفي ESP32 (حسب ورقة البيانات): VIH = 0.75·VDD و VIL = 0.25·VDD ، حيث VDD = 3.3 V.

LOW (0)غير معرّفةHIGH (1)0 V1.5 V3 V5 VArduino Uno · 5 VLOW (0)غير معرّفةHIGH (1)ممنوع!0 V0.825 V2.475 V3.3 V5 VESP32 · 3.3 V
مستويات الجهد في المدخل الرقمي: Arduino Uno عند 5 V و ESP32 عند 3.3 V (قيم حسب ورقة البيانات). الجهد فوق 3.3 V ممنوع على طرف ESP32
مثال 3.1
نحسب عتبتي المدخل في Arduino Uno، ونحدّد كيف يقرأ الطرف كل جهد من الجهود: 0.8 V ، 2.2 V ، 4.1 V.
الحل:
VIH = 0.6·VCC = 0.6·5 = 3 V
VIL = 0.3·VCC = 0.3·5 = 1.5 V

0.8 V < 1.5 V ، لذلك يُقرأ LOW. الجهد 4.1 V > 3 V ، لذلك يُقرأ HIGH.

الجهد 2.2 V يقع بين 1.5 V و 3 V: في المنطقة غير المعرّفة. القراءة غير مضمونة، وقد تتغيّر من مرة إلى أخرى.

VIH = 3 V , VIL = 1.5 V
سؤال 3.1: حسّاس يعطي في مخرجه 2.6 V. ماذا يقرأ Arduino Uno؟ وماذا يقرأ ESP32؟ (نحسب عتبتي ESP32 أولا.)
3.3 V مقابل 5 V: أطراف ESP32 تعمل بـ 3.3 V ولا تتحمّل 5 V. وصل مخرج حسّاس يعطي 5 V مباشرة إلى طرف ESP32 قد يُتلف الطرف أو المتحكّم كله. نخفض الجهد أولا بمُجزِّء جهد، كما سنرى في هذا الفصل.
ملاحظة: وماذا عن الراسبيري باي؟ أطرافه تعمل أيضا بـ 3.3 V ولا تتحمّل 5 V ، فكلّ ما نقوله هنا عن ESP32 — العتبتان ومُجزِّء الجهد — يصحّ عليه كما هو. والفرق في البرمجة فقط: في الراسبيري باي نقرأ الطرف ببايثون (مكتبة gpiozero) لا بلغة أردوينو.

מבוא צף ונגדי משיכה – المدخل العائم ومقاومات الرفع والخفض (Floating Input, Pull-up, Pull-down)

المدخل العائم (מבוא צף): هو طرف مدخل غير موصول بجهد محدّد: لا بـ 5 V ولا بـ GND. مقاومة المدخل كبيرة جدا، لذلك يلتقط الطرف العائم الضجيج الكهربائي من الأسلاك ومن اليد القريبة، ويتنقّل بين 0 و 1 عشوائيا.

ننظر إلى زرّ موصول بين 5 V والطرف D7 فقط (الرسم الأيمن). عند الضغط يصل الطرف إلى 5 V ويُقرأ 1. لكن عند ترك الزر لا يوصل الطرف بأي شيء، فهو عائم، والقراءة لا معنى لها. الحل: مقاومة تشدّ الطرف إلى مستوى ثابت عندما يكون الزر متروكا.

Arduino Uno5VD7GNDS?الطرف عائم
زرّ بدون مقاومة: عند الترك يبقى الطرف D7 عائما
Arduino Uno5VD7GNDSRPD10 kΩ
مقاومة خفض 10 kΩ: عند الترك يُقرأ 0، وعند الضغط 1
مقاومة الخفض (נגד משיכה למטה): (pull-down) مقاومة بين طرف المدخل و GND. عندما يكون الزر مفتوحا تشدّ الطرف إلى 0 V؛ وعند الضغط يصل الطرف إلى 5 V، ويمر في المقاومة تيار صغير I = VCCR.

في البرنامج نعرّف الطرف كمدخل عادي pinMode(7, INPUT)، فيعيد digitalRead(7) القيمة 1 عند الضغط و 0 عند الترك.

مثال 3.2
نحسب التيار والقدرة في مقاومة الخفض 10 kΩ عند الضغط على الزر، ونقارن بمقاومة 220 Ω. ثم نتأكد أن تيار التسرّب في المدخل (حتى 1 µA حسب ورقة البيانات) لا يرفع جهد الطرف المتروك فوق VIL.
الحل:
I = VCCR = 510000 = 0.5 mA
P = VCC·I = 5·0.5·10−3 = 2.5 mW

مع 220 Ω يصبح التيار 22.7 mA : هدر بلا فائدة، فالمدخل لا يحتاج إلى تيار تقريبا. وعند ترك الزر يمر في المقاومة تيار التسرّب فقط:

V = Ileak·R = 1·10−6 · 10000 = 10 mV

وهذا أصغر بكثير من VIL = 1.5 V ، فيُقرأ الطرف 0 بأمان.

I = 0.5 mA , P = 2.5 mW

مقاومة الرفع والمنطق المعكوس – נגד משיכה למעלה ולוגיקה הפוכה:

مقاومة الرفع (נגד משיכה למעלה): (pull-up) مقاومة بين طرف المدخل و VCC، والزر بين الطرف و GND. في الراحة تشدّ المقاومة الطرف إلى 5 V فيُقرأ 1، وعند الضغط يوصل الزر الطرف بـ GND فيُقرأ 0.

هذا هو المنطق المعكوس (לוגיקה הפוכה): الضغط يعطي 0 والترك يعطي 1. التوصيل بمقاومة رفع هو الأكثر شيوعا، لأن الزر يوصل بـ GND فقط، ولا نمدّ سلك 5 V إلى لوحة الأزرار. وفي ATmega328P توجد مقاومة رفع داخلية على كل طرف، نشغّلها بالأمر pinMode(pin, INPUT_PULLUP)، فلا نحتاج إلى مقاومة خارجية أصلا. قيمتها بين 20 kΩ و 50 kΩ (قيمة نموذجية حسب ورقة البيانات)، أي أنها ليست قيمة دقيقة.

Arduino Uno5VD2GNDRPU10 kΩS
مقاومة رفع خارجية 10 kΩ على الطرف D2
Arduino UnoD2GND5V20–50 kΩINPUT_PULLUPS
مقاومة الرفع الداخلية: نفس الدائرة بدون مقاومة خارجية
سؤال 3.2: نحسب مجال التيار في مقاومة الرفع الداخلية عندما يكون الزر مضغوطا (VCC = 5 V). وماذا يقرأ digitalRead() عند الضغط وعند الترك؟
زرّ مع مقاومة الرفع الداخلية على D2 Arduino Uno · button_pullup_internal.ino
const int BTN = 2;
const int LED = 13;             // on-board LED

void setup() {
  pinMode(BTN, INPUT_PULLUP);   // idle = HIGH
  pinMode(LED, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  int raw = digitalRead(BTN);
  bool pressed = (raw == LOW);  // inverted logic
  digitalWrite(LED, pressed ? HIGH : LOW);
  Serial.print("raw = ");
  Serial.print(raw);
  if (pressed) Serial.println("  pressed");
  else Serial.println("  released");
  delay(300);
}
Serial Monitor · قراءة نموذجية
raw = 1  released
raw = 1  released
raw = 1  released
raw = 0  pressed
raw = 0  pressed
raw = 1  released
raw = 1  released
خطأ شائع: ننسى أن المنطق معكوس فنكتب if (digitalRead(BTN) == HIGH) للضغط، فيعمل البرنامج بالعكس. مع INPUT_PULLUP الضغط هو LOW. وخطأ آخر: نكتب pinMode(BTN, INPUT) بدون مقاومة خارجية، فيبقى الطرف عائما.
تحذير: الأزرار تُوصل فقط إلى أطراف معرّفة كمداخل. إذا عُرّف الطرف كمخرج OUTPUT فالضغط يصنع قصرا، والتيار يتجاوز الحد الموصى به (20 mA) والحد الأقصى المطلق (40 mA) للطرف.

קפיצות מגע, זיהוי קצה וסינון קפיצות – ارتداد التلامس وكشف الحافة (Contact Bounce and Debouncing)

tactile
ارتداد التلامس (קפיצות מגע, ריטוט): عند إغلاق مفتاح ميكانيكي أو فتحه ترتدّ قطعة التلامس المعدنية عدة مرات قبل أن تستقر، فيفتح المفتاح ويغلق بسرعة خلال زمن قصير من رتبة ميلي ثانية. المتحكّم سريع جدا، فيرى كل ارتدادة كضغطة مستقلة.
12345671.535t [ms]VVIHVILارتداد التلامسمضغوط ومستقر
جهد الطرف D2 (مع مقاومة رفع) عند ضغطة واحدة: إشارة نموذجية مُركّبة للتوضيح. خلال الارتداد يعبر الجهد العتبتين 7 مرات قبل أن يستقر عند 0 V

عدّ الضغطات: كشف الحافة – זיהוי קצה:

نريد أن نعدّ كم مرة ضُغط الزر. الخطأ الأول: نزيد العدّاد ما دام الزر مضغوطا:

خطأ: هكذا نعدّ دورات الحلقة لا الضغطات
if (digitalRead(BTN) == LOW) {   // wrong: true in every loop while held
  count++;
}

الدالة loop() تدور آلاف المرات في الثانية، فضغطة واحدة تزيد العدّاد مئات أو آلاف المرات. الضغطة الواحدة هي تغيّر الحالة وليست الحالة نفسها، ولذلك نبحث عن الحافة.

كشف الحافة (זיהוי קצה): مقارنة القراءة الحالية بالقراءة السابقة. الحافة الهابطة (קצה יורד) هي انتقال من 1 إلى 0 (بداية الضغط مع مقاومة رفع)، والحافة الصاعدة (קצה עולה) هي انتقال من 0 إلى 1 (ترك الزر).

لكن كشف الحافة وحده لا يكفي: كل ارتدادة هي حافة هابطة أيضا. لذلك نجمع في البرنامج التالي كشف الحافة مع إزالة الارتداد.

إزالة الارتداد بالدالة millis() – סינון קפיצות בתוכנה:

إزالة الارتداد (סינון קפיצות): نقبل حالة جديدة للزر فقط بعد أن تبقى القراءة ثابتة مدة زمنية محدّدة، أطول من زمن الارتداد. كل تغيّر في القراءة يعيد تشغيل المؤقّت من جديد.

نحفظ في lastChange زمن آخر تغيّر في القراءة. إذا مرّ منذ ذلك الحين DEBOUNCE_MS ميلي ثانية، والقراءة تختلف عن الحالة المقبولة stableState، نقبلها. وإذا كانت الحالة الجديدة LOW فهذه حافة هابطة مؤكّدة: ضغطة واحدة. لا نستعمل delay() لأنه يوقف البرنامج كله، وmillis() يعطي الزمن منذ التشغيل بدون توقف.

عدّاد ضغطات مع إزالة الارتداد Arduino Uno · button_debounce_count.ino
const int BTN = 2;
const unsigned long DEBOUNCE_MS = 30;

int lastReading = HIGH;   // previous raw reading
int stableState = HIGH;   // accepted state
unsigned long lastChange = 0, presses = 0;

void setup() {
  pinMode(BTN, INPUT_PULLUP);
  Serial.begin(9600);
}

void loop() {
  unsigned long now = millis();
  int reading = digitalRead(BTN);
  if (reading != lastReading) {   // contact moved
    lastChange = now;             // restart timer
    lastReading = reading;
  }
  bool settled = (now - lastChange >= DEBOUNCE_MS);
  if (settled && reading != stableState) {
    stableState = reading;        // accept it
    if (stableState == LOW) {     // falling edge
      presses++;
      Serial.print("t = ");
      Serial.print(now);
      Serial.print(" ms  presses = ");
      Serial.println(presses);
    }
  }
}
Serial Monitor · قراءة نموذجية
t = 1232 ms  presses = 1
t = 2482 ms  presses = 2
t = 3743 ms  presses = 3
DEBOUNCE_MS = 0 · قراءة نموذجية
t = 1200 ms  presses = 1
t = 1200 ms  presses = 2
t = 1201 ms  presses = 3
t = 1202 ms  presses = 4
t = 1380 ms  presses = 5
t = 1381 ms  presses = 6

ضغطنا الزر 3 مرات، والشاشة الأولى تعطي 3 ضغطات. في الشاشة الثانية وضعنا DEBOUNCE_MS = 0، فتُقبل كل حافة فورا، وهذا هو كشف الحافة وحده. الضغطة الأولى وحدها عُدّت 6 مرات: 4 حواف في الضغط و 2 عند الترك، عندما لمس المعدن مرة أخرى للحظة. وبعد الضغطات الثلاث وصل العدّاد إلى 17 بدل 3.

مثال 3.3
في القراءة النموذجية لُمس الزر أول مرة عند t = 1200 ms ، وآخر ارتدادة كانت عند t = 1202.6 ms. لماذا طبع البرنامج الضغطة الأولى عند 1232 ms؟
الحل:

آخر تغيّر في القراءة كان عند 1202.6 ms ، والدالة millis() تعطي عددا صحيحا، فحُفظ lastChange = 1202.

تُقبل الحالة الجديدة عندما يمر DEBOUNCE_MS (أي 30 ms) بدون تغيّر:

t = 1202 + 30 = 1232 ms

التأخير بين اللمس والطباعة 32 ms لا يشعر به الإنسان، والارتدادات كلها قد صُفّيت.

t = 1232 ms
سؤال 3.3: في الضغطة الثانية لُمس الزر عند 2450 ms وآخر ارتدادة كانت عند 2452.2 ms. نغيّر DEBOUNCE_MS إلى 50. متى تُطبع الضغطة الثانية؟
ملاحظة: نختار زمن إزالة الارتداد أطول من أطول ارتداد متوقّع للمفتاح، وأقصر من أقصر ضغطة مقصودة. مخارج الحسّاسات الإلكترونية (الترانزستور، المقرن الضوئي) لا ترتدّ، فلا تحتاج إلى إزالة الارتداد؛ أما الأزرار، مفاتيح نهاية المشوار ومفاتيح العوّامة فهي ميكانيكية وترتدّ.
▶تجربة حية — الزر ومقاومة الرفعمحاكاة حيّة: نضغط الزر ونرى جهد الطرف مع مقاومة الرفع، القراءة الرقمية، وأثر الارتداد على العدّ.
⚠
المختبر غير متاح الآن. المادة في هذا الفصل كاملة من دونه — نواصل القراءة، ونعود إلى التجربة لاحقا أو نفتحها مباشرة من الرابط الذي تحت الإطار.
https://fahed-lab.com/micro/sensors_guide#button
نفتح المختبر في صفحة مستقلة ↗ fahed-lab.com/micro/sensors_guide#button

חיבור חיישן 5 V לבקר 3.3 V – توصيل حسّاس 5 V بمتحكّم 3.3 V (Level Shifting with a Voltage Divider)

كثير من وحدات الحسّاسات تعمل بـ 5 V ومخرجها الرقمي 5 V ، مثل طرف ECHO في الحسّاس فوق الصوتي HC-SR04 (الفصل 7). لا نوصل هذا المخرج مباشرة بطرف ESP32 أو بطرف الراسبيري باي. أبسط حلّ للإشارات البطيئة نسبيا هو مُجزِّء الجهد (מחלק מתח):

مُجزِّء الجهد · מחלק מתח
Vout = Vin·R2R1 + R2
الرمزالمعنىوحدة القياس
Vinجهد مخرج الحسّاس[V]
Voutالجهد على طرف المتحكّم[V]
R1 , R2المقاومة على التوالي، والمقاومة إلى GND[Ω]

شرطان للتصميم: Vout ≤ 3.3 V حتى عندما يكون Vin أعلى قليلا من 5 V ، و Vout ≥ VIH = 2.475 V حتى يُقرأ 1 بثقة.

ESP325VGPIO27GND5V SENSORVCCOUTGNDR110 kΩR215 kΩ5 V3 V
حسّاس رقمي يعمل بـ 5 V موصول بالطرف GPIO27 في ESP32 عبر مُجزِّء جهد. الأرضي مشترك
مثال 3.4
نختار R1 = 10 kΩ. نحسب أكبر R2 يعطي 3.3 V بالضبط، ثم نختار قيمة قياسية ونفحص الشرطين (نفترض أن جهد الحسّاس قد يصل إلى 5 V + 5% = 5.25 V).
الحل:
R2 = R1·VoutVin − Vout = 10·3.35 − 3.3 = 19.4 kΩ

القيمة القياسية 18 kΩ تعطي عند 5.25 V الجهد 3.38 V ، أكثر من 3.3 V. لذلك نختار R2 = 15 kΩ:

Vout = 5·1510 + 15 = 3 V
Vout,max = 5.25·1525 = 3.15 V
I = 525000 = 0.2 mA

3 V ≥ 2.475 V : يُقرأ HIGH، و 3.15 V < 3.3 V : الطرف آمن.

R2 = 15 kΩ , Vout = 3 V
عدّ الأجسام بحسّاس 5 V على ESP32 ESP32 · esp32_5v_sensor.ino
const int SENSOR = 27;    // via 10k / 15k divider

int lastState = LOW;
unsigned long objects = 0;

void setup() {
  pinMode(SENSOR, INPUT);
  Serial.begin(9600);
}

void loop() {
  int state = digitalRead(SENSOR);
  if (lastState == LOW && state == HIGH) {
    objects++;                  // rising edge
    Serial.print("objects = ");
    Serial.println(objects);
  }
  lastState = state;
}
Serial Monitor · قراءة نموذجية
objects = 1
objects = 2
objects = 3
سؤال 3.4: نستعمل R1 = 1 kΩ و R2 = 2 kΩ. نحسب Vout عندما يعطي الحسّاس 5 V. هل هذا التصميم آمن تماما؟
تنبيه: مُجزِّء الجهد يعمل فقط مع أرضي مشترك بين الحسّاس والمتحكّم. وهو مناسب لاتجاه واحد (من الحسّاس إلى المتحكّم). للإشارات السريعة جدا أو ذات الاتجاهين نستعمل وحدة ملاءمة مستويات جاهزة.

בידוד אות תעשייתי 24 V במצמד אופטי – عزل إشارة صناعية 24 V بمقرن ضوئي (Optocoupler Isolation)

الحسّاسات الصناعية ومفاتيح نهاية المشوار في لوحات التحكّم تعمل عادة بـ 24 V مستمر. مُجزِّء الجهد هنا ليس حلا جيدا: الأسلاك طويلة، والضجيج والقمم الكهربائية في المصنع قد تصل إلى المتحكّم. الحل الصناعي هو العزل.

المقرن الضوئي (מצמד אופטי): عنصر فيه ثنائي ضوئي (LED) وترانزستور ضوئي داخل غلاف واحد، بدون أي توصيل كهربائي بينهما. إشارة 24 V تُشعل الـ LED، والضوء يجعل الترانزستور يوصل في دائرة 5 V. هكذا تنتقل المعلومة بالضوء، وتبقى الدائرتان معزولتين.

نستعمل المقرن الضوئي الشائع PC817. القيم من ورقة البيانات (قيم نموذجية حسب ورقة البيانات): جهد الـ LED الأمامي VF ≈ 1.2 V ؛ نسبة نقل التيار (יחס העברת זרם) CTR = ICIF لا تقل عن 50% عند IF = 5 mA ؛ وجهد التشبّع VCE,sat ≤ 0.2 V.

Arduino Uno5VD4GNDRPU10 kΩPC817+−24 Vمخرج الحسّاسRLED4.7 kΩدائرة24 Vدائرة5 V
إشارة 24 V من حسّاس صناعي معزولة بالمقرن الضوئي PC817 قبل الطرف D4. الدائرتان لا تشتركان في الأرضي

المنطق هنا معكوس أيضا: إشارة 24 V موجودة ← الـ LED يضيء ← الترانزستور يوصل ← الطرف D4 عند 0 V (LOW). بدون إشارة الترانزستور مفتوح، ومقاومة الرفع RPU تشدّ الطرف إلى 5 V (HIGH).

مثال 3.5
نصمّم الدائرة:
(أ) نحسب RLED لتيار IF = 5 mA ونختار قيمة قياسية ونحسب قدرتها.
(ب) نفحص أن الترانزستور يتشبّع مع RPU = 10 kΩ.
الحل:

(أ) جهد المقاومة هو جهد الإشارة ناقص جهد الـ LED:

RLED = Vin − VFIF = 24 − 1.20.005 = 4560 Ω

نختار القيمة القياسية 4.7 kΩ ونحسب التيار والقدرة الحقيقيين:

IF = 24 − 1.24700 = 4.85 mA
P = IF2·RLED = (4.85·10−3)2 · 4700 = 0.111 W

نختار مقاومة قدرتها 0.25 W على الأقل.

(ب) التيار الذي تحتاجه مقاومة الرفع حتى ينزل الطرف إلى جهد التشبّع، والتيار الذي يضمنه المقرن:

IC,need = VCC − VCE,satRPU = 5 − 0.210000 = 0.48 mA
IC,min = CTR·IF = 0.5 · 4.85 = 2.43 mA

2.43 mA > 0.48 mA : الترانزستور يتشبّع، وجهد الطرف نحو 0.2 V ، أقل بكثير من VIL = 1.5 V.

RLED = 4.7 kΩ , IF = 4.85 mA , RPU = 10 kΩ
عدّ القطع عبر المقرن الضوئي Arduino Uno · opto_24v_counter.ino
const int IN24 = 4;     // opto output, 10k pull-up

int lastState = HIGH;   // HIGH = no 24 V signal
unsigned long items = 0;

void setup() {
  pinMode(IN24, INPUT);
  Serial.begin(9600);
}

void loop() {
  int state = digitalRead(IN24);
  if (lastState == HIGH && state == LOW) {
    items++;                    // 24 V appeared
    Serial.print("items = ");
    Serial.println(items);
  }
  lastState = state;
}
Serial Monitor · قراءة نموذجية
items = 1
items = 2
items = 3
سؤال 3.5: ما أصغر جهد إشارة يضمن تشبّع الترانزستور في الدائرة السابقة (RLED = 4.7 kΩ ، RPU = 10 kΩ ، CTR ≥ 50%)؟
خطأ شائع: نوصل أرضي دائرة 24 V بـ GND في Arduino فيضيع العزل كله. الدائرتان منفصلتان تماما، والمقرن الضوئي وحده يربط بينهما.
تحذير: نعمل على دوائر 24 V الصناعية فقط بإشراف المعلم، والتغذية مفصولة عند التوصيل. المقرن الضوئي على لوح التجارب لا يجعل العمل مع جهد الشبكة 230 V آمنا: إشارات الشبكة تدخل المتحكّم فقط عبر وحدة جاهزة ومصادق عليها.
إلى الأمام: في الفصل 8 نتعرّف على مخارج الحسّاسات الصناعية: مخرج NPN (يسحب التيار) ومخرج PNP (يغذّي التيار)، وكيف يوصل كل منهما إلى المقرن الضوئي.
▶تجربة حية — الحسّاسات الرقميةمحاكاة حيّة لحسّاسات رقمية صناعية: مفاتيح نهاية المشوار وحسّاسات الاقتراب ومخارجها.
⚠
المختبر غير متاح الآن. المادة في هذا الفصل كاملة من دونه — نواصل القراءة، ونعود إلى التجربة لاحقا أو نفتحها مباشرة من الرابط الذي تحت الإطار.
https://fahed-lab.com/hashmal/sensors#dsens
نفتح المختبر في صفحة مستقلة ↗ fahed-lab.com/hashmal/sensors#dsens

פסיקות חומרה לספירת פולסים – المقاطعات الخارجية لعدّ النبضات السريعة (Hardware Interrupts)

في كل البرامج السابقة قرأ loop() الطرف مرة بعد مرة. هذه الطريقة تسمى الاستطلاع (תשאול). إذا انشغل البرنامج بعمل طويل، مثل قراءة حسّاس بطيء أو كتابة على شاشة، قد تأتي نبضة قصيرة وتذهب بدون أن يراها.

المقاطعة (פסיקה): إشارة كهربائية على طرف توقف البرنامج الرئيسي فورا، وتنفّذ دالة قصيرة اسمها معالج المقاطعة (מטפל בפסיקה)، ثم يعود البرنامج إلى النقطة التي توقف عندها. في Arduino Uno تعمل المقاطعة الخارجية attachInterrupt() على الطرفين 2 و3 فقط.

مثال: مروحة حاسوب لها طرف TACH يعطي نبضتين في كل دورة (حسب المواصفة الشائعة لمراوح الحاسوب). المخرج من نوع «المجمّع المفتوح»: الترانزستور يشدّ الخط إلى GND فقط، فيحتاج إلى مقاومة رفع إلى 5 V. المروحة تتغذّى بـ 12 V ، لكن مخرجها لا يحمل 12 V ، ولذلك يكفي أرضي مشترك.

Arduino Uno5VD2GNDFAN 12V+12VTACHGND12VRPU10 kΩأرضي مشترك
مخرج TACH لمروحة 12 V موصول بالطرف D2 (المقاطعة INT0) مع مقاومة رفع إلى 5 V
عدّ نبضات المروحة بالمقاطعة Arduino Uno · fan_tach_interrupt.ino
const int TACH = 2;                 // INT0 on Uno
const unsigned long PULSES_PER_REV = 2;

volatile unsigned long pulses = 0;  // set in ISR
unsigned long lastPrint = 0;

void onPulse() { pulses++; }        // ISR: short

void setup() {
  pinMode(TACH, INPUT);       // 10k pull-up to 5V
  attachInterrupt(digitalPinToInterrupt(TACH),
                  onPulse, FALLING);
  Serial.begin(9600);
}

void loop() {
  if (millis() - lastPrint >= 1000) {
    lastPrint += 1000;
    noInterrupts();           // copy, then reset
    unsigned long n = pulses;
    pulses = 0;
    interrupts();
    Serial.print("pulses = ");
    Serial.print(n);
    Serial.print("  rpm = ");
    Serial.println(n * 60 / PULSES_PER_REV);
  }
}
Serial Monitor · قراءة نموذجية
pulses = 0  rpm = 0
pulses = 30  rpm = 900
pulses = 46  rpm = 1380
pulses = 54  rpm = 1620
pulses = 58  rpm = 1740
pulses = 59  rpm = 1770
سرعة الدوران من عدد النبضات · מהירות סיבוב
n = 60·Np
الرمزالمعنىوحدة القياس
nسرعة الدوران[rpm]
Nعدد النبضات في ثانية واحدة—
pعدد النبضات في كل دورة—
مثال 3.6
في السطر الأخير من الشاشة عُدّت 59 نبضة في ثانية. نحسب سرعة الدوران والزمن بين نبضتين.
الحل:
n = 60·Np = 60·592 = 1770 rpm
T = 1N = 159 = 16.9 ms

نبضة كل 16.9 ms تقريبا: برنامج فيه delay(100) مثلا كان سيفوّت معظمها، أما المقاطعة فتلتقطها كلها.

n = 1770 rpm
سؤال 3.6: مروحة تعطي نبضتين في الدورة تدور بـ 2400 rpm. كم نبضة يعدّ البرنامج في الثانية؟
خطأ شائع: نستعمل attachInterrupt() على طرف آخر في Uno، مثل الطرف 4، فلا يُعدّ شيء: المقاطعة الخارجية على 2 و 3 فقط. أما في ESP32 فيمكن ربط مقاطعة بكل طرف تقريبا؛ لكن الأطراف 34–39 فيه مداخل فقط وبدون مقاومة رفع داخلية.

ملخّص قوانين الفصل:

القانونالصيغة
عتبة المستوى العالي في ATmega328P (Uno)VIH = 0.6·VCC
عتبة المستوى المنخفض في ATmega328P (Uno)VIL = 0.3·VCC
عتبة المستوى العالي في ESP32VIH = 0.75·VDD
عتبة المستوى المنخفض في ESP32VIL = 0.25·VDD
تيار مقاومة الرفع أو الخفض عند الضغطI = VCCR
مُجزِّء الجهدVout = Vin·R2R1 + R2
مقاومة الـ LED في المقرن الضوئيRLED = Vin − VFIF
شرط التشبّع في المقرن الضوئيCTR·IF ≥ VCC − VCE,satRPU
سرعة الدوران من النبضاتn = 60·Np

قاموس المصطلحات – מילון מונחים:

بالعربيةبالعبرية (كما تظهر في الامتحان)English
المدخل الرقميמבוא ספרתיDigital input
المستوى المنطقيרמה לוגיתLogic level
عتبة المستوى العالي / المنخفضסף רמה גבוהה / נמוכהV_IH / V_IL threshold
المنطقة غير المعرّفةתחום לא מוגדרUndefined region
المدخل العائمמבוא צףFloating input
مقاومة الرفع (pull-up)נגד משיכה למעלהPull-up resistor
مقاومة الخفض (pull-down)נגד משיכה למטהPull-down resistor
المنطق المعكوسלוגיקה הפוכהInverted (active-low) logic
ارتداد التلامسקפיצות מגע (ריטוט)Contact bounce
إزالة الارتدادסינון קפיצותDebouncing
كشف الحافةזיהוי קצהEdge detection
الحافة الصاعدة / الهابطةקצה עולה / יורדRising / falling edge
مُجزِّء الجهدמחלק מתחVoltage divider
المقرن الضوئيמצמד אופטיOptocoupler
نسبة نقل التيارיחס העברת זרםCurrent transfer ratio (CTR)
الاستطلاعתשאולPolling
المقاطعةפסיקהInterrupt
معالج المقاطعةמטפל בפסיקהInterrupt service routine (ISR)

أسئلة للتمرين:

1.
نحسب VIH و VIL للمتحكّم ATmega328P عندما يتغذّى بـ 3.3 V.
الإجابات: VIH = 1.98 V ، VIL = 0.99 V
2.
كيف يقرأ طرف ESP32 كل جهد من الجهود:
(أ) 0.5 V
(ب) 1.6 V
(ج) 2.9 V؟
الإجابات: (أ) LOW (ب) غير معرّف (ج) HIGH
3.
نشرح بجملتين: لماذا يتغيّر ما يقرؤه طرف عائم عندما نقرّب يدنا من السلك؟
الإجابات: مقاومة المدخل كبيرة جدا، فيلتقط الطرف الضجيج والشحنات من المحيط، ولا شيء يشدّه إلى جهد ثابت
4.
زرّ مع مقاومة خفض 4.7 kΩ. نحسب التيار والقدرة في المقاومة عند الضغط (VCC = 5 V).
الإجابات: I = 1.06 mA ، P = 5.32 mW
5.
طالب استعمل مقاومة خفض 1 MΩ. تيار التسرّب في المدخل يصل إلى 1 µA. نحسب جهد الطرف عندما يكون الزر متروكا. هل القراءة مضمونة؟
الإجابات: V = 1 V : أقل من VIL = 1.5 V ، لكن الهامش صغير والمقاومة الكبيرة تلتقط الضجيج؛ لا نوصي بها
6.
نكتب سطري setup() وشرط if لزرّ على الطرف 3 مع مقاومة الرفع الداخلية، بحيث يتحقّق الشرط عند الضغط.
الإجابات: pinMode(3, INPUT_PULLUP); ، if (digitalRead(3) == LOW)
7.
زرّ يعطي في كل ضغطة 3 حواف هابطة عند الضغط وحافة هابطة واحدة عند الترك. ضغطناه 5 مرات. كم يعدّ برنامج كشف الحافة بدون إزالة الارتداد؟
الإجابات: 20
8.
في برنامج إزالة الارتداد DEBOUNCE_MS = 30. لُمس الزر عند 5000 ms وآخر ارتدادة عند 5004 ms. متى تُعدّ الضغطة؟
الإجابات: t = 5034 ms
9.
نصل حسّاسا يعطي 5 V بطرف ESP32 عبر مُجزِّء فيه R1 = 2.2 kΩ. نحسب أكبر R2 يعطي 3.3 V ، ونختار من القيم 3.3 kΩ ، 3.9 kΩ ، 4.7 kΩ أكبر قيمة لا تتجاوز 3.3 V ، ونحسب Vout.
الإجابات: R2,max = 4.27 kΩ ؛ نختار 3.9 kΩ ، Vout = 3.2 V
10.
إشارة صناعية 12 V تُشعل الـ LED في المقرن PC817 (VF = 1.2 V). نحسب RLED لتيار 5 mA ، ثم التيار مع القيمة القياسية 2.2 kΩ.
الإجابات: RLED = 2160 Ω ؛ IF = 4.91 mA
11.
في دائرة المقرن الضوئي نستبدل RPU بمقاومة 1 kΩ (IF = 4.85 mA ، CTR ≥ 50%). هل يتشبّع الترانزستور؟
الإجابات: يحتاج IC = 4.8 mA ويضمن المقرن 2.43 mA فقط: لا يُضمن تشبّعه، والطرف قد لا ينزل إلى LOW
12.
مروحة تعطي نبضتين في الدورة. عدّ البرنامج 75 نبضة في ثانية. نحسب سرعة الدوران والزمن بين نبضتين.
الإجابات: n = 2250 rpm ، T = 13.3 ms
13.
نذكر ثلاث قواعد لكتابة معالج المقاطعة، ونذكر على أي أطراف Uno تعمل attachInterrupt().
الإجابات: معالج قصير بلا Serial أو delay()؛ متغيّر volatile؛ نسخ المتغيّر مع إيقاف المقاطعات. الطرفان 2 و 3
الإجابات النهائية لأسئلة الفصل
3.1 ESP32: VIH = 2.475 V ، VIL = 0.825 V. يقرأ ESP32 الجهد 2.6 V HIGH، أما Uno فالجهد عنده في المنطقة غير المعرّفة
3.2 0.1 mA ≤ I ≤ 0.25 mA ؛ عند الضغط LOW (0)، وعند الترك HIGH (1)
3.3 t = 2502 ms
3.4 Vout = 3.33 V : أعلى قليلا من 3.3 V ، ولا يترك هامشا؛ الأفضل R2 أصغر
3.5 Vin,min = 5.71 V
3.6 N = 80 نبضة
لوحة القوانين: كل قوانين هذا الفصل مجموعة مع رسومها وجداول رموزها في الفصل 15 — لوحة القوانين الشاملة. (נוסחאון הספר)