ARM, STM32, آموزش های متنی

آموزش STM32 راه اندازی وقفه خارجی EXTI

آموزش STM32 راه اندازی وقفه خارجی EXTI

آموزش STM32 راه اندازی وقفه خارجی EXTI

در این آموزش یاد می گیریم چطور از وقفه های خارجی EXTI در میکروکنترلر STM32 استفاده کنیم. در بخش قبلی با راه اندازی واحد سریال UART آشنا شدیم. برای دیدن مقاله قبلی اینجا کلیک کنید.

اگر با محیط STM32cubeMX آشنایی ندارید، اینجا کلیک کنید.

اگر اولین بار است که از میکرو STM32 استفاده می کنید، اینجا کلیک کنید که یک شروع سریع داشته باشید.

پروژه عملی

در این پروژه از برد mini STM32 استفاده شده است که بر مبنای میکروکنترلر STM32f103c8t می باشد. پایه ی PB5 را به صورت وقفه خارجی پیکربندی می کنیم. با یک مقاومت پایین کش Pull down سطح پیشفرض این پایه را صفر منطقی در نظر می گیریم. با آمدن لبه ی بالا رونده بر روی این پایه وقفه عمل خواهد کرد.

زمانی که وقفه تشخیط داده شد، یک فلگ را تنظیم می کنیم. سپس در روتین برنامه در صورتی که وقفه رخ داده بود، LED متصل به پایه ی PC13 یک بار چشمک می زند. به صورت Built-in یک LED بر روی برد mini
STM32 متصل به این پایه وجود دارد.

تنظیمات در محیط cubeMX

برای آماده سازی پروژه از محیط cubeMX استفاده می کنیم.

  • مطابق تصویر زیر پایه ی PB5 را به صورت وقفه خارجی EXTI تنظیم می کنیم.
  • پایه ی PC13 را به صورت خروجی GPIO OUTPUT تنظیم می کنیم.
  • همینطور به منظور پروگرام و دیباگ برد، بخش Debug را به صورت Serial Wire تنظیم می کنیم.
آموزش STM32 راه اندازی وقفه خارجی EXTI

در بخش GPIO تنظیمات بیشتر برای وقفه خارجی را مطابق تصویر زیر انجام می دهیم.

  • پایه ی ورودی را حساس به لبه ی پایین رونده در نظر می گیریم.
  • حالت پیش فرض پایه ی وقفه خارجی با بک مقاومت خارجی Pull down صفر در نظر می گیریم. اگر نمی خواهید از مقاومت خارجی استفاده کنید، در این بخش بجای No pull-up and no pull-down می توانید از گزینه ی pull-down نیز استفاده کنید. در این صورت مقاومت داخلی میکرو به صورت پایین کش برای این پایه فعال خواهد شد. برای پروژه های واقعی حتما از مقاومت ها خارجی استفاده کنید!
آموزش STM32 راه اندازی وقفه خارجی EXTI

مطابق تصویر زیر وقفه EXTI line[9-5] را تیک می زنیم که فعال شود. برای این منظور به تپ NVIC مطابق تصویر رجوع می کنیم.

آموزش STM32 راه اندازی وقفه خارجی EXTI

حال که کار تنظیمات و پیکربندی اولیه ی پروژه تمام شد، از محیط cubeMX خروجی می گیریم. در این آموزش از محیط Keil استفاده می کنیم. اما شما برای هر IDE دیگری می توانید خروجی بگیرید، روال راه اندازی تفاوتی ندارد.

درک روال EXTI در HAL

از آنجایی که از سبک HAL استفاده می کنیم، نیاز است که ساختار آن را بشناسیم. وقفه ها در قایل stm32f1xx_it.c وجود دارند و روال تمام وقفه های فعال را در این بخش می توانید مشاهده کنید.

بخش مورد نظر ما در این آموزش، روال روتین وقفه خارجی است. روتین زیر که مربوط به روال وقفه خارجی می شود را در فایل یاد شده می توانید پیدا کنید.

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_5);
  /* USER CODE BEGIN EXTI9_5_IRQn 1 */

  /* USER CODE END EXTI9_5_IRQn 1 */
}

