stm32

وقفه های GPIO در میکروکنترلرهای stm32 با استفاده از توابع HAL

مدیریت یک سیستم سخت افزاری در حقیقت مدیریت رخدادهای غیر همزمانش هست که اکثرشون مربوط به لوازم جانبی میکروکنترلر میشن. مثلا یک تایمر به مقدار تعیین شده خودش میرسه و یا درگاه سریال خبر از رسیدن اطلاعات رو میده. بقیه وفقه ها از دنیای بیرون از میکروکنترلر میان مثل دکمه ای که به یک پایه GPIO متصل شده.

تمام میکروکنترلرها ویژگی دارن به نام وقفه یا interrupts . وقفه یک رخداد غیر همزمان هست که باعث میشه اجرای کد فعلی براساس اولویت تعیین شده (هر چی وقفه مهم تر باشه اولویتش بالاتره و باعث میشه وقف با اولویت پایین تر در حالت تعلیق قرار بگیره. جلوتر بصورت نمودار توضیح میدم) متوقف بشه و کد مربوط به وفقه به اجرا دربیاد. به کدی که در حالت وقفه اجرا میشه میگن ISR که مخفف کلمات Interrupt Service Routine هست.

وفقه ها منشا برنامه نویسی چند وظیفه ای هستن. این قابلیت باعث میشه سخت افزار تمام شرایط سیستم رو قبل از اجرای ISR حفظ کنه و جریان اجرای برنامه اصلی از دست نره. سیستم عامل ها بلادرنگ یا Real Time Operating System که به طور مخفف RTOS نامیده میشن هم از همین قابلیت سخت افزاری استفاده می کنن. در سیستم عاملها تمام کارها توسط رویه هایی به نام Task یا وظیفه انجام میشن.

وقفه ها هم میتونن منبع سخت افزاری داشته باشن و نرم افزاری. در ARM این دو توسط وقفه های سخت افزاری و استثنائات نرم افزاری ( software exceptions ) متمایز میشن. در ARM وقفه در حقیقت یک نوع استثناء یا exception به حساب میاد.

در میکروکنترلرهای سری Cortex-M واحدی وجود داره که وظیفه اش مدیریت همین استثنائات هست که بهش میگن NVIC. این عبارت مخفف کلمات Nested Vectored Interrupt Controller که اگر بخوام لغوی معنییش کنم میشه کنترل کننده وقفه برداری تو در تو (چی شد!!! … سخت نگیرید مهم اینه طرز کارش رو بفهمیم)

کنترلر NVIC

NVIC یک واحد سخت افزاری در میکروکنترلرهای Cortex-M هست که وظیفه اش رسیدگی به استثنائات هست. شکل زیر ارتباط بین واحد NVIC ، پردازنده هسته ( Processor Core ) و لوازم جانبی رو نشون میده. اگراز منظر هسته Cortex-M به شکل نگاه کنیم دو نوع وقفه خواهیم داشت. وفقه های داخلی که چیزهای مثل تایمر و درگاه سریال و غیره میان و وقفه هایی که خارجی هستن و منشا اونها پایه های I/O یا همون GPIO هست. همون طور که در شکل هم می بینید یک کنترولر به نام EXTI وجود داره که وظیفه اش اتصال بین سیگنال پایه های GPIO به کنترلر NVIC هست.

Vector Table

همونطور که از اسم پست هم مشخص هست ما قرار وقفه های مربوط به GPIO ها بررسی کنیم. اما در ابتدا این پست هم گفتم وفقه ها منابع مختلفی دارن که با توجه به وظیفه شون دارای اولویتهای متفاوتی هستند که پرداختن بهشون تو این پست بی معنی هست اما برای اینکه نسبت بهشون دید مناسبی پیدا کنید User Manual مربوط به میکرو کنترلر خودتون رو دانلود کنید و عبارت Vector Table رو در اون جستجو کنید. همونطور که مشاهده می کنید با منابع متعددی از وفقه ها مواجه می شید که ترتیب اولویت در این جدول قرار گرفتن. شاید تنها وفقه ای که تا الان بدون کد نویسی ازش استفاده کردید وقفه Reset باشه. اگر به Priority یا اولویتش در جدول نگاه کنید می بینید اولویتش -۳ هست یعنی بالاترین اولویت. مهم نیست میکروکنترلر داره چی کار میکنه دکمه ریست رو فشار بدید یا به هر نحوی پایه ریست رو فعال کنید میکروکنترلر اجرای برنامه رو در هرجایی که هست متوقف میکنه و اجرای برنامه رو از اول انجام میده. وقفه RESET جزو وقفه های غیر قابل چشم پوشی یا اصطلاحا None Maskable هست. برای اینکه پست خواب آور و غیر قابل تحمل نشه من وقفه های مربوط به هر بخش رو در پست مربوط به خودش بررسی میکنم.

