آموزش کامل وقفه ها در آردوینو
آشنایی با وقفه و مفهوم آن
وقفه ها در آردوینو و دستورات مرتبط با آن
جل یک مثال عملی از وقفه
بررسی یک مشکل متداول در وقفه ها و رفع آن
مزایا و اولویت در وقفه ها
مقدمه
در ادامه مجموعه جلسات آموزشی دوره آردوینو، قصد داریم تا با وقفه ها در آردوینو آشنا شویم. پیشنهاد می شود حتما در برنامه نویسی های خود از این موضوع استفاده کنید و سطح کدنویسی خود را بالا ببرید و به صورت حرفه ای کد نویسی کنید.
قبل از هر چیز، این دوره از مجموعه جلسات دوره آموزشی Arduino می باشد که در زیر، لینک این دوره آموزشی آورده شده است :
آشنایی با وقفه ها
اگه بخواهیم خیلی ساده وقفه را توضیح دهیم ، میتوانیم بگوییم که وقفه یک روشی است که پردازنده ها از آن استفاده می کنند و در آن پردازنده در حالی که برنامه عادی خود را اجرا می کند، به صورت پیوسته نیز بر وقوع یک حادثه دیگر (که به آن وقفه می گوییم) نظارت می کند. (اگر هنوز مفهومش را نفهمیده اید نگران نباشید. در ادامه این موضوع را دقیق تر می فهمید.)
به صورت کلی دو نوع وقفه وجود دارد:[post_shop]
وقفه های سخت افزاری: این وقفه ها وقتی اتفاق می افتند که یک اتفاق خارجی رخ داده باشد. مثلا یک پایه از حالت صفر به حالت 1 رفته باشد.
وقفه های نرم افزاری: این وقفه ها در اثر یک دستورالعمل نرم افزاری رخ می دهند.
در حالت کلی، میکروکنترلرهای AVR (که اکثر بردهای آردوینو نیز از آنها ساخته شده است) دارای قابلیت وقفه های نرم افزاری نمی باشند. به همین علت ما در این جلسه تمرکزمان را بر روی وقفه های سخت افزاری قرار می دهیم.
شکل زیر نمونه ای از یک وقفه سخت افزاری را نشان می دهد.
آشنایی با وقفه ها در آردوینو
وقتی که یک وقفه اتفاق می افتد، پردازنده ابتدا وضعیت برنامه ای که در حال اجرای آن است را ذخیره میکند و یادش می ماند که برنامه را تا کجا اجرا کرده است و سپس یک کد دیگر که مربوط به وقفه است را اجرا می کند. (به این کد کوچک معمولا interrupt service routine گفته می شود.) پس از اجرای این کد کوچک، پردازنده برمیگردد و ادامه کد خود را اجرا می کند.
برنامه نویس در برنامه خود یک تکه کد را تعریف می کند که وقتی یک وقفه اتفاق اتفاد آن تکه کد اجرا شود. در آردوینو ما از یک تابع به نام attachInterrupt برای انجام این کار استفاده می کنیم. در زیر این تابع آورده شده است :
attachInterrupt(digitalPinToInterrupt(pin), ISR, mode)
این تابع دارای سه پارامتر ورودی است :
پارامتر 1 (digitalPinToInterrupt(pin : شماره پین وقفه که به پردازنده می گوید که به عملکرد کدام پایه نظارت داشته باشد. پردازنده انتظار دارد که در این پایه وقفه اتفاق بیفتد. این پین بسته به نوع میکروکنترلری است که شما استفاده می کنید.
پارامتر 2 ISR : این پارامتر در واقع نام یک تابع است. وقتی که وقفه اتفاق افتاد، کدهای داخل این تابع اجرا می شود. (کلمه ISR مخفف Interrupt Service Routine می باشد.)
پارامتر 3 mode : این پارامتر تعیین کننده ی نوع وقفه می باشد. مثلا وقتی مثلا پایه مشخص شده، صفر منطقی یا یک منطقی شد، وقفه اتفاق بیفتد.
حل یک مثال عملی
در این مرحله می خواهیم یک مدار را به صورت عملی ببندیم و آن را تست کنیم. در شکل زیر این مدار آورده شد است. (نگران رنگ قرمز برد آردوینو نباشید. همه چیز مشابه همان آردوینو عادی می باشد.)
اگه خوب به شکل بالا دقت کنید، مشاهده میکنید که LED قرار داده شده اضافی می باشد و ما میتوانستیم از LED ای که بر روی خود برد وجود دارد استفاده کنیم. ولی به منظور نمایش بهتر، یک LED به صورت جداگانه متصل کردیم.
قبل از هر چیز لطفا مدار بالا را ببندید. در ادامه کدی آورده شده است که در این کد ما به صورت پیوسته مقدار LOW را به پایه مربوط به LED ارسال می کند. همچنین ما یک وقفه به پایه 2 متصل کرده ایم. به این پایه یک کلید متصل شده است که وقتی کلید فشار داده شود (و به سطح 1 منطقی برسد) میخواهیم روتین مربوط به وقفه اجرا شود و LED روشن گردد.
اکثر بردهای آردوینو دارای دو منبع وقفه خارجی می باشند. وقفه 0 که به پایه 2 متصل شده و وقفه 1 که به پایه 3 متصل شده است. برخی از بردها مثل Mega2560 تعداد بیشتری وقفه دارند.
/* Simple Interrupt Example 1 by: Jordan McConnell SparkFun Electronics created on 10/29/11 */ int ledPin = 13; // LED is attached to digital pin 13 int x = 0; // variable to be updated by the interrupt void setup() { //enable interrupt 0 (pin 2) which is connected to a button //jump to the increment function on falling edge pinMode(ledPin, OUTPUT); attachInterrupt(0, increment, RISING); Serial.begin(9600); //turn on serial communication } void loop() { digitalWrite(ledPin, LOW); delay(3000); //pretend to be doing something useful Serial.println(x, DEC); //print x to serial monitor } // Interrupt service routine for interrupt 0 void increment() { x++; digitalWrite(ledPin, HIGH); }
کد بالا را بر روی برد خود آپلود کنید. (اگر برد و وسایل را هم اکنون در اختیار دارید ، همین الان این کار را بدون هیچ وقفه ای انجام دهید تا به صورت عملی با وقفه آشنا شوید.)
در حلقه loop کد بالا، هر سه ثانیه یک سیگنال با سطح صفر منطقی به پایه شماره 13 ارسال می کند. در همین حال برنامه وضعیت پایه شماره 2 (که مربوط به وقفه صفر است) را نیز برای یک لبه بالا رونده رصد می کند. در واقع برنامه حواسش است که آیا این پایه مقدارش از صفر منطقی به یک منطقی (5 ولت) تغییر کرده است یا خیر (این حالت زمانی اتفاق می افتد که ما کلید را فشار دهیم.)
وقتی که وقفه اتفاق اتفاد تابع increment اجرا می شود. اگه به داخل این تابع نگاه کنید میبینید که اتفاق خاصی نمی افتد و صرفا یک متغیر به نام x به مقدارش یک واحد اضافه می شود و سطح منطقی پایه 13 به 1 می شود. یعنی پیغام روشن شدن به LED ارسال می گردد.
اگر این کد را اجرا کنید مشاهده میکنید که وقتی کلید را فشار می دهید، LED برای یک مدت زمان رندم روشن می ماند. اما این مدت زمان هیچوقت از 3 ثانیه بیشتر نمی شود. این که LED چه مدت زمان روشن بماند، بستگی به این دارد که وقفه در کد شما چه زمانی اتفاق بیفتد. مثلا اگر در حالی که نیمی از زمان سه ثانیه سپری شده است ، ما کلید را بزنیم ، LED ما یک فرمان روشن شدن می گیرد و به ادامه برنامه باز میگردد.
هنوز 1.5 ثانیه از زمان 3 ثانیه باقی مانده است. به همین علت 1.5 ثانیه دیگر نیز LED روشن می ماند. اما اگر آخر 3 ثانیه وقفه اجرا شود، LED زمان کمتری روشن می ماند. شکل زیر را ببینید:
حل یک مشکل متداول در وقفه ها
یکی از مشکلاتی که خیلی وقت ها هنگام کار با وقفه ها پیش می آید این است که وقتی ما یک بار کلید را فشار می دهیم، چند بار وقفه اتفاق می افتد. مثلا در کد بالا وقتی شما یک بار کلید را فشار می دهید، انتظار دارید که یک واحد نیز به مقدار x نیز اضافه شود. در حالی که چند بار این اتفاق رخ می دهد و x چند واحد اضافه می شود.
برای این که به این اتفاق نگاهی دقیق تر داشته باشیم، شکل سیگنال را باید مشاهده کنیم. وقتی ما یک کلید فشار میدهیم ، سیگنال شکلی شبیه به شکل زیر را دارد:
همانطور که در شکل بالا نیز مشاهده میکنید، پس از این که کلید فشار داده شد، چند بار پرش از سطح 0 به سطح 1 داشته ایم. در حالی که ما انتظار داریم فقط یک بار این اتفاق بیفتد. به این موضوع نویز ، bounce (پرش) گفته می شود. در واقع قطعات مکانیکی کلید طوری هستند که وقتی میخواهند در یک وضعیت ثابت قرار بگیرند، چندین بار contact اتفاق می افتد و این موضوع باعث ایجاد این سیگنال و چندین وقفه می شود.
برای حل این مشکل چند راه حل وجود دارد. راه حل سخت افزاری این موضوع آن است که یک مدار RC قرار دهیم و رفتن از سطح 0 به سطح 1 ، نرم افزار انجام شود. راه حل نرم افزاری نیز این است که دستوری قرار دهیم تا اگر فاصله بین دو وقفه از یک حد مشخص کمتر بود، وقفه آخر در نظر گرفته نشود. در کد زیر روش نرم افزاری آورده شده است :
/* Simple Interrupt example 2 by: Jordan McConnell SparkFun Electronics created on 10/29/11 */ int ledPin = 13; // LED is attached to digital pin 13 int x = 0; // variable to be updated by the interrupt //variables to keep track of the timing of recent interrupts unsigned long button_time = 0; unsigned long last_button_time = 0; void setup() { //enable interrupt 0 which uses pin 2 //jump to the increment function on falling edge pinMode(ledPin, OUTPUT); attachInterrupt(0, increment, RISING); Serial.begin(9600); //turn on serial communication } void loop() { digitalWrite(ledPin, LOW); delay(3000); //pretend to be doing something useful Serial.println(x, DEC); //print x to serial monitor } // Interrupt service routine for interrupt 0 void increment() { button_time = millis(); //check to see if increment() was called in the last 250 milliseconds if (button_time - last_button_time > 250) { x++; digitalWrite(ledPin, HIGH); last_button_time = button_time; } }
کد بالا دقیقا همان کد قبلی است با یک تفاوت ریز.
در تابع مربوط به وقفه، یک شرط if اضافه شده است و آن هم این است که فاصله بین دو قفه باید از 250 میلی ثانیه بیشتر باشد.
به عبارت دقیق تر ما ابتدا زمان لحظه ای که وقفه اتفاق می افتد را ثبت میکنیم و در متغیر button_time قرار می دهیم. در حلقه if نیز این زمان ثبت می شود. حال وقتی دوباره یک وقفه دیده شد، زمان وقفه جدید ضبط می شود و مشاهده میکنیم که آیا این زمان با زمان وقفه قبل تفاوت 250 میلی ثانیه ای دارد یا خیر. اگر داشت حلقه اجرا می شود ، اگر ادامه برنامه را اجرا می کنیم.
اولویت در وقفه ها
یکی از موضوعاتی که همیشه جای سوال بوده این است که اگر دو وقفه همزمان اتفاق بیفتد، کدام یک زودتر اجرا می شود. در اینجا بحثی مطرح می شود به نام اولویت وقفه ها یا interrupt priority . اکثر میکروکنترلرهای AVR از این موضوع پشتیبانی نمی کنند. ولی اگر مطمین هستید که میکروکنترلر شما از این موضوع پشتیبانی می کند، اولویت اتفاق افتادن وقفه ها بر حسب آدرس برداری آنها تعیین می شود. آنهایی که آدرس برداری پایین تری دارند، وقفه مربوط به آنها اولویت بالاتری خواهد داشت. اولویت Reset از همه وقفه های دیگر بالاتر می باشد.
اگر دوست دارید در این مورد اطلاعات بیشتری کسب کنید، میتوانید به دیتاشیت میکروکنترلر خود مراجعه کنید.
[/post_shop]
مزایای استفاده از وقفه ها در آردوینو
شاید این سوال برایتان پیش بیاید که اصولا چرا باید از وقفه ها استفاده کنیم ؟ وقفه ها چه مزیتی را در اختیار ما قرار می دهند ؟ چرا به صورت عادی از همان دستور digitalRead استفاده نمی کنیم و وضعیت پایه شماره 2 را مرتبا چک نمی کنیم تا اگر وضعیت پایه تغییر کرد، کاری را انجام دهیم ؟
پاسخ به این سوال به مساله مورد نظر شما برمی گردد. اگر شما به این موضوع نیاز داشته باشید که فقط در یک لحظه مشخص به وضعیت پین نیاز داشته باشید، احتمالا digitalRead برای این موضوع کافی خواهد بود. اگر به صورت پیوسته بخواهید وضعیت پین را چک کنید، باید در یک حلقه while از دستور digitalRead استفاده کنید. اگر چه ممکن است اطلاعاتی در آن میان هم از دست برود. (چون در یک لحظه ممکن است اتفاقی بیفتد و ما دستور digitalRead را در لحظه قبل چک کرده باشیم.) این اطلاعات از دست رفته ممکن است در عمل برای ما حیاتی باشد. اینجاست که وقفه به کمک ما می آید.
علاوه بر این موضوع، خواندن مداوم digitalRead باعث تلف شدن وقت پردازنده می شود در حالی که میتوان کدهای مفید تری را در این زمان اجرا کرد.
در این جلسه با وقفه ها آشنا شدیم و گفتیم که وقفه ها روند عادی برنامه را متوقف می کند و یک تابع دیگر با نام Interrupt Service routine را اجرا می کنند.
. علاوه بر آن با مشکل bounce در وقفه ها نیز روبرو شدیم که باعث ایجاد چندین وقفه می شد در حالی که باید یک بار وقفه اتفاق بیفتد.علاوه بر آن یادگرفتیم که چگونه میتوانیم با تکنیک های نرم افزاری آن را رفع کنیم.
در انتها نیز توضیح دادیم که وقفه ها میتوانند دارای اولویت باشند و اولویت آنها بر حسب آدرس برداری آنها تعیین می شود.
خوب دوستان برای این جلسه هم کافی است. مثل همیشه میتوانید ما را در تلگرام و یا اینستاگرام دنبال کنید.
دیدگاهتان را بنویسید