مقدمه
در مطالب جلسهی اول موضوع بحث ما درمورد شیوه استفادهاز پورتهای ورودی-خروجی در مد خروجی بود که در این پست میخواهم درمورد مد ورودی و حالتهای اینتراپت و معمولی صحبت کنیم. خب، برای ورودیکردن یک پین، اول باید منطق ورودی بودن یک پین رو مشخصکنیم که طبق گفتههای جلسه قبل ما نیاز داریم تا بیتهای رجیستر CRL-H را طبق نیاز تغییر دهیم تا بتوانیم از پین موردنظر به منظور ورودی بهره ببریم.
خب اگر پست قبلی رو دنبالکردهباشید میدونید که CRL-H طبق تصویربالا، هریک از رجیسترهای آن دارای چهارحالت است که مطابق تصویر زیر بیتهای رجیستر مثلا پین صفرم تنظیم میشود.
به تصویربالا با دقت بیشتری نگاهکنید حتما خواهید دید که در قسمت INPUT ما چهار حالت را در اختیار داریم که فعلا حالت ANALOG رو موردبررسی قرار نمیدهیم و بر سه حالت قبلی تمرکز میکنیم.
حالت input-floating:
در این حالت پین ما با هر نویز و یا ولتاژ خواسته و یا ناخواسته تحریک میشود و تغییر حالت میدهد که برای جلوگیری از این اتفاق ناخوشآیند از مقاومتهای پول خارجی استفاده میکنیم و رجیستر CRL-H ما هم در این حالت، عدد ۴ را به خود اتخاذ میکند:
GPIOA-> CRL &= ~ 0x0000000F;
این کد برای clearکردن پین صفرم استفاده میشود:
GPIOA-> CRL &= ~ 0x00000004;
این کد هم ورودی کردن پین موردنظر را انجام میدهد(floating).
حالت INPUT-PULL-DOWN:
در این حالت پین موردنظر با اعمال ولتاژ مثلا ۳.۳ولت تحریک میشود و این پین در حالت عادی صفر است. خب برای تنظیم این حالت میبینیم که دیتاشیت به ما میگه بیت CNF1 را برابر یک قرار میدهیم و همچنین رجیستر ODR را هم صفر میکنیم تا خروجی به حالت PULL Down قرار بگیرد.
GPIOA-> CRL &= ~ 0x0000000F;
این کد برای clearکردن پین صفرم استفاده میشود.
GPIOA-> CRL &= ~ 0x00000008;
این کد هم ورودی کردن پین موردنظر را انجام میدهد(PULL-DOWN).
GPIOA->ODR |= ~(1<<0);
برای پول داون کردن نیز از این کد استفاده میکنیم.
GPIOA->ODR |= (0<<0);
حالت INPUT-PULL-UP:
در این حالت پین موردنظر ما با اعمال صفر ولت تحریک میشود و این پین هم در حالت عادی برابر VCC است. در این حالت هم طبق دیتاشیت و عکس بالا CNF1 را هم یک قرار میدهیم اما رجیستر ODR را برای پین موردنظر یک میکنیم.
GPIOA-> CRL &= ~ 0x0000000F;
این کد برای clearکردن پین صفرم استفاده میشود.
GPIOA-> CRL &= ~ 0x00000008;
این کد هم ورودی کردن پین موردنظر را انجام میدهد (PULL-DOWN)
GPIOA->ODR |= (1<<0);
برای پولآپ کردن نیز از این کد استفاده میکنیم. حال که تنظیمات پین موردنظرمون را درست انجامدادیم، میرویم سروقت استفادهاز پین موردنظر. رجیستر مورداستفاده برای خواندن مقدار در حالت ورودی GPIOX-IDR است که شیوهی استفاده این رجیستر به این حالت است:
در حالت PULL-DOWN
If(GPIOA->IDR & (1<<0)) { کدی که میخواهیم بعد از فشردن کلید اجرا شود }
در حالت PULL-UP
If(! (GPIOA->IDR & (1<<0))) { کدی که میخواهیم بعد از فشردن کلید اجرا شود }
در حالت عادی رجیستر IDR بهصورت 0x0000000000000000 است که با & کردن 0x0000000000000001 مشخص میکنیم که این بیت ۱ شده است (یعنی ANDبیتی) خب من یه کد ساده مینویسم که نشونبدم پین موردنظرم ۱ شده یا نه و اگر ۱ شد led را روشن کنه در غیر اینصورت خاموش کنه:
#include <stm32f10x.h> int main (void) { RCC -> APB2ENR |= (1<<2); GPIOA -> CRL &= ~(0x000000FF) ; GPIOA -> CRL |= (0x00000038); GPIOA -> ODR |=(1<<0); while(1) { if( !(GPIOA -> IDR & (1<<0))) { GPIOA -> BSRR |= (1<<1); while(!(GPIOA -> IDR & (1<<0))); } else{ GPIOA -> BRR |= (1<<1); } }
این از پین ورودی اما یه زمانی لازمه ما از یک پین بهصورت اینتراپت استفادهکنیم تا بتوانیم بر اثر تحریک خارجی عملیات درحال اجرای CPU را متوقف نکنیم.
interrupt:
در فعال سازی اینتراپت چند پارامتر و پیکربندیای رو باید انجام بدیم.
فعالساز اینتراپت مورد (پین و یا پریفرال) نظر به صورت سراسری:
NVIC_EnableIRQ(نام اینتراپت مورد استفاده );
نام اینتراپت مورداستفاده رو در قسمت هدر میکرو خواهیم یافت. خب دوستان اینو تا یادم نرفته بگم که اینتراپت چیکار میکنه؟ مطمئنم میدونید اما گفتنش بد نیست! زمانیکه اینتراپت خارجی و یا اینتراپت داخلی (پریفرالها) اتفاق بیافته برنامهای که CPU درحال اجراش بود رو رها میکنه و برنامه ای که در تابع مخصوص اینتراپت نوشتهشده رو اجرا میکنه و بعداز اتمام کدهای تابع اینتراپت CPU برمیگرده و برنامه قبلی خودش رو ادامه میده. تابع اینتراپت برای اینتراپت خارجی به زیر تعریف میشود: (نام هر تابع اینتراپت در داخل فایل startup قرار دارد)
Void EXTI0_IRQHandler (void) { }
ما در stm32f10x به تعداد ۱۵ اینتراپت خارجی دراختیار داریم که در اکثر پایههای میکرو قابلدسترسی هستند.
نکته مهم: در این سری میکرو برای اینتراپتهای خارجی ۰ تا ۴ هرکدام تابعهای وقفه جداگانه در اختیار ما است اما از اینتراپت خارجی ۵ تا ۹ فقط یک تابع وقفه و از اینتراپتهای ۱۰تا ۱۵ هم یک تابع وقفه در اختیار داریم. بهطورمثال اگر بخواهیم همزمان از اینتراپت خارجی ۵ و ۸ و ۹ استفادهکنیم بهصورتزیر عملخواهیمکرد:
Void EXTI9-5_Handler (void) { If(EXTI -> PR & (1UL << 5)) { //your code EXTI -> PR |= (1UL << 5); } If(EXTI -> PR & (1UL << 8)) { //your code EXTI -> PR |= (1UL << 8); } If(EXTI -> PR & (1UL << 9)) { //your code EXTI -> PR |= (1UL << 9);//clear interrupt flag } }
توضیحات رجیسترهای بالا رو جلوتر خواهم گفت، اما شیوهی نوشتن تابع وقفه رو باهم یاد بگیریم:
تنظیمات AFIO:
خب با این رجیستر میتونیم مشخص کنیم که وقفه ۰ در چه پورتی (A یا B یا…) قرار بگیرد. در قسمت تنظیمات AFIO ما چهار رجیستر داریم که این رجیسترها عبارتند از:
External interrupt configuration register 1 (AFIO_EXTICR1)
External interrupt configuration register 2 (AFIO_EXTICR2)
External interrupt configuration register 3 (AFIO_EXTICR3)
External interrupt configuration register 4 (AFIO_EXTICR4)
خب این چیزی که از هر عکس بالا مشخصه AFIO_EXTICRX هرکدام چهار پین را برای هر پورت مشخص میکند. طریقه کدنویسی هم بهصورتزیر است:
AFIO -> EXTICR[x-1] |= AFIO_EXTICR1_EXTI0_PA;
X شماره رجیستر AFIO_EXTICRx است. خب کد بالا به این معناست که از داخل رجیستر EXTICR1 پین شماره ۰ از پورت A، که این (AFIO_EXTICR1_EXTI0_PA) جمله بهصورت define در داخل هدر میکرو وجود دارد. اما از نظر بیتی جمله AFIO_EXTICR1_EXTI0_PA بهصورتزیر نوشته میشود:
AFIO -> EXTICR[1-1] |= (0x0 << 0);
اگر بخواهیم پورت C را برای اینتراپت ۷ تنظیم کنیم بهصورتزیر عمل میکنیم(طبق تصویر AFIO_EXTICR2):
AFIO -> EXTICR[2-1] |= (0x2 << 12);
حال میرویم سر وقت رجیسترهای مختص اینتراپت خارجی:
Interrupt mask register (
این رجیستر برای به این منظور استفاده میشود که در هر پایهای اینتراپت بخواهیم داشتهباشیم اون پایه رو فعالکنیم.
EXTI -> IMR |= (1UL << 0);// for pin 0 interrupt EXTI -> IMR |= EXTI_IMR_M0; // for pin 0 interrupt
Rising trigger selection register (EXTI_RTSR):
این رجیستر برای این منظور استفاده میشود تا پین اینتراپت خارجی ما در لبه بالا رونده عکسالعمل ازخودش نشاندهد.
EXTI -> FTSR &=~ EXTI_FTSR_TR0;
این خط برای clearکردن لبه پایین رونده استفاده میشود.
EXTI -> RTSR |= EXTI_RTSR_TR0;
این خط برای فعال کردن لبه بالا رونده استفاده میشود.
Falling trigger selection register (EXTI_FTSR):
این رجیستر برای این منظور استفاده میشود تا پین اینتراپت خارجی ما در لبه پایین رونده عکسالعمل ازخودش نشاندهد.
EXTI -> RTSR &=~ EXTI_RTSR_TR0;
این خط برای clearکردن لبه بالا رونده استفاده میشود.
EXTI -> FTSR |= EXTI_FTSR_TR0;
این خط برای فعال کردن لبه پایین رونده استفاده میشود.
Pending register (EXTI_PR):
این رجیستر تشخیص میدهد که وقفه در کدام پایه اتفاق افتاده است که در تابع وقفه استفاده میشود.
If(EXTI -> PR & (1UL << 5)) { //your code EXTI -> PR |= (1UL << 5); }
بعداز تشخیص باید با ۱ کردن در این رجیستر عمل clear بهصورت نرمافزاری صورت بگیرد تا اینتراپتهای بعدی را هم تشخیصدهیم برای پایه موردنظر.
EXTI -> PR |= (1UL << 5); //clear
نکته همیشگی: برای استفادهاز هر پریفرالی حتما باید کلاک اون پریفرال رو فعال کنید وگرنه برنامه شما اصلا عمل نمیکنه.
RCC -> APB2ENR |= RCC_APB2ENR_AFIOEN;
نکته: برای استفادهاز اینتراپت خارجی باید حتما پین موردنظرمون را اول ورودی کنیم.
کد پایانی
امیدوارم موفق باشید.
منبع: سیسوگ