آموزش Espآموزش ESP32آموزش اینترنت اشیا

آموزش تنظیم وقفه Interrupts در برد ESP32

در بسیاری از پروژه‌ها، نیاز است که ESP32 در حین اجرای برنامه اصلی خود، به‌صورت مداوم رخدادهای خاصی را نیز پایش کند. یکی از روش‌های مؤثر و پراستفاده برای این منظور، استفاده از وقفه‌ها (Interrupts) است.

انواع وقفه در ESP32

ESP32 به ازای هر هسته پردازشی، حداکثر 32 اسلات وقفه در اختیار قرار می‌دهد. هر وقفه دارای یک سطح اولویت مشخص بوده و در دو دسته اصلی تقسیم‌بندی می‌شود:

  • وقفه سخت‌افزاری (Hardware Interrupt): در پاسخ به یک رویداد خارجی رخ می‌دهد.
    مثال: وقفه پایه GPIO هنگام فشرده شدن کلید، یا وقفه لمسی (Touch) هنگام تماس سطح لمسی.
  • وقفه نرم‌افزاری (Software Interrupt): در پاسخ به یک دستور یا رویداد داخلی نرم‌افزاری رخ می‌دهد.
    مثال: وقفه تایمر یا وقفه تایمر واچ‌داگ هنگام پایان زمان.وقفه GPIO در ESP32

در ESP32 می‌توان یک تابع سرویس‌دهی وقفه (Interrupt Service Routine یا ISR) تعریف کرد تا در زمان تغییر سطح منطقی پین GPIO فراخوانی شود.

تمامی پایه‌های GPIO در ESP32 قابلیت پیکربندی برای دریافت وقفه را دارند.

اتصال وقفه به پایه GPIO

در محیط Arduino IDE، برای تنظیم وقفه بر روی یک پایه خاص، از تابع attachInterrupt() استفاده می‌شود:

attachInterrupt(GPIOPin, ISR, Mode);

ورودی‌های تابع:

  • GPIOPin: شماره پایه GPIO که باید برای وقفه پایش شود.
  • ISR: نام تابعی که هنگام وقوع وقفه اجرا خواهد شد.
  • Mode: شرایطی که در آن وقفه باید فعال شود.

مقادیر مجاز برای Mode:

مقدار توضیح
LOW فعال شدن وقفه هنگام پایین بودن سطح پین
HIGH فعال شدن هنگام بالا بودن سطح پین
CHANGE فعال شدن هنگام تغییر سطح پین از بالا به پایین یا بالعکس
FALLING فعال شدن هنگام تغییر از HIGH به LOW
RISING فعال شدن هنگام تغییر از LOW به HIGH

حذف وقفه از یک پین

در صورتی که دیگر نیازی به پایش پایه نداشته باشید، می‌توانید با تابع detachInterrupt() وقفه را غیرفعال کنید:

detachInterrupt(GPIOPin);

تابع سرویس‌دهی وقفه (ISR)

تابع ISR، همان تابعی است که در هنگام وقوع وقفه اجرا می‌شود. ساختار آن به شکل زیر است:

void IRAM_ATTR ISR() {
// کد مربوط به پاسخ به وقفه
}

نکات مهم در تعریف ISR در ESP32:

  • تابع نباید هیچ ورودی یا خروجی داشته باشد.
  • باید تا حد امکان کوتاه و سریع نوشته شود؛ چرا که در زمان اجرای آن، عملکرد عادی برنامه متوقف می‌شود.
  • باید دارای صفت IRAM_ATTR باشد تا کد آن در حافظه RAM داخلی ESP32 ذخیره شود.

IRAM_ATTR چیست؟

هنگامی که قطعه‌ای از کد را با IRAM_ATTR علامت‌گذاری می‌کنیم، کامپایلر آن را در حافظه RAM داخلی (IRAM) ذخیره می‌کند. در غیر این صورت، کد در حافظه Flash ذخیره می‌شود که بسیار کندتر از RAM است.

از آنجا که وقفه‌ها باید در کوتاه‌ترین زمان ممکن پاسخ داده شوند، بارگذاری آن‌ها از حافظه Flash می‌تواند باعث تاخیر و اختلال عملکرد شود. به همین دلیل، طبق مستندات رسمی ESP32، تابع ISR باید در IRAM قرار گیرد.

پروژه نمونه وقفه در ESP32

بحث تئوری کافی است! بیایید به یک مثال عملی نگاه کنیم.

