راه اندازی وقفه ی ماژول ESP8266 در آردوینو یکی از مباحثی است که به ما این توانایی را میدهد که از ویژگی اجرای چند برنامه بهطور همزمان در آردوینو، استفادههای گوناگونی داشته باشیم. برای مثال ما در این آموزش بیان میکنیم که در پروژههای مربوط به اینترنت اشیاء (IOT) اگر بخواهید یک ماژول وای فای عملکرد عادی خود را داشته باشد (یعنی یک برنامه معمولی را اجرا کند) و درعین حال نوعی رویداد را بهطور دائم نظارت کند چگونه از روش ایجاد وقفه در آردوینو استفاده کنید.
حتماً تابه حال در زندگی روزمره برای شما پیش آمده که درطول انجام کاری، یک یا چند پروسه بصورت اتفاقی یا برنامهریزی شده باعث شده تا انجام کار قبلی را برای زمانی متوقف کنید، برای مثال فرض کنید مشغول خواندن یک کتاب هستید و در این حین تلفن همراهتان به صدا در میآید، بنابراین شما خواندن کتاب را متوقف میکنید تا با موبایل خود صحبت کنید و پس از آن به مطالعه ادامه دهید. شما با وقفه در مطالعه کتاب و پاسخ به یک تماس تلفنی و دوباره مطالعه ، موفق به انجام دو کار متفاوت در یک زمان شدهاید.
وقفه (Interrupt) چیست؟
وقفه یا اینتراپت (interrupt) به سیگنالی گفته میشود که در سیستم کامپیوتری برای انجام یک فرآیند به پردازنده فرستاده میشود که باعث ایجاد توقفی سریع در عملکرد آن میشود تا به پروسههای فعال دیگر نیز رسیدگی کند و با این روش عملکرد بهینهای را از خود نشان دهد.
انواع وقفه به دو دسته تقسیم میشود:
- وقفه های سخت افزاری – در پاسخ به یک رویداد خارجی اجرا میشوند. مثل وقفههایی که با استفاده از پینهای GPIO ماژول انجام میشوند. (زمانی که یک کلید فشرده میشود)
- وقفه های نرم افزاری – در پاسخ به یک دستورالعمل نرم افزاری اجرا میشوند. مثل یک وقفه تایمر ساده یا یک وقفه watchdog timer.
پینهای وقفه در ESP8266
شما برای راه اندازی وقفه ی ماژول ESP8266 میتوانید طوری پروگرام یا برنامهریزی برد وای فای خود را انجام دهید که هنگام تغییر سطح منطقی یک یا چند پایه GPIO، اینتراپت ایجاد شود. درواقع تمام پینهای ورودی-خروجی برد ESP8266 به جز پین شماره 16، میتوانند به گونهای پیکربندی شوند که به عنوان ورودیهای درخواست وقفه عمل کنند.
همانطور که میدانید این ماژول یکی از قطعات پرکاربرد در پروژههای الکترونیکی و اینترنت اشیاء به حساب میآید و ما در آکادامی روبوایکیو با ارائه آموزشهایی مرتبط مانند راه اندازی برد NodeMCU با آردوینو و انجام پروژههایی مانند کنترل دستگاههای الکترونیکی با موبایل توسط ساخت وب سرور و پروگرام esp8266 بهصورت بی سیم با روش OTA توانستیم شما را با چندین نمونه از کاربردهای این ماژول آشنا کنیم. و حالا در این مسیر نوبت به آن رسیده تا با ایجاد و راه اندازی وقفه ی ماژول ESP8266، گامی جدید برداریم، پس با ما تا انتهای این پروژه همراه باشید.
نحوه تنظیم وقفه در آردوینو با یک پین GPIO
در نرمافزار IDE آردوینو، ما برای فعالسازی اینتراپت یک پین، از تابع وقفه ()attachInterrupt به شکل زیر استفاده میکنیم:
attachInterrupt(GPIOPin, ISR, Mode);
این تابع وقفه در آردوینو شامل سه آرگومان ورودی است:
- GPIOPin: پایه GPIO را به عنوان ورودی وقفه تنظیم میکند و برای ماژول ESP8266 مشخص میکند بر کدام پایه نظارت داشته باشد.
- ISR: نام تابعی است که هر زمان وقفه رخ میدهد، فراخوانی خواهد شد.
- Mode: نوع فعال کردن وقفه را مشخص میکند و میتواند پنج حالت مختلف داشته باشد که در ادامه هر یک توضیح دادهشدهاست:
LOW | زمانی که سطح منطقی پین به LOW تغییر کند، وقفه رخ میدهد. |
HIGH | زمانی که سطح منطقی پین به HIGH تغییر کند، وقفه رخ میدهد. |
CHANGE | زمانی که سطح منطقی پین از LOW به HIGH یا بالعکس از HIGH به LOW تغییر کند، وقفه رخ میدهد. |
FALLING | زمانی که زمانی که سطح منطقی پین از HIGH به LOW تغییر کند، وقفه رخ میدهد. |
RISING | زمانی که سطح منطقی پین از LOW به HIGH تغییر کند، وقفه رخ میدهد. |
تابع روتین سرویس وقفه در آردوینو (Interrupt Service Routine)
تابع ISR ، از توابع روتین هنگام راهاندازی وقفه در آردوینو است و هرزمان که وقفهای بر روی پین GPIO رخ دهد، این تابع اجرا میشود که به صورت زیر مورد استفاده قرار میگیرد.
void ICACHE_RAM_ATTR ISR() {
Statements;
}
توابع ISRs در ماژول ESP8266 توابع خاصی هستند و قواعد منحصر به فردی دارند که اکثر توابع دیگر ندارند. از جمله این قواعد عبارتند از:
- یک تابع ISR نمیتواند هیچ پارامتری داشته باشد و نیز نمیتواند چیزی برگرداند.
- توابع ISR باید تا حد امکان کوتاه و سریع باشند، زیرا اجرای عادی برنامه را متوقف میکنند.
- طبق مستندات ESP8266، این توابع باید مشخصه ICACHE_RAM_ATTR داشته باشند.
مشخصه ICACHE_RAM_ATTR چیست؟
زمانیکه ما با ICACHE_RAM_ATTR به بخشی از یک کد، پرچم (فلگ) اضافه میکنیم، کد کامپایل شده در حافظه رم داخلی ماژول ESP8266 قرار میگیرد و درغیر اینصورت کد در حافظه فلش نگهداری میشود. توجه داریم که حافظه فلش در ماژول ESP8266 بسیار کندتر از حافظه رم داخلی است.
تصور کنید دستور وقفه ای که میخواهیم اجرا شود یک روتین سرویس وقفه (ISR) باشد که معمولاً میخواهیم بلافاصله اجرا شود، اگر پردازنده معطل بارگیری ISR از حافظه فلش شود، ممکن است همه چیز اشتباه انجام گیرد، بنابراین با استفاده از این مشخصه میتوانیم بهشکل سریعتری وقفه را در پردازنده راه اندازی کنیم.
ایجاد اتصالات سخت افزاری راه اندازی وقفه در آردوینو با ماژول ESP8266
تئوری کافیست! بیایید یک مثال عملی انجام دهیم:
دراین مرحله برای شروع، یک تک سوئیچ را به پین شماره 12 (D6) برد توسعه NodeMCU با ماژول وای فای ESP8266 متصل نمایید. نیازی به pullup نیست، چون برای این پین، پولآپ داخلی را فعال خواهیم کرد.
نمونه دستور توقف در آردوینو : یک وقفه ساده
برنامهای که در ادامه برای شما آماده کردهایم، نحوه استفاده از وقفه در محیط آردوینو با ماژول ESP8266 و نیز روش صحیح نوشتن یک تابع ISR را به شما نشان میدهد. این برنامه وضعیت پین GPIO شماره 12 (D6) را برای تشخیص لبه پایینرونده (FALLING) دنبال میکند. به عبارت دیگر بررسی میکند چه زمانی ولتاژ از سطح منطقی HIGH به LOW تغییر میکند، باتوجه به مدار بستهشده، این امر هنگام فشردن کلید اتفاق میافتد و در این زمان است که تابع ISR فراخوانی میشود. کد این تابع تعداد دفعاتی که کلید فشرده شدهاست را نیز میشمرد.
struct Button {
const uint8_t PIN;
uint32_t numberKeyPresses;
bool pressed;
};
Button button1 = {D6, 0, false};
void ICACHE_RAM_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;
}
}
ابتدا این کد را بر روی ماژول وای فای خود آپلود نمایید. سپس سریال مانیتور را باز کرده و بادریت را بر روی 115200 تنظیم کنید. با فشردن کلید، خروجی مشابه تصویر زیر دریافت خواهید کرد.
توضیحات کد ایجاد وقفه در آردوینو
ما در بخش ابتدایی برنامه یک ساختار (structure) تحت عنوان Button ایجاد میکنیم. این ساختار شامل سه عضو است: شماره پین، تعداد دفعاتی که کلید فشرده شدهاست و وضعیت کلید.
یک ساختار؛ مجموعهای از متغیرهاست که نوع متفاوت دارند (درحالیکه از نظر منطقی با هم مرتبط هستند) و تحت یک نام هستند.
struct Button {
const uint8_t PIN;
uint32_t numberKeyPresses;
bool pressed;
};
- پس از ایجاد ساختار Button، یک متغیر به نام button1 میسازیم و شماره پین را ” D6 ” ، تعداد دفعات فشرده شدن کلید را “0” و وضعیت پیشفرض کلید را با ” false ” مقداردهی میکنیم.
Button button1 = {D6, 0, false};
- کد زیر یک ISR است. همانطور که گفتیم، تابع ISR در ماژول ESP8266 باید مشخصه ICACHE_RAM_ATTR را داشته باشد.
ما باید در تابع ISR به آسانی شمارنده KeyPresses را یک واحد افزایش (++) و وضعیت کلید را به “True” تغییر دهیم.
void ICACHE_RAM_ATTR isr() {
button1.numberKeyPresses++;
button1.pressed = true;
}
- در تابع setup برنامه، ابتدا ارتباط سریال با کامپیوتر را مقداردهی که همان مقدار 115200 بوده و سپس پولآپ داخلی را برای پین D6 فعال میکنیم.
- سپس به ماژول وای فای ESP8266 میگوییم پین D6 را نظارت کند و هنگام تشخیص لبهی پایین رونده (FALLING)، یعنی زمانی که سطح منطقی این پین از HIGH به LOW تغییر کرد، تابع ISR فراخوانی شود.
void setup() {
Serial.begin(115200);
pinMode(button1.PIN, INPUT_PULLUP);
attachInterrupt(button1.PIN, isr, FALLING);
}
- در تابع loop برنامه، به سادگی با یک شرط بررسی میکنیم آیا کلید فشرده شدهاست یا خیر و اگر این اتفاق افتاده باشد، تعداد دفعات فشرده شدن کلید را چاپ میکنیم. در نهایت وضعیت کلید فشرده شده را به false تغییر میدهیم تا بتوانیم به دریافت وقفهها ادامه دهیم.
void loop() {
if (button1.pressed) {
Serial.printf("Button has been pressed %u times\n", button1.numberKeyPresses);
button1.pressed = false;
}
}
کنترل بانس کلید
مشکل رایجی که در هنگام کار با وقفه ها وجود دارد این است که آنها اکثر اوقات برای یک رویداد یکسان چندینبار راه اندازی میشوند. اگر به خروجی سریال در مثال بالا دقت کنید، متوجه خواهید شد که حتی زمانی که تنها یک مرتبه کلید را فشردهاید، شمارنده چندین بار افزایش یافتهاست.
برای درک اینکه چرا این اتفاق افتاده است، لازم است نگاهی به سیگنال وقفه بیندازیم. اگر وضعیت ولتاژ پین D6 را زمانی که کلید را فشار میدهیم با آنالیزگر سیگنال یا لاجیک آنالایزر(logic analyzer) بررسی کنیم، سیگنالی مشابه سیگنال زیر خواهیم داشت:
ممکن است شما احساس کنید که اتصال بلافاصله برقرار میشود، اما در حقیقت بخشهای مکانیکی درون کلید، قبل از اینکه در یک وضعیت مشخصی قرار گیرند چندین مرتبه به یکدیگر متصل میشوند و این باعث راه اندازی وقفه های متعدد میشود. این یک پدیده کاملاً مکانیکی است و بانس کلید (switch bounce) نامیده میشود. مانند پرتاب یک توپ که قبل از اینکه کاملاً روی زمین قرار گیرد، چندین مرتبه پرش میکند و بالا و پایین میرود.
البته درنظر داشته باشید که زمان تثبیت و پایداری این سیگنال بسیار کوتاه است و برای ما آنی به نظر میرسد، اما برای ماژول ESP8266 مدت زمان زیادی است و میتواند در آن بازه زمانی چندین دستور را اجرا کند.
ما برای جلوگیری از این اتفاق نیاز به انجام یک فرآیند برای حذف بانس یا پرش کلید داریم که این کار به دیبانس کردن (debouncing) معروف است. دو روش اصلی برای دیبانسینگ وجود دارد:
- سختافزاری: افزودن یک فیلتر RC برای نرم (کند) کردن تغییرات.
- نرمافزاری: نادیده گرفتن موقتی وقفههای بعدی برای یک مدت زمان کوتاه پس از اولین تحریک وقفه.
ما برای راه اندازی وقفه ی ماژول ESP8266 از روش دیبانس وقفه بهصورت نرم افزاری استفاده میکنیم. البته برای درک بهتر switch bounce و روشهای دیبانس گفتهشده میتوانید با مطالعه و یادگیری چگونگی ایجاد بانس کلید و نحوه حذف نویزهای حاصل از تغییر حالت کلید، این کار را انجام دهید.
کد آردوینو: نحوه دیبانس کردن یک وقفه
در اینجا، برنامه به گونهای نوشته شدهاست که نشان دهد چطور از طریق کدنویسی یک وقفه را debounce کنیم. در واقع در این برنامه اجازه میدهیم تابع ISR با هر بار فشردن کلید تنها یک مرتبه اجرا شود، به جای اینکه چندین مرتبه اجرا شود.
خطوطی از برنامه که تغییر داده شدهاند با رنگ خاکستری، مشخص شدند.
struct Button {
const uint8_t PIN;
uint32_t numberKeyPresses;
bool pressed;
};
Button button1 = {D6, 0, false};
//variables to keep track of the timing of recent interrupts
unsigned long button_time = 0;
unsigned long last_button_time = 0;
void ICACHE_RAM_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 تنها یک مرتبه فراخوانی میشود.
توضیحات کد دیبانس وقفه
هر زمان که تابع ISR اجرا میشود، زمان فعلی بازگردانده شده توسط دستور millis در آردوینو با آخرین زمانی که تابع ISR فراخوانی شده، مقایسه میگردد.
اگر این زمان کمتر از 250 میلیثانیه باشد، ماژول ESP8266 وقفه را نادیده گرفته و فوراً به آنچه درحال انجام آن بود، برمیگردد. درغیراینصورت، کدِ درون عبارت شرطی if را اجرا میکند. یعنی شمارنده یک واحد افزایش مییابد و مقدار متغیر ” last_button_time ” به روزرسانی میشود. بنابراین، تابع مقدار جدیدی برای مقایسه در زمانی که در آینده راه اندازی میشود، خواهد داشت.
سخن پایانی
ما با ایجاد و راه اندازی وقفه ی ماژول ESP8266 در آردوینو متوجه شدیم که با استفاده از این راهکار، دیگر نیاز به نوشتن شرط و حلقههای تکرار شونده برای بررسی مداوم وضعیت پردازشگر در برنامه خود نداریم و فقط کافی است با مشخص کردن مدت زمانی در تابع وقفه، تعیین کنیم پردازنده با چک کردن منبع وقفه، به فرآیندهای مد نظر ما پاسخ دهد.
سپاسگذاریم از شما که تا انتهای این بخش ما را همراهی کردید و امیدواریم درک این مطالب برای شما آسان بوده باشد. چنانچه در یادگیری این روش به مشکلی برخوردهاید یا در مراحل انجام آن سوالی برایتان بوجود آمده، میتوانید پیشنهاد یا پرسش خود را در قسمت دیدگاه همین صفحه با تیم کارشناس روبوایکیو درمیان بگذارید و پاسخ خود را در اسرع وقت از ما دریافت کنید.