خدایا با نام تو و برای تو
سلام و عرض ادب خدمت دوستان همراه، مهدی حسن زاده آملی هستم با جلسه دوم آموزش کاملا رجیستری stm32f10x درخدمت شما هستم. در مطالب جلسهی اول موضوع بحث ما درمورد شیوه استفاده از پورتهای ورودی-خروجی در مد خروجی بود که در این پست میخواهم درمورد مد ورودی و حالتهای اینتراپت و معمولی صحبت کنیم.
خب، برای ورودی کردن یک پین، اول باید منطق ورودی بودن یک پین رو مشخص کنیم که طبق گفتههای جلسه قبل ما نیاز داریم تا بیتهای رجیستر CRL-H را طبق نیاز تغییر دهیم تا بتوانیم از پین مورد نظر به منظور ورودی بهره ببریم.
خب اگر پست قبلی رو دنبال کرده باشید میدونید که CRL-H طبق تصویر بالا، هر یک از رجیسترهای آن دارای چهار حالت است که مطابق تصویر زیر بیتهای رجیستر مثلا پین صفرم تنظیم میشود.
به تصویر بالا با دقت بیشتری نگاه کنید حتما خواهید دید که در قسمت INPUT ما چهار حالت را در اختیار داریم که فعلا حالت ANALOG رو مورد برسی قرار نمیدهیم و بر سه حالت قبلی تمرکز میکنیم.
حالت input-floating:
در این حالت پین ما با هر نویز و یا ولتاژ خواسته و یا ناخواسته تحریک میشود و تغییر حالت میدهد که برای جلو گیری از این اتفاق ناخوشآیند از مقاومتهای پول خارجی استفاده میکنیم و رجیستر CRL-H ما هم در این حالت، عدد 4 را به خود اتخاذ میکند.
1 | GPIOA-> CRL &= ~ 0x0000000F; |
این کد برای clearکردن پین صفرم استفاده میشود.
1 | GPIOA-> CRL &= ~ 0x00000004; |
این کد هم ورودی کردن پین مورد نظر را انجام میدهد (floating).
حالت INPUT-PULL-DOWN:
در این حالت پین موردنظر با اعمال ولتاژ مثلا 3.3 ولت تحریک میشود و این پین در حالت عادی صفر است. خب برای تنظیم این حالت میبینیم که دیتاشیت به ما میگه بیت CNF1 را برابر یک قرار میدهیم و همچنین رجیستر ODR را هم صفر میکنیم تا خروجی به حالت PULL Down قرار بگیرد.
1 | GPIOA-> CRL &= ~ 0x0000000F; |
این کد برای clear کردن پین صفرم استفاده میشود.
1 | GPIOA-> CRL &= ~ 0x00000008; |
این کد هم ورودی کردن پین مورد نظر را انجام میدهد (PULL-DOWN).
1 | GPIOA->ODR |= ~(1<<0); |
برای پول داون کردن نیز از این کد استفاده میکنیم.
1 | GPIOA->ODR |= (0<<0); |
حالت INPUT-PULL-UP:
در این حالت پین موردنظر ما با اعمال صفر ولت تحریک میشود و این پین هم در حالت عادی برابر VCC است. در این حالت هم طبق دیتاشیت و عکس بالا CNF1 را هم یک قرار میدهیم اما رجیستر ODR را برای پین مورد نظر یک میکنیم.
1 | GPIOA-> CRL &= ~ 0x0000000F; |
این کد برای clear کردن پین صفرم استفاده میشود.
1 | GPIOA-> CRL &= ~ 0x00000008; |
این کد هم ورودی کردن پین مورد نظر را انجام میدهد (PULL-DOWN)
1 | GPIOA->ODR |= (1<<0); |
برای پول آپ کردن نیز از این کد استفاده میکنیم.
حال که تنظیمات پین مورد نظرمون را درست انجام دادیم ، میرویم سروقت استفاده از پین موردنظر. رجیستر مورد استفاده برای خواندن مقدار در حالت ورودی GPIOX-IDR است که شیوهی استفاده این رجیستر به این حالت است:
در حالت PULL-DOWN
1 2 3 4 5 | If(GPIOA->IDR & (1<<0)) { کدی که میخواهیم بعد از فشردن کلید اجرا شود } |
در حالت PULL-UP
1 2 3 4 5 | If(! (GPIOA->IDR & (1<<0))) { کدی که میخواهیم بعد از فشردن کلید اجرا شود } |
خب من یه کد ساده مینویسم که نشون بدم پین موردنظرم یک شده یا نه و اگر یک شد led را روشن کنه در غیر این صورت خاموش کنه
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #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:
در فعال سازی اینتراپت چند پارامتر و پیکربندیای رو باید انجام بدیم.
فعالساز اینتراپت مورد (پین و یا پریفرال) نظر به صورت سراسری:
1 | NVIC_EnableIRQ(نام اینتراپت مورد استفاده ); |
تابع اینتراپت برای اینتراپت خارجی به زیر تعریف میشود:
(نام هر تابع اینتراپت در داخل فایل startup قرار دارد)
1 2 3 4 | Void EXTI0_IRQHandler (void) { } |
ما در stm32f10x به تعداد 15 اینتراپت خارجی دراختیار داریم که در اکثر پایههای میکرو قابل دسترسی هستند.
نکته مهم: در این سری میکرو برای اینتراپتهای خارجی 0 تا 4 هرکدام تابعهای وقفه جداگانه در اختیار ما است اما از اینتراپت خارجی 5 تا 9 فقط یک تابع وقفه و از اینتراپتهای 10تا 15 هم یک تابع وقفه در اختیار داریم.
به طور مثال اگر بخواهیم همزمان از اینتراپت خارجی 5 و 8 و 9 استفاده کنیم به صورت زیر عمل خواهیم کرد:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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:
خب با این رجیستر میتونیم مشخص کنیم که وقفه 0 در چه پورتی (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 هر کدام چهار پین را برای هر پورت مشخص میکند.
طریقه کد نویسی هم به صورت زیر است:
1 | AFIO -> EXTICR[x-1] |= AFIO_EXTICR1_EXTI0_PA; |
X شماره رجیستر AFIO_EXTICRx است.
خب کد بالا به این معناست که از داخل رجیستر EXTICR1 پین شماره 0 از پورت A، که این (AFIO_EXTICR1_EXTI0_PA) جمله به صورت define در داخل هدر میکرو وجود دارد.
اما از نظر بیتی جمله AFIO_EXTICR1_EXTI0_PA به صورت زیر نوشته میشود:
1 | AFIO -> EXTICR[1-1] |= (0x0 << 0); |
اگر بخواهیم پورت C را برای اینتراپت 7 تنظیم کنیم به صورت زیر عمل میکنیم (طبق تصویر AFIO_EXTICR2):
1 | AFIO -> EXTICR[2-1] |= (0x2 << 12); |
حال میرویم سر وقت رجیسترهای مختص اینتراپت خارجی:
Interrupt mask register (EXTI_IMR):
این رجیستر برای به این منظور استفاده میشود که در هر پایهای اینتراپت بخواهیم داشته باشیم اون پایه رو فعال کنیم.
1 2 | EXTI -> IMR |= (1UL << 0);// for pin 0 interrupt EXTI -> IMR |= EXTI_IMR_M0; // for pin 0 interrupt |
Rising trigger selection register (EXTI_RTSR):
این رجیستر برای این منظور استفاده میشود تا پین اینتراپت خارجی ما در لبه بالا رونده عکس العمل ازخودش نشان دهد.
1 | EXTI -> FTSR &=~ EXTI_FTSR_TR0; |
این خط برای clear کردن لبه پایین رونده استفاده میشود.
1 | EXTI -> RTSR |= EXTI_RTSR_TR0; |
این خط برای فعال کردن لبه بالا رونده استفاده میشود.
Falling trigger selection register (EXTI_FTSR):
این رجیستر برای این منظور استفاده میشود تا پین اینتراپت خارجی ما در لبه پایین رونده عکس العمل ازخودش نشان دهد.
1 | EXTI -> RTSR &=~ EXTI_RTSR_TR0; |
این خط برای clear کردن لبه بالا رونده استفاده میشود.
1 | EXTI -> FTSR |= EXTI_FTSR_TR0; |
این خط برای فعال کردن لبه پایین رونده استفاده میشود.
Pending register (EXTI_PR):
این رجیستر تشخیص میدهد که وقفه در کدام پایه اتفاق افتاده است که در تابع وقفه استفاده میشود.
1 2 3 4 5 | If(EXTI -> PR & (1UL << 5)) { //your code EXTI -> PR |= (1UL << 5); } |
بعد از تشخیص باید با یک کردن در این رجیستر عمل clear به صورت نرم افزاری صورت بگیرد تا اینتراپتهای بعدی را هم تشخیص دهیم برای پایه موردنظر.
1 | EXTI -> PR |= (1UL << 5); //clear |
نکته همیشگی: برای استفاده از هر پریفرالی حتما باید کلاک اون پریفرال رو فعال کنید واِلله برنامه شما اصلا عمل نمیکنه.
1 | RCC -> APB2ENR |= RCC_APB2ENR_AFIOEN; |
نکته: برای استفاده از اینتراپت خارجی باید حتما پین مورد نظرمون را اول ورودی کنیم.
کد پایانی
امیدوارم موفق باشید.
سلام و وقت بخیر
استاد چرا انقد اشتباه منطقی توی متن و برنامه هاتون هست. اینارو اصلاح کنید خواننده تازه کار تو دردسر میفته
سلام دوست عزیز
همه مطالب رو ما نمی نویسیم، ولی نقد شما درست هست
ای کاش اشاره میکردید کجای متن اشتباه منطقی دیدید ؟
با سلام و عرض خسته نباشید
سپاس از شما بابت زمانی که صرف کردید و این مطلب مفید رو ارائه فرمودید.
در قسمت ورودی کردن پین های پورت، در کدی که قرار دادید رجیستر ها با مقدار نات، اَند شده اند که در این شرایط بیت مورد نظر یک نمیشود.
GPIOA-> CRL &= ~ 0x00000008;
نظر بنده این هست که این خط کد باید بصورت زیر اصلاح شود.
GPIOA-> CRL |= 0x00000008;
ممنون از شما
سلام دوست عزیز ولی شما دقیقا کدام خط رو میگید ؟
من متوجه نشدم
سلام
خیلی ممنون از مطالب مفید و اموزنده تون
ایشالله که بقیه اموزش رو هم قرار بدین
من سعی کردم کد وقفه رو برای پایه های A0 و A1 بنویسم که برای بقیه به اشتراک میگذارم:
#include
void EXTI0_IRQHandler (void)
{
EXTI-> PR=0x00000001; // 1<BSRR|=(1<<1);
int i;
for(int i=0;iBRR|=(1< APB2ENR |= (1< CRL &= ~(0x000000FF) ; //Clear PinA0 and PinA1
GPIOA -> CRL |= (0x00000038); //Enable PinA0 as input and PinA1 as output
GPIOA -> ODR |=(1<ODR |= (1< APB2ENR |= RCC_APB2ENR_AFIOEN; //Enable clock for interrupt (AFIO EN) 1< EXTICR[0] |= AFIO_EXTICR1_EXTI0_PA; //AFIO -> EXTICR[0] |= (0x0 < IMR |= 1 < RTSR |= 1 << 0;
//——————————————-
NVIC_EnableIRQ(EXTI0_IRQn);
//——————————————-
while(1)
{
}
return 0;
}
سلام ممنون از شما دوست عزیز
سلام.خسته نباشید و خدا قوت
سوال بنده این هست که منظور از ul در کنار عددی که داخل شماره بیت رجیستر قرار میگیره چیه؟ایا unsighnd large منظورش هست؟یا اینکه اون عدد در اون رجیستر اگر بیشتر از یک باشه به سمت چپ شیفت پیدا میکنه؟
سلام این عبارت یه کامپایلر میگه که نوع این عدد unsigned long باشه.
چرا ادامه ندادید ؟
ادامه پیدا میکنه دوست عزیز 🙂
سلام مهدی هستم نویسنده پست :متاسفانه درگیر خدمت سربازی و …بودم
بزودی ادامه خواهم داد مطمئن باشید
ببخشید بابت تاخیر
آقا مهدی ما هم منتظر ادامه مطلب هستیم
لطفا ادامه بدین . و اینکه نمی شه این فایل هارو به صورت پی دی اف بشه دانلود کرد چون همیشه که اینترنت در دسترس نیست اگه بشه خیلی خوب می شه 🙂 ممنون
سلام چرا پستهای مربوط به اموزش التیوم رو برداشتین؟تازه قسمت 7مونده بود و ادامه دار بود.لطفا این اموزش رو ادامه بدین خیلی پیگیر بودم
سلام دوست عزیز
پست های مربوط به اموزش التیوم از روی سایت پاک نشده . شما میتونین آنها را اینجا پیدا کنین
https://sisoog.com/?s=%D8%A2%D9%85%D9%88%D8%B2%D8%B4+%D8%A2%D9%84%D8%AA%DB%8C%D9%88%D9%85+%D8%AF%DB%8C%D8%B2%D8%A7%DB%8C%D9%86%D8%B1&post_type=post
برای رفاه حال دوستان به زودی دسته آموزش آلتیوم دیزاینر در قسمت آموزشها اضافه میشه.
ممنون از همراهی شما