پنج قسمت از آموزش های میکورکنترلر AVR با کامپایلر Atmel Studio منتظر شده است. در این آموزش ها به پیش نیاز هایی که برای شروع برنامه نویسی بر روی میکروکنترلر های AVR نیاز بوده، پرداخته شد. اما در این بخش از سلسله آموزش میکروکنترلر های AVR قصد داریم تا برنامه نویسی بر روی میکروکنترلر های AVR را شروع کنیم. مهم ترین بخشی که در یادگیری میکروکنترلر ها ابتدا به آن توجه میشود. بخش GPIO در میکروکنترلر ها است. در این بخش به توضیح بخش GPIO در میکروکنترلر های AVR میپردازیم. در ادامه با با مرجع تخصصی بردهای امبدد به زبان فارسی، دیجی اسپارک همراه باشید.
شناخت پایه های GPIO
کلمه GPIO مخفف عبارت General-Purpose Input/Output است. در اصل یک رابط ورودی و خروجی در مدار های اکترونیکی است. پایه هایی که در میکروکنترلر ها قابلیت تعریف شدن بعنوان ورودی و خروجی دارند را GPIO مینامند. GPIO در برخی از آیسی ها عنوان یک عملکرد اصلی ارائه میشود. اما در برخی دیگر از آیسی ها GPIO به عنوان یک پریفرال، مناسب برای برخی از عملکرد های اصلی دیگر مورد استفاده قرار میگیرد.
در ارتباطات موازی داده ها از طریق GPIO از فرستنده به سمت گرینده ارسال میشود. اینو نوع ارتباط ها عموما صرعت بیشتری نسبت به ارتباط های سریال دارند. اما هزینه پیاده سازی و نویز پذیری در آن نیز بسیار بیشتر از ارتباط سریال است.
GPIO در میکروکنترلر های AVR
در میکروکنترلر های AVR نیز مانند دیگر میکروکنترلر ها GPIO ها رابط اصلی میکروکنترلر با دیگر قطعات است. به طوری که به طور پیشقرض پایه بعنوان GPIO است. و در صورت فعال کردن یک پریفرال کاربرد آن پایه متفاوت خواهد شد. در میکروکنترلرهای AVR پایه ها در ۲ وضعیت ورودی و خروجی تعریف شده و برای موارد مختلف مورد استفاده قرار گیرد. همیشه اولین مثال برای یادگیری GPIO ها نوشتن برنامه LED به صورت چشمکزن است.
برای کنترل وضعیت روشن و یا خاموش بودن LED بایستی پایه ای که LED به آن متصل است را به عنوان خروجی تعریف کنید. سپس به با استفاده از دستورات مربوطه وضعیت پایه را بین ۰ و ۱ مشخص کنید. اما وضعیت دیگری که پایه های میکروکنترلر میتوانند داشته باشند، حالت ورودی است. در این جالت میکروکنترلر میتواند وضعیت ۰ و ۱ قرار گرفته بر روی پایه خود را تشخص داده و با توجه به این وضعیت کاری را انجام دهد.
مفهوم PORT و PIN در AVR
برای درک بهتر مورادی که در ادامه قرار است به توضیح آن به پردازیم. بهتر است که یک میکروکنترلر را انتخاب کرده و پایه های آن را مورد بررسی قرار دهیم. در این آموزش میکروکنترلر ATmega8 را برای بررسی انتخای میکنیم. اگر شما میکروکنترلر دیگری را مد نظر دارید میتوانید نام میکروکنترلر را به همراه عبارت Pinout در گوگل سرچ کنید. تا تصویری همانند تصویر زیر را بیابید.
در تصویر بالا شماره پایه های میکروکنترلر مشخص است. پایه های VCC و GND که خطوط تغذیه مکیروکنترلر هستند. و بایستی یک تغذیه ۵ ولت را با دقت به این پایه ها متصل کنید. پایه های AVCC و AREF هم مربوط به مبدل آنالوگ به دیجیتال است که در آموزش های بعد به آن میپردازیم. اما پایه های دیگری هستند که با عبارت های PD، PC، PB به همراه یک شماره مشخص شده اند. در میکروکنترلر های AVR، پایه های GPIO به دسته های ۸ تایی تقسیم میشوند که با نام پورت شناخته میشوند. بنابراین پایه های PB0 تا PB7 پایه های PORTB هستند (البته در میکروکنترلر هایی مانند میکروکنترلر ATmega8 و … برخی پورت ها تعداد GPIO های کمتری داشته باشند، مانند PORTC).
تعیین وضعیت GPIO ورودی یا خروجی
برای برنامه ریزی GPIO ها در میکروکنترلر های AVR رجیستر هایی وجود دارد که با مقدار دهی آنها میتوانید GPIO مورد نظر را بعنوان ورودی یا خروجی تعریف کنید. رجیستر DDRx برای انتخاب وضعیت GPIO بعنوان ورودی و خروجی است. x بیانگر پورت مورد نظر است. این رجیستر دارای ۸ بیت است که به ترتیب ارزش هر بیت برای مشخص کردن وضعیت یک پایه است. بیت پر ارزش برای پایه شماره ۷ و بیت کم ارزش برای پایه شماره ۰ است. برای درک بهتر به مثال زیر توجه کنید.
در راه اندازی کیپد ۸ پایه نیاز است. بنابر این یک پورت کامل میکروکنترلر برای راه اندازی کیپد استفاده میشود (برای مثال پورت D). نحوه راه اندازی کی پد به این صورت است که بایستی ۴ پایه از میکروکنترلر که به ردیف های کیپد متصل است را خروجی و ۴ پایه ای که ستون ها متصل است را به عنوان ورودی تعریف کنید. اتصالات به این صورت است که ردیف ها به پایه های PD0 تا PD3 متصل شده و ستون ها به PD4 تا PD7 متصل شده است. بنابراین رجیستر DDRD بایستی به صورت زیر مقدار دهی مشود.
DDRD = 0x0F;
روش بالا روش مقدار دهی به تمامی بیت های رجیستر به صورت یکجا است. اما میتوانید این روش را به صورت بیت به بیت هم انجام دهید. به این معنا که تنها یک پایه را به عنوان ورودی و یا خروجی تعریف کنید.
DDRD = (0 << 7) | (0 << 6) | (0 << 5) | (0 << 4) | (1 << 3) | (1 << 2) | (1 << 1) | (1 << 0);
مقدار دهی به GPIO میکروکنترلر
زمانی که یکی از GPIO های میکروکنترلر را به عنوان خروجی تعریف کنید. برای انجام عملیات های مختلف مانند روشن کردن LED و یا تولید پالس و … بایستی مقدار دیجیتالی بر روی این پایه قرار بگیرد. منظور از مقدار دیجیتال ۰ و ۱ بر روی پایه ها است. که در منطق TTL مقدار ۱ برابر VCC است که در میکروکنترلر های AVR ولتاژ VCC برابر ۵ ولت است. بنابراین زمانی که داخل برنامه مقدار ۱ به پایه ای نسبت داده شود. در واقعیت ولتاژ قرار گرفته بر روی آن پایه ۵ ویت خواهد بود.
رجیستری که عملیات مقدار دهی به GPIO هارا در میکروکنترلر های AVR برای ما انجام میدهد، رجیستر PORTx است. بجای x بایستی نام پورت مورد نظر خود را قرار دهید. این رجیستر نیز مانند رجیستر DDRx هشت بیتی است و هر بیت از آن برای مقدار دهی به یکی از پایه های پورت است. توجه داشته باشید که این رجیستر تنها زمانی کاربرد دارد که GPIO را بعنوان خروجی تعریف کرده باشید. برای مثال فرض کنید. پایه دوم پورت B را بعنوان خروجی تعریف کرده و قصد دارید این پایه را به صورت چشمکزن با تاخیر ۱ ثانیه در آورید. کد مربوطه به صورت زیر خواهد بود.
PORTB = (1 << 1); _delay_ms(1000); PORTB = (0 << 1); _delay_ms(1000);
تابع _delay_ms برای ایجاد تاخیر استفاده میشود. ورودی این تابع زمان تاخیر مورد نیاز بر واحد میلی ثانیه است. در آموزش های بعدی بیشتر به این تابع خواهیم پرداخت.
خواندن مقدار GPIO میکرکنترلر AVR
علاوه بر مقدار دهی به GPIO ها خواندن مقادیر آنها نیز بسیار مهم است. همانطور که گفته شد ساده ترین مثال برای خواندن مقدار GPIO ها استفاده از Push button است. البته این تنها کاربرد این بخش نیست. برای راه اندازی بسیاری از سنسور ها و یا پروتکل های ارتباطی در میکروکنترلر ها نیاز است که مقدار پایه ای که از سنسور و یا ماژول دیگری به میکروکنترلر متصل شده است، خوانده شده و بر اساس آن کاری انجام شود. اما خواندن مقادیر GPIO ها بر خلاف مقدار دهی به آنها ساده نیست و کمی پیچیده تر است. رجیستری که عملیات خواندن مقدار GPIO را برای ما انجام میدهد. رجیستر PINx است. این رجیستر نیز مطابق رجیستر های قبل دارای ۸ بیت است که هر بیت مشخص کننده یکی از پایه های پورت است. برای مثال اگر بخواهیم مقدار پایه سوم پورت C را بخوانیم دستور زیر اسن کار را برای ما انجام میدهد.
PINC & (0 << 2)
خروجی این دستور مقدار ۱ یا ۰ خوانده شده از پایه است. اما فرض کنید. کلیدی بر روی پایه سوم پورت C قرار داده اید. پس از فشرده شدن مقدار قرار گرفته شده بر روی پایه از ۱ به ۰ تغییر میکند. اما زمانی که شما دست خود را از روی کلید بردارید باز مقداری که بر روی پایه میکروکنترلر است همچنان ۰ باقی مانده است. برای رفع این مشکل بایستی از ۱ مقاومت که پایه را به VCC متصل کند، نیاز دارید. این مقاومت را مقاومت PULL-UP مینامند.
این مقاومت میتواند بیرون از IC بر روی پایه میکروکنترلر قرار بگیرد. اما این مقاومت داخل میکروکنترلر نیز قرار دارد. اما در حالت پیشفرض فعال نیست. برای فعال کردن این مقاومت، بایستی رجیستر PORTx را هنگامی که پایه در حالت ورودی تعریف شده است استفاده کنید. پس از فعال کردن این مقاومت اگر کلیدی به پایه میکروکنترلر متصل میکنید. بایستی سر دیگر کلید را به زمین متصل کنید و داخل برنامه شرط خود را به صورت زیر بنویسید.
if(PINC & (0 << 2)){ عملیات مورد نظر که بایستی پس از فشرده شدن کلید انجام شود، در این قسمت نوشته شود! }
دستور if یکی از دستورات شرطی زبان C است. در صورتی که شرظ قرار گرفته داخل پرانتز دستور if درست باشد. دستوراتی که بین {} قرار بگیرند اجرا خواهند شد.
جمع بندی
همانطور که در طول آموزش گفته شده است. میتوان گفت مهم ترین راه ارتباطی هر میکروکنترلری با دنیای بیرونی آن GPIO های میکروکنترلر هستند. ممکن است این GPIO تنها بعنوان ورودی و خروجی استفاده شوند. وهمچنین ممکن است غیر از IO نقش های دیگری مانند پایه پورتکل های مختلف مانند USART، SPI و I2C داشته باشند و یا بعنوان ورودی آنالوگ نیز مورد استفاده قرار بگیرند. در ادامه آموزش های میکروکنترلر AVR به راه اندازی پریفرال های مختلف نیز خواهیم پرداخت.
چنانچه در مراحل راه اندازی و انجام این پروژه با مشکل مواجه شدید. بدون هیچ نگرانی در انتهای همین پست، به صورت ثبت نظر سوالتان را مطرح کنید. من در سریعترین زمان ممکن پاسخ رفع مشکل شما را خواهم داد. همچنین اگر ایرادی در کدها و یا مراحل اجرایی وجود دارند میتوانید از همین طریق اطلاع رسانی کنید.
در پایان نظرات و پیشنهادات خود را با ما درمیان بگذارید و با اشتراک گذاری این آموزش در شبکه های اجتماعی از وبسایت دیجی اسپارک حمایت کنید.
سلام ؛ از مطالب خوبتان تشکر میکنم ضمنا سوالی دارم : چرا برای مقدار دهی به port در حالت خروجی صرفا از یک مساوی(=) استفاده میکنیم اما برای مقدار دهی به PIN باید دوتا مساوی (==) بذاریم ؟ (ممنون میشوم طبق روال دیجی اسپارک ، توضیح کامل بدهید )
با سلام
یک = به معنای مقدار دهی یک رجیستر مانند PORT و یا مقدار دهی به یک متغیر است. برای مثال شما قصد دارید مقدار ۱۰ را در متغیر a ذخیره کنید. بنابراین بایستی بنویسید a = 10.
اما دو مساوی (==) به معنای مقایسه دو مقدار با یکدیگر است. برای مثال شما قصد دارید مقدار یک رجیستر مانند PIN را با مقدار صفر یا یک مقایسه کنید و نتیجه ان را داخل متغیر b ذخیره کنید. در این صورتی بایستی بنویسید b = (PINA.0 == 1) در صورتی ک مقادیر مقایسه شده با یک دیگر برابر باشد، داخل متغیر b مقدار true که برابر عدد یک است. قرار میگیرد اما در صورتی که این مقادیر با یک دیگر برابر نباشند، مقدار false که برابر عددصفر است. داخل متغیر b قرار میگیرد.
سلام و ممنون از پاسخ خوبتون
یه سوال دیگه هم دارم اگر زحمتی نیست ؛
۱-من وقتی یک پورت رو خروجی تعریف کردم ، در حلقه while بجای استفاده از PORT از کلمه PIN استفاده کردم ( برای on-off کردن یک پایه ) جالب اینکه delay هرچقدر بزاریم باز ۲ برابر زمان میبرد تا یک سیکل on_off کند ( delay×۲ میشود ) .؛ علت “فنی” این قضیه چی میتواند باشد ؟
سوال ۲- حتی اگر پایه ورودی باشد (DDR=0X00) و ما مثلا PIN=0XFF بدهیم و در عین حال به رجیستر PORT=0X0F بدهیم ، باز میبینیم میکرو ، مقدارِ رجیستر PORT را بر رجیسترPIN مقدم داشته و PIN=PORT=0X0F میشود ! چرا ؟ مگر هر دو رجیستر نیستند چرا برای میکرو رجیستر PORT ارجحیت دارد ?
با سلام
رجیستر PIN برای خواندن از پورت تعریف شده و شما نباید در برنامه به این رجیستر مقدار دهید. ممکن است کامپایلر مقدار دهی به این رجیستر را نادیده بگیرد و اروری دریافت نکنید. اما این کار از نظر منطق مکیروکنترلر های AVR مشکل داشته و درست نیست.
سلام مهندس
چجوری میشه PORTx یا DDRx رو به عنوان ورودی تابع در atmel studio تعریف کرد؟
هم int و هم unsigned char در نظر گرفتم ولی قبول نکرد.
میشه نوع این دو رو بگید؟
اصلا میشه به عنوان ورودی یا خروجی یک تابع در نظر گرفتشون؟
با سلام
پارامتر ورودی تابع را از نوع uint8_t انتخاب کنید احتمالا مشکل رفع خواهد شد.