فعال سازی وقفه ها

وقتی که میکروکنترلر بوت میشه فقط وقفه یا استثنائات NMI ، Reset و Hard Fault بطور پیشفرض فعال هستند و مابقی استثنائات و وقفه های لوازم جانبی غیر فعال هستن و باید بنا به نیاز فعال بشن. برای فعال کردن یک IRQ از تابع HAL زیر استفاده می کنیم:

در این تابع IRQn_Type یک نوع شمارشی هست که شامل تمام وفقه ها و استثنائات تعریف شده برای میکروکنترلر مورد استفاده هست. این نوع شمارشی در داخل یک فایل هدر در کتابخونه HAL مربوط به میکروکنترلر قرار گرفته.

برای غیر فعال کردن یک IRQ از تابع زیر استفاده می کنیم:

لازم به توضیح هست که بگم توابع گفته شده فعال/غیرفعال سازی یک وقفه رو در سطح کنترلر NVIC انجام میدن. اگر به تصویر بالا با دقت نگاه کنید می بینید که هر کدوم از لوازم جانبی با یک مسیر یا خط مستقل به کنترلر NVIC متصل میشن. این یعنی اگر شما می خواهید از وقفه یکی از لوازم جانبی یا اصطلاحا Peripheral استفاده کنید هم باید تنظیمات مربوط به وقفه مربوط اون رو مستقلا انجام بدید و هم وفقه رو در سطح NVIC فعال کنید تا به درستی کار کنه. با استفاده از توابع HAL خیلی راحت میتونیم وفقه مربوط به یکی از لوازم جانبی رو فعال کنیم. یک مثال میزنم (فقط اشاره میکنم) با استفاده از تابع HAL_USART_Transmit_IT() که مربوط به ارسال داده درگاه سریال هست به طور ضمنی داریم وفقه مربوط به زمان ارسال اطلاعاتش رو هم تنظیم می کنیم. واضح هست این به تنهایی جواب نمیده و اگر میخوایم وفقه ارسال داده در درگاه سریال کار بکنه باید توسط تابع HAL_NVIC_EnableIRQ() اون رو در سطح NVIC هم فعال کنیم.

خطوط خارجی و NVIC

همونطور که در تصویر بالا دیدید میکروهای ST از طریق بخش EXTI Controller میتونن تعداد متغیری منابع وقفه خروجی رو مدیریت کنن. این که میگم متغیر دقیقا بستگی به میکرویی داره که دارید استفاده می کنید. به تصویر زیر نگاه کنید:

تصویر فوق نحوه اتصال پایه های GPIO رو به خطوط EXTI نشون میده. با توجه به این تصویر شما میتونید وفقه رو روی تمام پایه های میکرو فعال کنید فقط حواستون به دو نکته زیر باید باشه:

  • در هر خط EXTI فقط یک پایه GPIO میتونه فعال باشه بطور مثال اگر شما وقفه رو برای PA0 فعال کردید دیگه نمی تونید برای پایه PB0 هم وقفه رو فعال کنید.
  • بعضی از میکروها مثل سری STM32F4 بعضی از GPIO ها از خط IRQ مشترک در داخل NVIC استفاده می کنن برای اینکه در هنگام وقفه اونها رو هم از هم تفکیک کنید باید براشون کد بنویسید. به عنوان مثال می تونید به خطوط EXTI مربوط به سری STM32F4 در تصویر زیر نگاه کنید. در این سری میکرو ها خطوط EXTI10 الی EXTI15 مشترک هستن.

