نمونه برنامههای نوشته شده به زبان اسمبلی برای AVR
سه شنبه, ۲۹ ارديبهشت ۱۳۹۴، ۰۹:۴۰ ق.ظ
سلام خدمت همگی عزیزان
در ادامه مطلب، چند نمونه برنامه ساده به زبان assembly به همراه توضیحاتی مختصر درباره هر برنامه قرار دادم. مرور این برنامهها کمک خوبی به درک مفاهیم پایه از جمله مقدار دهی اولیه به رجیسترها، کار با پورتها و ایجاد حلقههای تکرار میکند.
پسوند فایلهای قرار داده شده asm. هستند که با نرم افزار notepad هم قابل نمایش و ویرایش هستند.
موفق و مؤید باشید.
برنامهای برای آشنایی با نحوه مقدار دهی به رجیسترها، خواندن و نوشتن در حافظه داده و دسترسی به فضای حافظه برنامه
- در این برنامه ابتداً با استفاده از دستورات EQU. و DEF. رجیسترهای مورد نیاز نام گذاری شدهاند. آدرس و نام رجیسترهای فضای I/O در صفحات انتهایی datasheet قید شدهاند. با استفاده از لینک زیر نیز میتوانید این صفحات را دریافت نمایید: ATMega32 I/O Registers Summary
- سپس توسط برخی از دستورات انتقال داده، چندین رجیستر از فضای حافظه داده مقدار دهی شدهاند.
- در بخش انتهایی کد، با استفاده از دستور LPM و اشاره گر Z، به دادههای تعریف شده در حافظه برنامه دسترسی پیدا کردهایم. دقت نمایید که مقدار رجیستر Z دو برابر آدرس شروع دادهها در حافظه برنامه است. دلیل این موضوع این است که خانههای حافظه برنامه 16 بیتی هستند. به عبارت دیگر هر یک از خانههای حافظه برنامه دو بایت از اطلاعات را در خود ذخیره میکنند. اما از آنجایی که خانههای حافظه داده 8 بیتی هستند، برای انتقال اطلاعات از حافظه برنامه به حافظه داده، باید بایتهای بالا و پایین خانههای حافظه برنامه را از هم تفکیک کنیم و هر یک را به صورت مجزا بخوانیم. دستور LPM همین عملیات را برای ما پیاده سازی میکند. تنها کاری که ما باید انجام دهیم این است که باید خانههای حافظه برنامه را به صورت تک بایتی تصور کنیم و بر این اساس اشاره گر Z را مقدار دهی نماییم.
برنامهای برای خواندن مقادیر پورتهای A و B، محاسبه اختلاف این دو مقدار و انتقال نتیجه محاسبه به پورت C
- در این برنامه فرض کردیم که دو DIP Switch هشت تایی به پورتهای A و B یک میکرو کنترلر ATMega32 متصل شدهاند و یک نمایشگر 7segment نیز به پورت C این میکرو متصل شده است تا نتیجه محاسبه بر روی آن نمایش داده شود.
- در فاز ابتدایی این برنامه پورتهای A و B به صورت ورودی و پورت C به صورت خروجی معرفی شدهاند.
- سپس اعدادی که هر یک از سوئیچها روی آن تنظیم شدهاند را از رجیسترهای PINA و PINB خوانده و اختلاف آنها را محاسبه مینماییم. (عمل خواندن به معنای انتقال داده از رجیسترهای PIN به رجیسترهای عمومی است)
- در انتها نیز، اختلاف محاسبه شده به پورت C انتقال یافته است.
- همچنین فرض کردیم که پایههای وصل شده به سوئیچها توسط چندین مقاومت، به زمین متصل شده یا به عبارت دیگر Pull-Down شدهاند. به این ترتیب در زمان قطع بودن یک سوئیچ، ولتاژ GND به پایه مربوط به آن منتقل میشود. برای مشاهده اتصالات پایهها به سوئیچها و مقاومتهای Pull-Down، به فایل شماتیک مراجعه فرمایید.
راهنمای مدت زمان اجرای دستورات در این برنامه
برنامهای برای ایجاد 1ms تأخیر (با صرف نظر از چند میکرو ثانیه زمان اضافی)
- در این برنامه با فرض استفاده از میکرو کنترلر ATMega32 با فرکانس کاری 4MHZ، با استفاده از دستورات انشعابی تأخیری معادل 1ms ایجاد میکنیم. البته مقدار دقیق این تأخیر چند میکرو ثانیه بیش از یک میلی ثانیه است که با توجه به آموزشی بودن این مثال، از این مقدار صرف نظر میکنیم.
- همان طور که میدانید رابطه فرکانس با مدت زمان یک سیکل (Clock Cycle Time) یا پالس کامل به این صورت است: f=1/T
- بر طبق این رابطه زمان یک پالس کامل در ATMega32 برابر با 0.25 میکرو ثانیه میباشد. بنابراین دستوراتی که در عرض یک پالس ساعت اجرا میشوند، زمان اجرایی معادل با 0.25 میکرو ثانیه دارند. قالب دستوراتی که با رجیسترهای عمومی کار میکنند، در عرض یک کلاک پالس، به اتمام میرسند. اما برخی از دستورات، مانند دستورات دسترسی به حافظه داده یا دستورات پرشی، نیازمند بیش از یک کلاک برای اجرا هستند. برای اطلاع از مدت زمان مورد نیاز برای اجرای هر دستور به برگه خلاصه دستورات اسمبلی مراجعه فرمایید.
- با این توضیحات برای تولید تأخیر در یک برنامه، میتوانیم مجموعهای از دستورات را در قالب حلقهها چندین مرتبه اجرا نماییم تا به تأخیر مورد نظر دست یابیم.
- در این برنامه، ابتداً با استفاده از حلقهای شامل دو دستور با مدت زمان 1 پالس و یک دستور با مدت زمان 2 پالس، تأخیری معادل با 1 میکرو ثانیه ایجاد شده است. پس از آن برای رسیدن به زمان 1 میلی ثانیه، این دستورات به کمک حلقههای تکرار، 1000 مرتبه اجرا شدهاند.
- همان طور که میدانید برای ایجاد یک حلقه تکرار، نیازمند در نظر گرفن شمارندهای هستیم که پس از هر بار اجرای حلقه، یک واحد افزایش بیابد یا در صورت شمارش نزولی، پس از هر بار اجرای حلقه، یک واحد کاهش پیدا کند. این شمارنده میتواند یکی از رجیسترهای عمومی R0 تا R31 باشد. نکتهای که در هنگام استفاده از رجیسترهای عمومی به عنوان شمارنده باید به آن توجه نمود، ظرفیت محدودشان میباشد. حداکثر مقداری که میتوان در یکی از این رجیسترها ذخیره نمود، عدد 11111111 در مبنای دو که معادل 255 در مبنای ده است، میباشد. بنابراین شمارندهای که با یکی از این رجیسترها طراحی شده باشد، حداکثر میتواند تا سقف 255 را شمارش نماید.
- در این مثال برای رفع این مشکل و رسیدن به تعداد 1000 مرتبه تکرار، دو شمارنده نزولی با مقادیر اولیه 250 و 4 برای ایجاد دو حلقه تو در تو در نظر گرفته شده است. حلقه اول یا حلقه اصلی، حلقهایست که با استفاده از آن تأخیری معادل 1 میکرو ثانیه ایجاد میکنیم. شمارنده این حلقه از مقدار 250 شروع به کار میکند و پس از هر بار اتمام حلقه، یک واحد کاهش مییابد. حلقه ثانویه، حلقهایست که در بر دارنده حلقه اول است و شمارنده آن که از عدد 4 شروع به کار مینماید، با هر بار صفر شدن شمارنده حلقه اصلی، یک واحد کاهش مییابد. بنابراین با استفاده از این دو حلقه، دستورات مورد نظر در مجموع 250*4 مرتبه اجرا میشوند که همان مقدار مطلوب برای ایجاد 1 میلی ثانیه تأخیر است.
- به دلیل وجود دستورات مربوط به حلقه بیرونی، زمانی نهایی اندکی بیش از 1ms خواهد بود.
- در حالت کلی برای دقیق کردن زمان تأخیر، میتوان شمارنده حلقه داخلی را اندکی کاهش داد و با اضافه کردن چندین دستور NOP در انتهای برنامه به مقدار دقیق تأخیر دست یافت.
- در این مثال خاص، اگر مقدار رجیستر R16 را برابر با 249 قرار دهیم به مقدار دقیق 1ms دست مییابیم.
- میتوان از این قطعه کد به عنوان زیربرنامه ایجاد 1ms تأخیر در برنامههای کاربردی دیگر نیز استفاده نمود.
زیر برنامهای برای ایجاد 1 ثانیه تأخیر
- این زیر برنامه در آدرس 0 از حافظه برنامه قرار داده شده است. میتوان این آدرس را به صورت دلخواه تغییر داد.
- کد نوشته شده به دو بخش تقسیم شده است. بخش اول، همان برنامه Delay است که در مثال قبل مشاهده نمودهاید با این تفاوت که برای دقیقتر شدن زمان تأخیر، شمارنده حلقه داخلی (R16) عدد 249 انتخاب شده است. در بخش دوم با استفاده از شمارنده های R18 و R19 مجدداً حلقهای با تعداد تکرار 999 مرتبه تشکیل شده است. در این حلقه بخش اول زیر برنامه، 999 مرتبه تکرار میشود که نهایتاً موجب ایجاد تأخیری در حدود 1 ثانیه خواهد شد.
- زمان دقیق اجرای زیر برنامه 1,000,004 میکرو ثانیه است.
برنامهای برای راه اندازی یک LED چشمک زدن
- در این برنامه فرض بر این است که دیود نوری (LED) به یکی از پایههای پورت B متصل شده است.
- برای خاموش و روشن کردن LED کافیست که با استفاده از یک حلقه ساده، مقدار رجیستر پورت B را هر چند لحظه یک بار مکمل کنیم.
- در این برنامه مدت زمانی که بین هر بار خاموش یا روشن شدن در نظر گرفته شده، در حدود یک ثانیه میباشد.
- جهت ایجاد یک ثانیه تأخیر، از زیر برنامه 1s-Delay-Subroutine که در مثال قبل به آن پرداخته بودیم، استفاده شده است.
- توجه داشته باشید که برای استفاده از زیر برنامه، باید اشاره گر پشته را مقدار دهی نماییم تا هنگام فراخوانی زیر برنامه، آدرس دستوری که پس از فراخوانی در بدنه اصلی برنامه نوشته شده است، در پشته ذخیره گردد.
- در این مثال، آدرس 100H به عنوان آدرس شروع پشته در نظر گرفته شده است.
برنامهای برای شمارش تعداد فشار داده شدن یک شاسی یا کلید فشاری
- در این برنامه فرض بر این است که یک کلید فشاری از یک سو به زمین و از سوی دیگر به پین شماره 0 از پورت A متصل شده است. بنابراین با فشار داده شدن کلید، ولتاژ 0 به پایه متصل به آن منتقل میشود.
- برای جلوگیری از به وجود آمدن حالت High Impedance روی پایه در زمانی که کلید قطع میباشد، مقاومت Pull Up داخلی برای این پایه فعال شده است. به این ترتیب مقدار پایه در زمان بالا بودن یا قطع بودن کلید، 1 منطقی و همان طور که گفته شد در هنگام پایین بودن یا فشار داده شدن آن، 0 منطقی خواهد بود.
- فاز ابتدایی برنامه، به انجام تنظیمات اولیه و مقدار دهی رجیسترها اختصاص دارد.
- عملیات فعال سازی Pull Up داخلی، با استفاده از تنظیمات زیر صورت گرفته است:
- 0 کردن بیت شماره 0 از رجیستر DDRA (تعریف پایه شماره 0 از پورت A به عنوان ورودی)
- 0 کردن بیت PUD یا Pull Up Disable در رجیستر SFIOR
- 1 کردن بیت شماره 0 از رجیستر PORTA
- در فاز بعدی، رجیستر PINA در R16 قرار گرفته است. سپس بیت شماره 0 از رجیستر R16 با عدد صفر مقایسه شده و در صورت تساوی که به معنای فشار داده شدن کلید است، رجیستر R20 که نقش شمارنده کلید را دارد، یک واحد افزایش یافته است.
- این عملیات در یک حلقه بینهایت، دائماً تکرار میشود و تا زمان روشن بودن میکروکنترلر ادامه مییابد.
- نکته مهمی که نباید از آن غفلت کرد این است که عملیات خواندن رجیستر PINA یا به عبارت دیگر انتقال محتویات این رجیستر به رجیستر R16 و همچنین مقایسه بیت شماره 0 از رجیستر R16 با عدد صفر، در مدت زمانی بسیار کوتاهی (کمتر از 1 میکرو ثانیه) انجام میگیرد و پس از اتمام کار، به دلیل قرار داشتن مجموعه این دستورات در یک حلقه، این عملیات بلافاصله از نو آغاز میشود. حال فرض کنیم که میخواهیم کلید را یک مرتبه فشار داده و رها کنیم. حداقل زمانی که کلید بر اثر فشار انگشت در حالت وصل قرار دارد، بیش از چند ده میلی ثانیه به طول خواهد انجامید. در طی این مدت، مجموعه دستورات مربوط به بررسی مقدار پین، چندین هزار بار احرا شده و در هر بار اجرا به دلیل 0 بودن ولتاژ روی پایه یا به عبارت دیگر وصل بودن کلید، یک واحد به مقدار شمارنده اضافه خواهد شد. بنابراین با یک بار فشار داده شدن کلید، شمارنده برنامه بارها به حداکثر مقدار خود رسیده و سر ریز خواهد کرد.
- برای رفع این مشکل، ابتداً با استفاده از دستور SBRC، ولتاژ روی پین که به بیت صفرم رجیستر R16 منتقل شده، بررسی شده است. سپس در صورت صفر بودن ولتاژ که به معنای فشار داده شدن کلید است، مقدار شمارنده، یک واحد افزایش یافته و به پورت B منتقل شده و پس از آن زیر برنامه Delay برای ایجاد نیم ثانیه تأخیر فراخوانی شده است. در نهایت پس از اتمام زیر برنامه، دستورات بررسی ولتاژ روی پین، مجدداً اجرا شده و کل این سیکل تکرار میشود. بدین ترتیب، با هر بار فشردن کلید، مقدار شمارنده تنها یک واحد افزایش پیدا میکند.
- به این نکته نیز توجه داشته باشید که در این مثال، اگر شخصی دست خود را بیش از نیم ثانیه روی کلید نگه دارد، شمارنده برنامه، به ازای هر نیم ثانیه، یک واحد افزایش خواهد یافت.