سرویس وقفه (Interrupt)، امکان ویژهای است که برای برخی دستورات یا I/Oها میتوانیم از آن استفاده کنیم. در این صورت آن دستور یا I/O به نوعی بر تمام روندها و دستورات و فعالیتهای پروسسور اولویت مییابد و میتواند سرویسدهی به آنها را متوقف کرده و خود از پروسسور سرویس و پاسخ دریافت کند. به عنوان مثال پردازندهای که در حال اجرای نرمال یک فرآیند است اما میتواند در حین آن مدام وقوع یا عدم وقوع اتفاق یا دستور خاصی را نیز بررسی کند. زمانی که این واقعهی بخصوص رخ دهد، پردازنده روند نرمال خود را متوقف کرده و ابتدا به این واقعه رسیدگی میکند. در این حالت گفته میشود که یک وقفهی خارجی اتفاق افتاده است. مثلا یک سنسور که به پردازنده متصل است میتواند در روند آن وقفه ایجاد کند. پس از اتمام سرویسدهی و رسیدگی به این اتفاق خاص، پردازنده مجددا روند نرمال اجرای فرآیند خود را ادامه میدهد.
در پروژهای که برای این جلسه انتخاب کردهایم، قصد داریم نحوهی استفاده و کار با وقفهها را در میکروکنترلر STM32 یاد بگیریم. به این منظور، از یک کلید فشاری به عنوان عامل ایجاد وقفهی خارجی در روند نرمال میکروکنترلر استفاده میکنیم. روند عادی کار میکرو را به این صورت تعریف میکنیم که اعدادی را به صورت افزایشی از صفر شروع به شماردن کند و آنها را بر روی یک نمایشگر LCD 16×2 نمایش دهد. با فشرده شدن کلید یا اعمال وقفهی خارجی، این روند متوقف شده، LED متصل به میکرو روشن میشود و بر روی LCD نیز کلمهی INTERRUPT نمایش داده میشود. این وقفه تا زمانی که کلید فشاری آزاد نشود ادامه مییابد.
انواع وقفهها و ISRها
وقفه ها را میتوان به طور کلی در دو گروه تقسیمبندی کرد.
وقفههای سخت افزاری
اگر سیگنال وقفهی ارسالی به پروسسور از طرف یک دیوایس خارجی مثلا یک کلید یا سنسور و یا حتی سختافزار جانبیای باشد که به پروسسور متصل است و میتواند برای آن سیگنال ارسال کند. این سیگنال پروسسور را متوقف ساخته و به او میگوید که عملیات و دستوری را که در سرویس وقفه (ISR) گفته شده است انجام دهد.
وقفههای نرم افزاری
وقفههایی که توسط نرمافزارهای موجود در سیستم برای پروسسور ارسال میشوند.
روتین سرویس وقفه (ISR)
سرویس ISR یا مدیریت وقفه، سرویسی است شامل مجموعهای از دستورات که هر زمان وقفهای رخ بدهد فعال شده و به پروسسور کمک میکند که روند نرمال را متوقف و رویداد خواسته شده در وقفه را اجرا نماید. همچنین پس از اتمام عملیات مورد نظر در وقفه، کمک میکند که پروسسور مجددا به کاری که پیش از رخداد وقفه به انجام آن مشغول بود را از سر بگیرد.
آشنایی با سینتکسهای وقفه در STM32
سرویس ISR در بوردهای آردوینو دارای سینتکسهای زیر است.
- (attachInterrupt (digitalPinToInterrupt(pin)
- ISR
- mode
و از آنجا که برای پروگرم کردن STM32 نیز از Arduino IDE استفاده میکنیم، میتوانیم همین سینتکس را برای STM32 نیز به کار بگیریم.
- (digitalPinToInterrupt(pin: در بورد Arduino Uno پینهای ۲ و ۳ و در بوردهای Mega پینهای ۲، ۳، ۱۸، ۱۹، ۲۰، ۲۱ مختص وقفه هستند. در میکروکنترلر STM32 هر کدام از پینهای GPIO را میتوانیم برای اعمال وقفه استفاده کنیم. فقط کافی است که در کدی که مینویسیم، شمارهی پینی که به این منظور استفاده میکنیم را مشخص کنیم. فقط در حالتی که بخواهیم در یک زمان بیش از یک وقفه داشته باشیم، لازم است تدابیری را اتخاذ کنیم.
- ISR: تابعی که در هنگام رخداد وقفههای خارجی فراخوانی میشود تا آن را مدیریت کند. این تابع هیچ آرگونی ندارد و از void نیز برای آن استفاده نمیکنیم.
- Mode: نوع transition مورد استفاده برای trig کردن وقفهی ایجاد شده:
- RISING: زمانی وقفه را آغاز میکند که پین مربوط به آن از وضعیت LOW به وضعیت HIGH تغییر پیدا کرده باشد.
- FALLING: زمانی وقفه را آغاز میکند که پین مربوط به آن از وضعیت HIGH به وضعیت LOW تغییر پیدا کرده باشد.
- CHANGE: هر زمان که پین مربوط به وقفه از تغییر وضعیت بدهد (چه از HIGH به LOW و چه برعکس)، وقفه را آغاز میکند.
دو نکتهی مهم برای استفاده از وقفه
- تابع ISR باید تا حد ممکن تابع کوچکی باشد.
- تابع تاخیر (()Delay) در درون تابع ISR کار نمیکند و نباید از آن استفاده شود.
وسایل مورد نیاز برای اجرای پروژه:
- میکروکنترلر STM32F103C8
- کلید فشاری
- LED
- مقاومت 10K
- LCD 16×2
نمودار مدار و اتصالات
یک پایهی کلید فشاری را به پین ۳.۳ ولت میکرو و پایه دیگر آن را از طریق یک مقاومت پول داون به پایهی PA0 میکروکنترلر وصل میکنیم. علت استفاده از مقاومت پول داون این است که با فشار دادن یا رها کردن کلید، میکرو تنها ولتاژهای HIGH و LOW را دریافت کند و نه سطوح میانی ولتاژ را. در صورتی که از این مقاومت استفاده نکنیم، میکرو تمام ولتاژهای میانی را هم دریافت کرده و برای تصمیمگیری در مورد آنها سردرگم خواهد شد.
اتصال بین میکروکنترلر و LCD
جدول زیر اتصالات میان میکرو و نمایشگر LCD 16X2 را نشان داده است.
پروگرم کردن میکروکنترلر STM32 برای استفاده از سرویس وقفه
در همین ابتدا بگوییم که پروگرم کردن میکرو برای این پروژه بسیار ساده است و اصلا در مورد آن نگران نباشید. طبق روال همیشگی، پس از توضیح قسمتهای مهم کد، کد کامل پروژه را هم در انتهای مطلب در اختیارتان قرار دادهایم. برای پروگرم کردن STM32، به پروگرمر FTDI نیاز نداریم و آن را از طریق اتصال پورت USB به کامپیوتر و Arduino IDE پروگرم میکنیم. اگر با این روش پروگرم کردن میکروی STM32 آشنا نیستید، میتوانید به لینک زیر مراجعه کنید.
بسیار خب، همانطور که در ابتدای آموزش گفتیم، پروژه را به این صورت تعریف میکنیم که روند نرمال پروسسور شمارش از صفر رو به بالا و نمایش هر عدد بر روی LCD است. در هر لحظهای که کلید فشار داده شود، این روند متوقف شده، LED روشن میشود و بر روی LCD نیز کلمهی INTERRUPT را نمایش میدهیم.
در ابتدا پایههایی که LCD به آنها متصل است را مشخص میکنیم. اگر شما احیانا LCD را به پایههای دیگری وصل کرده باشید این قسمت را متناسب با آن تغییر دهید.
const int rs= PB10,en= PB11,d4= PB0,d5= PB1,d6= PC13,d7= PC14;
سپس هدر فایل مربوط به LCD را میآوریم. این هدر فایل، کتابخانهای را که در آن چگونگی تعامل میکروکنترلر STM32 و LCD توضیح داده شده است، فراخوانی میکند.
همچنین اطمینان حاصل میکنیم که تابع LiquidCrystal دقیقا با همان پینهایی فراخوانی شود که در قسمت بالا مشخص کردهایم.
include<LiquidCrystal.h> LiquidCrystal lcd (rs,en,d4,d5,d6,d7);
از متغیرهای Global برای انتقال دادهها بین ISR و برنامهی اصلی میکرو استفاده میکنیم. متغیر ledOn را به صورت volatile و از نوعی بولین تعریف میکنیم تا بتواند دو مقدار True و False را بپذیرد.
volatile boolean ledOn = false;
در بخش تابع ()void setup، ابتدا یک پیغام اولیه را به مدت ۲ ثانیه بر روی LCD نشان میدهیم و سپس آن را پاک میکنیم.
lcd.begin(16,2); lcd.print("CIRCUIT DIGEST"); delay(2000); lcd.clear();
در داخل همین تابع، پینهای ورودی و خروجی را هم باید مشخص کنیم. پین PA1 را به عنوان خروجی LED و پین PA0 را برای ورودی کلید فشاری مشخص میکنیم.
pinMode(PA1,OUTPUT) pinMode(PA0,INPUT)
یک متغیر را هم باید برای شمارندهی میکرو که قرار است از صفر شروع به شماردن کند تنظیم کنیم. مقدار اولیه ی این متغیر را صفر قرار میدهیم.
int i = 0;
حال به مهمترین قسمت که استفاده از تابع ()attachInterrupt است میرسیم. این تابع هم در همین قسمت استفاده میشود.
attachInterrupt(digitalPinToInterrupt(PA0),buttonPressed,CHANGE)
پین PA0 را به عنوان وقفهی خارجی تنظیم میکنیم و مود ISR را بر روی CHANGE. یعنی با هر تغییری در وضعیت این پین، تابع وقفه به نام buttonPressed فراخوانی میشود. شما میتوانید به دلخواه خود نام دیگری را نیز برای تابع وقفه انتخاب کنید. همینطور میتواند ISR را در مودهای دیگر آن نیز استفاده کنیم و یا حتی کلید را به پین دیگری به جز PA0 متصل کنید.
حال وارد بخش ()void loop میشویم. در این بخش یک متغیر i را به عنوان شمارنده قرار میدهیم و هر بار یکی به آن اضافه میکنیم. مقادیر آن را نیز بر روی LCD نمایش میدهیم.
lcd.clear(); lcd.print("NUMBER:"); lcd.print(i); ++i; delay(1000);
در این بخش مهمترین کار استفاده از تابع مدیریت وقفه (interrupt handler) است. این تابع باید بر اساس نامی باشد که در تابع ()attachInterrupt انتخاب کردهایم. نامی که در آنجا استفاده کرده بودیم buttonPressed بود پس در اینجا نیز تابعی با نام ()void buttonPressed ایجاد میکنیم.
void buttonPressed() { if(ledOn) { ledOn=false; digitalWrite(PA1,LOW); } else { ledOn = true; digitalWrite(PA1,HIGH); lcd.setCursor(0,1); lcd.print("Interrupt"); } }
چگونگی عملکرد تابع buttonPressed در سرویس وقفه
براساس اینکه متغیر بولین ledOn چه مقداری داشته باشد؛ چراغ LED روشن یا خاموش خواهد شد.
اگر مقدار این متغیر False باشد، LED همچنان خاموش باقی خواهد ماند. اما زمانی که مقدار آن True میشود، LED روشن شده و بر روی LCD نیز Interrupt نوشته خواهد شد.
نکته: گاهی اوقات به علت اثر دیبانس کلید، ممکن است به جای یک تریگر دو یا تعداد بیشتری محاسبه شود. این مسئله ناشی از ضعفهای مکانیکی کلیدهای فشاری و اسپایکهای متعدد ولتاژ در آنهاست و اشکالی متوجه نحوهی پروگرم کردن شما نیست. برای کاهش دادن این اثر پیشنهاد میکنیم که از فیلترهای RC استفاده شود.
ویدئوی زیر اجرای کامل پروژهی استفاده از سرویس وقفه در میکروکنترلر STM32 را نشان داده است.
کد
//INTERRUPTS IN STM32F103C8 //CIRCUIT DIGEST const int rs= PB10,en= PB11,d4= PB0,d5= PB1,d6= PC13,d7= PC14; // declaring pin names and pin numbers of lcd #include<LiquidCrystal.h> // including lcd display library LiquidCrystal lcd (rs,en,d4,d5,d6,d7); // setting lcd and its parameters volatile boolean ledOn = false; // variable declared as global void setup() { lcd.begin(16,2); // setting LCD as 16x2 type lcd.print("CIRCUIT DIGEST"); // puts CIRCUIT DIGEST IN LCD delay(2000); // delay time lcd.clear(); // clears lcd display pinMode(PA1,OUTPUT); // set pin PA1 as output pinMode(PA0,INPUT); // set pin PA0 as input int i = 0; // declare variable i and initiliaze with 0 attachInterrupt(PA0,buttonPressed,CHANGE); // function for creating external interrupts } void loop() // void loops runs continuously { lcd.clear(); // clears lcd display lcd.print("NUMBER:"); // puts NUMBER: in LCD display lcd.print(i); // prints the values of i in LCD ++i; // increments value of i delay(1000); // delays time } void buttonPressed() // { if(ledOn) // if statement depends on LedOn value { ledOn=false; // Makes ledOn false if it is True digitalWrite(PA1,LOW); // digital writs the low vale to PA1 pin makes led OFF } else { ledOn = true; // Makes ledOn True if it is False digitalWrite(PA1,HIGH); // digital writs the HIGH vale to PA1 pin makes led ON lcd.setCursor(0,1); // sets cursor at first column and second row lcd.print("Interrupt"); // puts INTERRUPT in LCD display } }
ویدئو
- منبع: ترجمه از سایت circuitdigest.com
امیدواریم این آموزش برای شما مفید واقع شده باشه. دیگر آموزشهای میکروکنترلرهای STM32 را نیز مطالعه کنید. کامنت یادتون نره 🙂
اگر این نوشته برایتان مفید بود لطفا کامنت بنویسید.