اگر می خواهید از طریق فایلهای کتابخونه بدونید در میکرو stm32f103c8 که در برد Bluepill استفاده شده اوضاع چطوریه میتونید به محتویات فایل stm32f103xb.h یک نگاهی بندازید. چون حجم کد خیلی زیاد هست کد رو اینجا قرار نمی دم و فقط اشاره میکنم. در انتهای پست وقتی پروژه رو ایجاد کردید فایل مورد نظر رو پیدا کنید، بازش کنید و به مواردی که من اینجا اشاره میکنم دقت کنین. این که جای دقیق فایل رو نمیگم علتش اینه که بسته به نوع IDE که استفاده می کنید ممکنه مکان فایل متفاوت باشه که راحت با ابزار سیستم عاملی که در اختیار دارید میتونید پیداش کنید. خطوط ۸۹ الی ۹۳ دارن نشون میدن که برای GPIO های ۰ تا ۴ خطوط EXTI مستقل هستن. خط ۱۰۶ داره میگه GPIO های ۵ تا ۹ از خط EXTI مشترک استفاده می کنن. این مورد در خط ۱۲۳ برای GPIO های ۱۰ تا ۱۵ هم صدق میکنه.

اولویت بندی وقفه ها

در میکروکنترلرهای با هسته Cortex-M3/4/7 اولویت هر وقفه از طریق رجیستری به نام IPR مشخص میشه. این رجیستر ۸ بیتی هست و انتظار داریم حداقل ۲۵۵ سطح در اولویت بندی در اختیار ما قرار بده اما در عمل اینجوری نیست. علتش هم اینه که برای تعیین اولویت بندی فقط از چهار بیت وزن بالا استفاده میشه.

شکل بالا نشون میده محتویات این رجیستر چطوری تفسیر میشه. می تونیم این نتیجه گیری رو داشته باشیم که حداکثر تعداد اولویت بندی که می تونیم داشته باشیم ۱۶ تا هست: ۰xF0 ، ۰xE0 ، ۰xD0 ، ۰xC0 ، ۰xB0 ، ۰xA0 ، ۰x90 ، ۰x80 ، ۰x70 ، ۰x60 ، ۰x50 ، ۰x40 ، ۰x30 ، ۰x20 ، ۰x10 ، ۰x00 . پایین تر عدد بالاترین اولویت رو داره. مثلا وقفه ای که اولویت ۰x10 رو داشته باشه اولویتش از وفقه ای با مقدار ۰xA0 بالاتر هست. اگر دو وقفه رو در یک زمان شلیک کنن اونی که اولویت بالاتری (مجددا میگم … عدد کوچیکه) اجرا میشه. اگر پردازنده در حال اجرای یک وقفه باشه و یک وقفه با اولویت بالاتر شلیک بشه ، وقفه فعلی به حالت تعلیق در میاد و وقفه با اولویت بالاتر اجرا میشه. بعد از تمام وقفه با اولویت بالا، دنباله وقفه با اولویت پایین تر اجرا میشه.

البته موضوع به اون گل و بلبلی که فکر می کنید نیست چون بیت های رجیستر IPR به دو بخش تقسیم میشه. تعدادی از بیتها مربوط به حق تقدم یا preemption هستن و تعداد از اونها مربوط به اولویتهای فرعی یا sub-priority. اولویتی که بین اجرای کدهای ISR حکمرانی میکنه اولویت از نوع preemption هست. اگر یک ISR اولویتی بالاتر از یک ISR دیگه داشته باشه زودتر اجرا میشه یا در حقیقت داره حق اجرا شدن در مقابل ISR اولویت پایین تر رو preempt میکنه یا به انحصار در میاره. سخت نگیرید بریم جلوتر بهتر متوجه میشید.

