دليل · المقاطعات · التوقيت
تخلَّ عن delay() — المقاطعات وmillis() على Arduino وESP32
أول ما يتعلّمه الجميع هو delay(). وثاني ما يصطدم به الجميع هو جداره: أثناء تنفيذ
delay(1000) لا تفعل اللوحة شيئًا — لا تقرأ زرًّا ولا تحدّث شاشة ولا تُبقي
Wi-Fi حيًّا. إليك المخرج في خطوتين: توقيت غير حاجب بـ millis()، والتقاط الأحداث لحظة
وقوعها بـ المقاطعات.
الخطوة 1 — millis() بدل delay()
معظم لحظات «أحتاج تأخيرًا» تعني في الحقيقة «افعل هذا كل فترة، لكن أبقِ كل شيء آخر يعمل». لهذا وُجد
millis() — يعيد عدد الملّي ثانية منذ الإقلاع، فتتحقّق من مقدار الوقت المنقضي بدل التجميد:
unsigned long prev = 0;
const unsigned long period = 1000; // ms
void loop() {
unsigned long now = millis();
if (now - prev >= period) {
prev = now;
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // blink
}
// ...everything else keeps running here, no freeze
} الحلقة لا تتوقف أبدًا: تنظر إلى الساعة في كل دورة فقط. هذا النمط الواحد يحلّ محلّ 90% من استدعاءات
delay()، وهو ما يتيح للوحة أن تُومض ليدًا وتقرأ مستشعرًا وتخدم صفحة في آنٍ واحد. لكن للاستقصاء
فجوة — بين تحقّقين قد تفوتك حادثة سريعة. هنا تأتي المقاطعات.
الخطوة 2 — المقاطعات: لا تستقصِ، بل تلقَّ إشعارًا
المقاطعة هي «نقرة على الكتف» عتادية: عندما يتغيّر طرف، يترك المعالج ما بيده ويشغّل دالة صغيرة (ISR — روتين خدمة المقاطعة) ثم يعود تمامًا حيث توقّف. تلجأ إليها حين لا يجوز أن تفوتك حادثة ولا تقدر على استقصائها:
- ضغطة زر — تفاعل لحظة الضغط مهما كان
loop()مشغولًا به. - مُشفّر دوّار — النبضات أسرع من أن تُستقصى بثقة.
- مستشعر تدفّق/نبض — عُدّ الحوافّ دون أن تفوتك واحدة.
- الاستيقاظ من النوم العميق عبر طرف (انظر النوم العميق).
التوصيل: زرّ يُسحب إلى الأرض
أنظف زرّ لا يحتاج قطعًا خارجية: رِجْل إلى GPIO والأخرى إلى GND، وتُفعّل مقاومة الرفع الداخلية للرقاقة
بـ INPUT_PULLUP. يبقى الطرف HIGH في السكون ويُقرأ LOW عند الضغط — لذا التحفيز على
FALLING.
INPUT_PULLUP يعني «دون مقاومة خارجية». مبنيّ في BoardLab.على ESP32 يصلح أيّ GPIO تقريبًا كمقاطعة؛ أما على Uno الكلاسيكي فـ D2 وD3 فقط. لُفّ الطرف
دائمًا بـ digitalPinToInterrupt() — فهو يربط الطرف بخطّ المقاطعة الصحيح. ومَكمَن على ESP32:
الأطراف المخصّصة للدخل فقط GPIO34–39 لا تملك مقاومات رفع داخلية، فالزرّ هناك يحتاج
فعلًا مقاومة خارجية ~10 كΩ إلى 3V3.
الخطوة 3 — إزالة الارتداد، وإلا حُسبت ضغطة واحدة خمسًا
لا يصنع الزرّ الميكانيكي حافّة نظيفة واحدة — بل ترتدّ التلامسات، متذبذبة بين HIGH/LOW لبضع ملّي ثانية. دون معالجة يُطلَق ISR عدة مرات للضغطة الواحدة. الحل: تجاهُل أيّ حافّة تصل بعد سابقتها بسرعة مفرطة:
const byte BUTTON = 19; // your interrupt pin
volatile uint32_t presses = 0;
volatile uint32_t lastUs = 0;
void IRAM_ATTR onPress() { // ESP32: the ISR must live in IRAM
uint32_t now = micros();
if (now - lastUs > 25000) { // ignore bounces within 25 ms
presses++;
lastUs = now;
}
}
void setup() {
Serial.begin(115200);
pinMode(BUTTON, INPUT_PULLUP); // button to GND, no external resistor
attachInterrupt(digitalPinToInterrupt(BUTTON), onPress, FALLING);
}
void loop() {
static uint32_t shown = 0;
if (presses != shown) { // do the slow work OUT here, not in the ISR
shown = presses;
Serial.printf("presses: %u\n", shown);
}
} حارس micros() يبتلع الارتدادات ضمن 25 ملّي ثانية مع بقائه مستجيبًا للضغطات الحقيقية.
تفضّل الحلّ العتادي؟ مكثّف 100 نانوفاراد عبر الزر (أو مرشّح RC) يُنعّم الحافّة — لكن الحارس البرمجي
مجاني وكافٍ لكل شيء تقريبًا.
قواعد ISR التي تعضّ فعلًا
- أبقِها قصيرة. ارفع علمًا أو زِد عدّادًا، ثم عالِج الأمر في
loop(). ISR طويل يحجب كل شيء بما فيه Wi-Fi. - شارِك عبر
volatile. أيّ متغيّر يلمسه كلٌّ من ISR وloop()يجب أن يكونvolatile، وإلا فقد يخزّن المترجم نسخة قديمة. - ESP32: علّم ISR بـ
IRAM_ATTR. يجب أن يعمل من RAM — بدونه قد تنهار اللوحة إذا وقعت المقاطعة أثناء عملية على الفلاش. - لا
Serialولاdelay()ولا عمل ثقيل داخل ISR. اطبع واحسب فيloop()كما في المثال. - على AVR ذي 8 بت القيم متعددة البايتات ليست ذرّية. تقرأ
longيحدّثه ISR؟ عطّل المقاطعات لفترة وجيزة (noInterrupts()/interrupts()) حول القراءة.
معًا: millis() يقتل delay() اليومي، والمقاطعات تلتقط الأحداث التي لا يجوز أن
تفوتك. وحين يصير كلاهما ذاكرة عضلية، تكفّ اللوحة عن التلعثم وتبدأ بفعل عدة أشياء دفعة واحدة — وعلى ESP32
يمكنك المضيّ أبعد ووضع العمل على النواة الثانية.
توصّل زرًّا أو مستشعرًا بالطرف المناسب؟ تحقّق أولًا أنه متاح وقادر على المقاطعة.