اطلاعیه

Collapse
No announcement yet.

آموزش AVR-GCC

Collapse
X
 
  • فیلتر
  • زمان
  • Show
Clear All
new posts

    #31
    پاسخ : آموزش ( avrstudio5 ( AVRGCC

    با سلام مجدد
    دارم بهتون هشدار میدم. ممکنه توی خاکی بزنین و برای خودتون دردسر درست کنین.

    خوب من در مورد این اشاره گر ها یه مقدار رفتم مطالعه کردم تابتونم تابعی که نوشتید رو برای خودم تفسیر کنم چیزی که اینجا به نظر مهم امد و شما ازون استفاده کرده بودین تقسیم فضای حافظه ای که به اون متغیر از جنس int اختصاص داده میشه به این ترتیب که چون حافظه تخصیص یافته به اون دو بایتی هست و کاری که میخواهیم بکنیم یک عملیات تک بایتی بوده پس میاییم یک اشاره گر تک بایتی تعریف میکنیم و آدرس اون رو برابر با آدرس بایت اول متغیر میکنیم و از طرفی چون نوع اشاره گر unsigned char هست ولی نوع متغیر از نوع int و دوبایتی باید یک تبدیل نوع (type casting) انجام بدیم تا آدرس اولین خانه متغیر n به s منتسب بشه بعدش هم که اگر درست متوجه شده باشم ، محتوایات بایت بالا و پایین از طریق این اشاره گر ومتغیر میانی c جابهجا میشه چیزی که برام روشن نبود این بود که چجوری شد که یک دفعه متغیر اشاره گر به صورت مستقیم بدون (*) به c منتسب شد ولی حالا فهمیدم که درواقع وقتی که میگیم یک متغیر از نوع اشاره گر هست و اون اشاره میکنه به محلی از حافظه میشه اون رو یه آریه فرض کرد که درایه اولش (صفرم) به محتویات اون خونه ای که اشاره گر بهش انتساب شده و درایه بعدی یکی بعلاوه طول آریه مثلا اگه یک متغیر اشاره گر از نوع int تعریف کنیم و مثلا خونه 1000 حافظه به اون اختصاص داده بشه درایه صفرم این اشاره گر میشه محتویات خانه 1000 و 1001 از نوع int درایه یکم این اشاره گر میشه محتویات خانه 1002 و 1003 از نوع int و قص علی هذا !
    توجه به مطالبی که خوندم امدم این تابع شما رو به صورت زیر باز نویسی کردم به نظرتون درست متوجه شدم یا نه ؟

    کد:
    int swab(int n)
    {
      unsigned char *s = (unsigned char *)&n;
      unsigned char c;
    
      c = s[0];
      s[0] = s[1];
      s[1] = c;
      return n;
    }


    شد :


    کد:
    void swab( int n)
    {
      unsigned char *s = (unsigned char *)&n;
      unsigned char c;
    
     c=*s;
    *s=*s++;
    *++s=c;
    }


    ممنون از توجهتون

    دیدگاه


      #32
      پاسخ : آموزش ( avrstudio5 ( AVRGCC

      شاید تفسیرتون تا حدودی درست باشه اما کدتون غلطه. چرا وقتی با دو بایت کار داریم محتوای s بعد از دو بار افزایش بکار میره؟ کد شما رو میشه اینطوری هم نوشت (با فرض اینکه کامپایلر کد مطابق منظور ما رو تولید میکنه):


      c = s[0];
      s[0] = s[1];
      s[2] = c;


      شاید این کد درستتر باشه:

      c = *s;
      *s = *++s;
      *s = c;


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

      ضمنا!
      این کد یه کمی گیج میزنه. ولو اینکه درست هم کار کنه بهتره از این کد استفاده نکنین. چون تحلیلش "مشکله" و نسبت به کاری که انجام میده "ساده و سبک" هزینه زمانی بالایی داره. کد پیچیده نوشتن مهم نیست. مهم اینه که کد کار کنه. و با کمترین هزینه قابل توسعه باشه.

      تازه ما بحثمون یه چیز دیگه بود!
      اینو به عنوان مثال گفتم.

      دیدگاه


        #33
        پاسخ : آموزش ( avrstudio5 ( AVRGCC


        ولی بهترین روش برای تست همچین کدی نوشتن و کامپایلش و آنالیز نتیجه اش باشه.
        چون قاعدتا در یک جایگزینی باید مقدار سمت راست اول محاسبه بشه و در مقدار سمت چپ قرار بگیره.
        الان تستش کردم نه مال من درست بود نه مال شما نتیجه داد اما بیشتر بابت همین موردی که اینجا ذکر کردید اینجا مطرح کردم :

        ضمنا!
        این کد یه کمی گیج میزنه. ولو اینکه درست هم کار کنه بهتره از این کد استفاده نکنین. چون تحلیلش "مشکله" و نسبت به کاری که انجام میده "ساده و سبک" هزینه زمانی بالایی داره.
        کد پیچیده نوشتن مهم نیست. مهم اینه که کد کار کنه. و با کمترین هزینه قابل توسعه باشه.
        من قصد همچین کاری رو نداشتم فقط این کار رو به خاطر اینکردم چون اصلا اون مفهوم استفاده از آریه به این شکل رو نمیدونستم و میخواستم ببینم که نتیجه مشابه ای رو ایجاد میکنه و من درست متوجه شدم که قرائن میگن نه هنوز نفهمیدم چی شده !


        تازه ما بحثمون یه چیز دیگه بود!
        اینو به عنوان مثال گفتم.
        ببخشید منظورتون کدوم بحثه (اسمبلی ، شما گفتین که دیگه ادامه ندم )
        منظورتون رو از مثال متوجه نشدم .


        ممنون که وقت میزارید

        دیدگاه


          #34
          پاسخ : آموزش ( avrstudio5 ( AVRGCC

          تفسیر چیزی مثل

          *s = *++s

          قاعدتا باید اینجوری باشه:
          اول s رو یکی اضافه کن و بعد محتواشو برگردون. حالا این رو بذار توی آدرسی که s بهش اشاره میکنه. یعنی در اصل:
          ++s
          *s = *s
          یعنی باد هوا!
          اینو با BC3.1 چک کردم. دقیقا این میشه!

          دیدگاه


            #35
            پاسخ : آموزش ( avrstudio5 ( AVRGCC

            با سلام مجدد
            خوب اول ، یاد استاد بخیر ...

            نمیدونم این کار من دوباره کاریه تاپیک آموزش WINAVR هست یا نه !؟
            خوب ، همونطور که مطلع هستید ورژن 5 AVRSTUDIO باگ های متعددی در موردش گزارش شده اما به نظرم رسید که چون کامپایلر همون AVR_GCC هست و توضیحات میتونه تو WINAVR هم مورد استفاده قرار بگیره و چون خودم وقتی یه چیزی رو توضیح میدم بیشتر یاد میگیرم و اشتباهاتم درمیاد به خاطر همین سعی میکنم که این تاپیک رو ادامه بدم .

            در ادامه مباحث جزئی تر خواهند بود تقریبا روند کار همونیه که تا اینجا بوده سعی میکنم که در مورد کار با WINAVR و ساخت میک فایل و نوشتنش هرچی بلدم رو بگم . (آقا اساتید که فرصت نمیکنن ، یکی مثل من باید بیاد یه همچین تاپیکی بزنه و مسائل رو دست و پاشکسته بگه تا اساتید در صورت فرصت ، اون ها رو اصلاح و تکمیل کنند کاری که خوراک استاد آقازاده بود ).

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

            خوب یه مروری بکنیم ببینیم تا اینجا چی گفتم :

            آموزش avrstudio5 :
            1. ایجاد پروژه جدید .
            2. visual assistx .
            3. تنظیمات پروژه .
            4. کامپایل و ساخت فایل HEX .
            5. استفاده از تابع sprintf برای تبدیل مقادیر اعشاری(flaot) به رشته .

            مطالب مربوط به syntax برنامه نویسی در کامپایلر avr_gcc:
            6. دسترسی به حافظه فلش .
            7. استفاده از حافظه فلش برای ذخیره سازی و نمایش رشته .
            8. نوشتن برنامه ترکیبی از c و اسمبلی.(در AVRSTUDIO5)

            و انشالله مطالبی که در ادامه خواهم گفت :

            ادامه مباحث مربوط به SYNTAX:
            در آینده خیلی نزدیک :
            9. دسترسی به حافظه EEPROM
            10. نحوه نوشتن ISR
            11. نوشتن برنامه ترکیبی از c و اسمبلی.(در WINAVR)
            12. نحوه نوشتن inline assembly
            در آینده نزدیک :
            13. نحوه تولید MFILE و مطالبی در رابطه با نوشتن آن
            14. نحوه ایجاد پروژه جدید در PN و کامپایل آن
            15. نحوه نوشتن کتابخانه
            در آینده نچندان نزدیک :
            16. بررسی کلمات کلیدی در AVR_GCC
            اگه عمری باشه :
            17. راه اندازی کتابخانه های نوشته شده برای AVR_GCC - بخش اول : معرفی کتابخانه ها
            18. راه اندازی کتابخانه های نوشته شده برای AVR_GCC - بخش دوم : LCD کاراکتری
            19. راه اندازی کتابخانه های نوشته شده برای AVR_GCC - بخش سوم : LCD گرافیکی
            20. راه اندازی کتابخانه های نوشته شده برای AVR_GCC - بخش چهارم : راه اندازی ADC
            21. راه اندازی کتابخانه های نوشته شده برای AVR_GCC - بخش پنجم : راه اندازی USART
            22. راه اندازی کتابخانه های نوشته شده برای AVR_GCC - بخش ششم : راه اندازی سخت افزار های جانبی با استفاده از I2C و 1WIRE
            23. ...
            ...

            *قول میدم که این دفعه کارایی رو که گفتم انجام بدم .

            دوستان یه نظر سنجی هم ایجاد کردم در رابطه با اینکه بنظرتون در ادامه کار از avrstudio4 استفاده کنیم یا winavr ممنون میشم تو اون هم شرکت کنید.

            ممنون که تحمل میکنید.

            دیدگاه


              #36
              پاسخ : آموزش ( avrstudio5 ( AVRGCC

              9.مختصری در رابطه با انواع داده

              خوب تو زبان استانداد C99 انواع داده ها به شرح زیر هست :


              و به طور خلاصه شده :



              حالا تو کامپایلر
              AVR-GCC
              یک فایل هدر تهیه شده که در اون برای هر نوع داده یک اسم خاص ، تعریف شده که این دو تا مزیت داره :
              1. افزایش قابل حمل بودن برنامه.
              2. موقع انتخاب داده مجبور نیستیم که از ترکیب های طولانی برای تعریف یک داده خاص استفاده کنیم لذا بجای اون از این تعاریف استفاده میکنیم .
              خوب این فایل هدر که معمولا تو اکثر کتابخونه هایی که از اینترنت دانلود میشه وجود داره ،

              inttypes.h

              هست که به این صورت در ابتدای برنامه اینکلودش میکنیم :


              #include <inttypes.h>


              البته تعاریف اصلی اگه خواستید اون ها رو ببنید داخل فایل هدر دیگه ای هست به نام :

              stdint.h

              که در ابتدای فایل هدر

              inttypes.h

              اینکلود شده.
              برخی از این تعاریف با توجه به جدول بالا به قرار زیر هستند :


              /** \ingroup avr_stdint
              8-bit signed type. */

              typedef signed char int8_t;

              /** \ingroup avr_stdint
              8-bit unsigned type. */

              typedef unsigned char uint8_t;

              /** \ingroup avr_stdint
              16-bit signed type. */

              typedef signed int int16_t;

              /** \ingroup avr_stdint
              16-bit unsigned type. */

              typedef unsigned int uint16_t;

              /** \ingroup avr_stdint
              32-bit signed type. */

              typedef signed long int int32_t;

              /** \ingroup avr_stdint
              32-bit unsigned type. */

              typedef unsigned long int uint32_t;

              /** \ingroup avr_stdint
              64-bit signed type.
              \note This type is not available when the compiler
              option -mint8 is in effect. */

              typedef signed long long int int64_t;

              /** \ingroup avr_stdint
              64-bit unsigned type.
              \note This type is not available when the compiler
              option -mint8 is in effect. */

              typedef unsigned long long int uint64_t;

              خوب تو استاندارد C99 امکان تعریف متغیر های تک بیتی هم اضافه شده برای اینکار باید در ابتدای برنامه فایل هدر
              stdbool.h رو به صورت زیر اینکلود کنید :

              #include <stdbool.h>

              حالا بریم داخل فایل هدرش ببینیم چه خبره ،


              #ifndef __cplusplus

              #define bool _Bool
              #define true 1
              #define false 0

              #else /* __cplusplus */

              /* Supporting <stdbool.h> in C++ is a GCC extension. */
              #define _Bool bool
              #define bool bool
              #define false false
              #define true true

              #endif /* __cplusplus */


              خوب از اونجا که تو C++ کلمات کلیدی و bool وfalse وtrue از قبل تعریف شده اند اینجا در صورتی که برنامه با c++ نوشته شده باشه دیفاین ها رو رو همون حالت دیفالت خودش نگه میدارهولی تو زبان c چون اینها تعریف نشدند و از طرفی کلمه کلیدی که بواسطه اینکلود کردن stdbool.h میشه با اون متغیر های تک بیتی تعریف کرد ، _Bool
              هست برای یکسان کردن این با سینتکسش در c++ میاد از تعریف

              #define bool _Bool

              استفاده میکنه و همینطور در مورد false و true چون اینها جزو استاندارد های c نیستند امده این ها رو هم تعریف کرده به این ترتیب :


              #define true 1
              #define false 0


              حالا فرض کنید که من میخوام یه فلگ تعریف کنم و مقدار پیشفرض اون رو true قرار بدم ، برای اینکار مینویسم :



              bool my_flg=true;


              پایان قسمت نهم .

              دیدگاه


                #37
                پاسخ : آموزش ( avrstudio5 ( AVRGCC

                10.دسترسی به حافظه ( EEPROM (Electrically Erasable Programmable Read-Only Memory

                خوب همه دیگه میدوند که EEPROM چیه و برای چی استفاده مییشه .
                میرم سر اصل مطلب ، خوب ما یا میخواییم توی EEPROM بنویسیم یا ازش بخونیم .
                توجه کنید که فقط میتونیم بنویسیم یا بخونیم والا غیر !

                خوب حالا میتونیم با نوشتن یکسری روتین ها اینکار رو انجام بدیم یا اینکه از کتابخونه eeprom.h استفاده کنیم ، من راه دوم رو انتخاب میکنم و توضیح میدم ، پس باید در ابتدای برنامه فایل هدر eeprom.h رو به صورت زیر اینکلود کنید :

                #include <avr/eeprom.h>


                توجه1 : اگه نمیدونید بدونید(توضیح واضحات) ، همونطور که میبینید eeprom.h به صورت مستقیم اینکلود نشده ،چون دایرکتوری مستقیم نرم افزار WinAVR-20100110\avr\include هست ، که حالا هر فایل هدری که تو این دایرکتوری قرارگرفته باشه مثل inttypes.h باید بطور مستقیم اینکلود بشه ؛ بنابراین اینجا باید اول اسم ساب دایرکتوری و بعد فوروارد اسلش وبعد اسم فایل هدر رو وارد کنید .

                توجه2 : اگه نمیدونید بدونید(توضیح واضحات)، برای اینکلود کردن فایل های هدر اصلی نرم افزار باید اونها رو داخل
                < >
                و برای اینکلود کردن فایل های هدر کتابخونه ای که خودمون به دایرکتوری اصلی پروژمون اضافه میکنیم باید داخل
                " "
                قرار داده بشه .
                در واقع داریم با این کارمون (" &quot به کامپایلر میفهمونیم که آقا این فایل هدر رو تو دایرکتوری پروژه جاری جستجو کن ، نه جای دیگه.

                و اما ادامه عریضه ؛

                بعد از اینکلود کردن eeprom.h میریم توش ببینیم چه خبره ،
                اولین چیزی که میبینم :


                ** \def EEMEM
                \ingroup avr_eeprom
                Attribute expression causing a variable to be allocated within the
                .eeprom section. */
                #define EEMEM __attribute__((section(".eeprom&quot))


                خوب اینجا یه چیزی به اسم EEMEM تعریف میشه حالا این تعریف چی داره میگه ،

                نقل قول از استاد آقازاده :

                فایل کامپایل شده دارای بخش (section) های متفاوتیه. text - data - bss - progmem و غیره.
                این بخشها:

                text: شامل دستورالعملهای برنامه نوشته شده هستند که در avr در فلش ذخیره میشه.
                data: شامل کلیه متغیر های عمومی و static هست که مقدار اولیه داشته باشه.
                bss: شامل کلیه متغیر های عمومی و static هست که مقدار اولیه نداشته باشه. این محدوده در executable جایی رو اشغال نمیکنه اما در image نهایی وجود داره و در ابتدای اجرای برنامه با مقدار 0x00 پر میشه.
                progmem: در حقیقت همون flash هست ولی اطلاعات ثابت و فقط خواندنی مثل رشته های کاراکتری ثابت در اون ذخیره میشه.

                اتمام نقل قول

                پس میتونیم اینطور نتیجه بگیریم که در واقع با نوشتن

                __attribute__((section(".eeprom&quot))


                داریم به کامپایلر میگیم که این بخش از حافظه یعنی EEPROM رو معادل EEMEM کنه تا بعدا که خواستیم از این بخش استفاده کنیم بجای نوشتن


                __attribute__((section(".eeprom&quot))


                از EEMEM استفاده کنیم .

                مورد بعدی که میبینیم ، یه ماکرو هست به این قرار:


                /** \def eeprom_busy_wait
                \ingroup avr_eeprom
                Loops until the eeprom is no longer busy.
                \returns Nothing.
                */
                #define eeprom_busy_wait() do {} while (!eeprom_is_ready())




                خوب همونطور که میدونید برای نوشتن و یا خوندن از حافظه EEPROM یه مدت زمانی لازمه به این ترتیب که CPU
                برای نوشتن تو EEPROM بسته به ولتاژ تغذیه مدتی بین 1.5 تا 4 میلی ثانیه احتیاج داره و زمانی هم که داریم از
                EEPROM میخونیم CPU به مدت 4 سیکل متوقف میشه و هیچ دستوری رو اجرا نمیکنه. (اگه خواستید در این رابطه بیشتر توضیح میدم) که حالا این آماده بودن یا نبودن رو میشه با این ماکرو چک کرد اگه این رو یک جای برنامه بنویسید ، تا زمانی که EEPROM آماده نشه دائم یه حلقه اجرا میشه و تو همون جا میمونو هیچ دستور دیگه ای به جز وقفه رو اجرا نمیکنه.
                البته کاربرد اصلی این تو همون سورس همین هدر هست .

                و اما مورد بعدی که میبینیم ، توابع خوندن از حافظه EEPROM هست که به قرار زیر هستند :



                /** \ingroup avr_eeprom
                Read one byte from EEPROM address \a __p.
                */
                uint8_t eeprom_read_byte (const uint8_t *__p) __ATTR_PURE__;

                /** \ingroup avr_eeprom
                Read one 16-bit word (little endian) from EEPROM address \a __p.
                */
                uint16_t eeprom_read_word (const uint16_t *__p) __ATTR_PURE__;

                /** \ingroup avr_eeprom
                Read one 32-bit double word (little endian) from EEPROM address \a __p.
                */
                uint32_t eeprom_read_dword (const uint32_t *__p) __ATTR_PURE__;

                /** \ingroup avr_eeprom
                Read one float value (little endian) from EEPROM address \a __p.
                */
                float eeprom_read_float (const float *__p) __ATTR_PURE__;



                خوب همونطور که مشخصه همه این توابع یک ورودی دارند که درواقع آدرس اون محل از حافظه EEPROM
                هست که باید خونده بشه و یک خروجی که میشه مقدار اون آدرس از حافظه EEPROMخوب تنها نکته ای که موقع استفاده از اینها وجود داره انتخاب نوع مناسب با نوعه داده ای هست که میخوایم بخونیم.(به نظرم این مسئله هم چیزه خاصی نیست و تقریبا همه میدونند اما اگه کسی اینجا براش سوال مطرحه حتما بگه .)

                بعد از اینها یدونه تابع دیگه هم برای خوندن وجود داره که همچین فرق فوکل با بقیه !
                که به قرار زیر هست :




                ** \ingroup avr_eeprom
                Read a block of \a __n bytes from EEPROM address \a __src to SRAM
                \a __dst.
                */
                void eeprom_read_block (void *__dst, const void *__src, size_t __n);



                اولین چیزی که به نظر ناآشنا میاد وجود اشاره گر void-type هست ، حالا میگم اینا چین اما اول یه اورویو :

                مختصری در رابطه با اشاره گر :
                خوب همونوطور که مطلع هستید به طور معمول اشاره گر ها به آدرسی بعا یک فرم و بهتر بگم اندازه که با یه نوع مشخص میشه اشاره میکنند مثلا اگه آدرس مورد نظر 8 بیتی باشه بایداز "uint8_t*" و اگه 16 بیتی باشند باید از
                "int16_t*" البته بهتره اینجا به جای 8 بیتی بگم ، unsigned byte و بجای 16 بیتی بگم ، signed int چون احتمالا ما داریم به آدرس یه متغیر از یک نوع خاص مثل اینها اشاره میکنیم و البته اینهم بگم اشاره گر میتونه به صورت
                typecast هم باشه مثلا فرض کنید میخواییم خونه 100 از حافظه EEPROM رو بخونیم (البته در انتها مثالش هم هست) این جا باید از typecast استفاده کنیم و تو ورودی اون توابعی که برای خوندن از داخل EEPROM
                هستند اینجوری بنویسیم:



                (uint8_t*)100



                ( در مورد typecasting سعی میکنم یه بخش رو به این امر اختصاص بدم نمیخوام هیچ جای ابهامی باشه پس اگه تو این کسی مشکل داره حتما بگه ، لطفا)

                داشتم میگفتم پس ما معملا برای اشاره به یه آدرس با یه اندازه مشخص از اشاره گر استفاده میکنیم
                اما پس این چیه ؟
                اول اینکه همنوطور که از اسمشون مشخصه این ها اشاره گر های بدون نوع هستند یعنی میشه هر اشاره گری با هر نوعی روبه اون منتسب کرد، به عبارت بهتر Void pointerزمانی که نوع داده ای که با اون اشاره میشه در یک تابع مشخص نیست و یا مهم نیست کاربرد داره به این تریتب که نوشتن اشارهگر به صورت void داریم به محلی از حافظه اشاره میکنیم که قراره دیتا در اون قرار بگیره و یا از اون خونده بشه اما نوع و اندازه اش مشخص نیست ولی میتونه هرچیزی باشه حالا چرا اینجا استفاده شده این بخاطر اینکه مخواد به کاربر این امکان داده بشه تا هر دیتا رو که بخواد با توجه به کارکرد تابع از یه محلی از حافظه eeprom به محلی دیگه از حافظه sram انتقال بده و این یعنی اصلا مهم نیست که اون دیتا میخواد چی باشه فقط باید بایت هایی که از eeprom کپی مشن و به sram منتقل میشن درست باشه (نقطه شروعشون معین باشه تا بهصورت کامل و صحیح منتقل بشه)

                پس به این ترتیب میتونیم بگیم :

                ورودی اول تابع یعنی ،


                void *__dst



                آدرسی از حافظه هست که دیتا در اون پیست میشه.(آدرس شروع دیتا)

                ورودی دوم تابع یعنی ،


                const void *__src


                آدرسی از حافظه EEPROM هست که دیتا از اونجا کپی میشه (آدرس شروع دیتا)

                و ورودی سوم هم یعنی ،


                size_t __n


                مبین تعداد بایت هایی هست که قرار از آدرس شروع دیتا در حافظه EEPROM یا همون ورودی دوم تابع از حافظه
                EEPROM کپی و درداخل حافظه SRAM از آدرس شروع تعیین شده یعنی ورودی اول پیست بشه .

                و اما ادامه فایل هدر ،
                در ادامه با توابع نوشتن در حافظه EEPROM برخورد میکنیم که به قرار زیر هستند :



                /** \ingroup avr_eeprom
                Write a byte \a __value to EEPROM address \a __p.
                */
                void eeprom_write_byte (uint8_t *__p, uint8_t __value);

                /** \ingroup avr_eeprom
                Write a word \a __value to EEPROM address \a __p.
                */
                void eeprom_write_word (uint16_t *__p, uint16_t __value);

                /** \ingroup avr_eeprom
                Write a 32-bit double word \a __value to EEPROM address \a __p.
                */
                void eeprom_write_dword (uint32_t *__p, uint32_t __value);

                /** \ingroup avr_eeprom
                Write a float \a __value to EEPROM address \a __p.
                */
                void eeprom_write_float (float *__p, float __value);




                خوب همونطور که حتما متوجه شدید این توابع تنها دو تا ورودی دارند وخروجی هم اصلا ندارد .
                ورودی اول اشاره گر به آدرسی از حافظه EEPROM هست که قراره دیتا مورد نظر در اون نوشته بشه (آدرس شروع) و ورودی دوم هم دیتایی هست که قراره در آدرس مورد نظر از حافظه EEPROM یعنی ورودی اول تابع نوشته بشه.

                یعد از اینها دوباره با یه تابع دیگه از نوع BLOCK برخورد میکنیم که به قرار زیر هست :



                /** \ingroup avr_eeprom
                Write a block of \a __n bytes to EEPROM address \a __dst from \a __src.
                \note The argument order is mismatch with common functions like strcpy().
                */
                void eeprom_write_block (const void *__src, void *__dst, size_t __n);



                دقیقا برعکس حالت قبل ،
                ورودی اول آدرس (آدرس شروع) حلی از حافظه EEPROM هست که قراره دیتایی از محلی از حافظه SRAM
                یعنی ورودی دوم پیست بشه .

                ورودی دوم ، آدرس (آدرس شروع) محلی از حافظه SRAM هست که قراره تعدادی بایت به اندازه ورودی سوم از اون کپی بشه .

                وورودی سوم ، تعداد بایتی هست که باید از آدرس شروع از حافظه SRAM یعنی ورودی دوم کپی بشه و در داخل حافظه EEPROM از آدرس شروع مورد نظر یعنی ورودی اول پیست بشه .

                در ادامه با توابعی به قرار زیر برخورد میکنیم :



                /** \ingroup avr_eeprom
                Update a byte \a __value to EEPROM address \a __p.
                */
                void eeprom_update_byte (uint8_t *__p, uint8_t __value);

                /** \ingroup avr_eeprom
                Update a word \a __value to EEPROM address \a __p.
                */
                void eeprom_update_word (uint16_t *__p, uint16_t __value);

                /** \ingroup avr_eeprom
                Update a 32-bit double word \a __value to EEPROM address \a __p.
                */
                void eeprom_update_dword (uint32_t *__p, uint32_t __value);

                /** \ingroup avr_eeprom
                Update a float \a __value to EEPROM address \a __p.
                */
                void eeprom_update_float (float *__p, float __value);



                خوب عملکرد این توابع خیلی جالب هست وبعضا حیاتی !
                خوب اونطور که از توضیحاتش برمیاد این تابع میاد مقدار محلی از حافظه eeprom از آدرس شروع یعنی ورودی اول با توجه به مقدار ورودی دوم پدیت میکنه.
                خوب این یعنی چی ؟ اینکه همون نوشتن شد ؟!
                خوب همونطور که میدونید عمر حافظه ها رو با تعداد دفعات رید و رایت میسنجند مثلا eeprom تو avr ها
                عمری بالغ بر 100000 بار رید و رایت دارند !

                (توجه کنید که این عمر هر سلول یا هر خونه از eeprom هست .)

                حالا فرض کنید ما تو برناممون یه تابع داریم که تو روتین اصلی برنامه قرار گرفته و کارش نوشتن یکسری تنظیمات یا مثلا شمارش هست که داره دائم اجرا میشه با سرعتی معادل با فرکانس کار میکرو و حالا کسر تاخیر های حاصل از روتین های دیگه برنامه ، خوب همینرو در نظر بگیرید به نظرتون یه همچین روتین جند بار در ثانیه داره اجرا میشه !؟
                اینجاست که دیگه باید به فکر یه راه چاره بود برای حفظ سلامت حافظه eeprom!

                خوب ما اگه بیاییم از همون تابع نوشتن معمولی استفاده کنیم بدون هیچ گونه پیش شرطی دائم یه مقدار داخل
                حافظه eeprom
                write میشه!
                اما اگه بیاییم از این توابع پدیت استفاده کنیم ، این توابع با این شرط عمل write رو انجام میدن که ، مقدار داده تغییر کرده باشه!

                خوب این یه عیب داره و دو تا حسن :
                اول عیبش : عیبش اینکه به خاطر وجود پیش شرط زمان اجرای تابع همینطور کد معادل اون افزایش پیدا میکنه !

                حسن اول : همونطور که قبلا توضیح دادم موقع نوشتن در حافظه eepromبه مدت 1.5 تا 4 میلی ثانیه متوقف میشه!
                خوب از طرفی اگه اون شرط اجرای عمل write برقرار نباشه عمل writeانجام نمیشه و وقفه ای هم در کار نخواهد بود ، بنابراین به طور کل سرعت کار و اجرای برنامه به مراتب بیشتر خواهد شد!

                حسن دوم : حفظ سلامتی حافظه eeprom و جلوگیری از فرسوده شدن سلول های اون در اثر عملیات مکرر write!

                و اما توضیحشون ،
                خوب همونطور که حتما متوجه شدید این توابع تنها دو تا ورودی دارند وخروجی هم اصلا ندارد .
                ورودی اول اشاره گر به آدرسی از حافظه EEPROM هست که قراره دیتا مورد نظر در اون نوشته بشه (آدرس شروع) و ورودی دوم هم دیتایی هست که قراره در آدرس مورد نظر از حافظه EEPROM یعنی ورودی اول تابع نوشته بشه.

                بعد از اینها دوباره با یه تابع دیگه از نوع BLOCK برخورد میکنیم که به قرار زیر هست :


                /** \ingroup avr_eeprom
                Update a block of \a __n bytes to EEPROM address \a __dst from \a __src.
                \note The argument order is mismatch with common functions like strcpy().
                */
                void eeprom_update_block (const void *__src, void *__dst, size_t __n);



                ورودی اول آدرس (آدرس شروع) حلی از حافظه EEPROM هست که قراره دیتایی از محلی از حافظه SRAM یعنی ورودی دوم پیست بشه .
                ورودی دوم ، آدرس (آدرس شروع) محلی از حافظه SRAM هست که قراره تعدادی بایت به اندازه ورودی سوم از اون کپی بشه .

                وورودی سوم ، تعداد بایتی هست که باید از آدرس شروع از حافظه SRAM یعنی ورودی دوم کپی بشه و در داخل حافظه EEPROM از آدرس شروع مورد نظر یعنی ورودی اول پیست بشه .

                و در ادامه دو تا ماکرو میبینیم یکی برای نوشتن و دیگری هم برای خوندن ، که به قرار زیر هستند :
                اول نوشتن :


                /** \def _EEPUT
                \ingroup avr_eeprom
                Write a byte to EEPROM. Compatibility define for IAR C. */
                #define _EEPUT(addr, val) eeprom_write_byte ((uint8_t *)(addr), (uint8_t)(val))



                خوب این ماکرو اونطور که از توضیحش مشخصه برای استفاده از برنامه های نوشته شده برای کامپایلر IAR
                در کامپایلر AVR-GCC هست
                در واقع جزو PORTING IAR FOR AVR-GCC به حساب میاد (فکر کنم)

                خوب ماکرو اینه :


                _EEPUT(addr, val)



                ورودی اول یعنی ،addr آدرس (آدرس شروع) محلی از حافظه eepromهست که می خواهیم داده ما یعنی ورودی دوم در اون ذخیره بشه .

                ورودی دوم یعنی ،val داده ای هست که میخواهیم در حافظه eeprom ذخیره کنیم.

                توجه : این ماکرو فقط برای داده های تک بایتی هست ولی میشه از همین ماکر ایده گرفت برای توسعه ماکرو های مشابه .

                و ماکرو بعدی که برای خوندن از حافظه eeprom هست به قرار زیر هست :



                /** \def _EEGET
                \ingroup avr_eeprom
                Read a byte from EEPROM. Compatibility define for IAR C. */
                #define _EEGET(var, addr) (var) = eeprom_read_byte ((const uint8_t *)(addr))
                ماکرو اینه :
                _EEGET(var, addr)



                ورودی اول یعنی ،var متغیری هست که داده خونده شده از eeprom در اون ریخته میشه.

                و ورودی دوم یعنی ، addr آدرس (آدرس شروع) حافظه eeprom هست که قراره داده از اون خونده بشه.

                خوب دوستان این تاپیک دیگه جا نداره مثال ها باشه واسه تاپیک بعدی ، فعلا ...



                دیدگاه


                  #38
                  پاسخ : آموزش ( avrstudio5 ( AVRGCC

                  خوب و اما ادامه داستان EEPROM :

                  1. تعریف متغیر با جانشانی در حافظه EEPROM :

                  فرم اصلی به صورت زیر هست :


                  "نوع متغیر" EEMEM "اسم متغیر" ;


                  مثال :

                  #include <avr/eeprom.h>

                  uint8_t EEMEM NonVolatileChar;
                  uint16_t EEMEM NonVolatileInt;
                  uint8_t EEMEM NonVolatileString[10];


                  توجه 1: اگه نمیدونید بدوندید ، به نظرم اینجا لفظ متغیر کلا اشتباه چون متغیر چیزیه که بشه اون رو درخلال برنامه عملات مختلف مثل عملیات جمع و ضرب و تفریق و یا مقایسه و کلا هر نوع عملیات دیگه رو روش پیاده کرد . اما اینجا ما در واقع با آدرس یه قسمتی از حافظه EEPROM سر و کار داریم که حالا امیدم یه اسم به اون اختصاص دادیدم پس فقط میتونیم توش بنویسیم و یا ازش بخونیم !

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

                  #include <avr/eeprom.h>

                  uint8_t EEMEM NonVolatileChar;
                  uint16_t EEMEM NonVolatileInt;
                  uint8_t EEMEM NonVolatileString[10];

                  int main(void)
                  {
                  uint8_t SRAMchar;

                  SRAMchar = NonVolatileChar;
                  }



                  2. نحوه خواندن داده از حافظه EEPROM :
                  خوب خوندن به سه صورت ممکنه :
                  2-1. خوندن مقدار یک متغیر که در حافظه EEPROM جانشانی شده :
                  فرم کار به شکل زیر هست :


                  "داده واقع در SRAM خام"=eeprom_read_"نوع مناسب با داده مورد نظر "(&"اسم متغیرجانشین در EEPROM&quot


                  مثال :

                  #include <avr/eeprom.h>

                  uint8_t EEMEM NonVolatileChar;
                  uint16_t EEMEM NonVolatileInt;
                  uint8_t EEMEM NonVolatileString[10];

                  int main(void)
                  {
                  uint8_t SRAMchar;

                  SRAMchar = eeprom_read_byte(&NonVolatileChar);
                  }



                  2-2. خواندن داده از یک آدرس مشخص از حافظه EEPROM :
                  فرم کار به این شکل هست :

                  "داده واقع در SRAM خام"=eeprom_read_"نوع مناسب با داده مورد نظر "(("نوع داده مناسب "*)"آدرس مورد نظر در حافظه EEPROM&quot



                  مثال :


                  #include <avr/eeprom.h>

                  void main(void)
                  {
                  uint16_t WordOfData;

                  WordOfData = eeprom_read_word((uint16_t*)46);
                  }



                  2-3. کپی کردن داده با تعداد بایت معین از داخل حافظه EEPROM داخل حافظه SRAM :
                  فرم کار به شکل زیر هست :


                  eeprom_read_block_((void*)&"اسم متغیر جانشین شده در EEPROM" , (const void*)&"اسم متغیر تعریف شده در SRAM" , "تعداد بایت مورد نظر &quot


                  ابته ابن برای حالتیه که بخواییم از اسم متغیر ها اتفاده کنیم ، احیانا اگه یه زمانی خواستید که از یه آدرس خواص از حافظه EEPROM داخل حافظه SRAM کپی کنید ، "&" حذف میشه و جای اسم متغیر آدرس مورد نظر نوشته میشه .

                  مثال:


                  #include <avr/eeprom.h>

                  void main(void)
                  {
                  uint8_t StringOfData[10];

                  eeprom_read_block((void*)&StringOfData, (const void*)12, 10);
                  }



                  3. نحوه نوشتن داده در EEPROM :

                  نوشتن به چهار روش کلی تقسیم میشه :

                  3-1. نوشتن داده با استفاده از توابع WRITE :
                  فرم کار به شکل زیر هست :

                  void eeprom_write_"نوع داده مناسب" (&"اسم متغیر جانشین شده در EEPROM", ""


                  مثال:

                  #include <avr/eeprom.h>

                  uint8_t EEMEM NonVolatileChar;
                  uint16_t EEMEM NonVolatileInt;
                  uint8_t EEMEM NonVolatileString[10];

                  int main(void)
                  {
                  uint8_t SRAMchar;

                  eeprom_write_byte (&NonVolatileChar, SRAMchar);
                  }


                  3-2. کپی کردن داده از داخل حافظه SRAM داخل حافظه EEPROM :
                  فرم کار به شکل زیر هست :



                  void eeprom_write_block ((const void *)&"اسم متغیر در EEPROM",( void *)&"اسم متغیر در SRAM", "تعداد بایت مورد نظر"




                  مثال :

                  #include <avr/eeprom.h>

                  uint8_t EEMEM NonVolatileChar;
                  uint16_t EEMEM NonVolatileInt;
                  uint8_t EEMEM NonVolatileString[20];

                  int main(void)
                  {
                  uint8_t SRAMchar[20];

                  void eeprom_write_block ((const void *)&NonVolatileString,(void *)&SRAMchar, 12);
                  }


                  3-3. پدیت کردن مقدار خانه ای مشخص در حافظه EEPROM :
                  فرم کار به شکل زیر هست :


                  eeprom_update_byte (&"اسم متغیر در حافظه EEPROM","اسم متغیردر حافظه SRAM"



                  مثال :


                  #include <avr/eeprom.h>

                  uint8_t EEMEM NonVolatileChar;
                  uint16_t EEMEM NonVolatileInt;
                  uint8_t EEMEM NonVolatileString[20];

                  int main(void)
                  {
                  uint8_t SRAMchar;

                  eeprom_update_byte (&NonVolatileChar, SRAMchar);

                  }



                  3-4. پدیت کردن عمل کپی از حافظه SRAM داخل حافظه EEPROM :
                  فرم کار به شکل زیر هست :


                  eeprom_update_block ((const void *)&"اسم متغیر در حافظه EEPROM", (void *)&"ایم متغیر درSRAM","تعداد بایت مورد نظر"


                  مثال :


                  #include <avr/eeprom.h>

                  uint8_t EEMEM NonVolatileChar;
                  uint16_t EEMEM NonVolatileInt;
                  uint8_t EEMEM NonVolatileString[20];

                  int main(void)
                  {
                  uint8_t SRAMchar[20];

                  eeeprom_update_block ((const void *)&NonVolatileString, (void *)&SRAMchar, 12);

                  }



                  پایان قسمت دهم .

                  دیدگاه


                    #39
                    پاسخ : آموزش AVR-GCC

                    11.مروری بر چند ماکرو پر کاربرد از کتابخانه avr/io.h
                    1.ماکروی اول :

                    _SFR_IO_ADDR(sfr)

                    تعریف ماکرو به قرار زیر هست :

                    #define _SFR_IO_ADDR(sfr) ((sfr) - __SFR_OFFSET)


                    خوب این ماکرو یک ورودی داره یعنی ، sfr این ماکرو آدرس رجیستر مورد نظر یعنی ورودی به ماکرو رو برمیگردونه و کاربدش مثلا زمانیه که میخواییم به زبان اسمبلی یه رجیستر خواص رو که آدرسش رو نمیدونیم مقدار دهی کنیم مثلا :


                    #include <avr/io.h>
                    #include <avr/interrupt.h>

                    .global SIG_OVERFLOW0
                    SIG_OVERFLOW0:
                    push r0
                    push r1
                    in r0,_SFR_IO_ADDR(SREG)
                    push r0
                    clr r1

                    ....

                    pop r0
                    out _SFR_IO_ADDR(SREG),r0
                    pop r1
                    pop r0
                    reti


                    ماکروی دوم :

                    _BV(bit)



                    تعریف ماکرو به قرار زیر هست :

                    #define _BV(bit) (1 << (bit))

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


                    ;Enable receiver and transmitter
                    ldi r16, _BV(RXEN)|_BV(TXEN)
                    out UCSRB,r16


                    هر کدوم از بیت های رجیستر ها داخل فایل هدر هر میکرو تعریف شده و معادل یه عدد هست از 0 تا 7 به این شکله :

                    #define UCSRB _SFR_IO8(0x0A)
                    #define TXB8 0
                    #define RXB8 1
                    #define UCSZ2 2
                    #define TXEN 3
                    #define RXEN 4
                    #define UDRIE 5
                    #define TXCIE 6
                    #define RXCIE 7

                    (البته این برای مگا 16 هست)
                    حالا تو اینجا میشه با استفاده از این تعاریف برای واضح تر شدن برنامه برای مراجعات بعدی بجای عدد از اینها استفاده کرد به این ترتیب مثلا تو برنامه بالا بیت های 3 و 4 رجیستر UCSRB یک میشه به این ترتیب که اول میاد مقدار 1 رو از بیت صفرم به سمت چپ به اندازه 3 تا بیت و بعد دوباره از صفر به اندازه 4 بیت شیفت داده میشه که نتیجه اون میشه یک شدن توام بیت 3 و 4 .

                    ماکروی سوم :

                    #bit_is_set(sfr, bit)

                    تعریف ماکرو ،

                    #define bit_is_set(sfr, bit) (_SFR_BYTE(sfr) & _BV(bit))

                    خوب از این ماکرو برای چک کردن یک بودن یک بیت خاص از یک رجیستر 8 ببتی استفاده میشه .
                    این ماکرو دو ورودی داره

                    ورودی اول یعنی، sfr اسم رجیسیتر مورد نظر ورودی دوم یعنی ،bit شماره بیتی هست که میخواییم وضعیت یک بودنش رو چک کنیم .
                    به این ترتیب اگه بیت مورد نظر یک باشه خروجی برابر با یک مقدار غیر صفر و در غیر اینصورت برابر با صفر خواهد بود.

                    ماکروی چهارم :

                    bit_is_clear(sfr, bit)

                    خوب از این ماکرو برای چک کردن صفر بودن یک بیت خاص از یک رجیستر 8 ببتی استفاده میشه .
                    این ماکرو دو ورودی داره

                    ورودی اول یعنی، sfr اسم رجیسیتر مورد نظر ورودی دوم یعنی ، bit شماره بیتی هست که میخواییم وضعیت صفر بودنش رو چک کنیم .
                    به این ترتیب اگه بیت مورد نظر یک باشه خروجی برابر با 0 و در غیر اینصورت برابر با یک مقدار غیر صفر خواهد بود.


                    پایان قسمت یازدهم

                    دوستان منتظر پست بعدی باشید مطمئنم که خوشتون میاد قول میدم تا چند ساعت دیگه آمادش کنم .

                    دیدگاه


                      #40
                      پاسخ : آموزش AVR-GCC

                      12. نحوه نوشتن روتین وقفه ( ISR (interrupt service routine

                      در ابتدای برنامه فایل هدر interrupt.h رو به صورت زیر اینکلود کنید :

                      #include <avr/interrupt.h>


                      خوب بریم ببینیم توش چه خبره ،
                      اولین ماکروی که بهش بر میخوریم ، به قرار زیر هست :

                      sei()


                      تعریف این ماکرو به صورت زیر هست :

                      # define sei() __asm__ __volatile__ ("sei" :


                      خوب همونطور که میبنید این ماکرو معادل یه دستور inline asembly هست که توسط اون بیت I رجیستر وضعیت یعنی، SREG یک میشه ، با این دستور وقفه سراسری فعال میشه .

                      بعد از این ماکروی زیر رو در پیش داریم یعنی ،

                      cli

                      تعریف این ماکرو به صورت زیر هست :

                      # define cli() __asm__ __volatile__ ("cli" :

                      خوب همونطور که میبنید این ماکرو معادل یه دستور inline asembly هست که توسط اون بیت I رجیستر وضعیت یعنی، SREG صفر میشه ، با این دستور وقفه سراسری غیر فعال میشه .

                      قبل از اینکه وارد بحثهای هیجان انگیز بشم بزارید یه ماکروی کوچولو دیگه رو هم مغرفی کنم

                      ماکروی بعدی

                      reti()

                      این تعریف ماکرو هست :

                      define reti() __asm__ __volatile__ ("reti" :

                      کاری که این ماکرو میکنه یک کردن بیت I از رجیستر SREG هست و بعد از اون برگشت به آدرس قبل از پرش از روتین یه وقفه.(تابع نه ها تو برگشت از توابع از دستور اسمبلی RET استفاده میشه ).

                      بعد از این به ماکروی زیر برخورد میکنیم :

                      ISR(vector, [attributes])

                      خوب این ماکرو برای تعریف رویتن وقفه استفاده میشه ،

                      ورودی اولش یعنی ، vector یه عدد که مبین شماره بردار وقفه هست اما موقع نوشتن دیگه بجای وقفه از اسم اون که در داخل فایل هدر میکرو مورد نظر دیفاین شده استفاده میشه ، مثلا اسم بردار های وقفه در میکرو مگا 16 در فایل هدر iom16.h به قرار زیر هست :



                      کد:
                      /* Interrupt vectors */
                      /* Vector 0 is the reset vector. */
                      /* External Interrupt Request 0 */
                      #define INT0_vect			_VECTOR(1)
                      #define SIG_INTERRUPT0			_VECTOR(1)
                      
                      /* External Interrupt Request 1 */
                      #define INT1_vect			_VECTOR(2)
                      #define SIG_INTERRUPT1			_VECTOR(2)
                      
                      /* Timer/Counter2 Compare Match */
                      #define TIMER2_COMP_vect		_VECTOR(3)
                      #define SIG_OUTPUT_COMPARE2		_VECTOR(3)
                      
                      /* Timer/Counter2 Overflow */
                      #define TIMER2_OVF_vect			_VECTOR(4)
                      #define SIG_OVERFLOW2			_VECTOR(4)
                      
                      /* Timer/Counter1 Capture Event */
                      #define TIMER1_CAPT_vect		_VECTOR(5)
                      #define SIG_INPUT_CAPTURE1		_VECTOR(5)
                      
                      /* Timer/Counter1 Compare Match A */
                      #define TIMER1_COMPA_vect		_VECTOR(6)
                      #define SIG_OUTPUT_COMPARE1A		_VECTOR(6)
                      
                      /* Timer/Counter1 Compare Match B */
                      #define TIMER1_COMPB_vect		_VECTOR(7)
                      #define SIG_OUTPUT_COMPARE1B		_VECTOR(7)
                      
                      /* Timer/Counter1 Overflow */
                      #define TIMER1_OVF_vect			_VECTOR(8)
                      #define SIG_OVERFLOW1			_VECTOR(8)
                      
                      /* Timer/Counter0 Overflow */
                      #define TIMER0_OVF_vect			_VECTOR(9)
                      #define SIG_OVERFLOW0			_VECTOR(9)
                      
                      /* Serial Transfer Complete */
                      #define SPI_STC_vect			_VECTOR(10)
                      #define SIG_SPI				_VECTOR(10)
                      
                      /* USART, Rx Complete */
                      #define USART_RXC_vect			_VECTOR(11)
                      #define SIG_USART_RECV			_VECTOR(11)
                      #define SIG_UART_RECV			_VECTOR(11)
                      
                      /* USART Data Register Empty */
                      #define USART_UDRE_vect			_VECTOR(12)
                      #define SIG_USART_DATA			_VECTOR(12)
                      #define SIG_UART_DATA			_VECTOR(12)
                      
                      /* USART, Tx Complete */
                      #define USART_TXC_vect			_VECTOR(13)
                      #define SIG_USART_TRANS			_VECTOR(13)
                      #define SIG_UART_TRANS			_VECTOR(13)
                      
                      /* ADC Conversion Complete */
                      #define ADC_vect			_VECTOR(14)
                      #define SIG_ADC				_VECTOR(14)
                      
                      /* EEPROM Ready */
                      #define EE_RDY_vect			_VECTOR(15)
                      #define SIG_EEPROM_READY		_VECTOR(15)
                      
                      /* Analog Comparator */
                      #define ANA_COMP_vect			_VECTOR(16)
                      #define SIG_COMPARATOR			_VECTOR(16)
                      
                      /* 2-wire Serial Interface */
                      #define TWI_vect			_VECTOR(17)
                      #define SIG_2WIRE_SERIAL		_VECTOR(17)
                      
                      /* External Interrupt Request 2 */
                      #define INT2_vect			_VECTOR(18)
                      #define SIG_INTERRUPT2			_VECTOR(18)
                      
                      /* Timer/Counter0 Compare Match */
                      #define TIMER0_COMP_vect		_VECTOR(19)
                      #define SIG_OUTPUT_COMPARE0		_VECTOR(19)
                      
                      /* Store Program Memory Ready */
                      #define SPM_RDY_vect			_VECTOR(20)
                      #define SIG_SPM_READY			_VECTOR(20)


                      ورودی دوم ، یعنی [attributes] :
                      ورردی دوم یه ماکرو هست که تعیین کننده فرم عملکرد روتین هست که میتونه انواع زیر باشه :

                      توجه1:اگهنمیدونید بدونید ، معماری AVR بگونه ای هست که بعد از وقوع یک وقفه در صورتی که وقفه سراسری فعال شده باشه HANDELER به آدرس بردار وقفه مورد نظر پرش میکنه و همون موقع بیت I از رجیستر SREG
                      صفر میشه، یعنی با ورود به روتین وقفه ، وقفه سراسری غیر فعال میشه به خاطر همین کامپایلر بعد از اتمام دستورات اصلی روتین و پاپ ها میاد با اجرای دستور RETI بیت I رو یک میکنه و بعد بر میگرده به آدرس قبل از پرش .

                      1. ISR_BLOCK :
                      زمانی که ما از این خاصیت استفاده میکنیم داریم به کامپایلر میگیم که ، این روتین بلاک شده هست و هیچ وقفه ای اجازه نداره تا زمانی که روتین تمام نشده یا به عبارتی دستور RETI اجرا نشده ، رخ بده .

                      2.ISR_NOBLOCK:
                      زمانی که ما از این خاصیت استفاده میکنیم داریم به کامپایلر میگیم که ، این روتین بلاک نشده هست و هر وقفه ای اجازه داره تا زمانی که روتین تمام نشده یا به عبارتی دستور RETI اجرا نشده ، رخ بده .

                      توجه 2: اگه نمیدونید بدونید ، اتفاقی که تو حالت دوم میفته اینکه به محض ورود به روتین وقفه اول دستور SEI
                      اجرا میشه که با اجرای این بیت I از رجیستر SREG یک میشه ، یعنی وقفه سراسری مجددا بعد از ورود به وقفه فعال میشه ، حالا اگر در حین اجرای روتین وقفه ، وقفه دیگه ای رخ بده handler پرش میکنه به آدرس بردار اون وقفه و اون رو اجرا میکنه و بعد برمیگرده به روتین وقفه اول و بعد هم ادامه داستان . (توجه کنید که همچنان بیت I یک هست چون روتین وقفه قبلی با دستور RETI به اتمام رسیده )

                      3.( ISR_ALIASOF( target_vector :

                      توجه3 : اگه نمیدونید بدونید ، یه مبحثی هست تو AVR-GCC به نام Two vectors sharing the same code
                      یعنی اشتراک گزاردن یک کد (یا یک روتین) برای اجرا در روتین دو تا وقفه متفاوت .

                      خوب حتما متوجه شدید منظور چیه ، مثلا یه زمانی هست که ما دو تا وقفه داریم که روتینشون کاملا مشابه هست یعنی کاری که تو روتین اولی انجام میشه دقیقا همون کار توی روتین دومی انجام میشه ، (تو زبان فارسی یه مبحثی هست در باره حذف غرینگی در نوشتار(که امرن من هیچ وقت رعایت نکردم (میدونم)))

                      اینجا هم یه همچین چیزی هست یعنی نیاز نیست که یک کار مشابه رو تکرار کنیم و با استفاده از این ماکرو میایم به کامپایلر میگیم که آقا من یه وقفه دیگه هم دارم که روتینش مشابه با همین هست و اون هم موقع تولید کد این مسئله رو در نظر میگیره و دیگه کد الکی تولید نمیکنه بله بجای پرش به بردار فراخونی شده به بردار وقفه اول پرش میکنه!(دمش گرم (بابا تو دیگه کی هستی !)) نتیجه این کار میدونید چیه کاهش کد به اندزاه یه روتین کامل وقفه !

                      حالا چجوری باید از این استفاده کرد ، با یه مثال همه چیز رو روشن میکنیم:

                      مثال : برنامه ای بنویسید که در آن با اجرای وقفه خارجی صفر و یک وضعیت فعلی پین صفرم پورت B معکوس گردد .

                      ضوابط :
                      1.وقفههای خارجی حساس به لبه بالا رونده باشند .
                      2.در صورت امکان از هدر بورد AT90USB162 استفاده کنید :mrgreen:

                      جواب :


                      کد:
                      #include <avr/io.h>
                      #include <avr/interrupt.h>
                      
                      
                      int main(void)
                      
                      {
                      
                      
                      DDRB|=_BV(PIN0);//PIN0 OF PORTB SET TO OUT PUT
                      
                      // Crystal Oscillator division factor: 1
                      CLKPR=0x80;
                      CLKPR=0x00;
                      
                      // External Interrupt(s) initialization
                      // INT0: On
                      // INT0 Mode: Rising Edge
                      // INT1: On
                      // INT1 Mode: Rising Edge
                      // INT2: Off
                      // INT3: Off
                      // INT4: Off
                      // INT5: Off
                      // INT6: Off
                      // INT7: Off
                      EICRA=0x0F;
                      EICRB=0x00;
                      EIMSK=0x03;
                      EIFR=0x03;
                      
                      sei();// Global enable interrupts
                      
                      
                      for(;;);
                      
                      return (0);
                      
                      }
                      
                      ISR(INT0_vect)
                      {
                        // user code here
                      PORTB^=_BV(PIN0);	
                      }
                      ISR(INT1_vect, ISR_ALIASOF(INT0_vect));


                      اینم اون قسمت هیجان انگیز کد اسمبلی معادل که خیلی قربون صدقش رفتم :


                      کد:
                      000000ae <__vector_1>:
                      return (0);
                      
                      }
                      
                      ISR(INT0_vect)
                      {
                       ae:	1f 92    	push	r1
                       b0:	0f 92    	push	r0
                       b2:	0f b6    	in	r0, 0x3f	; 63
                       b4:	0f 92    	push	r0
                       b6:	11 24    	eor	r1, r1
                       b8:	8f 93    	push	r24
                       ba:	9f 93    	push	r25
                        // user code here
                      PORTB^=_BV(0);	
                       bc:	85 b1    	in	r24, 0x05	; 5
                       be:	91 e0    	ldi	r25, 0x01	; 1
                       c0:	89 27    	eor	r24, r25
                       c2:	85 b9    	out	0x05, r24	; 5
                      }
                       c4:	9f 91    	pop	r25
                       c6:	8f 91    	pop	r24
                       c8:	0f 90    	pop	r0
                       ca:	0f be    	out	0x3f, r0	; 63
                       cc:	0f 90    	pop	r0
                       ce:	1f 90    	pop	r1
                       d0:	18 95    	reti
                      
                      000000d2 <__vector_2>:
                      {
                        // user code here
                      	PORTB^=_BV(0);	
                      }*/
                      //ISR(INT1_vect, ISR_ALIASOF(INT0_vect));
                       d2:	0c 94 57 00 	jmp	0xae	; 0xae <__vector_1>


                      خوب اگه توضیح لازمه لطفا دریغ نکید، حتما بگید .
                      ----------------
                      خوب اگه موافق باشید قبل از اینکه خاصیت چهارم رو بگم این مبحث Two vectors sharing the same code رو جمع کنم .

                      قدیم ندیما بجای اینکه از این فورم بالا برای این کار استفاده کنند میومدند از یه ماکروی دیگه به نام ،


                      ISR_ALIAS(vector, target_vector)


                      استفاده میکردند .خوب توضیحش ،

                      ورودی اول،یعنی vector میشه اسم بردار وقفه دوم که روتین مشابهی با روتین وقفه اول داره

                      ورود دوم ، یعنی target_vector میشه اسم بردار وقفه اول .

                      نحوه استفاده ،
                      نحوه استفاده اش دیقیقا به همون شکل ماکروی قبلی با این تفاوت که جای ماکروی قبلی میشینه یعنی مثال بالا رو اگه بخواییم با این ماکرو باز نویسی کنیم خواهیم داشت :


                      کد:
                      #include <avr/io.h>
                      #include <avr/interrupt.h>
                      
                      
                      int main(void)
                      
                      {
                      
                      
                      DDRB|=_BV(PIN0);//PIN0 OF PORTB SET TO OUT PUT
                      
                      // Crystal Oscillator division factor: 1
                      CLKPR=0x80;
                      CLKPR=0x00;
                      
                      // External Interrupt(s) initialization
                      // INT0: On
                      // INT0 Mode: Rising Edge
                      // INT1: On
                      // INT1 Mode: Rising Edge
                      // INT2: Off
                      // INT3: Off
                      // INT4: Off
                      // INT5: Off
                      // INT6: Off
                      // INT7: Off
                      EICRA=0x0F;
                      EICRB=0x00;
                      EIMSK=0x03;
                      EIFR=0x03;
                      
                      sei();// Global enable interrupts
                      
                      
                      for(;;);
                      
                      return (0);
                      
                      }
                      
                      ISR(INT0_vect)
                      {
                        // user code here
                      PORTB^=_BV(PIN0);	
                      }
                      ISR_ALIAS(INT1_vect, INT0_vect);


                      خوب حالا میریم سراغ خاصیت چهارم ، یعنی

                      3.ISR_NAKED:

                      توجه 4: اگه نمیدونید بدونید ، ما وقتی تو یه برناممون از یه وقفه استفاده میکنیم و روتین اون رو براش تو برنامه مینویسیم تو اون روتین تنها چیزی که ما مینوسیم کدهای زبان سی هست(البته من فرض میکنم که شما از این لاین اسمبلی استفاده نمیکنید) اما همونطور که میدونید میکرو یه رجیستر حیاتی و همه کاری داره به اسم رجیستر وضعیت که با SREG مشخص میشه (در مورد این رجیستر توضیح نمیدم دیگه ولی اگه کسی در رابطه با این رجیستر سوالی داشت حتما بپرسه کم لطفی نکنید ، خواهشن) که برخی از بیت های این رجیستر در اثر عملیات مختلف دچار تغییر میشن اما نه هر دستوری (برای اطلاعت بیشتر به APPNOTE ATMEL که در باره دستورات اسمبلی هست یا به قسمت Instruction Set Summary داخل دیتا شیت میکرو ها مراجعه کنید ، به ستون FLAG
                      توجه کنید مثل عکس زیر )


                      علاوه بر این چون تو روال اصلی برنامه (هرر والی قبل از پرش به روتین وقفه) بعضی از رجیستر مورد استفاده قرار میگیرند و احتمال داره تو روتین وقفه کامپایلر بیاد دوباره از همون ها استفاده کنه میاد کلا برای حفظ مقادیر فعلی این رجیستر ها این ها رو به اصطلاح PUSH میکنه تو یه بخشی از حافظه SRAM به اسم دیتا استک(به این قسمت از کد میگن "prologue&quot

                      و بعد شروع میکنه به کامپایل کد سی ما و پس از اون میاد دوباره همه اون هایی رو کهPUSH کرده بود به اصطلاح POP میکنه سرجای اولشون وبعد از اون میاد با اجرا دستور RETI اول بیت I که موقع ورود به روتین وقفه صفر شده بود رو یک میکنه و بعد برمیگرده به آدرس قبل از پرش .(به این قسمت از کد میگن "epilogue&quot

                      خوب حالا این رو گفتی که چی !؟
                      خوب ببینید ما یه زمانی هست تو روتین وقفه مون برنامه ای که مینویسیم هیچ کدوم از این تداخل ها رخ نمیده اما چون کاپایلر اینو نمیفهمه میاد دوباره همه اون کار ها رو انجام میده ، خوب اولین تاثیر منفی این کار کامپایلر کاهش سرعت اجرای روتین وقه و تاثیر منفی دوم افزایش الکی کد هست .

                      حالا راه حل چیه ؟

                      خوب یه مبحثی هست به اسم ، Manually defined ISRs

                      یعنی یه روتین وقف کاملا منوال ، یعنی دیگه تو این حالت کامپایلر سر خود از خودش کد الکی به روتین وقفه اضافه نمیکنه و تمام تنها همون کدی که برنامه نویس نوشته کامپایل میشه (خیلی هیجان انگیز شد نه !)

                      حالا چجوری این کار رو بکنیم ؟

                      برای اینجام اینکار کافیه روتین وقفه رو با خاصیت یعنی اینجوری بنویسید :

                      ISR(INT0_vect,ISR_NAKED)


                      حالا برای اینکه بیشتر متوجه بشید که من چرا اینقدر با آب وتاب این رو تعریف کردم بیایید همون مثال قبل رو با این مارو بنویسیم اون وقت ببنید من حق دارم یا نه !

                      جواب مثال:


                      کد:
                      #include <avr/io.h>
                      #include <avr/interrupt.h>
                      
                      
                      int main(void)
                      
                      {
                      
                      
                      DDRB|=_BV(PIN0);//PIN0 OF PORTB SET TO OUT PUT
                      
                      // Crystal Oscillator division factor: 1
                      CLKPR=0x80;
                      CLKPR=0x00;
                      
                      // External Interrupt(s) initialization
                      // INT0: On
                      // INT0 Mode: Rising Edge
                      // INT1: On
                      // INT1 Mode: Rising Edge
                      // INT2: Off
                      // INT3: Off
                      // INT4: Off
                      // INT5: Off
                      // INT6: Off
                      // INT7: Off
                      EICRA=0x0F;
                      EICRB=0x00;
                      EIMSK=0x03;
                      EIFR=0x03;
                      
                      sei();// Global enable interrupts
                      
                      
                      for(;;);
                      
                      return (0);
                      
                      }
                      
                      ISR(INT0_vect,ISR_NAKED)
                      {
                        // user code here
                      PORTB^=_BV(PIN0);	
                      }
                      ISR_ALIAS(INT1_vect, INT0_vect);


                      اینم کد اسمبلی معادلش :


                      کد:
                      000000ae <__vector_1>:
                      }
                      
                      ISR(INT0_vect,ISR_NAKED)
                      {
                        // user code here
                      PORTB^=_BV(PIN0);	
                       ae:	85 b1    	in	r24, 0x05	; 5
                       b0:	91 e0    	ldi	r25, 0x01	; 1
                       b2:	89 27    	eor	r24, r25
                       b4:	85 b9    	out	0x05, r24	; 5
                      reti();
                       b6:	18 95    	reti
                      
                      000000b8 <__vector_2>:
                      {
                        // user code here
                      	PORTB^=_BV(PIN0);	
                      }*/
                      //ISR(INT1_vect, ISR_ALIASOF(INT0_vect));
                       b8:	0c 94 57 00 	jmp	0xae	; 0xae <__vector_1>


                      یادتون باشه ، فقط زمانی از این ماکرو استفاده کنید که مطمئنید که رجیستر های که تو روال های دیگه در حال استفاده هستند تو این جا استفاده نشده اند و یا مثلا اگه مطمئن نیستید اول خروجی رو ببنید بعد که دید از چه رجیستر هایی استفاده شده خودتون تو روتینوقفه به صورت این لاین اسمبلی عملیات PUSH و POP رو انجام بدید.

                      چطور بود ؟

                      خوب و اما دامه داستان زیاد هیجان انگیز نشید که دو تا سورپرایز دارم براتون در حد لالیگا! :mrgreen:

                      تو این فایل هدر یه دیفاین داریم به اسم BADISR_vect این دیفاین معادل تمام بردار های وقفه هست . یعنی اگه این رو جای ایم بردار یه وقفه تو ورودی ISR بنویسیم اگه وقفه ای رخ بده که فعال شده باشه اما براش به طور مشخص ISR نوشته نشده باشه ، بعد از وقوع وقفه handeler میکنه پرش میکنه به اصطلاح بردار

                      __bad_interrupt

                      و بعد از اونجا پرش میکنه به بردار وقفه معادل دیفاین BADISR_vect یعنی

                      __vector_default


                      و بعد اون روتین رو اجرا میکنه .

                      توجه5 : اگه نمیدونید بدونید ، کامپایلر اگه وقفه ای قعال بشه آدرس بردار اون اسمی متناسب با شماره اون اختصاص میده مثلا ما تو مثال قبلمون از وقفه خارجی صفر و یک استفاده کردیم به خاطر همین کامپایلر آدرس بردار وقفه اون ها رو به صورت زیر نام گزاری میکنه :

                      4: 0c 94 57 00 jmp 0xae ; 0xae <__vector_1>
                      8: 0c 94 5c 00 jmp 0xb8 ; 0xb8 <__vector_2>


                      و بقیه رو هم چون استفاده نکردیم اون ها رو با

                      __bad_interrupt


                      نام گزاری میکنه به این ترتیب:

                      کد:
                      c:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       10:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       14:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       18:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       1c:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       20:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       24:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       28:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       2c:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       30:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       34:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       38:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       3c:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       40:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       44:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       48:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       4c:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       50:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       54:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       58:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       5c:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       60:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       64:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       68:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       6c:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>
                       70:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>


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

                      جواب مثال :


                      کد:
                      #include <avr/io.h>
                      #include <avr/interrupt.h>
                      
                      
                      int main(void)
                      
                      {
                      
                      
                      DDRB|=_BV(PIN0);//PIN0 OF PORTB SET TO OUT PUT
                      
                      // Crystal Oscillator division factor: 1
                      CLKPR=0x80;
                      CLKPR=0x00;
                      
                      // External Interrupt(s) initialization
                      // INT0: On
                      // INT0 Mode: Rising Edge
                      // INT1: On
                      // INT1 Mode: Rising Edge
                      // INT2: Off
                      // INT3: Off
                      // INT4: Off
                      // INT5: Off
                      // INT6: Off
                      // INT7: Off
                      EICRA=0x0F;
                      EICRB=0x00;
                      EIMSK=0x03;
                      EIFR=0x03;
                      
                      sei();// Global enable interrupts
                      
                      
                      for(;;);
                      
                      return (0);
                      
                      }
                      
                      ISR(BADISR_vect)
                      {
                        // user code here
                      PORTB^=_BV(PIN0);	
                      }
                      ISR_ALIAS(INT1_vect, INT0_vect);


                      اینم خروجی کامپایلر :


                      کد:
                      000000aa <__bad_interrupt>:
                       aa:	0c 94 57 00 	jmp	0xae	; 0xae <__vector_default>
                      
                      000000ae <__vector_default>:
                      return (0);
                      
                      }
                      
                      ISR(BADISR_vect)
                      {
                       ae:	1f 92    	push	r1
                       b0:	0f 92    	push	r0
                       b2:	0f b6    	in	r0, 0x3f	; 63
                       b4:	0f 92    	push	r0
                       b6:	11 24    	eor	r1, r1
                       b8:	8f 93    	push	r24
                       ba:	9f 93    	push	r25
                        // user code here
                      PORTB^=_BV(PIN0);	
                       bc:	85 b1    	in	r24, 0x05	; 5
                       be:	91 e0    	ldi	r25, 0x01	; 1
                       c0:	89 27    	eor	r24, r25
                       c2:	85 b9    	out	0x05, r24	; 5
                      
                      }
                       c4:	9f 91    	pop	r25
                       c6:	8f 91    	pop	r24
                       c8:	0f 90    	pop	r0
                       ca:	0f be    	out	0x3f, r0	; 63
                       cc:	0f 90    	pop	r0
                       ce:	1f 90    	pop	r1
                       d0:	18 95    	reti
                      
                      000000d2 <__vector_2>:
                      {
                        // user code here
                      	PORTB^=_BV(PIN0);	
                      }*/
                      //ISR(INT1_vect, ISR_ALIASOF(INT0_vect));
                       d2:	0c 94 55 00 	jmp	0xaa	; 0xaa <__bad_interrupt>


                      و حالا سورپراز!
                      ببنید بعضی اوقات که خیلی هم نادره امکان داره ما وقفه ای رو فعال کرده باشیم اما یا هیچ روتینی براش ننوشتیم یا اگه هست هیچ کدی نداره اما بالاخره کامپایلر موقع کامپایل با اون مثل یه روتین معمولی برخورد میکنه و کد های الکی خودش رو تولید میکنه . باعث کاهش سرعت میشه حالا فرض کنید که ما مثلا میکرو رو برده باشیم به یکی از مدهای کم مصرف مثلا مد Idle حالا میخواییم از این مد بعد از یه عملیاتی که دارای وقفه هست خارج بشیم یکی از این عملیات ها اتمام تبدیل آنالوگ به دیجیتال هست خوب ما داریم از آنالوگ به دیجیتال که استفاده میکنیم حالا برای اینکه از حالت sleep خارج بشیم کافی وقفه مورد نظر فعال بشه و برای جلوگیری از تولید کد الکی توسط کامپایلر میاییم از ماکروی زیر برای تعریف روتین وقفه مورد نظر استفاده میکنیم :


                      EMPTY_INTERRUPT(ADC_vect);



                      خروجی معادل اسمبلی این میشه :


                      000000d2 <ADC_vect>:

                      d2: 18 95 reti



                      پایان قسمت دوازدهم

                      دیدگاه


                        #41
                        پاسخ : آموزش AVR-GCC

                        13. معرفی چند تابع و ماکرو پر کاربرد از هدر های اصلی کامپایلر (بخش دوم)

                        تابع اول تابع تاخیر یا همون ، DELAY هست برای استفاده از این توابع فایل هدر delay.h رو به صورت زیر در ابتدای برنامه اینکلود کنید :

                        #include <util/delay.h>


                        حالا بریم ببینیم توش چه خبره ،

                        تو اینجا ما دو تا تابع استاتیک میبینیم که به صورت "این لاین " تعریف شدند که به قرار زیر هستند :


                        static inline void _delay_us(double __us) __attribute__((always_inline));
                        static inline void _delay_ms(double __ms) __attribute__((always_inline));


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

                        اما در مورد static inline function :

                        نقل قول از استاد آقازاده :
                        مثلا فرض کنین پروژه ما از چند سورس مجزا (که include نشن و جدا گانه کامپایل بشن) تشکیل شده. در این حالت object های هر سورس بعد از کامپایل شدن در فاز لینک بهم وصل میشن تا فایل نهایی رو تولید کنن. در این حالت اگر متغیر عمومی یا تابعی بصورت static معرفی بشه (البته طبق قواعد عمومی زبان C) دیگه به اسم خودش در object نهایی معرفی نمیشه. یعنی نمیشه از object های دیگه شناسایی بشه و نتیجتا قابل صدا کردن هم نیست:


                        کد:
                        file main.c:
                        
                        void main(void)
                        {
                          func1_in_test_c();
                          wait_for_response();
                        }
                        
                        
                        file test.c
                        
                        void func1_in_test_c(void)
                        {
                        }
                        
                        
                        file uart.c
                        
                        static void wait_for_response(void)
                        {
                        }
                        
                        --------
                        
                        a part of project map file
                        
                         .text     0x000000a4    0xa main.o
                                0x000000a4        main
                         .text     0x000000ae    0x2 test.o
                                0x000000ae        func1_in_test_c
                         .text     0x000000b0    0x0 uart.o
                         .text     0x000000b0    0x0 c:/winavr/bin/../lib/gcc/avr/4.3.3/avr5\libgcc.a(_exit.o)
                                0x000000b0        . = ALIGN (0x2)
                        
                        
                        --------
                        
                        compile error
                        
                        Z:\Projects\test\test\default/../main.c:8: undefined reference to `wait_for_response'



                        اگر توجه کنین میبینین که با اینکه تمامی توابع در پروژه موجوده و کامپایلر WinAVR خطا میده که نماد wait_for_response رو نمیشناسه. این بخاطر استفاده از لغت static در هنگام نوشتن تابع هست که به کامپایلر حالی میکنه که object نهایی نباید حاوی اسم این تابع باشه.


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

                        int next_index(int n)
                        {
                        n++;
                        if( n == limit )
                        n = 0;
                        return n;
                        }

                        براحتی میشه در جایی که استفاده شده insert کرد. چون هم رفتارش برای کامپایلر مشخصه و هم میزان استک مورد استفاده و هم کد ساده ای داره. اما این یکی کد رو در نظر بگیرین:
                        int fact(int n)
                        {
                        if(n == 2)
                        return 2;
                        return fact(n-1)*n;
                        }

                        اگر این تابع رو به فرم inline تعریف کنیم کامپایلر قراره چکار کنه؟ کامپایلر به اطلاعات زمان اجرا دسترسی نداره. تا این کد هم صدا زده نشه که نمیشه فهمید مقدار n چقدر هست و چند بار باید این کد در محلی که صدا زده شده (من جمله خود تابع) اضافه بشه. پس اگر من این تابع رو inline معرفی کنم و کامپایلر هم بخواد نمیتونه inline استفاده اش کنه.

                        بنا براین من توسط استفاده از لغت inline فقط امکان inline کردن یک تابع رو به کامپایلر پیشنهاد میدم. اون ممکنه بتونه یا نتونه. ممکنه انجام بده یا نده. مثلا فرض کنین یک تابع با حجم کدی در حدود 100 بایت 100 جای متفاوت صدا زده شده. اگر قرار باشه inline بشه چیزی نزدیک 10 کیلو به حجم کد اضافه میکنه. حالا میکرویی داریم با 8 کیلو حافظه فلش. بهینه سازی هم روی حجم هست. اگر بخواد این کد رو inline کنه که دیگه این برنامه بدرد هیچی نمیخوره. چون توی فلش جا نمیشه. ولی ممکنه تشخصی بده شما چون گفتی "بهینه سازی حجمی" پس این لغت inline رو برای این تابع ندیده بگیره بهتره. بخاطر همین بعضی کامپایلر ها جدای از inline دستوری بنام forceinline هم دارن. که به کامپایلر میگه تحت هر شرایطی اگر میتونی inline کنی حتما انجام بده.

                        اتمام نقل قول .
                        __________________________________________________ __
                        ////////////////////////////////////////////////////////////////////////////////////////////////////////
                        معمولا برای اطمینان از عملکزد صحیح میکرو در حین انجام برنامه میان از تایمر واچ داگ استفاده میکنند .

                        توجه 1 : اگه نمیدونید بدونید ، کلا به دوصورت میشه از واچ داگ برای این منظور استفاده کرد :
                        1. واچ داگ رو فعال کنیم و تو تمام حلقه ها و توابع اون رو ریست کنیم ، عیب این روش اینه که اگر احیانا میکرو در خلال اجرا تو یه حلقه بسته گیر کنه دائم واچ داگ رو ریست میکنهو نمیزاره میکرو ریست بشه .
                        2. در روش دوم میان واچ داگ رو فعال میکنن . تو تمامی حلقه ها و توابع میان یسری فلگ رو یک میکنن بعد از اتمام اجرای تمامی حلقه ها و توابع اون آخر میان چک میکنن که آیا تمامی فلگ ها یک شده یا نه اگر شده بود که واچ داگ ریست و تمامی فلگ ها ریست میشن در غیر اینصورت با گذر زمان میکرو به دلیل عدم اجرا صحیح ریست خواهد شد .

                        حالا برای استفاده از واچ داگ میایم فایل هدر wdt.h رو به صورت زیر در ابتدای برنامه اینکلود میکنیم :

                        #include <avr/wdt.h>


                        بریم داخلش ببینیم جه خبره ،

                        خوب اولین چیزی که میبینیم ماکروی زیر هست :

                        #define wdt_reset() __asm__ __volatile__ ("wdr&quot

                        خوب همونطور که میبینید این ماکرو معادل یه دستور "این لاین" اسمبلی هست که توسط اون واچ داگ ریست میشه (یعنی مقدار تایمر واچ داگ صفر میشه)

                        کاربرد ماکرو هم که مشخصه ، هر جا خواستیم واچ داگ ریست بشه اونجا از این ماکرو استفاده میکنیم .

                        ماکروی بعدی ، به قرار زیر هست :

                        #define wdt_enable(value)

                        (تعریفشو دیگه نزاشتم حجم پست زیاد میشه (الکی))

                        خوب اگه برید داخل هدر فایل میبینید که این هم معادل دستورات اسمبلی هست که به صورت "این لاین" نوشته شدن .

                        خوب این ماکرو یه ورودی داره ورودی ماکرو درواقع مبین مدت زمانی هست که طول میکشه بعد از روشن شدن واچ داگ ، واچ داگ ریست بشه که برای سادگی و خوانا تر شدن تو همین فایل یسری اعداد که در واقع بیانگر مقدار پرسکالر از فرکانس اصلی واچ داگ هست (اگه سوالی دراین مورد دارید حتما بگید) که این اعداد به صورت زیر دیفاین شدند :


                        کد:
                        #define WDTO_15MS  0
                        
                        /** \ingroup avr_watchdog
                          See \c WDT0_15MS */
                        #define WDTO_30MS  1
                        
                        /** \ingroup avr_watchdog See
                          \c WDT0_15MS */
                        #define WDTO_60MS  2
                        
                        /** \ingroup avr_watchdog
                          See \c WDT0_15MS */
                        #define WDTO_120MS 3
                        
                        /** \ingroup avr_watchdog
                          See \c WDT0_15MS */
                        #define WDTO_250MS 4
                        
                        /** \ingroup avr_watchdog
                          See \c WDT0_15MS */
                        #define WDTO_500MS 5
                        
                        /** \ingroup avr_watchdog
                          See \c WDT0_15MS */
                        #define WDTO_1S   6
                        
                        /** \ingroup avr_watchdog
                          See \c WDT0_15MS */
                        #define WDTO_2S   7
                        
                        #if defined(__DOXYGEN__) || defined(WDP3)
                        
                        /** \ingroup avr_watchdog
                          See \c WDT0_15MS
                          Note: This is only available on the 
                          ATtiny2313, 
                          ATtiny24, ATtiny44, ATtiny84, 
                          ATtiny25, ATtiny45, ATtiny85, 
                          ATtiny261, ATtiny461, ATtiny861, 
                          ATmega48, ATmega88, ATmega168,
                          ATmega48P, ATmega88P, ATmega168P, ATmega328P,
                          ATmega164P, ATmega324P, ATmega644P, ATmega644,
                          ATmega640, ATmega1280, ATmega1281, ATmega2560, ATmega2561,
                          ATmega8HVA, ATmega16HVA, ATmega32HVB,
                          ATmega406, ATmega1284P,
                          AT90PWM1, AT90PWM2, AT90PWM2B, AT90PWM3, AT90PWM3B, AT90PWM216, AT90PWM316,
                          AT90PWM81,
                          AT90USB82, AT90USB162,
                          AT90USB646, AT90USB647, AT90USB1286, AT90USB1287,
                          ATtiny48, ATtiny88.
                          */
                        #define WDTO_4S   8
                        
                        /** \ingroup avr_watchdog
                          See \c WDT0_15MS
                          Note: This is only available on the 
                          ATtiny2313, 
                          ATtiny24, ATtiny44, ATtiny84, 
                          ATtiny25, ATtiny45, ATtiny85, 
                          ATtiny261, ATtiny461, ATtiny861, 
                          ATmega48, ATmega88, ATmega168,
                          ATmega48P, ATmega88P, ATmega168P, ATmega328P,
                          ATmega164P, ATmega324P, ATmega644P, ATmega644,
                          ATmega640, ATmega1280, ATmega1281, ATmega2560, ATmega2561,
                          ATmega8HVA, ATmega16HVA, ATmega32HVB,
                          ATmega406, ATmega1284P,
                          AT90PWM1, AT90PWM2, AT90PWM2B, AT90PWM3, AT90PWM3B, AT90PWM216, AT90PWM316,
                          AT90PWM81,
                          AT90USB82, AT90USB162,
                          AT90USB646, AT90USB647, AT90USB1286, AT90USB1287,
                          ATtiny48, ATtiny88.
                          */
                        #define WDTO_8S   9

                        اینم از نحوه استفاده اش :


                        FOR Example that would select a watchdog timer expiry of approximately
                        500 ms:
                        \code
                        wdt_enable(WDTO_500MS);


                        ماکروی بعدی ، به قرار زیر هست :

                        #define wdt_disable()


                        خوب اگه برید داخل هدر فایل میبینید که این هم معادل دستورات اسمبلی هست که به صورت "این لاین" نوشته شدن .

                        کاری که میکنه اینکه کلا تایمر واچ داگ رو در صورت امکان خاموش میکنه .

                        /////////////////////////////////////////////////////////////////////////////////
                        یکی از امکانات میکرو کنترلر AVR ، امکان وارد شدن به مدهای SLEEP هست .

                        توجه2 : اگه نمیدونید بدونید ، مدهای SLEEP امکان خاموش کردن بخش های بدون استفاده میکرو رو فراهم میکنه و از این طریق در مصرف توان صرفه جویی می کنه ، میکرو با ایجاد مدهای SLEEP متنوع ، امکان ذخیره سازی توان رو برحسب نیاز کاربر فراهم میکنه .
                        توجه 3 : اگه نمیدونید برید مطالعه کنبد ، همونطور که گفته شد میکرو دارای مدهای SLEEP متنوعی هست که باید بسته به نیازتون از اون ها استفاده کنید ، کلا برای استفاده باید با این مدها و نحوه خارج شدن از اون ها آشنا باشید تا بدونید که کدوم مد بدرد کدوم کار میخوره ، اینجا دیگه باید برید داخل دیتا شیت چرخ بزنید ، هرجا رو که متوجه نشدید حتما سوال کنید ، لطفا.

                        حالا اینجا برای وارد شدن به این مدهای SLEEP یه فایل هدر sleep.h هست که باید به صورت زیر در ابتدای برنامه اینکلود بشه :

                        #include <avr/sleep.h>

                        توجه 4 : اگه نمیدونید بدونید ، برای وارد شدن به مدهای sleep باید اول بیت SEاز رجیستر MCUCR یک بشه بعد به کمک بیتهای SM0 , SM1,SM2 مد مورد نظر فعال بشه . حالا اگر یه وقفه رخ بده میکرو بیدار میشه و بعد از یه توقف به اندازه 4 سیکل روتین وقفه رو اجرا میکنه و بعد از برگشت میره دستور بعد از ورود به مد SLEEP رو اجرا میکنه ، خوب حالا برای اینکه بتونیم دوباره وارد مد SLEEP بشیم باید اول اون بیت SE رو صفر کنیم تا تا تو سیکل بعدی دوباره میکرو بتونه وارد مد SLEEP بشه .

                        تو این فایل سه تا تابع داریم ، به قرار زیر
                        تابع اول :
                        sleep_enable()
                        کار این تابع اینکه بیت SE رو یک کنه .
                        نه ورودی داره نه خروجی .
                        تابع دوم :
                        sleep_cpu()
                        کار این تابع اینکه وارد مد انتخاب شده بشه .
                        نه ورودی داره نه خروجی .
                        تابع سوم :
                        sleep_disable()
                        کار این تابع اینکه بیت SE رو صفر کنه .
                        نه ورودی داره نه خروجی .
                        خوب یه تابع دیگه هم داریم که برای ساده تر کردن کار استفاده میشه و میاد این سه تابع بالا رو پشت سر هم اجرا میکنه :
                        sleep_mode()

                        خوب همونطور که میدونید وقتی از Brown Out Detector (BOD) استفاده میکنیم واحد مربوطه یه مقدار توانی رو مصرف میکنه ، تو بعضی از میکرو ها این امکان وجود داره که قبل از ورود به مد SLEEP این Brown Out Detector (BOD)غیر فعال بشه که حالا برای انجام این کار یه تابع تو این فایل در نظر گرفته شده تا بشه قبل از ورود به مد SLEEP از ان استفاده کرد و واحدBrown Out Detector (BOD) رو غیر فعال کرد .
                        این تابع که خدمتتون عرض کردم به قرار زیر هست :
                        sleep_bod_disable()

                        خوب همونطور که میدونید قبل از ورود به مد SLEEP باید مد مورد نظر با توجه به نیازمون انتخاب بشه و از طرفی هم هر میکرویی یکسری از مد ها رو فقط ساپورت میکنه مثلا میکرو هایی که ما بیشتر استفاده میکنیم مثل مگا 8 وگا16ومگا 32 و مگا 64 و مگا 128 و... ، مدهایی که ساپورت میکنن به شرح زیر هستند :

                        #define SLEEP_MODE_IDLE (0)
                        #define SLEEP_MODE_ADC _BV(SM0)
                        #define SLEEP_MODE_PWR_DOWN _BV(SM1)
                        #define SLEEP_MODE_PWR_SAVE (_BV(SM0) | _BV(SM1))
                        #define SLEEP_MODE_STANDBY (_BV(SM1) | _BV(SM2))
                        #define SLEEP_MODE_EXT_STANDBY (_BV(SM0) | _BV(SM1) | _BV(SM2))

                        خوب حالا برای اینکه بتونیم یکی از این مد ها رو انتخاب کنیم یه ماکرو تعریف شده به قرار زیر :

                        #define set_sleep_mode(mode) \
                        do { \
                        _SLEEP_CONTROL_REG = ((_SLEEP_CONTROL_REG & ~(_BV(SM0) | _BV(SM1) | _BV(SM2))) | (mode)); \
                        } while(0)

                        خوب این ماکرو یه ورودی داره و اون هم همون مدی هست که میخواهیم واردش بشیم ، نحوه استفاده اش هم که مشخصه ، به هر کدوم از مدها خواستیم وارد بشیم اسم مد مورد نظر رو از روی اون دیفاین های بالا انتخاب و تو آرگومان ماکروی بالا مینویسیم .

                        خوب تو این فایل برای وارد شدن به مد SLEEP سه تا روتین پیشنهاد شده ،
                        اولین روتین که ساده ترین اون هست به قرار زیر هست :

                        Example:
                        \code
                        #include <avr/sleep.h>

                        ...
                        set_sleep_mode(<mode>
                        sleep_mode();
                        \endcode

                        همونطور که میبینید تو این روتین اول مد انتخاب میشه بعد بیت SE یک میشه بعد وارد مد انتخاب شده میشه و بعد هم بعد از بیدار شدن بیت SE صفر میشه .
                        البته توجه کنید که اگر میخوایید با یکی از وقفه ها زا مد SLEEP خارج بشید باید اینتراپت رو قبل از ورود به مد SLEEPفعال کنید برای این منظور روتین زیر پیشنهاد شده :

                        Example:
                        \code
                        #include <avr/interrupt.h>
                        #include <avr/sleep.h>

                        ...
                        set_sleep_mode(<mode>
                        cli();
                        if (some_condition)
                        {
                        sleep_enable();
                        sei();
                        sleep_cpu();
                        sleep_disable();
                        }
                        sei();
                        \endcode

                        خوب اینجا هم که همه چیز واضح هست ، فقط یه چیزی اضافه شده و اون هم یه ساختار IF با شرط some_condition هست که این برای مواقعی هست که مثلا ما میخواهیم قبل از ورود به مد SLEEP یه چیزی رو جک کنیم اگه برقرار بود وارد مد بشیم اگه نبود نشیم .

                        حالا برای غیر فعال کردن Brown Out Detector (BOD) قیل از ورود به مد SLEEP روتین زیر پیشنهاد شده :

                        \code
                        #include <avr/interrupt.h>
                        #include <avr/sleep.h>

                        ...
                        set_sleep_mode(<mode>
                        cli();
                        if (some_condition)
                        {
                        sleep_enable();
                        sleep_bod_disable();
                        sei();
                        sleep_cpu();
                        sleep_disable();
                        }
                        sei();
                        \endcode

                        اینم دیگه نیاز به توضیح فکر نمیکنم داشته باشه ،اگه سوالی بود حتما بپرسید لطفا.

                        پایان قسمت سیزدهم

                        دوستان با اجازتون "نوشتن برنامه ترکیبی از c و اسمبلی.(در WINAVR) " بهتره بمونه بعد از آشنا شدن با ایجاد پروژه در winavr و ایجاد میک فایل ، پس پست بعد در رابطه با نحوه نوشتن inline assembly هست ، انشالله فرصت شد تا آخر شب آمادش میکنم .

                        دیدگاه


                          #42
                          پاسخ : آموزش AVR-GCC

                          14. نحوه نوشتن Inline Assembly (بخش اول)


                          به طور خلاصه یعنی اینکه هر جای برنامه به این شکل از دستورات اسمبلی استفاده کنیم دقیقا همون موقع کامپایل میشه کد ما بعد از معادل اسمبلی کد c ماقبل خودش .
                          کار بردش برای ذخیره کردن زمان اجرا تو روتین های time critical و همینطور زمانهایی هست که ما میخواییم یه کاری بکنیم اما با زبان c و باید به اسمبلی بنویسیم ، مثلا میخواییم با یسری از دستورات خاص اسمبلی تو یه جایی استفاده کنیم اما کد c که معادل با اون بشه رو پیدا نمیکنیم .

                          منبع اصلی این پست : Inline Assembler Cookbook هست اما مطالب بصورت پراکنده از جاهای مختلف جمع آوری شده اما اون ها هم منبع اصلیشون همین هست.

                          خوب دوستان من فقط اونچیزی رو که فهمیدم میگم و چون اصولا اینجا کسی کاری به کار من نداره پس هرچی من میگم ! :redface:
                          یادم باشه یه تاپیک بزنم تا خودم سوتی های خودمو بگیرم ! :mrgreen:

                          خوب کلا برای نوشتن دستورات اسمبلی تو avr-gcc از فرم زیر استفاده میشه :

                          asm(code : output operand list : input operand list [: clobber list]);

                          خوب همونطور که مشاهده میکنید آرگومان به چهار قسمت تقسیم شده و جداسازی هم بوسیله کلون (انجام میشه

                          قسمت اول ، code :
                          خوب این قسمت اصلی ترین بخش هست ، تو این قسمت کد اسمبلی نوشته میشه و توجه کنید هر کدی که تو این قسمت مینویسد باید داخل دبل کوتیشتن (" &quot نوشته بشه ، حالا این کد میتونه بی نهایت خط کد اسمبلی باشه ، برای اینکه به کامپایلر بفهمنیم اینها تو دستورات جدا از هم هستند باید بعد از هر خط دستور این رو "\n\t" بعد اضافه کنیم و بعد دستور بعدی رو در خط بعدی بنویسیم .

                          همین جا یه چیز بگم بعد کاملش میکنم اگه فقط قصدتون اضافه کردن یکسری کد اسمبلی هست و هیچ گونه وروردی و خروجی خاصی ندارید فقط کافیه به شکلی که گفتم دستورات اسمبلیتون رو بنویسید و بعد دو تا کلون بزارید (کلون سوم میتونه باشه میتونه نباشه مهم نیست ) برای مثال :

                          asm ( "lds r24, 0x0102" "\n\t"
                          "mov r25, r24" "\n\t"
                          "in r24, 0x0b" "\n\t"
                          "out 0x0b, r25" "\n\t"
                          "sts 0x0103, r24" "\n\t" :;

                          قسمت دوم ،
                          output operand list
                          تو این قسمت لیست عملوندها ی خروجی قرار میگیرند و توسط کاما (,) از هم جدا میشن .
                          بعدا بیشتر میگم در موردشون .
                          قسمت سوم ،
                          input operand list :
                          تو این قسمت لیست عملوند های ورودی قرار میگیرند و توسط کاما از هم جدا میشن
                          قسمت سوم ، clobber list:
                          Clobbered register (معنیش نمیدونم یعنی چی ) تو این قسمت معمولا چیزی نوشته نمیشه .

                          خوب از اونجا که معمولا برای کامپایل معمولا کامپایلر رو روی حالت اپتیمایزیشن حجم تنظیم میکنیم و حالا به خاطر استراتژی هایی که کامپایلر برای بهینه کردن کد در نظر میگیره امکان داره که کامپایلر رجیستر ها یی که در طول دستورات inline asm استفاده شده و یا حتی کله کد رو حذف کنه (برای اینکه بهتر متوجه بشید یه کد ساده رو که بعدا میگم رو به همسن فرم ساده بنویسید و بعد برید خروجی کامپایلر رو نگاه کنید )، برای حل این مشکل از کلمه کلیدی
                          volatile استفاده میکنیم به این شکل :

                          asm volatile(code : output operand list : input operand list [: clobber list]);


                          به عنوان مثال کد زیر رو در نظر بگیرید :

                          asm volatile("in %0, %1" : "=r" (value) : "I" (_SFR_IO_ADDR(PORTD)) );

                          اینجا قسمت اول میشه این :

                          "in %0, %1"

                          که یه دستور اسمبلی هست
                          قسکت دوم میشه این :

                          "=r" (value)

                          که میشه عملوند خروجی
                          و قسمت سوم هم میشه این :

                          "I" (_SFR_IO_ADDR(PORTD))

                          که میشه عملوند ورودی
                          قسمت چهارم هم که خالی گزاشته شده .
                          خوب حالا یه توضیح مختصر در رابطه با فرم کد های نوشته شده ،
                          تو قسمت اول بعد از دستور اسمبلی بجای عملوندهای معمول که تو زبان اسمبلی استفاده میشن دو تا عدد که با علامت درصد امدن رو میبیبنیم خوب در واقع اینها دارند به ترتیب به عملوند خروجی و ورودی اشاره میکنند یعنی
                          0% داره به عملوند خروجی و 1% داره به عملوند ورودی اشاره میکنه یعنی ،

                          %0 refers to "=r" (value) and
                          %1 refers to "I" (_SFR_IO_ADDR(PORTD)).

                          حالا برای خوانا تر شدن کد میشه به جای 0 و 1 یک اسمی چیزی که مرتبط باشه رو داخل براکت نوشت و به صورت زیربعد از علامت های درصد و قبل از عملوندها استفاده کرد ، به عنوان مثال :

                          asm("in %[retval], %[port]" :
                          [retval] "=r" (value) :
                          [port] "I" (_SFR_IO_ADDR(PORTD)) );



                          خوب برای اینکه مطالب خوب جا بیوفته بهتره که با مثال های ساده شروع کنیم و اون ها رو تحلیل کنیم و بعد از اون ها ایده بگیریم برای نوشتن کد اسمبلی خودمون ، پس میریم که داشته باشیم:
                          فرض کنید که میخواییم وقفه سراسری رو فعال کنیم برای اینکار باید اینطوری بنویسیم :

                          asm volatile(“cli”:;

                          یا مثلا فرض کنید که بخواییم وقفه سراسری رو غیر فعال کنیم، باید به صورت زیر بنویسم :

                          asm volatile(”sei”:;

                          حالا فرض کنید که بخواییم برای کد اسمبلیمون کامن بنویسیم ، برای اینکار مثل کاری که تو ide های اصلی اسمبلی میکنیم اینجا هم از سیمی کلون به صورت زیر استفاده میکنیم مثلا : پ

                          asm volatile( “nop ;this is comment“ ”\n\t”

                          “nop ;this ASM inline includes 2 nops“ ”\n\t”

                          :;

                          توجه 1 : اگه نمیدونید بدونید ، منظور از این ، “\n\t” اینه که داره به کامپایلر میگه برو خط بعد و به اندازه یه تب (4 تا اسپیس) فاصله بزار
                          موقعی که داریم از inline asm استفاده میکنیم از یه سری تعریف از پیش تعریف شده برای دسترسی به ییک سری رجیستر های خاص استفاده کنیم ، این تعاریف به قرار زیر هستند :

                          Symbol Register
                          __SREG__ Status register at address 0x3F
                          __SP_H__ Stack pointer high byte at address 0x3E
                          __SP_L__ Stack pointer low byte at address 0x3D
                          __tmp_reg__ Register r0, used for temporary storage
                          __zero_reg__ Register r1, always zero

                          یه توضیح کوچولو در رابطه با رجیستر r0 و رجیستر r1 ،
                          رجیستر r0 یا همون __tmp_reg__ میتونه آزادانه تو کد های اسمبلی استفاده بشن و نیازی به ذخیره سازیش نیست
                          رجیستر r1 یا همون __zero_reg__ مقدارش همواره صفر هست .
                          خوب حالا بهتره بشنیم پای صحبت استاد آقازاده در مورد این دوتا :
                          *******************
                          نقل قول از استاد آقازاده :
                          رجیستر r0 برای استفاده temporary استفاده میشه. مثلا برای عملیات دم دستی و دسترسی به اطلاعات داخل فلش در شرایطی که از atmega استفاده نمیشه. (توضیح: lpm و elpm در atmega میتونه روی هر رجیستری کار کنه ولی در بقیه موارد این دستورات مقدار برگشتی رو در r0 قرار میدن.
                          مقدار صفر مقدار پر کاربردیه. برای پرهیز از صفر کردن یک رجیستر با clr یا eor یا ldi از رجیستر r1 برای نگهداری مقدار صفر استفاده میشه.
                          کد آغازین و پایانی روال وقفه:

                          //prolog code
                          push r1
                          push r0
                          in r0,0x3f
                          push r0
                          clr r1
                          push r24
                          push r25

                          // function code
                          ...

                          // epilog code
                          pop r25
                          pop r24
                          pop r0
                          out 0x3f,r0
                          pop r0
                          pop r1
                          reti

                          تفسیر:
                          این یک روتین وقفه است پس ممکنه در هر موقعی (هر جای روال در حال اجرایی) اجرا بشه. ضمنا معلوم نیست که شرایط رجیستر ها در اون لحظه چطوری بوده. اما چون میخواییم کد C بنویسیم باید شرایط دلخواه کامپایلر WinAVR رو ایجاد کنیم (در کد ویژن رجیستر ها استفاده های متفاوتی دارن). خوب:

                          مقدار قبلی رجیستر های r0 و r1 رو ذخیره میکنیم. با استفاده از r0 مقدار status word رو گرفته و ذخیره میکنیم. و r1 رو مساوی با صفر قرار میدیم. از این به بعد هر رجیستری که مورد استفاده قرار گرفته شده باشه بایستی ذخیره بشه و در هنگام بازگشت مقدارش بازیابی بشه. که اینجا فقط r24 و r25 استفاده شده بوده.

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

                          اگر غیر از این فرض کنیم شاید کد ما کار کنه ولی مطمئنا جایی پیش میاد که اجرای برنامه مختل میشه.

                          اتمام نقل قول
                          ___________________________
                          توجه : اون بخشی رو که بلود کردم رو با دقت بخونید و خوب حضمش کنید .
                          حالا میریم عملوندهای ورودی و خروجی بیاریم تو گود !
                          خوب برای نوشتن این عملوند ها باید از یه چیزی به اسم "constraint string و بعد از اون عبارت زبان c که در واقع همون عنوان ورودی و خروجی به حساب میاد"
                          که حالا این constraint string ها به قرار زیر هستند :

                          Constraint Used for Range
                          a Simple upper registers r16 to r23
                          b Base pointer registers pairs y, z
                          d Upper register r16 to r31
                          e Pointer register pairs x, y, z
                          q Stack pointer register SPH:SPL
                          r Any register r0 to r31
                          t Temporary register r0
                          w Special upper register pairs r24, r26, r28, r30
                          x Pointer register pair X x (r27:r26)
                          y Pointer register pair Y y (r29:r28)
                          z Pointer register pair Z z (r31:r30)
                          G Floating point constant 0.0
                          I 6-bit positive integer constant 0 to 63
                          J 6-bit negative integer constant -63 to 0
                          K Integer constant 2
                          L Integer constant 0
                          l Lower registers r0 to r15
                          M 8-bit integer constant 0 to 255
                          N Integer constant -1
                          O Integer constant 8, 16, 24
                          P Integer constant 1
                          Q (GCC >= 4.2.x) A memory address based on Y or Z pointer with displacement.
                          R (GCC >= 4.3.x) Integer constant. -6 to 5


                          خوب سوالی که الان بوجود میاد اینه که ، آقا ما برای هر کارسی کدوم یکی از این ها رو باید استفاده کنیم ؟
                          اگه دقت کنید هر Constraint معادل یه چیزی هست و از طرفی اگه با دستورات اسمبلی آشنا باشید میدونید که هر دستوری رو میشه روی یک سری مکان های حافظه اجرا کرد خوب برای حل این مشکل یه جدول دیگه ارائه شده به صورت زیر :

                          Mnemonic Constraints Mnemonic Constraints
                          adc r,r add r,r
                          adiw w,I and r,r
                          andi d,M asr r
                          bclr I bld r,I
                          brbc I,label brbs I,label
                          bset I bst r,I
                          cbi I,I cbr d,I
                          com r cp r,r
                          cpc r,r cpi d,M
                          cpse r,r dec r
                          elpm t,z eor r,r
                          in r,I inc r
                          ld r,e ldd r,b
                          ldi d,M lds r,label
                          lpm t,z lsl r
                          lsr r mov r,r
                          movw r,r mul r,r
                          neg r or r,r
                          ori d,M out I,r
                          pop r push r
                          rol r ror r
                          sbc r,r sbci d,M
                          sbi I,I sbic I,I
                          sbiw w,I sbr d,M
                          sbrc r,I sbrs r,I
                          ser d st e,r
                          std b,r sts label,r
                          sub r,r subi d,M
                          swap r


                          حالا خود این کاراکتر های Constraint میتونید همراه یه constraint modifier بیان که حالا هر کدوم از این ترکیب ها معنی خاص خدش رو داره و در جای خودش استفاده میشه
                          اما اگه این کاراکتر های Constraint تنها استفاده بشن تنها قایل خوندن هستند (کم کم متوجه منظورم میشید نمیتونم یجا بگم)
                          که حالا این constraint modifier ها به قرار زیر هستند :

                          Modifier Specifies
                          = Write-only operand, usually used for all output operands.
                          + Read-write operand
                          & Register should be used for output only


                          یه نکته این modifier فقط برای عملوند های خروجی استفاده میشن یعنی عملوندهای خروجی فقط قابل نوشتن هستن ، نمیشه اون ها روخوند .
                          پس میتونیم نتیجه بگیریم که عملوند های ورودی فقط قابل خوندن هستند اما اگه یه زمانی ما به یه عملوند مشابه در وروی و خروجی نیاز داشتیم چی ؟ نمیشه که یه عملوند هم فقط قابل خوندن باشه هم فقط قابل نوشتن !
                          برای حل این مشکل میاییم در حالت کلی از اعداد که از صفر شروع میشه تا هر چند که لازم باشه ، بعد از علامت درصد قرار میدیم ، یعنی چی ؟

                          خوب همونطورقبلا که گفتم این اعداد به عملوندها اشاره میکنند حالا ما میتونیم با مدیریت در استفاده از این اعداد به کامپایلر بگیم که کدوم عملوند خروجی و ورودی باید تو کدوم دستور استفاده بشه ، حالا با مثال مسئله رو بیشتر باز میکنیم .
                          مثال 1 :

                          asm volatile("swap %0" : "=r" (value) : "0" (value));

                          خوب همونطور که میدونید دستور
                          swap
                          چهار بیت بالا و پایین عملوند رو جابجا میکنه ، پس باید ورودی و خورجی یکی باشه چرا ؟ چون مگه نباید نتیجه اعمال دستور swap ریخته بشه دوباره داخل همون عملوندی که وارد شده ، پس دیگه چرا نداره !
                          از طرفی هم که گفتیم عملوند خروجی فقط قابل نوشتن و عملوند ورودی فقط قابل خوندن هست ، برای حل این مشکل امدیم از همون تکنیک اعداد استفاده کردیم ، به کد نگاه کنید ، داره میگه swap کن عملوند ورودی رو با شماره صفر و بعد اون رو بریز تو عملوند خروجی با شماره صفر ، دید پس عملوند ورودی و خروجی یکی شدند به این ترتیب اگه کد معادل رو بعد از کامپایل ببینید به این صورت میشه :

                          14e: 80 91 04 01 lds r24, 0x0104
                          152: 82 95 swap r24
                          154: 80 93 04 01 sts 0x0104, r24

                          خوب شاید اینجا براتون سوال ایجاد شده باشه که این value پس چیه ؟
                          اینvalue همون عبارت زبان سی هست که اول عرایضم گفتم و باید تو برنامه به زبان سی تعریف شده باشه مثلا من تو برنامه بالا امدم انرو به صورت گلوبال و از نوع تک بایتی تعریف کردم و به خاطر همین برای مقدار دهی به اون و خوندنش از دستورات دسترسی به حافظه sram استفاده کرده یعنی lds و sts.

                          مثال 2 :

                          asm volatile("in %0,%1" "\n\t"
                          "out %1, %2" "\n\t"
                          : "=&r" (input)
                          : "I" (_SFR_IO_ADDR(PORTD)), "r" (output)
                          );

                          خوب اول یه توضیح کوچیک بعد تحلیل کد ، تو جاهایی که ما نیاز باشه که دو تا رجیستر مختلف رو به یه عملوند خروجی منتسب کنیم چون برای ما خروجی نهایی مهم هست میاییم بجای استفاده از "=r" از "=&r" استفاده مکنیم

                          خوب حالا تحلیل ، تو این مثال همنطور که میبیند از دستور in استفاده شده از این دستور برای خوندن مقدار رجیستر های i/o استفاده میشه پس باید عملوند ورودی یبق ترتیب موجود باید آدرس رجییتسر مورد نظر و عملوند خروجی باید یکی از رجیستر های عمومی باشه ، خوب همونطور که مشاهده میکنید 0% داره به "=&r" (input) اشاره میکنه و 1% داره به "I" (_SFR_IO_ADDR(PORTD)) اشاره میکنه
                          حالا دستور بعدی ، تو خط دوم از دستور out استفاده شده این دستور طبق ترتیب باید عملوند دوم که باید از جنس رجیستر های I/O باشه رو میریزه داخل یکی از رجیستر های عمومی .
                          کاری که اینجا انجام میشه اینکه الان با اجرای دستور OUT مقدار متغیر outputریخته میشه داخل رجیستر portd .

                          خوب پس با اجرای این کد مقدار پورت d باید ریخته بشه داخل متغیر input و مقدار متغیر output ریخته بشه داخل پورت d
                          خوب برای روشن تر شدن مطلب من میام اول برنامه به متغیر های برنامه یه بخش مشخص از حافظه رو اختصاص یدم به قرار زیر :

                          #define output (*(uint8_t*)0x0112)
                          #define input (*(uint8_t*)0x0113)

                          (دوستان من تو اینجا از یک مرحله تایپ کستینگ استفاده کردم ، اگه متوجه نشدید چی به چیه حتما سوال کنید )
                          خوب حالا بریم خروجی کامپایلر رو برای کد بالا به انضمام این تعاریف فوق ببینیم :

                          126: 80 91 12 01 lds r24, 0x0112
                          12a: 98 2f mov r25, r24
                          12c: 8b b1 in r24, 0x0b ; 11
                          12e: 9b b9 out 0x0b, r25 ; 11
                          130: 80 93 13 01 sts 0x0113, r24

                          خوب همونطر که میبینید اینجا مقدار رجیستر portd ریخته میشه داخل داخل متغیر input

                          و
                          مقدار متغیر output ریخته میشه داخل رجیستر portd
                          اگه شما خودتون بخوایید یه همچین کاری رو بکنید ، منظورم اینه که خودتون اگر قرار بود همچین کاری روبه زبا اسمبلی بنویسید چکار میکردید ؟
                          به کد اسمبلی دقت کنید ، همونطور که میدونید برای خوندن از حافظه sram بصورت مستقیم باید از دستور lds استفاده کرد و برای نوشتن در اون به صورت مستقیم از دستور sts
                          که این عمل خوندن و نوشتن به واسطه یه رجیستر عمومی انجام میشه ، یعنی برای خوندن باید مقدار از حافظه sram ریخته بشه داخل یکی از رجیستر های عمومی و برای نوشتن باید مقدار مورد نظر اول ریخته بشه داخل یک رجیستر عمومی و بعد لود بشه داخل حافظه sram

                          بنابراین ما اینجا صد در صد به دو تا رجیستر عمومی احتیاج داریم تا بتونیم خواسته مسئله رو براورده کنیم وگرنه اطلاعات متغیر ورودی یعنی output از دست میره و اونچیزی که سرآخر داخل رجیستر portd ریخته میشه همون چیزیه که قبلا بوده !
                          به خاطر همین موضوع بجای این "=r" مینویسیم "=&r" !
                          حالا اگه این "&"رو نزاریم ، کد خروجی میشه این :

                          126: 80 91 12 01 lds r24, 0x0112
                          12a: 8b b1 in r24, 0x0b ; 11
                          12c: 8b b9 out 0x0b, r24 ; 11
                          12e: 80 93 13 01 sts 0x0113, r24

                          مسئله بعدی که شاید اینجا مبهم باشه اینکه پس چرا ما اینجا دوتا ورودی داریم و یک خروجی پس مگه نباید تو دستور دوم عملوند ورودی ریخته بشه داخل عملوند خروجی ؟
                          ببینید نکته ای که اینجا وجود داره اینه که طبق اون جدول دوم ، زمانی که ما داریم از دستور out استفاده میکنیم عملوند اول باید از نوع I و عملوند دوم باید از نوع rباشه
                          (به کوچیک بزرگ بودن حروف دقت کنید ، داریم با کامپایلر زبان c کار میکنیم)
                          از طرفی عملوند از نوع I نمیتونه هیچ وقت به عنوان عملوند خروجی مورد استفاده قرار بگیره پس باید تو قسمت عملوند ورودی استفاده بشه ، حالا برای مقدار دهی به اون فقط کافیه شماره ای که تو قسمت کد نوشتیم به اون اشاره کنه ،
                          اگه دقت کنید میبینید که تو کد اول عدد صفر داره به عملوند خروجی از نوع rو عدد 1 داره به عملوند ورودی اول یعنی I اشاره میکنه ، پس یک داره به PORTD اشاره میکنه !
                          حالا تو خط دوم برای ریختن عملوند ورودی دوم داخل همون رجیستر PORTD باید از عدد 1 برای اشاره به PORTD استفاده کنیم و برای اشاره به عملوند بعدی تو قسمت ورودی عدد بعدی یعنی 2 رو استفاده کنیم .
                          توجه کنید که اعداد از صفر شروع به افزایش میکنند .

                          حالا فرض کنید که من میخوام بجای اینکه متغیر OUTPUTبره داخل رجیستر PORTD بره داخل رجیستر PIND باید چکار کنم ؟
                          خوب طبق مطالبی که گفتم باید دو تا چیز مد نظر باشه ؟
                          1. دستور کد چیه و بنا به جدول چه نوع عملوندی رو قبول میکنه ؟
                          2. اون رو تو کدوم قسمت از کد قرار بدم ، تو قسمت عملوند ورودی یا خروجی ؟
                          3. از یه عدد مناسب برای اشاره به اون استفاده کنم ؟
                          جواب سوال اول : چون داریم از دستور OUT استفاده میکنیم باید عملوند اول I و عملوند دوم از نوع r باشه .
                          جواب سوال دوم ، همونطور که گفتم چون عملوند از نوع I نمیتونه هیچ وقت تو قسمت عملوند خروجی قرار بگیره باید بزاریمش تو قسمت عملوند ورودی .
                          جواب سوال سوم ، خوب ما باید از ابتدا ببنیم که اعداد به چه صورت استفاده شدند ، عدد صفر داره به عملوند خروجی اشاره میکنه ، عدد یک داره به عملوند ورودی اول اشاره میکنه ، حالا تو خط دوم باید به یه عملوند دیگه اشاره کنیم پس عدد بعدی یعنی 2 به عملوند ورودی دوم یعنی اون چیزی که ما تازه اضافه کردیم یعنی آدرس PIND باید اشاره کنه و عدد بعدی یعنی 3 باید به متغیر بعدی که باید عملوند ورودی باشه و از نوع r یعنی عملوند ورودی سوم اشاره کنه .
                          پس کدمون رو باید به صورت زیر اصلاح کنیم :

                          asm volatile("in %0,%1 " "\n\t"
                          "out %2, %3" "\n\t"
                          : "=&r" (input)
                          : "I" (_SFR_IO_ADDR(PORTD)),"I" (_SFR_IO_ADDR(PIND)), "r" (output)
                          );

                          این خروجی کامپایلر :

                          126: 80 91 12 01 lds r24, 0x0112
                          12a: 98 2f mov r25, r24
                          12c: 8b b1 in r24, 0x0b ; 11
                          12e: 99 b9 out 0x09, r25 ; 9
                          130: 80 93 13 01 sts 0x0113, r24


                          ادامه در پست بعدی ...

                          دیدگاه


                            #43
                            پاسخ : آموزش AVR-GCC

                            ...ادامه از پست قبل :

                            خوب حالا بریم دوباره بریم سراغ همون SWAP
                            این دفعه فرض کنید که میخواییم بایت بالا و پایین یه متغیر رو جبا هم عوض کنیم
                            کد پیشنهادی برای اینکار :

                            asm volatile("mov __tmp_reg__, %A0" "\n\t"
                            "mov %A0, %B0" "\n\t"
                            "mov %B0, __tmp_reg__" "\n\t"
                            : "=r" (value)
                            : "0" (value)
                            );

                            خوب حالا ببینیم چه اتفاقی میوفته ، تو اینجا کاری که ما میخواییم بکنیم ایه که بایت بالا و پایین متغیر 16 بیتی value رو با هم عوض کنیم ، چطوری ؟
                            خوب همونطور که میدونید وقتی که مامیاییم یه متغیر رو به صورت 16 بیتی تعریف میکنیم این متغیر در واقع میاد دو تا خونه از حافظه sram اشغال میکنه ، حالا کافیه که خونه اول رو بریزیم تو یه متغیر میانجی بعد بخونه دوم روبریزیم تو خونه اول و در انتها متغیر میانجی رو بریزیم تو خونه اول.

                            حالا برای انجام اینکار پس ما 3 تا رجیستر میخواییم یکی به عنوان میانجی و دوتای دیگه هم برای خوندن و نوشتن در حافظه sram.و همینطور یه ورودی داریم و یه خروجی که جفتشون یک متغیر هستند
                            اگه یادتون باشه قبلا گفتیم که تو کامپایلر avr-gcc از r0 به عنوان متغیر میانجی استفاده میشه البته تحت یه شرایطی که استاد آقازاده فرمودند و باید رعایت بشه ، و از طرفی هم اگه یادتون باشه گفتم که بجای r0 میتونیم از معادل اون یعنی __tmp_reg__ استفاده کنیم ، اون دوتا رجیستر دیگه رو هم میسپاریم به عهده خوده کامپایلر تا خودش انتخاب کنه ، استفاده از عملوند نوع "r".
                            خوب حالا تحلیل کد ، تو اینجا تو قسمت کد خط اول از دستور mov استفاده شده طبق جدول عملوند ها باید از نوع r,r باشند ، خوب عملوند اول رو __tmp_reg__ و عملوند دوم هم که باید خونده بشه و داخل رجیستر میانجی ریخته بشه باید طوری بنویسیم که به پایین ترین 8 بیت متغیر 16 بیتی ورودی مون اشاره کنه به خاطر همین میاییم به صوزت %A0 مینویسیم تو خط بعدی باید بایت بالا رو بریزیم تو بایت پایین پس دوباره با استفاده از دستور mov میایم اینکار رو میکنیم خوب اینجا باید عملوند اول به بایت پایین و عملوند دوم به بایت بالا اشاره کنه و هر دوهم باید به یه متغیر اشاره کنند پس جفتشون عددشون باید 0 باشه به این ترتیب عملوند اول %A0 و عملوند دوم %B0 باید باشه ، تو خط بعدی باید رجیستر میانجی روکپی کنیم تو بایت بالا ، دوباره از mov استفاده میکنیم و تبعا عملوند اول برای اشاره به بایت بالای متغیر ورودی %B0 و عملوند دوم برای اشاره با متغیر میانجی باید __tmp_reg__ باشه حالا تو قسمت عملوند خروجی باید چی بزاریم ؟
                            خوب همونطور که میبیند ما تو کدمون دوبار داریم رو عملوند خورجی می نویسم و دو بار داریم از عملونذ خروجی میخونیم و از طرفی هر دو هم باید یه متغیر باشند چون قراره عملیات تنها رئی یه متغیر انجام بشه و نتیجه هم داخل همون ریخته بشه.
                            پس میاییم تو قسمت اول چون دستور mov هست از "=r" استفاده میکنیم دقت کنید که دیگه اینجا نیازی به & نیست چون موقع خوندن اطلاعات از ورودی خود کامپایلر مجبور میشه که از دوتا رجیستر جدا استفاده کنه و خودش همون ها رو تو محاسبات درگیر میکنه البته اگر هم بزارید هیچ فرقی نمیکنه ، اما تو قسمت سوم یعنی عملوند خروجی میتونیم از "r" استفاده کنیم یا از عدد 0 به صورت "0" چون فقط میخواییم عمل اشاره درست انجام بشه .
                            حالا من میام value رو به صورت زیر تعریف میکنم :

                            #define value (*(uint16_t*)0x0114)

                            خوب اینهم خروجی کامپایلر :

                            126: 80 91 14 01 lds r24, 0x0114
                            12a: 90 91 15 01 lds r25, 0x0115
                            12e: 08 2e mov r0, r24
                            130: 89 2f mov r24, r25
                            132: 90 2d mov r25, r0
                            134: 90 93 15 01 sts 0x0115, r25
                            138: 80 93 14 01 sts 0x0114, r24

                            حالا فرض کنید که یه متغیر 32 بیتی داریم و میخواییم دو بایت بالا و پایینش رو با هم عوض کنیم ، یعنی بایت اول باید بره جای بایت چهارم و بایت چهارم بیاد جای بایت اول و بعد بایت دوم بره جای بایت سوم و بایت سوم بره جای بایت دوم
                            خوب اگه دقت کنید اینجا ما در واقع میخواییم دوبار عملیات مثال قبل رو انجام بدیم ، کد پیشنهادی برای این کار به صورت زیر هست :

                            asm volatile("mov __tmp_reg__, %A0" "\n\t"
                            "mov %A0, %D0" "\n\t"
                            "mov %D0, __tmp_reg__" "\n\t"
                            "mov __tmp_reg__, %B0" "\n\t"
                            "mov %B0, %C0" "\n\t"
                            "mov %C0, __tmp_reg__" "\n\t"
                            : "=r" (value)
                            : "0" (value)
                            );

                            و حالا من میام value رو به صورت زیر تعریف میکنم :

                            #define value (*(uint32_t*)0x0116)

                            اونوقت این میشه خروجی کامپایلر :

                            126: 80 91 16 01 lds r24, 0x0116
                            12a: 90 91 17 01 lds r25, 0x0117
                            12e: a0 91 18 01 lds r26, 0x0118
                            132: b0 91 19 01 lds r27, 0x0119
                            136: 08 2e mov r0, r24
                            138: 8b 2f mov r24, r27
                            13a: b0 2d mov r27, r0
                            13c: 09 2e mov r0, r25
                            13e: 9a 2f mov r25, r26
                            140: a0 2d mov r26, r0
                            142: 80 93 16 01 sts 0x0116, r24
                            146: 90 93 17 01 sts 0x0117, r25
                            14a: a0 93 18 01 sts 0x0118, r26
                            14e: b0 93 19 01 sts 0x0119, r27


                            خوب دیگه فکر کنم که فهمیدید چی میشه ، پس تفسیر پای خودتون ، اگه سوالی بود حتما بپرسید لطفا.
                            فقط اینو بگم که برای دسترسی به هر بایت از حروف لاتین به ترتیب از بایت اول تا بایت چهارم از حروف A تا D استفاده شده.
                            خوب اگه یادتون باشه ما یه Modifier داشتیم به صورت "+"
                            خوب از این طبق توضیحش زمانی استفاده میشه که ما یه متغیر داریم که هم ورودی هم خروجی ، مثل همین مثالهای بالا
                            بنابراین میتونیم تو مثالهای (swap) بیاییم قسمت عملوند ورودی رو حذف کنیم و تو قسمت عملوند خروجی بجای "=r"
                            بنویسیم "+r"
                            یعنی تمام مثال های مربوط به SWAP زو میتونیم اینجوری بنویسیم :
                            مثال اول SWAP :

                            sm volatile("swap %0" : "+r" (value) );

                            مثال دوم SWAP :

                            asm volatile("mov __tmp_reg__, %A0" "\n\t"
                            "mov %A0, %B0" "\n\t"
                            "mov %B0, __tmp_reg__" "\n\t"
                            : "+r" (val)

                            );

                            مثال سوم SWAP :

                            asm volatile("mov __tmp_reg__, %A0" "\n\t"
                            "mov %A0, %D0" "\n\t"
                            "mov %D0, __tmp_reg__" "\n\t"
                            "mov __tmp_reg__, %B0" "\n\t"
                            "mov %B0, %C0" "\n\t"
                            "mov %C0, __tmp_reg__" "\n\t"
                            : "+r" (val2)

                            );


                            خوب ، حالا یه زمانی هست که میخواهیم از اشاره گر ها برای خوندن حافظه داده (sram) بصورت غیر مستقیم استفاده کنیم یعنی با استفاده از دستورات ld , st ، خوب برای اینکه بتونیم از رجیستر های شاره گر یعنی x, y, z استفاده کنیم همنطور که گفتم باید از دستورات ld , st استفاده کنیم .
                            خوب ، طبق جدول داریم که دستورات ld , st باید عملوند ها شون به صورت زیر باشند :

                            ld r,e
                            st e,r

                            خوب تو این واقع کامپایلر ائلین انتخابش رجیستر z هست و همونطور که قبلا تو مثال ها گفته شد برای دسترسی به بایت های بالا و پایین از حروف لاتین استفاده میشه ، با اندازه بزرگ ، اما اونجا برای اشاره به خود رجیستر استفاده میشد نه آدرسش یعنی عملکردش به صورت یه اشاره گر نبود اما اینجا ما نیاز داریم به یه عملوند برای اشاره به آدرس خوب برای اینکار باز هم از همون تکنیک استفاده میشه با این تفاوت که این بار حروف باید در اندازه کوچکشان استفاده شوند .
                            یعنی ما قبلا داشتیم :

                            %A0 refers to r30
                            %B0 refers to r31

                            و الان با استفاده از %a0 میایم به آدرس پایین اشاره میکنیم
                            مثال :
                            قبل از اینکه مثال رو بریم ، اگه یادتون باشه آخرین قسمت کد رو یه قسمتی به اسم clobber list تشکیل میداد ، حالا این چیه ؟
                            خوب اگه دقت کرده باشید ما تا اینجا هر وقت خواستیم از رجیسترها استفاده کنیم به دو صورت عمل کردیم :
                            1. با نوشتن Constraint ها ی مناسب و همین طور انتخاب عملوند مناسب تو بخش کد (منظورم همون چیزیه که بعد از علامت درصد مینوشتیم) از کامپایلر میخواستیم که خودش رجیستر مناسب رو انتخاب کنه و در نظر داشته باشه که اون رجیستری که استفاده کرده تو کار سایر بخش ها تداخل ایجاد نکنه .
                            2. یا اینکه از __tmp_reg__ استفاده کردیم .

                            اما یه زمانی هست که میخواییم یه رجیستر خواص استفاده کنیم مثلا r20 خوب اگه ما اینکار رو بکنیم ، کامپالیر میاد و همین رو منتقل میکنه به فایل lss و از رو اون هم اسمبلر کد هگز رو تولید میکنه ، حالا شما آیا میتونید تضمین کنید که کامپایلر رجیستر r20 هیچ جای برنامه استفاده نکرده ؟
                            مسلما خیر !
                            پس چاره چیه ؟
                            برای حل این مشکل باید تو قسمت clobber list لیست رجیستر هایی رو که استفاده کردیم رو وارد کنیم تا به کامپایلر میگیم که آقا موقع کامپایل اینرو در نظر داشته باش که اگه لازم بود مقدار این رجیستر رو ذخیره کنی و بعد دوباره بازیابی .
                            خوب بریم سراغ مثال ، کاری که میخوایم بکنیم اینه که مقدار یه خونه از حافظه sram رو یک واحد اضافه کنیم .
                            خوب همونطور که میدونید عملگر های حسابی محدوده عملکردشون رجیستر های عمومی هست ، مشخصا ما اینجا از دستور inc استفاده میکنیم و طبق جدول عملوندی که میشه برای این کار استفاده کرد از نوع r هست
                            پس باید اول اون خونه از حافظه رو بخونیم و بعد برزیم توی یکی از رجیستر ها بعد یکی زیادش کنیم و در انتها مقدار جدید رو توی حافظه داده لود کنیم
                            برای خوندن و نوشتن تو حافظه داده اینبار از دستورات غیر مستقیم استفاده میکنیم یعنی از ld, st
                            خوب همونطور که گفته شد برای کار با اینها باید از رجیسترهای اشاره گر استفاده کرد و نحوه استفاده اشم که گفتم .
                            این کد پیشنهادی برای اینکار هست :

                            asm volatile(
                            "cli" "\n\t"
                            "ld r24, %a0" "\n\t"
                            "inc r24" "\n\t"
                            "st %a0, r24" "\n\t"
                            "sei" "\n\t"
                            :
                            : "e" (ptr)
                            : "r24"
                            );

                            خوب دیگه نیز به توضیح فکر کنم نداره ، فقط یه مورد شاید به نظر ناآشنا بیاد و اون هم غیر فعال کردن و فعال کردن وقفه سراسری باشه ، چرا اینکار رو کرده ؟
                            خوب دلیلش اینه که ما داریم از اشاره گر ها استفاده میکنیم و چون اشاره گر ها در صورت وقوع وقفه جزو رجیسترهایی نیست که کامپایلر اون ها رو push و pop میکنه نتیجتا باید برای حفظ جانب احتیاط اول کد وقفه سراسری رو غیر فعال و در انتها فعال کنیم .
                            من ptr رو به صورت زیر تعریف کردم :

                            #define ptr (uint8_t*)0x0120

                            اینهم خروجی کامپایلر برای مثال فوق :

                            14e: e0 e2 ldi r30, 0x20 ; 32
                            150: f1 e0 ldi r31, 0x01 ; 1
                            152: f8 94 cli
                            154: 80 81 ld r24, Z
                            156: 83 95 inc r24
                            158: 40 83 st Z, r20
                            15a: 78 94 sei

                            البته تو کد بالا میتونستیم برای اینکه برای کامپایلر موقع کامپایل به لحاظ اوپتیمایز محدودیت ایجاد نکنیم از رجیستر موقت __tmp_reg__استفاده کنیم . یعنی بیاییم مثال بالا رو اینجوری بنویسیم :

                            asm volatile(
                            "cli" "\n\t"
                            "ld __tmp_reg__, %a0" "\n\t"
                            "inc __tmp_reg__" "\n\t"
                            "st %a0, __tmp_reg__" "\n\t"
                            "sei" "\n\t"
                            :
                            : "e" (ptr)
                            );

                            درواقع
                            __tmp_reg__
                            جزو رجیستر هایی هست که کامپایلر اون رو تو روتین های مختلف مثل وقفه ذخیره و بازیابی میکنه .
                            خوب ما شاید بخواییم که رحچیستر وضعیت رو قبل از اجرای کدها ذخیره کنیم و بعد در انتها اونها رو بازیابی کنیم اما این کار نیاز به یه رجیستر دیگه داره و باید اون رو به صورت Clobber به کامپایلر معرفی کنیم ، خوب این مسئله باعث محدودیت کامپایلر در پیداه کردن استراتژهای اوتیمایز میشه ، برای حل این مشکل نیتونیم از یهمتغیر محلی استفاده کنیم و با استفاده از عملوند های مناسب و انتخاب متغیر محلی به عنوان خروجی این اجازه رو به کامپایلر بدیم تا هر جور که صلاح میدونه رجیستر وضعیت رو ذخیره و بازیابی کنه ، به این شکل :

                            {
                            uint8_t s;
                            asm volatile(
                            "in %0, __SREG__" "\n\t"
                            "cli" "\n\t"
                            "ld __tmp_reg__, %a1" "\n\t"
                            "inc __tmp_reg__" "\n\t"
                            "st %a1, __tmp_reg__" "\n\t"
                            "out __SREG__, %0" "\n\t"
                            : "=&r" (s)
                            : "e" (ptr)
                            );
                            }

                            اما اینجا هنوز یه مشکلی هست !
                            مشکلی که ایجاد میشه اینه که امکان داره که کد اسمبلی متغیری که ptrبه اون اشاره میکنه رو تغییر بده و اون موقع همه چیز بهم میریزه .
                            اما چون کامپایلر میخواد تو حالت اوپتیمایز عمل کامپایل رو انجام بده دیگه این ها رو در نظر نمگیره و حافظه رو پدیت نمیکنه و این باعث از دست رفتن مقادیر حسابشده میشه .
                            خوب برای اینکه به کامپایلر بگیم که ما داریم از یه همچین متغیرهایی استفاده میکینم تو بخش clobber list مینویسیم :
                            "memory"
                            به ترتیب زیر :

                            کد:
                            {
                              uint8_t s;
                              asm volatile(
                                "in %0, __SREG__"      "\n\t"
                                "cli"            "\n\t"
                                "ld __tmp_reg__, %a1"    "\n\t"
                                "inc __tmp_reg__"      "\n\t"
                                "st %a1, __tmp_reg__"    "\n\t"
                                "out __SREG__, %0"     "\n\t"
                                : "=&r" (s)
                                : "e" (ptr)
                                : "memory"
                              );
                            }

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

                            volatile uint8_t *ptr;

                            ناگفته نمونه که موقعیت هایی که ما نیاز به استفاده از این clobber ها هستیم بسیار نادر هست و میشه معمولا مشکل رو به روش های بهتری حل کرد .

                            پایان قسمت چهاردهم

                            دیدگاه


                              #44
                              پاسخ : آموزش AVR-GCC

                              15. نحوه نوشتن Inline Assembly (بخش دوم)
                              Assembler Macros:

                              خوب بعضی اوقات هست که ما یک کد اسمبلی رو میخواییم چند جا استفاده کنیم حالا درصورتی که کد کوتاه باشه میشه به بجای نوشتن دائم یه کد میتونیم اون رو به صورت ماکرو تعریف کنیم ، چیزی که به کرات تو کتابخونه ها دیده میشه .
                              خوب اینجا مطلبی عنوان شده که در صورت نوشتن ماکرو های اسمبلی در داخل فایل هخای هدر بهتره به جای asm از __asm__ و بجای volatile از __volatile__ استفاه بشه تا از بروز warnings در هنگام کامپایل جلوگیری بشه .

                              مثال :
                              اگه یادتون باشه تو قسمت 11 یکسری ماکرو معرفی کردم ، یه ماکروی دیگه که از همون دست مارکرو ها هست ، ماکروی (loop_until_bit_is_clear(port,bit هست ، کاری که میکنه اینه که میاد یه بیت از یه پورت که ما براش مشخص میکنیم چک میکنه و تا زمانی که اون بیت یک باشه یا به عبارت صحیح تر تا زمانی که صفر نشه ، یه حلقه رو دائم اجرا میکنه ، حالا کد اسمبلی این ماکرو به صورت زیر هست :

                              کد:
                              #define loop_until_bit_is_clear(port,bit) \
                                  __asm__ __volatile__ (       \
                                  "L_%=: " "sbic %0, %1" "\n\t"   \
                                       "rjmp L_%="        \
                                       : /* no outputs */    
                                       : "I" (_SFR_IO_ADDR(port)), 
                                        "I" (bit)  
                                  )


                              البته اگه اینو رو استفاده کنید کامپایلر خطا میده چون همونطور که میدونید اگه ما یه ماکرو رو بخواهییم تو چند خط بنویسیم در انتهای یک خط باید یک بک اسلش بزاریم ، خوب اینجا سه خط آخر بک اسلش ندارند و در ضمن جلوی یکی از خطوط کامنت نوشته شده و باید حذف بشه ، که حالا من اونو به صورت زیر اصلاحش کردم .

                              قبل از اینکه صورت اصلاح شده رو ببینیم یه توضیحی در مورد لیبل تو کد های inline asmbly بدم ، خوب هر وقت که خواستیم به یه لیبلی پرش کنیم باید بعد از لیبل =% رو هم به صورتی که در مثال بالا ملاحظه میکنید ، اضافه کنیم .

                              حالا من امدم مثال بالا رو به صورت یه فایل هدر نوشتم :

                              کد:
                              ifndef _ASMMACRO_H
                              #define _ASMMACRO_H
                              
                              
                                    
                              			
                              #define loop_until_bit_is_clear_mojtaba(port,bit) \
                                  __asm__ __volatile__ (       \
                                  "L_%=: " "sbic %0, %1" "\n\t"   \
                                       "rjmp L_%="        \
                                       :     					\
                                       : "I" (_SFR_IO_ADDR(port)), \
                                        "I" (bit)  \
                                  )
                              			
                              #endif	/* asmmacro.h */


                              (اینجا چون این ماکرو از قبل برای کامپایلر تو هدر io.h تعریف شده بود ، موقع کامپایل خطا میداد به خاطر همین من اسمم رو به آخر ماکرو اضافه کردم)
                              به عنوان مثال از نحوه استفاده از این ماکرو ، فرض کنید ما میخواییم یه پین 1 پروت b تا زمانی که پین 2 پورت d صفر نشده خاموش باشه و بعد از اینکه پین 2 پورت d صفر شد پین 1 پورت b یک بشه :

                              کد:
                              loop_until_bit_is_clear_mojtaba(PIND,PIN2);
                              PORTB|=_BV(PIN1);


                              C Stub Functions:
                              خوب همونطور که گفته شد ماکرو زمانی استفاده میشه که کد ما کوچیک باشه ، حالا برای کد های طولانی راه حل بهتر استفاده از تابع هست ، یعنی بیاییم تابعی رو تعریف کنیم و بجای نوشتن کد سی ، کد های inlne asmbly رو داخلش بنویسیم به عنوان مثال :

                              کد:
                              void delay(uint8_t ms)
                              {
                                uint16_t cnt;
                                asm volatile (
                                  "\n"
                                  "L_dl1%=:" "\n\t"
                                  "mov %A0, %A2" "\n\t"
                                  "mov %B0, %B2" "\n"
                                  "L_dl2%=:" "\n\t"
                                  "sbiw %A0, 1" "\n\t"
                                  "brne L_dl2%=" "\n\t"
                                  "dec %1" "\n\t"
                                  "brne L_dl1%=" "\n\t"
                                  : "=&w" (cnt)
                                  : "r" (ms), "r" (delay_count)
                                  );
                              }

                              توجه کنید که اینجا بجای استفاده مستقیم از یه رجیستر به صورت موقت و تعریف اون در clobber list از متغیر محلی استفاده کرده تا دست کامپایلر رو موقع کامپایل برای اجرای استراتژی های اوپتیمایز باز بزاره.

                              خوب همونطور که حتما متوجه شدید کد بالا ه تابع تاخیر هست که مقدار ورودی باید 8 بیتی باشه و تاخیر هم بر حسب میلی ثانیه اعمال میشه . البته اگه خواستید ازش استفاده کنید باید delay_count رو براش محاسبه و تعریف کنید ، مقدارش برابر با فرکانس کلاک میکرو تقسیم بر 4000 ، مثلا الان میکرو من با فرکانس 8 مگ کار میکنه من اینو باید برابر با 2000 قرار بدم و توجه کنید که این رو یا باید به صورت دیفاین تعریف کنید یا به صورت یه متغیر سراسری 16 بیتی .

                              برای مثال، همون برنامه بالا رو در نظر بگیرید با این تفاوت که در صورت برقراری شرط موجود پین 1 پورت b وضعیتش معکوس بشه و بعد یه تاخیر 200 میلی ثانیه ای اعمال بشه :
                              خوب اول باید اول برنامه delay_count رو مقدار دهی کنیم ، مثلا :

                              کد:
                              #define  delay_count 2000

                              بعد از اون هم بدنه برنامه به صورت زیر میتونه باشه :

                              کد:
                              loop_until_bit_is_clear_mojtaba(PIND,PIN2);
                              PORTB^=_BV(PIN1);
                              delay(200);


                              مثال 2:تو این مثال اسم یه رجیستر داده میشه و تابع مقدار اون رجیستر و رجیستر بعدی رو در قالب یه متغیر 16 بیتی برمیگردونه :

                              کد:
                              uint16_t inw(uint8_t port)
                              {
                                uint16_t result;
                                asm volatile (
                                  "in %A0,%1" "\n\t"
                                  "in %B0,(%1) + 1"
                                  : "=r" (result)
                                  : "I" (_SFR_IO_ADDR(port))
                                  );
                                return result;
                              }


                              مورد استفاده این تو خوندن رجیستر های 16 بیتی مثل رجیستر تایمر کانتر 1 هست .


                              پایان قسمت پانزدهم




                              دیدگاه


                                #45
                                پاسخ : آموزش AVR-GCC

                                دوستانی که میخوان با AVRStudio4 کار کنند ، یه PDF عالی هست شاید همه داشته باشند یا دیده باشند اما گفتم بهر حال لینکش رو اینجا هم بزارم ، بد نیست :

                                لینک دانلود : CprogrammingInAVRStudio.pdf

                                نویسنده : Sepehr Naimi

                                دیدگاه

                                لطفا صبر کنید...
                                X