تصویر بالا رو نگاه کنید. تو این تصویر فقط داریم در مورد اولویت preemption یا اولویت انحصاری صحبت میکنیم. مقدار عددی اولویت A از همه بیشتره پس حق تقدمش از همه پایین تره. در اولویت بالاتر B و بعد از اون C قرار داره. وقفه A در لحظه t0 شروع به اجرا میکنه. یک دفعه در لحظه t1 وقفه B شلیک میشه. منطقی هست که A به حالت تعلیق درمیاد و B شروع به اجرا میکنه. B در حال اجرا هست که باز وقفه C میگه میخوام اجرا بشم. چی میشه؟ هیچی دیگه B هم میگه روم سیاه میره کنار A وامیسته تا جناب C اجرا بشه. C که کارش تموم شد B از کنار A بلند میشه میره کارش رو ادامه. B که کارش تموم شد A بدبخت کاری رو که شروع کرده بود ادامه میده. اما زیر اولویت ها چه تاثیری در اجرا وقفه دارن؟ برای اینکه این موضوع متوجه بشید تصویر زیر رو نگاه کنید:

تو این تصویر از نظر اولویت انحصاری یا اصلی همه وقفه ها مثل هم هستن. اما از نظر زیر اولویت، B از حق تقدم پایین تری نسبت به دوتای دیگه برخورداره. این رو هم باید دقت کنیم که وقفه A و C چه از نظر اولویت انحصاری و چه از نظر زیر اولویت عینا مثل هم هستن و هیچ فرقی با هم ندارن.

در لحظه t0 اولویت A شلیک میشه و شروع به اجرا میکنه. همین طوری که داره اجرا میشه B میگه میخوام اجرا بشم. میکرو بهش میگه بشین سر جات درسته اولویت اصلیت با A یکی هست اما از نظر زیر اولویت پایین تری داداش پس باید صبر کنی میگه باشه. A پوزخند میزنه و ادامه میده. یک دفعه در t2 وقفه C میگه من می خوام اجرا بشم. همه چیم هم اوکی هست. هم انحصاریم مثل A هست و هم زیر اولویتم. برگه رضایت والدین رو هم آوردم. پردازنده میگه کاملا درسته. تو که C باشی و A به یک اندازه برای برای من عزیز هستین. اما چون A زودتر شروع کرده بذار ادامه بده کارش که تموم شد تو ادامه بده. B میگه ای آقا من تو صف بودم که؟!!! میکرو میگه داداش زیر اولویتت پایینه بفهم !!! B میگه باشه فهمیدم بازم صبر میکنم. A که کارش تموم میشه طبق قرار C شروع میکنه. C که کارش تموم B تازه کارش شروع میکنه. (نمی دونم چرا دلم برای B سوخت)

خب حالا سوال پیش میاد که چطوری باید تنظیمات مربوط به اولویتهای انحصاری ( Preempt Priority ) و زیر اولویتها ( Subpriority ) زو انجام بدیم. قبل از اینکه دستورش رو بگم شکل زیر رو نگاه کنید:

شکل بالا تمام پنج حالتی که رجیستر IPR میتونه داشته باشه رو نشون میده همچنین در جدول زیر می تونید ببینید که در هر حالت چند اولویت انحصاری و چند زیر اولویت می تونید داشته باشید. هر کدوم از این حالات در حقیقت یک گروه حساب میشن که در جدول نام متناظر هر گروه هم درج شده.

اولویت گروه NVICتعداد اولویت انحصاریتعداد زیر اولویت ها
NVIC_PRIORITYGROUP_0016
NVIC_PRIORITYGROUP_128
NVIC_PRIORITYGROUP_244
NVIC_PRIORITYGROUP_382
NVIC_PRIORITYGROUP_4160

برای انجام تنظیمات مربوط به EXTI ، اولویت انحصاری و زیر اولویت HAL تابع زیر رو در اختیار ما قرار میده:

درتابع فوق IRQn نام مربوط به IRQ ی هست که قرار استفاده بشه. مثلا شما اگر پایه بخواهید وقفه مربوط به پایه PA3 میکرو stm32f103c8 که در برد BluePill مورد استفاده قرار گرفته فعال کنید با توجه به دیتاشیت این پایه از خط IRQ به نام EXTI3_IRQn استفاده میکنه پس برای فعال کردنش باید نام این IRQ رو به عنوان آرگومان اول تابع پاس داده بشه. دو پارامتر بعدی مربوط به اولویتهای Preempt Priority و Subpriority که با توجه به جدول بالا در هر گروه میتونه مورد استفاده قرار بگیره. این مقدایر بطور خودکار به چهار بیت پر وزن مربوط به رجیستر IPR منتقل میشن.

