در قسمت پیشین از سری آموزش STM32 با توابع LL، در مورد حالتهای مختلف GPIO صحبت شد. در این قسمت میخواهیم با Exception ها و وقفه ها در HAL، چگونگی تغییر روند پردازنده و رفتن به روال وقفه و همچنین با اولویت وقفهها آشنا شویم. سپس به طور خاص از وقفه خارجی در یک پروژه نمونه استفاده کنیم.
رخداد یک Eception، هنگام روند عادی اجرای برنامه.
Exception چیست؟
هنگامیکه یک پردازنده در حالت عادی خودکار میکند، مقدار شمارنده برنامه (PC) پس از اجرای هر دستور افزایش مییابد بهگونهای که متناسب با فضای آدرس مربوط به دستورات، مقدار آن تغییر کرده و دستورات به ترتیب اجرا میشوند. دراینبین انشعابها و فراخوانی زیر روالها نیز وجود دارند که شمارنده برنامه را به مکان دیگری در حافظه منتقل میکنند.
اما این روند اجرا با وقوع Exception یا شرایط استثنایی، ممکن است تغییر کند. این شرایط استثنا برای توانمند ساختن پردازنده در مدیریت رخدادهای خاص در نظر گرفتهشدهاند. چند نمونه از این رخدادها عبارتاند از:
- وقفههای تولیدشده توسط دستگاههای خارجی.
- زمانی که پردازنده سعی میکند یک دستور تعریف شده را اجرا کند.
- هنگام استفاده از توابع مجاز سیستمعامل.
برای اینکه روند کار عادی پردازنده دچار اشکال نشود، ضروری است که در زمان رسیدگی به این Eception ها، ابتدا حالت قبلی پردازنده ذخیره شود. بدین طریق، پس از اجرای روال مربوط به رخداد پیش آمده، پردازش برنامه میتواند به حالت قبل بازگردد و اجرای دستورات از سر گفته شود.
در لیست زیر 7 نوع از Exception هایی که برای پردازندههای ARM تعریفشدهاند، آورده شده است:
- ریست: این شرایط زمانی رخ میدهد که پایه مربوط به ریست پردازنده فعال شود. شرایط مورد انتظار برای رخ دادن این نوع Exception، زمان روشن سیستم یا درزمانی است که پردازنده ریست میشود و شرایط روشن شدن تکرار خواهد شد. (ریست نرمافزاری با انشعاب به بردار ریست در آدرس 0x0000 انجام میشود.)
- دستور تعریفنشده: این شرایط زمانی اتفاق میافتد که دستور در حال اجرا، هم برای پردازنده و هم برای کمک پردازندههای متصل به آن، ناشناخته باشد.
- وقفه نرمافزاری (SWI): این حالت، یک دستور وقفه است که توسط کاربر تعریف میشود و بهصورت همزمان (synchronous) اجرا خواهد شد. این نوع Exception به برنامه در حال اجرا در حالت User این امکان را میدهد که اجازه دسترسی به عملیاتی را داشته باشد که تنها در حالت Supervisor مجاز هستند (مثل یک تابع RTOS)
- خطای Prefetch: زمانی اتفاق میافتد که پردازنده بخواهد دستوری را پردازش کند که fetch نشده (زیرا آدرس دستور غیرمجاز بوده) است.
- خطای داده: این حالت زمانی رخ میدهد که یک دستور انتقال داده بخواهد از/در یک آدرس غیرمجاز عمل store/load انجام دهد.
- IRQ: زمانی رخ میدهد که پین مربوط به درخواست وقفه خارجی در پردازنده، فعال شود (یعنی به منطق Low برود) و همچنین بیت I در رجیستر CPSR، صفر باشد.
- FIQ: این حالت زمانی اتفاق میافتد که پین مربوط به درخواست وقفه پرسرعت خارجی در پردازنده، فعال شود (یعنی به منطق Low برود) و همچنین بیت F در رجیستر CPSR، صفر باشد.
با توجه به صحبتهای انجامشده نتیجه میگیریم که وقفه درواقع زیرمجموعهای از Exception ها محسوب میشود.
وقفه یا Interrupt چیست؟
برای ارائه یک تعریف جامع برای وقفه میتوان گفت وقفه سیگنالی است که توسط منبع سختافزاری یا نرمافزاری و در زمان نیاز یک فرایند یا Event به رسیدگی پردازنده، تولید میشود. این سیگنال به پردازنده میگوید که یک فرایند مهم نیازمند پردازش است و باید در پروسه فعلی وقفه ایجاد شود و به این فرایند رسیدگی گردد. در دستگاههای ورودی/خروجی، یکی از خطوط باس کنترلی برای سیگنال وقفه استفاده میشود که به این خط، خط سیگنال ISR (روتین سرویس وقفه) میگویند.
دیگرام سادهای از نحوه رفتن پردازنده به روال وقفه و بازگشت از آن
اما سؤال دیگری که ممکن است ایجاد شود، این است که فرآیند رسیدگی به وقفه چگونه انجام میشود و هنگام دریافت سیگنال وقفه چه اعمالی صورت میگیرند؟ فرض کنیم که در زمان دریافت سیگنال وقفه، پردازنده در حال انجام دستور یا فرایند i باشد. وقتی پردازنده سیگنال وقفه را دریافت میکند، ابتدا فرایند i تکمیل میشود و سپس آدرس اولین دستور روتین وقفه (ISR) در شمارنده برنامه بارگذاری میشود و آدرس دستوری که اجرای آن متوقفشده (i+1) نیز در مکان موقتی از حافظه ذخیره خواهد شد. بدین طریق، پس از رسیدگی به وقفه، پردازنده میتواند به فرایندی که آن را متوقف کرده بود برگردد و اجرای دستور i+1 را از سر گیرد.
زمانی که پردازنده به وقفه رسیدگی میکند، باید به دستگاه درخواستکننده وقفه سیگنالی ارسال کند و به آن پیغام دهند که درخواست وقفه دریافت شده است، زیرا در غیر این صورت دستگاه به ارسال کردن درخواست وقفه ادامه خواهد داد. علاوه بر این، پردازنده باید همه رجیسترها را در حافظه ذخیره کند تا بعد از اتمام سرویسدهی به وقفه بتواند به عملکرد قبلی خود بازگردد. این فرایند موجب افزایش تأخیر در مدتزمان بین دریافت درخواست وقفه تا شروع اجرای ISR خواهد شد. به این مدتزمان، دوره عکسالعمل وقفه یا Interrupt Latency گفته میشود.
به طور کلی مدیریت یک وقفه شامل مراحل زیر است:
- درخواست وقفه توسط یک منبع نرمافزاری یا سختافزاری تولیدشده است.
- پردازنده در پاسخ به درخواست وقفه، اجرای برنامه فعلی را متوقف میکند. در این زمان درصورتیکه منبع وقفه سختافزاری باشد، سیگنالی از سوی پردازنده دریافت میکند که نشاندهنده دریافت موفق درخواست وقفه است. درنتیجه درخواست وقفه را غیرفعال میکند.
- عملیات درخواست شده توسط وقفه، اجرا میشود.
- امکان وقفه مجدداً فعالشده و برنامه متوقفشده، ادامه داده میشود.
مدیریت وقفه ها در HAL
تمامی پروسسورهای معماری ARM v7 دارای امکانات و ویژگیهایی هستند که به کمک آنها میتوان بهطور بسیار کارآمد Exception ها و وقفهها را مدیریت کرد. ازجمله این امکانات میتوان به واحد NVIC (Nested Vectored Interrupt Controller) اشاره کرد. این واحد، وظیفه مدیریت وقفههای تودرتو، ورود به وقفهها و خروج از وقفهها را بر عهده دارد و بدینصورت بار انجام این کار از دوش پردازنده برداشته میشود.
قابلذکر است که معماری وقفه و اولویت آنها بهگونهای انعطافپذیر طراحیشده و قابل تنظیم است تا بتواند از قابلیتهای RTOS (Real Time Operating System) پشتیبانی کند.
در میکروهای مورداستفاده در این آموزشی یعنی STM32f103C8 و STM32f103RET6 که هر دو از یک سری هستند، واحد NVIC توانایی مدیریت 43 کانال وقفه را دارد و از tail-chaining نیز پشتیبانی میکند. همچنین وقفهها تا 16 سطح مختلف قابل اولویتبندی هستند.
اولویت وقفهها
پیشتر اشاره شد که در معماری ARM Cortex M، این امکان وجود دارد که به هر وقفه یک درجه اولویت (از میان تعداد سطوح قابل انتخاب) اختصاص داد. این درجه اولویت بدین معنی است که اگر چندین درخواست وقفه به پردازنده داده شود، اولویت پاسخگویی و اجرای عملیات با درخواستی است که اولویت بیشتری دارد. میخواهیم با یک مثال این موضوع را روشنتر کنیم. اگر در یک شرایط فرضی، وقفهی IRQ2 به پردازنده داده شود و در حین رسیدگی به این وقفه، یک درخواست وقفه دیگر با اولویت بالاتر به نام IRQ1 برسد، روند اجرا و اطلاعات مربوط به IRQ2 در حافظه ذخیره میشود و پردازنده به روال IRQ1 میرود. پس از اتمام روال IRQ1 پردازنده روال IRQ2 را ادامه خواهد داد.
وقفه خارجی
واحد کنترلکننده وقفه خارجی (EXTI) در میکروکنترلر، شامل 20 Edge detector، برای تشخیص لبه در سیگنال ورودی میشود. هر خط متصل به EXTI را میتوان بهصورت مستقل برای Event یا وقفه تنظیم کرد. همچنین نوع تریگر نیز در حالتهای لبه بالارونده، پایینرونده و یا هردو قابل تنظیم است. ویژگیهای اصلی کنترلکننده عبارتاند از:
- وجود تریگر و ماسک مستقل برای هر خط Interrupt/event.
- بیت Status مستقل برای هر خط Interrupt.
- امکان تولید تا 20 درخواست Interrupt/event نرمافزاری.
- تشخیص سیگنالهای خارجی که عرض پالس آنها کمتر از دوره تناوب کلاک APB2 است.
بلاک دیاگرام کنترلکننده EXTI.
پایههای GPIO به صورت زیر به 16 خط مربوط به interrupt/event خارجی متصل شدهاند:
4 خط باقیمانده از 20 خط EXTI نیز بدینصورت است:
- خط EXTI16 به خروجی PVD
- خط EXTI17 به RTC Alarm event
- خط EXTI18 به USB Wakeup event
- خط EXTI19 به Ethernet Wakeup event
اکنونکه با وقفهها و نحوه مدیریت آنها آشنا شدیم، میخواهیم به سراغ توسعه یک پروژه ساده برای نشان دادن کاربرد وقفه خارجی برویم. بدین منظور ابتدا پروژه موردنظر را ایجاد میکنیم.
ایجاد پروژه
بهمنظور ایجاد پروژه برای این قسمت، مراحل گفتهشده در قسمتهای قبل را تا بخش تنظیم کلاک و دیباگ به همان صورت طی میکنیم. مثل پروژه قبل پایه PC13 را در حالت خروجی تنظیم میکنیم. پایه PB6 را نیز بهعنوان یک خروجی دیگر تنظیم میکنیم. اکنون یک پایه را بهصورت زیر برای فعال کردن وقفه خارجی قرار میدهیم:
سپس از قسمت GPIO بهصورت زیر، تنظیمات مربوط به نوع تریگر را در حالت بالارونده و نوع pull-up/pull-down را نیز در حالت No pull0up and no pull-down قرار میدهیم:
حالا باید از تب NVIC، وقفه مربوط به خط EXTI8 را فعال کنیم؛
همچنین در صورتی که بخواهیم اولویت وقفه تنظیم شده را تغییر بدهیم، میتوانیم به بخش NVIC برویم و تغییرات مورد نظر را در اولویت وقفهها اعمال کنیم؛
مثل قبل نرخ کلاک را نیز از تب Clock Configuration، روی 72 مگاهرتز تنظیم میکنیم و سپس وارد بخش کدنویسی میشویم.
نوشتن کد پروژه
در فایل main.c مثل پروژه قبلی، در بدنه while(1) کد مربوط به خاموش و روشن شدن LED متصل به پایه PC13 را مینویسیم:
1 2 3 4 5 6 7 | /* USER CODE BEGIN 3 */ // LED ON HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); HAL_Delay(250); // LED OFF HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); HAL_Delay(250); |
اکنون به سراغ فایل stm32f1xx_it.c میرویم که مربوط به وقفهها میشود. در این فایل تابع EXTI9_5_IRQHandler که مربوط به روال وقفههای خارجی 5 تا 9 است را پیدا میکنیم. این تابع باید بهصورت زیر باشد:
1 2 3 4 5 6 7 8 9 10 | void EXTI9_5_IRQHandler(void) { /* USER CODE BEGIN EXTI9_5_IRQn 0 */ /* USER CODE END EXTI9_5_IRQn 0 */ HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_8); /* USER CODE BEGIN EXTI9_5_IRQn 1 */ /* USER CODE END EXTI9_5_IRQn 1 */ } |
درون بدنه این تابع، روی اسم تابع HAL_GPIO_EXTI_IRQHandler کلیک راست میکنیم و گزینه Open Declaration را انتخاب میکنیم تا به تعریف این تابع برویم. با انجام این کار، تعریف تابع در فایل stm32f1xx_hal_gpio.c برای ما باز میشود. این تابع بهصورت زیر است:
1 2 3 4 5 6 7 8 9 | void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin) { /* EXTI line interrupt detected */ if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u) { __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin); HAL_GPIO_EXTI_Callback(GPIO_Pin); } } |
مشاهده میکنیم که بدنه این تابع تنها از یک if تشکیلشده است. بهصورتی که اگر پین تنظیمشده بهعنوان وقفه خارجی فعال (منطق High) باشد، ابتدا درخواست وقفه پاک میشود و سپس تابع HAL_GPIO_EXTI_Callback، که مربوط به انجام اعمال موردنظر هنگام درخواست وقفه است، فراخوانی خواهد شد. اکنون باید تابع HAL_GPIO_EXTI_Callback را در پروژه تعریف کنیم. این تابع را در فایل main.c و بهصورت زیر مینویسیم:
1 2 3 4 5 6 7 8 9 10 | /* USER CODE BEGIN 4 */ // EXTI Line8 External Interrupt ISR Handler CallBack void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if(GPIO_Pin == GPIO_PIN_8) // If The INT Source Is EXTI Line8 (A8 Pin) { HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_6); // Toggle The Output (LED) Pin } } /* USER CODE END 4 */ |
در بدنه این تابع مشخص کردهایم که درصورتیکه درخواست وقفه از طرف پایه 8 باشد، حالت پین خروجی PB6 عوض شود. در این مرحله نوشتن کد بهپایان رسیده است. از پروژه build میگیریم و روی میکرو دانلود میکنیم.
درصورتیکه همهی مراحل بهدرستی طی شده باشند، با هر بار فشار دادن کلید، حالت LED متصل به پایه PB6، عوض میشود و خاموش و روشن خواهد شد. در صورت نگهداشتن کلید نیز LED شروع به چشمک زدن خواهد کرد. مشاهده میکنیم که عملیات روشن و خاموش شدن LED متصل به پایه PB6 مستقل از LED پایه PC13 است. زیرا برنامه مربوط به چشمک زدن PC13 در روال کار عادی میکرو قرار دارد و مرتب اجرا میشود.
در این قسمت از سری آموزش STM32 با توابع HAL، در مورد وقفهها صحبت شد. در قسمت بعدازاین سری، با واحد USART و نحوه ارسال اطلاعات بهوسیله آن، آشنا خواهیم شد. با ما همراه باشید.
سلام و عرض تشکر بابت آموزش
چرا وقتی توی تابع وقفه بیش از یک خط کد مینویسم فقط کد خط اول اجرا میشه؟
پروژه رو برای STM32F103C8T6 نوشتم و کلاک رو روی 8MHz گذاشتم
سلام. اینطور نیست. بدون مشکل باید کار بده.
میتونید سوالتون رو به طور کامل به همراه کد توی انجمن سیسوگ قرار بدید تا بررسی بشه
ولی فکر کنم یه delay گذاشتید توی تابع وقفه
توی تابع وقفه نمیتونید delay بزارید
سلام. ممنون از مطلب مختصر و مفیدتون
فقط خوشبحالتون، میکروهای شما با فرکانس 72 گیگ کار می کنن. میکروهای ما سرعتش هزار برابر کمتر هست. واقعا خوشبحالتون 🙂
اصلاح شد 🙂
سلام
تشکر بابت مطالب بسیار مفیدتون
من مراحل بالا رو کامل انجام دادم فقط ی تفاوتی ک دیدم این بود که با نگه داشتن کلید LED چشمک نمیزند و حجالت خود را حفظ میکند.
(چون با نگه داشتن کلید لبه بالا رونده یا پایین رونده یکبار ایجاد میشود؟!)
ممنون از اطلاعات خوبتون میخواستم بدونم میشه وقفه داخلی نیز با stm32 در محیط نرم افزار ایجاد کرد به طوری که هر 0.01 ثانیه وقفه ایجاد کند مثلا برای قطع و وصل رله ها
سلام دوست عزیز
خوب چرا برای این کار از تایمر استفاده نمیکید ؟