همانطور که مشاهده می شود در این روال یک تابع به نام HAL_GPIO_EXTI_IRQHandler فراخوانی می شود. اگر این تابع را با زدن دکمه F12 رهگیری کنیم به پیاده سازی این تابع در فایل stm32f1xx_hal_gpio.c می رسیم که مربوط به درایورهای HAL است. توجه کنید، که فایل های درایور را نباید بی دلیل دستکاری کنید، تنها دلیل برای دستکاری فایل های درایور وجود باگ است!

به این ترتیب روتین زیر را مشاهده می کنید که یک تابع را به صورت Call back فراخوانی می کند.

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);
  }
}

در این روتین تابع HAL_GPIO_EXTI_Callback را می بینیم که به صورت weak تعریف شده است. روتین آن در همین فایل پیاده سازی شده است و به صورت زیر است.

__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(GPIO_Pin);
  /* NOTE: This function Should not be modified, when the callback is needed,
           the HAL_GPIO_EXTI_Callback could be implemented in the user file
   */
}

هیچ چیز پیچیده ای وجود نداره!

خبر خوب این هست توابعی که به صورت weak تعریف شده اند، توابعی هستند که کاربر که ما باشیم می توانیم روتین آن ها را مجدد با همین نام پیاده سازی کنیم. حالا که متوجه شدید ساختار به چه صورت است بیاید، روتین جایگزین این تابع را در main.c تعریف کنیم.

شروع برنامه نویسی

بسیار خوب بلاخره به مرحله جذاب ماجرا می رسیم، آستین ها را بالا بزنیم و کار را شروع کنیم. به فایل main.c بر می گردیم. یادتون هست پروژه چی بود؟ اگر نه یک بار مرور کنید و برگردید.

بسیار عالی، یک فلگ تعریف می کنیم با نام btn_event_f که مقدار اولیه ی false و یا 0 دارد. اگر وقفه رخ دهد این متغییر را در روتین وقفه true و یا 1 تنظیم می کنیم.

توجه

  • این فلگ باید به صورت Global تعریف شود.
  • در صورتی که از مود های بهینه سازی کامپایلر بخواهید استفاده کنید، برای اینکه برنامه درست کار کند باید این متغییر را به صورت volatile تعریف کنید.

در ادامه روتین روال وقفه را در تابع call back تعریف می کنیم، پس کدهای زیر را به main.c اضافه می کنیم.

volatile uint8_t btn_event_f = false;

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if(GPIO_Pin == GPIO_PIN_5)
	{
		btn_event_f = true;
	}
}

در حلقه ی بینهایت super loop درون تابع main ، کد زیر را قرار می دهیم.

if(btn_event_f == true)
{
	btn_event_f = false;
	HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
	HAL_Delay(500);
	HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
	HAL_Delay(500);
}

همانطور که مشخص است قرار بود در صورتی که فلگ set شده باشد، ادامه برنامه را main برنامه پیگیری کنیم. روش استفاده از فلگ یک روش موثر برای استفاده از وقفه هاست چرا که مهم است تا جای ممکن روتین وقفه ها کوتاه نگه داشته شود.

  • به این ترتیب در صورتی که فلگ set شده باشد، فلگ را false می کنیم که برای بار بعدی، وقفه را بتوانیم تشخیص دهیم.
  • در ادامه LED یک بار با فاصله زمانی 500 میلی ثانیه چشمک می زند.

منابع پیشنهادی جهت یادگیری بیشتر STM32

می توانید در دوره آموزش STM32 در لینک زیر شرکت کنید.


لینک های مفید

شروع کار با میکروکنترلر stm32 و stm32cubeMX

برنامه نویسی به روش RTOS امروز یک ضرورت است؟

دانلود کتاب الکترونیکی RTOS


اگر سوال، پیشنهاد، نظر و یا … دارید، از قسمت نظرات در پایین همین صفحه، می توانید با ما و بقیه ی خوانندگان درمیان بگذارید.

author-avatar

درباره مرتضی زندی