خب به اندازه کافی تئوری گفتم بیائید با یک مثال این پست رو تموم کنیم. به طبق معمول یک پروژه جدید ایجاد کنید. سپس تنظیمات وقفه رو به ترتیب تصوایر زیر انجام بدید و در نهایت دکمه Generate رو بزنید:

مطابق شکل پایه PC13 در حالت Output و پایه PA3 رو در حالت EXTI3 قرار بدید
مقاومت Pull Up مربوط به پایه PA3رو فعال می کنیم. GPIO mode رو طوری تنظیم می کنیم که وقفه با لبه بالا رونده پالس ورودی فعال بشه
برای فعال شدن وقفه مربوط به PA3 وقفه سراسری رو فعال می کنیم.
تنظیمات مربوط به Preemption Priority و Sub Priority تو این قسمت انجام میشه که ما اونها رو در حالت پیشفرض رها میکنیم چون قرار یک مثال ساده داشته باشیم

خب تنظیمات تا اینجا کامل هست. دکمه Generate Code رو کلیک کنید و پروژه رو باز کنید. قبل از اینکه کد رو در فایل main.c وارد کنیم بیاین نگاهی به فایل stm32f1xx_it.c که در همون مسیر فایل main.c قرار داره یک نگاهی بندازیم. فایل رو باز کنید و کدها رو به سمت پایین مرور کنید. اگر تنظیمات Cube رو درست انجام داده باشید به کد مثل کنید زیر برخورد می کنید:

این دقیقا تابعی هست که موقع فعال شدن وقفه خط EXTI_IRQ3 پایه PA3 از طریق اون به کنترلر NVIC وصل میشه. در تابع Callback ی که ما می نویسیم وضعیت پین GPIO رو چک می کنیم. پین GPIO از اینجا به تابع Callback پاس داده میشه.

حالا فایل main.c رو باز کنید و بعد از عبارت /* USER CODE BEGIN 0 */ کد زیر رو تایپ یا کپی کنید:

Callback فوق کار خاصی انجام نمیده فقط در زمان فعال شده وقفه وضعیت LED متصل به برد رو معکوش میکنه.حالا متن کامل فایل main.c باید مثل زیر باشه:

از اونجائیکه از مدار پست مربوط به آموزش GPIO داریم استفاده میکنیم ۹۰ درصد کل مشابه پست قبلی هست و من فقط تفاوتها رو میگم

در تنظیمات GPIO مربوط به پین PA3 خاصیت Mode برابر با GPIO_MODE_IT_RISING قرار گرفته. همونطور که قبل تر هم تو مراحل انجام تنظیمات پروژه در Cube هم گفتم ما قصد داریم وقفه با لبه بالا رونده پالس ورودی فعال بشه. این خاصیت این کار رو برای ما انجام میده.

در خط ۱۷۹ تنظیمات مربوط به اولویت انحصاری و زیر اولیت انجام شده که هردو برابر ۰ هستن. البته با این دستور در اوسط آموزش و نحوه استفاده اش آشنا شدیم.

در خط ۱۸۰ فعال سازی وقفه سراسری انجام شده.

مدار مورد استفاده همون مداری هست که در پست آموزش GPIO ازش استفاده کردیم:

اگر در محیط Keil هستید با فشار دادن دکمه F7 برنامه رو کامپایل و با زدن F8 کد رو آپلود کنید. سپس دکمه ریست بر بروی برد BluePill رو فشار بدبد. حالا با فشار دادن دکمه متصل به PA3 ال ئی دی روی برد یک بار روشن و با فشار مجدد دکمه خاموش میشه که این کار از طرق وقفه انجام میشه. این مثال خیلی ساده بود اما بعد از آموزش مربوط به درگاه سریال نحوه راه اندازی کی پد ۴×۴ رو با استفاده از وقفه ها آموزش میدم.

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

ساعد

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


پنج × 8 =