بیایید یک دکمه فشاری را به GPIO#18 (D18) برد ESP32 وصل کنیم. برای این پین نیازی به هیچ pullup ندارید زیرا پین را به صورت داخلی pull up خواهیم کرد.

سیم‌کشی دکمه‌های فشاری به ESP32 برای وقفه GPIO

اگر در مورد این مطلب سوالی دارید در قسمت نظرات بپرسید

کد نمونه: وقفه ساده با ESP32

کد زیر استفاده از وقفه‌ها و روش صحیح نوشتن یک روال سرویس وقفه را نشان می‌دهد.

این برنامه GPIO#18 (D18) را برای لبه FALLING زیر نظر می‌گیرد. به عبارت دیگر، به دنبال تغییر ولتاژ از منطق HIGH به منطق LOW است که هنگام فشار دادن دکمه رخ می‌دهد. وقتی این اتفاق می‌افتد، تابع isr فراخوانی می‌شود. کد درون این تابع تعداد دفعات فشار دادن دکمه را می‌شمارد.

struct Button {
const uint8_t PIN;
uint32_t numberKeyPresses;
bool pressed;
};

Button button1 = {18, 0, false};

void IRAM_ATTR isr() {
button1.numberKeyPresses++;
button1.pressed = true;
}

void setup() {
Serial.begin(115200);
pinMode(button1.PIN, INPUT_PULLUP);
attachInterrupt(button1.PIN, isr, FALLING);
}

void loop() {
if (button1.pressed) {
Serial.printf("Button has been pressed %u times\n", button1.numberKeyPresses);
button1.pressed = false;
}
}

پس از آپلود کد، دکمه EN را روی ESP32 فشار دهید و مانیتور سریال را با نرخ انتقال داده 115200 باز کنید. با فشار دادن دکمه، خروجی زیر را دریافت خواهید کرد.

خروجی وقفه gpio esp32 روی مانیتور سریال

در ابتدای کد، ساختاری به نام Button ایجاد می‌کنیم. این ساختار سه عضو دارد:

  1. شماره پین
  2. تعداد فشرده شدن کلید
  3. وضعیت فشرده شده

یک ساختار struct مجموعه‌ای از متغیرهایی با انواع مختلف (اما از نظر منطقی مرتبط با یکدیگر) تحت یک نام واحد است.

struct Button {
const uint8_t PIN;
uint32_t numberKeyPresses;
bool pressed;
};

سپس یک نمونه از ساختار Button ایجاد می‌کنیم و شماره پین را 18، تعداد فشرده شدن کلید را 0 و وضعیت پیش‌فرض فشرده شده را false مقداردهی اولیه می‌کنیم.

Button button1 = {18, 0, false};

کد زیر یک روال سرویس وقفه است. همانطور که قبلاً ذکر شد، ISR در ESP32 باید دارای ویژگی IRAM_ATTR باشد.

در ISR، ما به سادگی شمارنده KeyPresses را 1 واحد افزایش می‌دهیم و حالت فشرده شدن دکمه را روی True تنظیم می‌کنیم.

void IRAM_ATTR isr() {
button1.numberKeyPresses += 1;
button1.pressed = true;
}

در بخش Setup، ابتدا ارتباط سریال با کامپیوتر را مقداردهی اولیه می‌کنیم و سپس pullup داخلی را برای پین GPIO D18 فعال می‌کنیم.

در مرحله بعد، به ESP32 می‌گوییم که پین D18 را رصد کند و وقتی پین از HIGH به LOW یعنی لبه FALLING می‌رود، روال سرویس وقفه isr را فراخوانی کند.

Serial.begin(115200);
pinMode(button1.PIN, INPUT_PULLUP);
attachInterrupt(button1.PIN, isr, FALLING);

در بخش Loop کد، ما به سادگی بررسی می‌کنیم که آیا دکمه فشرده شده است یا خیر و سپس تعداد دفعاتی که کلید تاکنون فشرده شده است را چاپ می‌کنیم و حالت فشرده شدن دکمه را روی false تنظیم می‌کنیم تا بتوانیم به دریافت وقفه‌ها ادامه دهیم.

if (button1.pressed) {
Serial.printf("Button 1 has been pressed %u times\n", button1.numberKeyPresses);
button1.pressed = false;
}

دیبانس دکمه