خلق کردن شماره 1 علاقه مندی های من هست. برنامه نویسی بهترین ابزاری بوده که تا الان برای این کار پیدا کردم. بیس الکترونیک دارم و دستی بر آتش در دنیای رشته های مجاور. تجربه سال ها فعالیت در تیم های R&D رو دارم، در این بین در حوزه ی امبدد سیستم ها فعالیتم پررنگ تر هست. عاشق یادگیری و یاد دادن هستم و تلاش می کنم تجربیاتم رو به زبانی ساده از طریق EasyMCU.org به نحوی کاربردی و موثر با علاقه مندان این حوزه به اشتراک بذارم.

8 نظر در “آموزش STM32 راه اندازی وقفه خارجی EXTI

  1. manavisroh گفت:

    سلام خدا قوت
    الان طبق فرمایش شما وقتی که یک فلگ را ست کرده و در حلقه while می گذاریم (کارهای زمان دار دیگری هم در حلقه داریم) حلقه مسیر روتین خود را طی می کند و پس از رسیدن به خط فلگ زمان طول می کشد که روتین وقفه اجرا بشه در حالی که ما منظورمان از وقفه اولویت فوری است لطفا راهنمایی بفرمایید

    1. درود بر شما.
      بله، برای همین معماری پروژه مهم هست و بخش های برنامه باید همگن باشند. زمانی که یک پروژه قرار است طراحی شود، اول باید نیازمندی های پروژه برای پاسخ زمانی و ضرب العجل های مورد نیاز درنظر گرفته شود و سپس روش و معماری مناسب برای طراحی پروژه انتخاب و در نهایت نسبت به برنامه نویسی اقدام کرد.
      در این مقاله صرفا یک روش مرسوم پیاده سازی و البته پر استفاده معرفی شده و هدف آموزش نحوه راه اندازی وقفه خارجی بوده.
      طبیعتا وقتی از این روش استفاده میشود، حلقه while هم نباید شامل delay خارج از حد انتظار باشد و تا جای ممکن حلقه باید سریع طراحی شود که پاسخ دهی سیستم قابل قبول باقی بماند.
      هر چه حلقه while کندتر باشد، ماکسیمم زمان jitter افزایش پیدا میکند. در برنامه نویسی برمتال روش های مختلفی برای پیاده سازی real-time وجود دارد. در پروژه های بزرگتر با ضرب العجل های حساس و مالتی تسکینگ زیاد و ازدیاد تسک ها، روش برنامه نویسی RTOS و استفاده از سیستم عامل ها انتخاب بهتری هستند.
      با آرزوی بهترین ها.

  2. سعید گفت:

    سلام.
    اینکه در روتین وقفه فلگ ست کنید و بعدا در تابع main فلگ را ست کنید اصلا اصولی نیست و کلا با فلسفه وقفه خارجی تناقض دارد. ما زمانی از وقفه خارجی استفاده می کنیم که قرار باشد رویدادی غیر منتظره ، خارج از برنامه زمانی مشخص، و یا پر سرعت و حساس به زمان اتفاق بیفتد و نیاز به یک واکنش سریع و فوری در همان لحظه دارد. تابع main ممکن است شامل صدها یا هزاران خط کد کد دیگر هم باشد و ممکن است تاخیر زیادی در هر بار اجرا داشته باشد و برای اینکار اصلا مناسب نیست. ببینید در همین کدهای مربوط به پردازش فلگ وقفه خودتان هم 1000 میلی ثانیه تاخیر گذاشته اید که حتی اگر تابع main تاخیر دیگری نداشته باشد ، عملا هر یک ثانیه یکبار وضعیت فلگ وقفه بررسی می شود. حالا فرض کنید که در این یک ثانیه ای که گذشت، 50 بار روتین وقفه اجرا شده باشد، شما 49 عدد از آنها را از دست داده اید و فقط یکی را پردازش کرده اید. کدهای روتین وقفه باید درون خود روتین وقفه یا تابع دیگری باشد که از روتین وقفه فراخوانی شود ولی کوتاه و بدون تاخیر زمانی باشد.

    1. درود بر شما.
      ممنون از کامنت شما. در کامنت دیگری دوستی همین موضوع رو اشاره کردند و توضیحاتی در این خصوص ارائه دادم.
      در برنامه نویسی برمتال روش های مختلفی برای پیاده سازی نیازمندی های real-time وجود دارد که نوع و نیاز پروژه تعیین کننده روش و معماری پروژه است، منتها آشنایی با روش های مختلف پیاده سازی و همگن بودن پروژه بسیار مهم است.
      روشی که شما اشاره کردید یک روش هست با مزایا و معایب خودش و صحیح است. روش فلگ ارائه شده در این مقاله هم روش دیگریست با مزایا و معایب خودش و صحیح است. روش های دیگری هم وجود دارد و به همین دو مورد محدود نمیشود، منتها هر روش ملاحظات خاص خود را دارد و باید همگن باشد. مشخصا در مورد روش فلگ، زمان مورد نیاز برای اجرای محتویات سوپر لوپ، باید کمتر از ددلاین مورد نیاز سیستم باشد، در غیر اینصورت مناسب نخواهد بود.
      مثال ارائه شده هم صرفا یک مثال آموزشی است برای نشان دادن تنظیمات وقفه خارجی و نحوه استفاده از روش فلگ. طبیعتا در پروژه واقعی از delay به این شکل عریان استفاده نمی شود!
      در نظر بگیرید یک push-button برای کاربر متصل به پایه وقفه داریم و همینطور یک شفت انکودر متصل به پایه دیگر وقفه. آیا دغدغه یکسانی نسبت به response time و نحوه هندل کردن این دو تسک وجود دارد؟ آیا این دو تسک تعداد رخداد یکسان و شرایط و اولویت برابر دارند؟
      لذا روش درست و غلط و دید 0 و 100 ای مشکل ایجاد میکند، چرا که هیچ روشی مطلق خوب نیست، بلکه نیاز و نوع پروژه و زمان response time و ضرب العجل های مورد نیاز پروژه و خیلی فاکتورهای دیگر تعیین کننده بهترین و مناسبترین روش برای انجام آن پروژه خاص خواهد بود. یک نسخه به عنوان بهترین روش برای تمام پروژه ها وجود ندارد.
      در مواردی که مالتی تسکینگ و تسک های زیاد با اولویت بندی های متفاوت داریم و ددلاین ها حساسیت بیشتری دارند استفاده از RTOS نسبت به برمتال معمولا ارجح تر است، که البته این روش هم مزایا و معایب خود را دارد. در برخی موارد حتی نیاز به درنظر گرفتن ساب سیستم های متعدد هستیم، که این مورد هم مزایا و معایب خودش را دارد. روش های پیاده سازی متنوعی وجود دارد، اما همه چیز به نیاز پروژه بستگی دارد.
      با آرزوی بهترین ها.

  3. abbas گفت:

    ممنون مهندس بابت اطلاعات مفیدتون

    1. درود بر شما.
      خواهش میکنم. خوشحالم مفید بوده. مرسی از وقتی که گذاشتی و نظرت رو نوشتی.
      با آرزوی بهترین ها.

  4. محسن گفت:

    سلام ممنون از اموزش های خوبتون عالی
    میخواستم بدونم چطور میشه وقفه اینتراپت را برای پین مورد نظر خاموش کرد در زمانی که وقفه رخ می دهد و پایان تابع وقفه دوباره فعالش کرد
    void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
    {
    if (GPIO_Pin == GPIO_PIN_12)
    {
    __NVIC_DisableIRQ(EXTI15_10_IRQn);
    HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_13);
    __NVIC_EnableIRQ(EXTI15_10_IRQn);
    }
    }

    1. درود بر شما.
      مرسی از کامنتت. خوشحالم مفید بوده.
      در STM32 هر پایه به صورت مستقل اولویت وقفه نداره و به ازاء تعدادی پایه، یک اولویت وقفه وجود داره. بنابراین با استفاده از تابع NVIC_DisableIRQ میتونید به خواسته اتون برسید.
      با آرزوی بهترین ها.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

این سایت از اکیسمت برای کاهش جفنگ استفاده می‌کند. درباره چگونگی پردازش داده‌های دیدگاه خود بیشتر بدانید.