اغلب در یک پروژه می خواهید ESP32 برنامه عادی خود را در حالی که به طور مداوم برای نوعی رویداد نظارت می کند انجام دهد. یکی از راه حل های رایج برای این امر استفاده از وقفه است.
وقفه در ESP32
ESP32 حداکثر 32 اسلات وقفه برای هر هسته فراهم می کند. هر وقفه دارای سطح اولویت خاصی است و می توان آن را به دو نوع طبقه بندی کرد.
وقفه های سخت افزاری – این وقفه ها در پاسخ به یک رویداد خارجی رخ می دهند. به عنوان مثال، یک وقفه GPIO (زمانی که یک کلید فشار داده می شود) یا یک وقفه لمسی (زمانی که لمس تشخیص داده می شود).
وقفه نرم افزار – این وقفه ها در پاسخ به یک دستورالعمل نرم افزار رخ می دهد. به عنوان مثال، یک وقفه ساده تایمر یا یک وقفه تایمر نگهبان (زمانی که تایمر تمام می شود).
ESP32 GPIO وقفه
در ESP32 میتوانیم یک تابع روتین سرویس وقفه تعریف کنیم که زمانی فراخوانی میشود که پین GPIO سطح منطقی خود را تغییر دهد.
تمام پین های GPIO در یک برد ESP32 را می توان طوری پیکربندی کرد که به عنوان ورودی درخواست وقفه عمل کند.
اتصال وقفه به پین GPIO
در آردوینو IDE، ما از تابعی استفاده می کنیم که attachInterrupt()
برای تنظیم وقفه بر اساس پین به پین نامیده می شود. نحو شبیه زیر است.
attachInterrupt(GPIOPin, ISR, Mode);
این تابع سه آرگومان را می پذیرد:
GPIOPin – پایه GPIO را به عنوان پایه وقفه تنظیم می کند که به ESP32 می گوید کدام پایه را نظارت کند.
ISR – نام تابعی است که با هر بار وقوع وقفه فراخوانی می شود.
حالت – تعیین می کند که چه زمانی وقفه باید راه اندازی شود. پنج ثابت به عنوان مقادیر معتبر از پیش تعریف شده اند:
کم | هر زمان که پین LOW باشد، وقفه را فعال می کند |
بالا | هر زمان که پین HIGH باشد، وقفه را فعال می کند |
تغییر دادن | هر زمان که پین مقدار خود را از HIGH به LOW یا LOW به HIGH تغییر می دهد، وقفه را فعال می کند |
سقوط | هنگامی که پین از HIGH به LOW می رود، وقفه را راه اندازی می کند |
رو به افزایش | هنگامی که پین از LOW به HIGH میرود، وقفه را راهاندازی میکند |
جدا کردن وقفه از پین GPIO
وقتی می خواهید ESP32 دیگر پین را نظارت نکند، می توانید detachInterrupt()
تابع را فراخوانی کنید. نحو شبیه زیر است.
detachInterrupt(GPIOPin);
روال سرویس را قطع کنید
روال سرویس وقفه (ISR) تابعی است که هر بار که وقفه ای در پین GPIO رخ می دهد فراخوانی می شود.
نحو آن مانند زیر است.
void IRAM_ATTR ISR() {
Statements;
}
ISR ها در ESP32 انواع خاصی از توابع هستند که قوانین منحصر به فردی دارند که اکثر توابع دیگر ندارند.
- یک ISR نمی تواند هیچ پارامتری داشته باشد و نباید چیزی را برگرداند.
- ISRها باید تا حد امکان کوتاه و سریع باشند زیرا اجرای نرمال برنامه را مسدود می کنند.
IRAM_ATTR
طبق مستندات ESP32 باید این ویژگی را داشته باشند .
IRAM_ATTR چیست؟
هنگامی که یک قطعه کد را با IRAM_ATTR
ویژگی علامت گذاری می کنیم، کد کامپایل شده در RAM داخلی (IRAM) ESP32 قرار می گیرد. در غیر این صورت کد در فلش نگهداری می شود. و فلش در ESP32 بسیار کندتر از رم داخلی است.
اگر کدی که می خواهیم اجرا کنیم یک روال سرویس وقفه (ISR) است، معمولاً می خواهیم آن را در اسرع وقت اجرا کنیم. اگر مجبور بودیم «منتظر» باشیم تا ISR از Flash بارگیری شود، ممکن است همه چیز به طرز وحشتناکی اشتباه پیش برود.
اتصال سخت افزاری
تئوری بس است! بیایید به یک مثال عملی نگاه کنیم.
بیایید یک دکمه فشاری را به GPIO#18 (D18) در ESP32 وصل کنیم. برای این پین نیازی به کشش ندارید زیرا ما پین را به صورت داخلی به سمت بالا می کشیم.
کد مثال: وقفه ساده
طرح زیر استفاده از وقفه ها و روش صحیح نوشتن روتین سرویس وقفه را نشان می دهد.
این برنامه GPIO#18 (D18) را برای لبه سقوط تماشا می کند. به عبارت دیگر، به دنبال تغییر ولتاژ از منطق بالا به منطق پایین است که با فشار دادن دکمه رخ می دهد. وقتی این اتفاق می افتد تابع 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 باز کنید. با فشار دادن دکمه خروجی زیر را دریافت خواهید کرد.
توضیح کد
در ابتدای طرح ساختاری به نام Button
. این ساختار دارای سه عضو است – شماره پین، تعداد فشار کلید و حالت فشرده. FYI، یک ساختار مجموعه ای از متغیرها از انواع مختلف (اما از نظر منطقی مرتبط با یکدیگر) تحت یک نام واحد است.
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;
}
در قسمت تنظیمات کد، ابتدا ارتباط سریال با رایانه شخصی را مقداردهی اولیه می کنیم و سپس pullup داخلی را برای پین D18 GPIO فعال می کنیم.
در مرحله بعد به ESP32 می گوییم که پین D18 را نظارت کند و isr
هنگامی که پین از HIGH به LOW می رود، یعنی لبه FALLING، روال سرویس وقفه را فراخوانی کند.
Serial.begin(115200);
pinMode(button1.PIN, INPUT_PULLUP);
attachInterrupt(button1.PIN, isr, FALLING);
در قسمت حلقه کد به سادگی چک می کنیم که آیا دکمه فشار داده شده است یا خیر و سپس تعداد دفعات فشردن کلید را تا کنون پرینت می کنیم و حالت فشرده شدن دکمه را روی false قرار می دهیم تا بتوانیم به دریافت وقفه ها ادامه دهیم.
if (button1.pressed) {
Serial.printf("Button 1 has been pressed %u times\n", button1.numberKeyPresses);
button1.pressed = false;
}
مدیریت Switch Bounce
یک مشکل رایج در مورد وقفه ها این است که اغلب برای یک رویداد چندین بار تحریک می شوند. اگر به خروجی سریال مثال بالا نگاه کنید، متوجه می شوید که حتی اگر دکمه را فقط یک بار فشار دهید، شمارنده چندین بار افزایش می یابد.
برای اینکه بفهمید چرا این اتفاق می افتد، باید به سیگنال نگاهی بیندازید. اگر در حین فشار دادن دکمه، ولتاژ پین روی آنالایزر سیگنال را کنترل کنید، سیگنالی مانند زیر دریافت خواهید کرد:
ممکن است احساس کنید که تماس بلافاصله برقرار می شود، اما در واقع قطعات مکانیکی داخل دکمه قبل از اینکه در حالت خاصی قرار گیرند چندین بار با هم تماس پیدا می کنند. این باعث می شود چندین وقفه ایجاد شود.
این یک پدیده کاملاً مکانیکی است که به عنوان ” جهش سوئیچ ” شناخته می شود، مانند انداختن یک توپ – چندین بار قبل از اینکه در نهایت روی زمین فرود آید، پرش می کند.
زمان تثبیت سیگنال بسیار سریع است و برای ما تقریباً آنی به نظر می رسد، اما برای ESP32 این مدت زمان زیادی است. می تواند چندین دستورالعمل را در آن بازه زمانی اجرا کند.
فرآیند حذف جهش سوئیچ را ” باززدایی ” می نامند. دو راه برای رسیدن به این هدف وجود دارد.
- از طریق سخت افزار : با افزودن یک فیلتر RC مناسب برای صاف کردن انتقال.
- از طریق نرم افزار : با نادیده گرفتن موقت وقفه های بیشتر برای مدت کوتاهی پس از شروع اولین وقفه.
کد مثال: حذف یک وقفه
در اینجا طرح بالا بازنویسی شده است تا نشان دهد چگونه یک وقفه را به صورت برنامهریزی بازگردانیم. در این طرح ما اجازه می دهیم 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 برای هر فشار یک دکمه فقط یک بار فراخوانی می شود.
توضیح کد:
این اصلاح کار می کند زیرا هر بار که ISR اجرا می شود، زمان جاری بازگشتی توسط millis()
تابع را با زمانی که ISR آخرین فراخوانی شده است مقایسه می کند.
اگر در 250 میلیثانیه باشد، ESP32 وقفه را نادیده میگیرد و بلافاصله به کاری که انجام میداد برمیگردد. در غیر این صورت، کد را در دستور اجرا میکند if
و شمارنده را افزایش میدهد و last_button_time
متغیر را بهروزرسانی میکند، بنابراین تابع مقدار جدیدی برای مقایسه با زمانی که در آینده راهاندازی میشود، دارد.
ترجمه از https://lastminuteengineers.com/handling-esp32-gpio-interrupts-tutorial/
آخرین دیدگاهها