یک مشکل رایج در مورد وقفه‌ها این است که اغلب برای یک رویداد چندین بار فعال می‌شوند. اگر به خروجی سریال مثال بالا نگاه کنید، متوجه خواهید شد که حتی اگر فقط یک بار دکمه را فشار دهید، شمارنده چندین بار افزایش می‌یابد.

مشکل پرش وقفه gpio در esp32

برای فهمیدن دلیل این اتفاق، باید به سیگنال نگاهی بیندازید. اگر هنگام فشار دادن دکمه، ولتاژ پین روی تحلیلگر سیگنال را کنترل کنید، سیگنالی مانند این دریافت خواهید کرد:

سیگنال پرش سوئیچ

ممکن است احساس کنید که تماس بلافاصله برقرار می‌شود، اما در واقع قطعات مکانیکی درون دکمه چندین بار قبل از اینکه در یک حالت خاص قرار بگیرند، با هم تماس پیدا می‌کنند. این باعث می‌شود چندین وقفه فعال شوند.

این یک پدیده کاملاً مکانیکی است که به عنوان “بانس سوئیچ” شناخته می‌شود، مانند انداختن یک توپ – قبل از اینکه در نهایت روی زمین فرود بیاید، چندین بار بالا و پایین می‌رود.

زمان تثبیت سیگنال بسیار سریع است و برای ما تقریباً آنی به نظر می‌رسد، اما برای ESP32 این یک دوره زمانی طولانی است. می‌تواند چندین دستورالعمل را در آن دوره زمانی اجرا کند.

فرآیند حذف پرش سوئیچ “دیبانس” نامیده می‌شود. دو راه برای دستیابی به این هدف وجود دارد.

  • از طریق سخت‌افزار: با اضافه کردن یک فیلتر RC مناسب برای هموار کردن انتقال.
  • از طریق نرم‌افزار: با نادیده گرفتن موقت وقفه‌های بیشتر برای مدت کوتاهی پس از فعال شدن اولین وقفه.

کد مثال: دیبانس دکمه وقفه ESP32

در اینجا کد بالا بازنویسی شده است تا نحوه دیبانس یک وقفه به صورت برنامه‌نویسی شده را نشان دهد. در این کد، ما اجازه می‌دهیم که ISR فقط یک بار با هر بار فشار دادن دکمه اجرا شود، به جای اینکه چندین بار اجرا شود.

struct Button {
const uint8_t PIN;
uint32_t numberKeyPresses;
bool pressed;
};

Button button1 = {18, 0, false};

//variables to keep track of the timing of recent interrupts
unsigned long button_time = 0;
unsigned long last_button_time = 0;

void IRAM_ATTR isr() {
button_time = millis();
if (button_time - last_button_time > 250)
{
button1.numberKeyPresses++;
button1.pressed = true;
last_button_time = button_time;
}
}

void setup() {
Serial.begin(115200);
pinMode(button1.PIN, INPUT_PULLUP);
attachInterrupt(button1.PIN, isr, FALLING);
}

void loop() {
if (button1.pressed) {
Serial.printf("Button has been pressed %u times\n", button1.numberKeyPresses);
button1.pressed = false;
}
}

بیایید دوباره به خروجی سریال هنگام فشردن دکمه نگاه کنیم. توجه داشته باشید که ISR فقط یک بار برای هر بار فشردن دکمه فراخوانی می‌شود.

debounce وقفه gpio در esp32
این راه حل به این دلیل کار می‌کند که هر بار که ISR اجرا می‌شود، زمان فعلی برگردانده شده توسط تابع millis() را با آخرین زمانی که ISR فراخوانی شده است مقایسه می‌کند.

اگر در محدوده 250 میلی‌ثانیه باشد، ESP32 وقفه را نادیده می‌گیرد و بلافاصله به کاری که انجام می‌داد برمی‌گردد. در غیر این صورت، کد داخل عبارت if را اجرا می‌کند و شمارنده را افزایش می‌دهد و متغیر last_button_time را به‌روزرسانی می‌کند، بنابراین تابع مقدار جدیدی برای مقایسه با زمانی که در آینده فعال می‌شود، دارد.

5 (1 نفر)

برای دریافت مطالب جدید کانال تلگرام یا پیج اینستاگرام ما را دنبال کنید.

محمد رحیمی

محمد رحیمی هستم. سعی میکنم در آیرنکس مطالب مفید قرار بدهم. سوالات مربوط به این مطلب را در قسمت نظرات همین مطلب اعلام کنید. سعی میکنم در اسرع وقت به نظرات شما پاسخ بدهم.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *