اطلاعیه

Collapse
No announcement yet.

بررسی APPNOTE های ATMEL

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

    بررسی APPNOTE های ATMEL

    با سلام
    یک دو سه روزی هست که با کامپایلر IAR دارم کار میکنم ، چیزی که در رابطه با این کامپایلر متوجه شدم این بود که به راحتی کدویژن و بسکام که قبلا باهاشون کار میکردم نمیشه برنامه نوشت ( البته برنامه داریم تا برنامه ، برنامه که من می نویسم مسلما خیلی آماتور هست ) و شاید دلیلش هم نبود کتابخانه های فراوان باشه مثلا توی کدویژن و بسکام کتابخانه هایی مثل LCD.H هست اما این کامپایلر همچین کتابخانه هایی رو نداره !
    اما چیزی که من رو بیشتر راغب کرد تا به این نرم افزار دلببندم شکل برنامه نویسیش بود نمیدونم من رو یاد برلندC می ندازه که من باهاش خاطرات خوبی دارم منظورم اینه که آدم تازه احساس میکنه که داره برنامه می نویسه (البته این یک احساس کاملا شخصی هست ) خلاصه مطلب با تعقیب مطالب انجمن XMEGA در رابطه با این کامپایلر چیزی رو که فهمیدم این بود که برای کار با این کامپایلر و اصلا برنامه نویسی اصولی باید اون شکل برنامه نویسی رو ببوسم بزارم کنار تا خودم کنار نرفتم و برای این منظور از آنجا که APPNOTE های ATMEL روش برنامه نویسی استاندارد رو به کاربراش یاد میده تصمیم گرفتم که شروع به مطالعه این APPLICTION ها بکنم .
    و حالا قصد دارم تا هر APPNOTE رو که میخونم دریافت هام رو اینجا بنویسم تا دوستان قایل بدوند و اشتباهاتم رو بهم بگند تا این خشتها را بتونم سالم روی هم بزارم :

    APPNOTE ای که می خوام باهاش شروع کنم AVR 035 هست :



    1.مدهای آدرس دهی : در معماری AVR به چهار نوع اشاره گر حافظه برای استفاده از حافظه داده و حافظه برنامه وجود دارد مثل SP که به محلی که آدرس برگشت از توابع در آن ذخیره می شود اشاره می کنه و اشاره گری که کامپایلر ها به عنوان پارامتر استک اختصاص میدند ( این یعنی چه؟) و اشاره گرهایی که کامپایلر برای بار گزاری و ذخیره داده از اون ها استفاده می کنند .

    * تمام اشاره گرها یک کلمه ای(single-word) هستند و در نتیجه برای اجرا به دو سیکل ماشین احتیاج دارند .

    مدهای آدرس دهی اشاره گر ها :
    1.آدرس دهی مستقیم : برای مقدار دهی مستقیم اشاره گر
    مثال :

    char i[3];
    char *pointer1=&i[0];
    *pointer1 =0x00;

    2-آدرس دهی غیر مستقیم با جایگزینی : برای آدرس دهی غیر مستقیم اشاره گرها و همچنین مقدار دهی غیر مستقیم متغیر ها
    مثال :

    char i[3];
    char *pointer1=&i[0];
    pointer1 =&i[1];
    i[2]=*pointer1;

    3-آدرس دهی غیر مستقیم اشاره گر و مقدار دهی متغیر ها با افزایش مقدار اشاره گر بعد از انتساب
    مثال :

    char i[3];
    char *pointer1=&i[0];
    *pointer1++ =0x00;
    i[1]=*pointer1++;

    4-آدرس دهی غیر مستقیم اشاره گر و مقدار دهی متغیر ها با کاهش قبل از انتساب
    مثال :

    char i[3];
    char *pointer1=&i[0];
    *--pointer1=0x00;
    i[1]=*--pointer1;


    2.دسترسی به رجیسترهای I/O : تمام رجیسترهای I/O در فایل های هدر مربوط به هر میکرو که به صورت “ioxxxx.h” نام گذاری شده اند تعریف شده است که XXXX به نام میکرو اشاره دارد
    مثلا برای مگا 16 داریم : iom16.h
    که البته تمام این فایل ها در فایل هدر ioavr.h فراخوانی شده اند که با فراخوانی این هدر در ابتدای برنامه کامپایلر با توجه به تنظیمات انجام شده فایل هدر مورد نظر را به برنامه الحاق مینماید .

    1- خواندن رجیستر های I/O :
    مثال :

    char temp; /* Declare a temporary variable*/
    /*To read and write to an I/O register*/
    temp = PIND; /* Read PIND into a variable*/
    // IN R16,LOW(16) ; Read I/O memory

    2-نوشتن در رجیسترهای I/O :
    مثال :

    TCCR0 = 0x4F; /* Write a value to an I/O location*/
    // LDI R17,79 ; Load value
    // OUT LOW(51),R17 ; Write I/O memory

    3-SET کردن بیت n ام از یک رجیستر :
    مثال :

    DDRB|=(1<<DDB2);
    PORTB |= (1<<PINB2);
    // SBI LOW(23),LOW(2)
    // SBI LOW(24),LOW(2)

    در اینجا PINB2 , DDB2 هر تنها عنوان کننده شماره بیت مورد نظر می باشند از اینرو هر معادل هم و همچنین معادل مثلا PB2,PD2,PIND2,POTRA2,2,... میباشند .
    4-clear کردن بیت n ام از یک رجیستر :
    مثال :

    ADCSR &= ~(1<<ADEN);
    // CBI LOW(6),LOW(7)

    در اینجا نیز از آنجا که ADEN بیت هفتم از رجیستر ADCSR می باشد میتوان بجای آن از 7,PD7,PINB7,....استفاده نمود البته این کار به جهت از بین رفتن خوانای برنامه انجام نمیشود .
    5-SET کردن همزمان چند بیت :
    مثال :

    DDRD |= 0x0C; /* Set bit 2 and 3 in DDRD register*/
    // IN R17,LOW(17) ; Read I/O memory
    // ORI R17,LOW(12) ; Modify
    // OUT LOW(17),R17 ; Write I/O memory

    6-clear کردن همزمان چند بیت:
    مثال:

    ACSR &= ~(0x0C); /* Clear bit 2 and 3 in ACSR register*/
    // IN R17,LOW(8) ; Read I/O memory
    // ANDI R17,LOW(243) ; Modify
    // OUT LOW(8),R17 ; Write I/O memory

    7-چک کردن یک بودن یک بیت از یک رجیستر :
    مثال:

    if(USR & (1<<TXC)) /* Check if UART Tx flag is set*/
    PORTB |= (1<<PB0);
    // SBIC LOW(11),LOW(6) ; Test direct on I/O
    // SBI LOW(24),LOW(0)

    8-چک کردن صفر بودن یک بیت از یک رجیستر :
    مثال:

    while(!(SPSR & (1<<WCOL))) ;/* Wait for WCOL flag to be set */
    // ?0003:SBIS LOW(14),LOW(6); Test direct on I/O
    // RJMP ?0003

    9-چک کردن یک بودن یا صفر بودن چند بیت از یک رجیستر یا به عبارتی برابر بودن رجیستر با یک سری بیت مرتب شده :
    مثال:

    if(UDR & 0xF3) /* Check if UDR register "and" 0xF3 is non-zero
    {
    }
    // IN R16,LOW(12) ; Read I/O memory
    // ANDI R16,LOW(243) ; "And" value
    // BREQ ?0008 ; Branch if equal
    //?0008:
    }

    10-SET ، CLEAR و چک کردن بیت n ام از یک رجیستر با استفاده از ماکرو :
    مثال:

    #define SETBIT(ADDRESS,BIT) (ADDRESS |= (1<<BIT))
    #define CLEARBIT(ADDRESS,BIT) (ADDRESS &= ~(1<<BIT))
    /* Macro for testing of a single bit in an I/O location*/
    #define CHECKBIT(ADDRESS,BIT) (ADDRESS & (1<<BIT))
    /* Example of usage*/
    if(CHECKBIT(PORTD,PIND1)) /* Test if PIN 1 is set*/
    {
    CLEARBIT(PORTD,PIND1); /* Clear PIN 1 on PORTD*/
    }
    if(!(CHECKBIT(PORTD,PIND1))) /* Test if PIN 1 is cleared*/
    {
    SETBIT(PORTD,PIND1); /* Set PIN 1 on PORTD*/
    }


    پایان قسمت اول (دیگه کم آوردم )

    #2
    پاسخ : بررسی APPNOTE های ATMEL

    3. دسترسی به حافظه داده :
    1-خواندن و نوشتن در حافظه داده با استفاده از آدرس خانه حافظه مورد نظر:
    مثال :

    #include <ioavr.h>
    #define reg (* (unsigned char *) 0x8004) /* Declare a memory mapped I/O address*/
    void main( void )
    {


    unsigned char temp; /* Local temp variable */
    reg = 0x05; /* Write a value to the memory mapped address*/
    temp = reg; /* Read the memory mapped I/O address */

    در مثال فوق خانه 0x8004 از حافظه داده برابر با 0x05 و سپس داخل متغیر temp ریخته می شود .
    2- دسترسی پیاپی به حافظه داده با استفاده از آدرس خانه حافظه مورد نظر:
    یک روش کارامد برای این منظور تعریف یک اشاره گر ثابت و تعریف یک مقدار اولیه برای آن و سپس کم وزیاد کردن آن برای دسترسی به خانه مورد نظر از حافظه می یاشد :
    مثال :

    #include <ioavr.h>
    #define data 0x0003
    #define address_high 0x0002
    #define address_low 0x0001
    void main( void )
    {
    DDRC=0xff;
    (* (char *) 0x0803)=0x44;
    /* Start address for memory map */
    unsigned char *pointer = (unsigned char *) 0x0800;
    // LDI R30,LOW(0) ; Init Z-pointer
    // LDI R31,8
    *(pointer+address_low) |= 0x40; /* Read and modify one address*/
    // LDD R18,Z+1 ; Load variable
    // ORI R18,LOW(64) ; Modify
    // STD Z+1,R18 ; Store Back
    *(pointer+address_high) = 0x00; /* Write an address*/
    // STD Z+2,R30 ; Store zero
    PORTC = *(pointer+data); /* Read an address*/
    // LDD R16,Z+3 ; Load variable
    // OUT LOW(21),R16 ; Output to port
    while(1){
    };
    }

    در این مثال خانه 0x801=0x40 و خانه 0x802=0x00 و خانه 0x0803=0x44 و همچنین PORTC برابر با مقدار موجود در خانه 0X0803 می شود .

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

    دیدگاه


      #3
      پاسخ : بررسی APPNOTE های ATMEL

      4. دسترسی به فضای داده EEPROM داخلی میکرو :
      از این فضای داخلی میکرو میتوان در روال عادی برنامه با استفاده ازماکروهایی که در فایل هدر ina90.h قرار دارند برای کار با کامپایلر IAR ، که البته در ورژن 5.4 که بنده باهاش کار میکنم با INCLUDE کردن این هدر متوجه شدم که در واقع این هدر در نهایت با الحاق هدر Intrinsics.h امکان استفاده از ماکرو های __EEGET(VAR, ADR) و _EEPUT(ADR,VAL) را میسر میکند .

      1-محتویات ماکرو __EEGET به همراه توضیح نحوه عملکرد اش به ترتیب زیر می باشد:

      #define _EEGET(VAR,ADR) /* Read data in EEPROM address ADR into variable VAR
      */ \
      { \
      while(EECR & 0x02); /* Check if EEPROM is ready*/ \
      EEAR = (ADR); /* Write EEPROM address register*/ \
      EECR |= 0x01; /* Set the read strobe*/ \
      (VAR) = EEDR; /* Read the data into variable in the next cycle */ \
      }

      فرم ساده شده در هدر Intrinsics.h :

      #define __EEGET(VAR, ADR) {while (EECR & 0x02); \
      EEAR = (ADR); EECR = 0x01; (VAR) = EEDR;}

      توجه : بک اسلش ها بیانگر ادامه دار بودن ماکرو هست .
      2-محتویات ماکرو __EEPUT به همراه توضیح نحوه عملکرد اش به ترتیب زیر می باشد:

      #define _EEPUT(ADR,VAL) /* Write data in VAL into EEPROM address ADR*/\
      {\
      while(EECR&0x02); /* Check if EEPROM is ready*/ \
      EEAR = (ADR); /* Write EEPROM address register*/ \
      EEDR = (VAL); /* Write EEPROM data register*/ \
      EECR |= 0x04; /* Set master write enable signal*/ \
      EECR |= 0x02; /* Set write strobe*/ \
      }

      فرم ساده شده در هدر Intrinsics.h :

      #define __EEPUT(ADR,VAL) {while (EECR & 0x02); \
      EEAR = (ADR); EEDR = (VAL); EECR = 0x04; EECR = 0x02;}

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

      3-خواندن و نوشتن در خانه ای از حافظه EEPROM با استفاده از ماکرو های __EEPUT و __EEGET :
      مثال :

      #include <ioavr.h>
      #include <ina90.h>
      #define EE_ADDRESS 0x010 /* Define address constant for EEPROM data*/
      void main(void){
      unsigned char temp; /* Local variable for temporary storage */
      UDR=0X44;
      _EEPUT(EE_ADDRESS,0x55);
      _EEGET(temp,EE_ADDRESS); /* Read data from EEPROM*/
      temp += UDR; /* Add UART in m8515 data to temp variable */
      _EEPUT(EE_ADDRESS,temp); /* Write data to EEPROM*/

      while(1){
      };
      }

      در اینجا در ابتدا مقدار 0X55 در خانه به آدرس EE_ADDRESS یعنی 0X010 نوشته شده سپس مقدار همین خانه خوانده و در متغیر temp ریخته میشود و سپس مقدار temp+UDR یعنی 0X99 در همین خانه از EEPROM ذخیره می شود .

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

      پایان قسمت سوم

      دیدگاه


        #4
        پاسخ : بررسی APPNOTE های ATMEL

        5.استفاده بهینه از متغیر ها و انواع داده :

        1-متغیر ها و انواع داده : از انجا که avr یک میکروکنترلر 8 بیتی هست در مواقعی که استفاده از متغیر های 16 و 32 بیتی کاملا ضروری هست در عین حال باید سعی شود تعداد این متغیر ها محدود باشد .
        در جدول زیر حجم کد تولید شده برای متغیر های 8 و 16 بیتی آمده است :


        2-استفاده بهینه از متغیرها : توابع داده هایی را به عنوان ورودی دریافت و داده هایی را به عنوان خروجی برمیگرداند ، متغیرهایی که در داخل توابع تعریف میشوند متغیر های محلی و آنهایی هم که خارج از توابع تعریف میشوند سراسری نامیده میشوند .
        متغیر های محلی که باید بعد از اجرای توابع مقدار خودشان را حفظ نمایند باید از نوع متغیر های محلی ایستا (static local variables)تعریف شوند .
        مکان هایی مشخص در sram به جهت ذخیره سازی متغیرهای سراسری تخصیص داده شده که نباید مورد استفاده های دیگر واقع گردد چرا که سبب اشغال فضای با ارزشی از sram خواهد شد .
        تعداد زیاد متغیرهای سراسری باعث کاهش خوانایی و امکان تغییرات مشکلتر در آینده خواهد شد .
        متغیر های محلی زمانی که تعریف میگردند ترجیحا در یک رجیستر عمومی ذخیره میشوند ، مقادیر متغیرهای محلی در توابع تا انتهای تابع و یا تا جایی که دیگر به جایی رجوع داده نشده باشند در یک رجیستر عمومی باقی می مانند .
        البته متغیرهای سراسری نیز قبل از استفاده نیز باید در یک رجیستر عمومی بار گزاری شودند.
        در مثال زیر حجم کد تولیدی و همچنین سرعت اجرای دستورات بر روی دو متغیر محلی و سراسری مقایسه گشته :
        مثال :

        char global; /* This is a global variable */
        __C_task void main(void)
        {
        char local; /* This is a local variable*/
        global -= 45; /* Subtraction with global variable*/
        // LDS R16,LWRD(global) ; Load variable from SRAM to register R16
        // SUBI R16,LOW(45) ; Perform subtraction
        // STS LWRD(global),R16 ; Store data back in SRAM
        local -= 34; /* Subtraction with local variable*/
        // SUBI R16,LOW(34) ; Perform subtraction directly on local
        : variable in register R16
        }

        در متال فوق دستورات اسمبلی LDS و STS که جهت دسترسی مستقیم به متغیرهای در حافظه SRAM استفاده شده اند دستورالعمل های دو کلمه ای یا 4 بایتی هستند که هر کدام در سیکل اجرا میشوند .
        در جدول زیر مقدار حجم کد تولیدی و تعداد سیکل لازم برای اجرای متغیر های سراسری و محلی نشان داده شده است :


        توجه : یک متغیر محلی ایستا در ابتدای تابع از داخل فضای sram در یک از رجیستر های عمومی بار گزاری شده و سر آخر در انتهای تابع به محل واقع در sram برگردونده میشه این در حالی هست که اگر از یک متغیر سراسری برای کار درداخل یک تابع استفاده شود برای هر بار دسترسی به متغیر مورد نظر باید در ابتدا در یکی از رجیستر های عمومی بارگزاری ، عملیات انجام و سپس نتیجه به محل واقع در sram بازگردانده شود از این رو میتوان بیان کرد که استفاده از متغیرهای محلی ایستا برای کار در توابع برای استفاده از آن متغیر به دفعات بیشتر از یک بار باعٍ تولید کد بهینه تر و سرعت اجرای بالاتر تابع میشود.

        برای محدود کردن استفاده از متغیر های سراسری میتوان از توابع عمومی که در زبان c استفاده میشوند بهره برد . از طرفی کامپایلر در صورت استفاده تا دو متغیر از انواع : (char,int, long, float, double) در بین توابع مختلف داخل رجیسترهای عمومی R16 - R23 لود و برداشت میشوند و برای بیشتر از دو متغیر و یا آرایه ها و ساختار ها (arrays, structs) یا در داخل استک نرم افزاری واقع مشوند و یا در بین توابع به عنوان اشاره گر به یک موقعیت در stam استفاده میشوند.

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

        typedef struct
        {
        char sec;
        }t;
        t global /* Declare a global structure*/
        char min;
        __C_task void main(void)
        {
        t *time = &global;
        // LDI R30,LOW(global) ; Init Z pointer
        // LDI R31,(global >> 8) ; Init Z high byte
        if (++time->sec == 60)
        {
        // LDD R16,Z+2 ; Load with displacement
        // INC R16; Increment
        // STD Z+2,R16 ; Store with displacement
        // CPI R16,LOW(60) ; Compare
        // BRNE ?0005 ; Branch if not equal
        }
        if ( ++min == 60)
        {
        // LDS R16,LWRD(min) ; Load direct from SRAM
        // INC R16 ; Increment
        // STS LWRD(min),R16 ; Store direct to SRAM
        // CPI R16,LOW(60) ; Compare
        // BRNE ?0005 ; Branch if not equal
        }
        }

        در مثال فوق زمانی که از متغیر های سراسری در قالب ساختار استفاده میشود کامپایلر از رجیستر اشاره گر Z و دستورالعمل های LDD , STD برای مقدار دهی به صورت غیر مستقیم استفاده میکند و در زمانی که از متغیر های سراسری به صورت عادی استفاده میشود کامپایلر از دستورات مستقیم LDS , STS استفاده مینماید.
        در جدول زیر حجم کد تولیدی برای متغیر های سراسری ساختار ی و بدون ساختار را نشان داده شده است :


        البته این مورد ذکر شده برای متغیر سراسری ساختاری شامل ارزش دهی رجیستر اشاره گر Z نمی شود از این رو کد تولیدی برای دسترسی به یک بایت در دو نوع متغیر یکسان هست اما اگر ساختار شامل دو بایت و یا بیشتر از آن باشد آن زمان هست که بهینه تر بودن کد تولید شده برای متغیر های سراسری ساختاری مشخص میشود .

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


        دیدگاه


          #5
          پاسخ : بررسی APPNOTE های ATMEL

          6.بهینه سازی فلگ های عمومی :
          در ببیشتر مواقع برای کنترل روند اجرای برنامه نیاز به استفاده از فلگ های سراسری (global flags) و یا به عبارتی یکیسری بیت مرتب شده داریم
          استفاده از فلگ ها در قالب متغیرهای سراسری یک امر ناکارآمد محسوب میشود چرا که قبل از چک کردن یک فلگ باید آن را در داخل یک رجیستر عمومی بار گذاری نمود . برای رفع این مشکل و به عبارت بهتر بهینه نمودن این مسئله می توان فلگ ها در داخل یک از رجیستر های عمومی که از قیل تعیین شده و مختص این کار هست قرار داد یا اینکه فلگ ها را در یکی از رجیستر های I/O مثل UBRR یا EEDR یا EEAR به شرط آنکه مثلا برای استفاده از UBAR نباید UART فعال و مورد استفاده قرار گیرد یا برای استفاده از EEDR , EEAR نباید حافظه از EEPROM استفاده شود.
          توجه : در مورد استفاده از یک رجیستر خاص نیز باید کامپایلر قابلیت تعین یک رجیستر خاص برای اختصای دادن به این امر باشد .
          توجه کنید که استفاده از رجیستر های I/O به طور کاملا موثر باعث بهینه تر شدن کد تولیدی میشود البته اگر این مورد به دو بخش نیز تقسیم میشود:
          1- رجیستر های I/O بالای 0X1F که معادل همون آدرس 0X3F در حافظه داده می باشد.
          2-رجیستر های I/O بالای 0X1F که معادل همون آدرس 0X3F در حافظه داده می باشد .


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

          در مثالهای زیر مقایسه ای بین کد تولید شده در رابطه با این فلگ ها یا به اصطلاح بیت های مرتب شد(bit-field) در 4 شکل تعریف متغیر موجود انجام شده است :

          مثال 1: فلگ های سراسری تعریف شده در حافظه SRAM :

          typedef struct bitfield{// Bitfield structure
          unsigned char bit0:1;
          unsigned char bit1:1;
          unsigned char bit2:1;
          }bitfield;
          Global Flags in RAM: bitfield global_flag; // Bitfield in a global variable
          ...
          global_flag.bit1 = 1;
          // LDI R30,LOW(global_flag)
          // LDI R31,(global_flag) >> 8
          // LD R16,Z
          // ORI R16,0x02
          // ST Z,R16

          همانطور که میبینید حجم کد تولیدی با توجه به دستورات اسمبلی معادل برابر با 10 بایت می باشد .

          مثال 2: فلگ های سراسری تعریف شده در یک رجیستر از پیش تعیین شده ، رجیستر R15:


          __no_init __regvar unsigned char reg_flag@ 15;
          //Global register R15
          ...
          reg_flag |= 0x01;
          // SET
          // BLD R15,0

          توجه : دستور no_init __regvar دیگر به رجیستر R15 اجازه تغییر پذیری به عنوان یک رجیستر عمومی را نمی دهد .
          همانطور که از دستور معادل اسمبلی برمی آید حجم کد تولیدی معادل 4 بایت می باشد .

          مثال 3 : فلگ های سراسری تعریف شده در یک رجیستر I/O که بالاتر از 0X1F یا همان 0X3F از حافظه داده واقع شده است :


          __no_init volatile bitfield high_io_flag@0x55;
          // Bitfield in I/O above 0x1F, note that 0x30 offset is added to address
          ...
          high_io_flag.bit2 = 1;
          // IN R16,0x35
          // ORI R16,0x04
          // OUT 0x35,R16

          همانطور که از دستور اسمبلی معادل بر می آید حجم کد تولیدی برابر با 6 بایت می باشد .

          مثال 4 : فلگ های سراسری تعریف شده در یک رجیستر I/O که پایین تر از 0X1F یا همان 0X3F از حافظه داده واقع شده است :


          __no_init volatile bitfield low_io_flag@0x35;
          // Bitfield in I/O location below 0x1F
          ...
          low_io_flag.bit1 = 1;
          // SBI 0x15,0x01

          همانطور که از دستور اسمبلی معادل بر می آید حجم کد تولیدی برابر با 2 بایت می باشد .

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



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

          پایان قسمت پنجم

          دیدگاه


            #6
            پاسخ : بررسی APPNOTE های ATMEL

            7.Bit-field vs. Bit-mask :
            در برخی مواقع ممکن این امر لازم باشد که برای ذخیره سازی یکسری بایت های بارزش از یک دیتای ذخیره شده چندین بیت را در داخل یک بایت دیگر ذخیره نماییم . استفاده معمول از این فلگ های بیتی هست که در قالب یک بایت جمع آوری میگردند که میتوان این رو به صورت Bit-mask یا Bit-field تعریف شود .

            در این مثال نحوه استفاده از Bit-mask و Bit-field در قالب یک بایت نشان داده شده است
            مثال :

            /* Use of bit-mask for status bits*/
            /* Define bit macros, note that they are similar to the I/O macros*/
            #define SETBIT(x,y) (x |= (y)) /* Set bit y in byte x*/
            #define CLEARBIT(x,y) (x &= (~y)) /* Clear bit y in byte x*/
            #define CHECKBIT(x,y) (x & (y)) /* Check bit y in byte x*/
            /* Define Status bit mask constants */
            #define RETRANS 0x01 /* bit 0 : Retransmit Flag*/
            #define WRITEFLAG 0x02 /* bit 1 : Flag set when write is due*/
            #define EMPTY 0x04 /* bit 2 : Empty buffer flag*/
            #define FULL 0x08 /* bit 3 : Full buffer flag*/
            __C_task void main(void)
            {
            char status; /* Declare a status byte*/
            CLEARBIT(status,RETRANS); /* Clear RETRANS and WRITEFLAG*/
            CLEARBIT(status,WRITEFLAG);
            /*Check if RETRANS flag is cleared */
            if (!(CHECKBIT(status, RETRANS)))
            {
            SETBIT(status,WRITEFLAG);
            }
            }


            1-کد معادل Bit-mask اسمبلی تولید شده توسط کامپایلر c اگر متغیر های استا در داخل توابع استفاده شده در برنامه به عنوان متغیر محلی بکار ورد همچنین گزینه دیگر برای اینکار استفاده از فضای آزاد رجیستر های I/O
            به عنوان Bit-mask یا Bit-field می باشد .
            2-Bitfields می توانند در حافظه SRAM یا در رجیستر های I/O تعریف گردند . متغیر های سراسری تعریف شده در داخل رجیستر های عمومی تنها قابلیت استفاده عادی خود را بدون دسترسی بیتی دارند برای ایجاد دسترسی بیتی در متغیرهای سراسری میتوان از bit-masks استفاده نمود .
            3-در استاندارد ANSI تعیین نشده که Bitfields به چه صورتی داخل بایت جمع آوری گردند برای مثال ممکنه Bitfields در یک کامپایلر ممکنه از MSB شروع به چیدن کنند و در کامپایلر دیگری از LSB . این درصورتی هست که با استفاده از bitmasks میتوان یک کنترل کامل بر روی ترتیب قرار گیری بیت ها در داخل یک متغیر داشت .

            8.مقدار دهی متغیر های سراسری :
            در ابتدای امر اجرا کد ها مقدار تمامی متغیر های سراسری ریست شده و برابر با صفر میگردند مگر آنکه در محلی که معرفی میشوند به آنها یک مقدار اولیه اختصاص داده شده باشد .
            مثال :

            unsigned char global_counter = 100;
            unsigned int global_flags ;// All global variables are initialized to 0

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

            9.دسترسی به حافظه FLASH :
            فرم معمول برای تعریف یک ثابت به صورت زیر می باشد :


            const char max = 127;

            این ثابت از حافظه فلش در زمان اجرای برنامه داخل SRAM کپی شده و داخل آن می ماند تا برنامه مربوطه اجرا شود .
            excuse me i can't understand this sentence : "This is considered to be waste of SRAM."
            برای ذخیره کردن فضای sram می توان ثابت ها را در حافظه flash ذخیره نمود و در مواقع لزوم آن ها را در برنامه بارگزاری نمود.
            فرم تعریف ثابت ها در حافظه flash در کامپایلر IAR :


            __flash char max = 127;
            __flash char string[] = "This string is stored in flash";
            int main( void )
            {
            unsigned char led;
            char __flash *flashpointer; /* Declare flash pointer*/
            flashpointer = &string[0]; /* Assign pointer to flash location*/
            UDR = *flashpointer; /*Read data from flash and write to UART*/
            }

            رشته های ذخیره شده در حافظه فلش را می توان به صورت مستقیم و از طریق اشاره گر مورد استفاده قرار داد.

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


            پایان قسمت ششم

            دیدگاه


              #7
              پاسخ : بررسی APPNOTE های ATMEL

              11.توابعی که چندین مقدار را برمی گردانند :
              در بعضی مواقع بخاطر آنکه یک تابع باید چند مقدار را برگرداند متغیر های سراسری تعریف می شوند
              در AVR تابع می تواند تا 4 بایت را بوسیله دستور return برگرداند که در" scratch registers" ( فکر کنم منظورش همون رجیستر های عمومی هست به مثال زیر دقت کنید ) بارگزاری مینماید .

              میتوان برای برگرداندن چند متغیر به طور هم زمان بوسیله دستور return استفاده از برخی فوت فن های برنامه نویسی بهره برد به مثال زیر دقت کنید :

              unsigned int read_io(void)
              {
              return (PIND<<8 | TCNT0); // Function is returning 2 values
              }
              // IN R17,0x10 // First value is shifted 8 bytes
              // IN R16,0x32 // Second values read into register
              // RET

              یعنی خروجی تابع یک متغیر دو بایتی می باشد که می توان آن را به متغیری مثل unsigned int temp انتساب داد .
              توجه : همانطور که در کد اسمبلی معادل مشاهده میشود خروجی اول در 8 بیت بالا و خروجی دوم در 8 بیت پایین ذخیره میشوند .
              برای فراخوانی این تابع مانند توابع دیگر عمل میکنم تنها نکته ای که باید ذکر کرد نحوه استفاده از خروجی ها تابع هست برای این منظور نیز می توان از همان تکنیک های زبان cبهره برد به مثال زیر که برای فراخوانی تابع و تفکیک خروجی ها می باشد دقت کنید :

              int main( void )
              {
              unsigned int temp; // Declare a temporary variable
              temp = read_io(); // Read value from function
              PORTB = temp>>8; // Use only high byte
              TCNT0 = temp&0x00FF; // Use only low byte
              }
              // CALL read_io // Call the function
              // OUT 0x18,R17 // Use high byte or PIND<<8
              // OUT 0x32,R16 // Use low byte or TCNT0


              12. مدل حافظه :
              در کوچکترین avr ها با حافظه sram کمتر از 256 بایت مدل حافظه tiny در حالت های زیادی می توانند در حالتهای زیادی استفاده شوند .
              زمانی که از مدل حافظه tiny استفاده می شود ، تمام متغیر های موجود در SRAM با استفاده از اشاره گر های 8 بیتی به جای 16 بیتی قایل دسترسی هستند که نتیجه آن تولید کد با حجم کمتر می باشد .
              توجه کنید که این اشاره گرهای 8 بیتی قادرند تا به آدرس 160بایت از RAM + رجیسترهای I/O + رجیسترهای عمومی اشاره کنند از این رو برای مطمئن شدن از آنکه کامپایلر تمام رنج آدرس دهی را بکار می برد باید مطمئن شد که فایلهای LINKER به گونه ای پیکره بندی شده اند که CSTACK و RSTACK در بالاترین نقطه از فضای SRAM واقع شده باشند.

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

              دیدگاه


                #8
                پاسخ : بررسی APPNOTE های ATMEL

                13. روند کنترل :

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

                __C_task void main(void) /* Declare main() as C_task*/
                {
                }


                2-حلقه ها :
                بهینه ترین حلقه تکرار بی پایان، حلقه تکرار بینهایت for( ; ; ) { }; میباشد :
                مثال :

                for( ; ; )
                {
                /* This is an eternal loop*/
                }
                // ?0001:RJMP ?0001 ; Jump to label

                توجه :کدهای معادل حلقه do{ }while(expression) بهینه تر از کدهای اسمبلی معادل حلقه while{ } و for{expr1; expr2; expr3) میباشد. درمثال زیر کد اسمبلی معادل حلقه do{ }while(expression) نشان داده شده است :


                char counter = 100; /* Declare loop counter variable*/
                // LDI R16,100 ; Init variable
                do
                {
                } while(--counter); /* Decrement counter and test for zero*/
                ?0004EC R16 ; Decrement
                // BRNE ?0004 ; Branch if not equal


                استفاده از متغیر ها با دستور پیش کاهش (Pre-decrement) مثلا (--counter) بعنوان شمارشگر یک حلقه معمولا باعث تولید بهینه ترین کد میشود .
                کد معادل برای مواقعی که از دستور بیش کاهش(Predecrement) برای شمارش استفاده میشود از کدمعادل دستور پس افزایش (post-increment) برای ایجاد حلقه با همان کارایی بهینه تر میباشد چرا که اجرای چنین حلق هایی وابسته به همان فلگها بعد از کاهش یک پله ای می باشد اما در مورد شروط پس افزایشی باید یک شرط ثابت دیگر نیز در هر مرتبه علاوه بر اجرای دستور پس افزایش فلگ، این شرط نیز چک شود .


                14.Macros vs. Functions :
                بیشتر توابع ای که در 3 یا 4 خط و یا کمتر به کد اسمبلی توسط کامپایلر تبدیل می شوند میتوانند به عنوان یک ماکرو کدهای بهینه تری را ایجاد کنند . هنگام استفاده از ماکرو ها در زمان کامپایل نام ماکرو ها بوسیله کد های واقعی جایگزین میگردند . برای توابع خیلی کوچک کامپایلر کدهای کمتر با سرعت بیشتری را تولید میکند بنابراین استفاده از ماکرو ها منجر به تولید حجم کد کمتر و سرعت بیشتری میشود تا فراخوانی یک تابع برای اجرای یک دستور خاص.
                مثال زیر نشان می دهد که یک دستور خاص چطور میتواند در یک تابع و همچنین در قالب یک ماکرو عمل کند :

                مثال تابع یک دستور خاص :


                /* Main function to call the task*/
                __C_task void main(void)
                {
                UDR = read_and_convert(); /* Read value and write to UART*/
                }
                /* Function to read pin value and convert it to ASCII*/
                char read_and_convert(void)
                {
                return (PINB + 0x48); /* Return the value as ASCII character */
                }

                و کد اسمبلی معادل مثال فوق :

                main:
                // RCALL read_and_convert ; Call function
                // OUT LOW(12),R16 ; Write to I/O memory

                read_and_convert:
                // IN R16,LOW(22) ; Read I/O memory
                // SUBI R16,LOW(184) ; Add 48 to value
                // RET ; Return


                مثال اجرای همان دستور با تعریف یک ماکرو :


                /* A macro to do the same task*/
                #define read_and_convert (PINB + 0x48)
                /* Main function to call the task*/
                __C_task void main(void)
                {
                UDR = read_and_convert; /* Read value and write to UART*/

                و کد اسمبلی معادل مثال فوق :

                main:
                // IN R16,LOW(22) ; Read I/O memory
                // SUBI R16,LOW(184) ; Add 48 to value
                // OUT LOW(12),R16 ; Write I/O memory


                در جدول زیر حجم کد تولیدی و زمان اجرای توابع و ماکرو ها نشان داده شده است :



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

                دیدگاه


                  #9
                  پاسخ : بررسی APPNOTE های ATMEL

                  15.ایجاد فایل های داده در حافظه EEPROM :
                  متغیر های EEPROM باید به عنوان متغیر های سراسری تعریف شوند برای تعریف یک متغیر در حافظه EEPROM میتوان از پیشوند eeprom__ استفاده کرد به عنوان مثال :

                  __eeprom volatile unsigned char ee_var@0x05 = 0x55;

                  در مثال فوق یک متغیر یک بایتی با نام ee_var که آدرس آن 0x05 میباشد در حافظه EEPROM با مقدار اولیه 0X55 تعریف شده است.

                  همچنین برای خواندن ونوشتن در این متغیر به مانند متغیر های دیگر عمل میکنیم بع عنوان مثال :


                  __eeprom volatile unsigned int ee_var@0x05 = 0x55;
                  __C_task void main(void)
                  {
                  unsigned char temp;
                  temp = ee_var;
                  // LDI R20,LOW(ee_var)
                  // LDI R21,(ee_var) >> 8
                  // CALL __eeget8_16
                  PORTB = temp;
                  // OUT 0x18,R16
                  ee_var = TCNT0;
                  // IN R16,0x32
                  // LDI R20,LOW(ee_var)
                  // LDI R21,(ee_var) >> 8
                  // CALL __eeput8_16
                  }


                  توجه کنید زمانی که متغیر های تعریف شده در حافظه EEPROM مقدار دهی اولیه میشوند میتوان برای اعمال این مقادر اولیه در حافظه EEPROM میکروکنترلر ، می توان فایلی که کامپایلر IAR با نام "eepeom_اسم برنامه " در مسیر زیر ایجاد مینماید را با پروگرامر در داخل حافظه EEPROM میکروکنترلر دانلود نمود :
                  "eepeom_اسم برنامه "\Release\Exe

                  18. 16 نکته برای کاهش حجم کد نهایی :

                  1- عمل کامپایل در حالت full size optimization انجام شود.


                  2- هر زمان که امکانش وجود داشت به جای استفاده از متغیر های سراسری از متغیر های محلی استفاده شود.

                  3- همواره از کوچکترین متغیر متناسب با نوع کار مورد نظر استفاده و در صورت امکان بهتر است که بدون علامت(unsigned) تعریف شود .

                  4- بهتر است اگر یک متغیر غیر محلی که تنها داخل یک تابع ارجاع داده شده است به صورت ایستا (static) تعریف گردد .

                  5- در صورت امکان دادهای غیر محلی در قالب سختار تعریف شوند این امر امکان اینکه برای آدرس دهی غیر مستقیم اشاره گر به طور مجدد بارگزاری نشود افزایش می دهد .

                  6- برای ایجاد حلقه های بی پایان از for( ; ; ) { } استفاده شود .

                  7- در صورت امکان برای ایجاد حلقه از سختار do { } while(expression) استفاده شود .

                  8- در صورت امکان از حلقه های پایین شمار و با فلگ پیش کاهشی(pre-decrement) استفاده شود.

                  9- دسترسی مستقیم به رجیستر های I/O یا به عبارتی عدم استفاده از اشاره گر برای دسترسی به این رجیسترها

                  10- اگر تابع main در هیچ کجای برنامه فراخوانی نشده بهتر است که با عنوان "C_task__" تعریف شود :

                  __C_task void main(void)
                  {


                  11- استفاده از ماکروها بجای توابع برای انجام دستورات خاصی که کد اسمبلی معادلشان کمتر از 2 الی 3 خط میشود.

                  12- کاهش حجم INTVEC (قسمتی از کد که فهرست بردار های وقفه و ریست را در خود برای استفاده از وقفه ها حفظ می کند مثلا برای مگا 16 که 4 بایت می باشد ) به اندازه مورد نیاز یا اینکه تمام قسمت های کد را یکی کرده تا کامپایلر به طور خودکار از فضای موجود به اندازه نیاز استفاده کند .

                  13- کدهایی با قابلیت استفاده مجدد که در قالب ماژلار هستند. جمع آوری چندین تابع در یک ماژول یا به عبارتی در یک فایل و استفاده از آن در برنامه قابلت استفاده مجدد از کد را افزایش می دهند .

                  14- برای دسترسی به حافظه داده از اشاره گر ها با یک مقدار آفست استفاده و یا ساختار هایی برای این امر تعریف شوند.

                  15- در بعضی مواقع انجام عمل کامپایل در حالت "full speed optimization" منجر به تولید کدهای با حجم کمتر از حالت "full size optimization" میشود برای تشخیص این مسئله عمل کامپایل را بر روی یک ماژول بر مبنای یک ماژول خاص انجام میدهیم و نتیجه هر دو حالت از بهینه سازی را در مورد آن ماژول بررسی میکنیم تا بهترین حالت را برای انجام عمل کامپایل تعیین کنیم .

                  16- در صورت امکان از فراخوانی توابع داخل روتین وفقه ها اجتناب شود .

                  17- از کوچکترین مدل حافظه ممکن استفاده گردد .

                  18- Optimize C_startup to not initialize unused segments (i.e., IDATA0 or IDATA1 if all
                  variables are tiny or small). oo:
                  من از این 18 هیچی نفهمیدم

                  17. 5 نکته برای کاهش نیاز به استفاده از RAM :

                  1- تمام ثابت ها باید با استفاده از پیشوند flash__ در حافظه فلش ذخیره شوند .

                  2- از تعریف متغیر ها به صورت سراسری که در واقع از آنها به طور محلی استفاده میشود اجتناب شود چرا متغیر های محلی در حافظه استک بازنشانی شده و پس از اتمام عملیات در تابع محلی مورد نظر دوباره از فضای استک پاک میشوند این امر علاوه بر کاهش فضای مصرفی sram با عث تولید کد کمتری میشود چرا که دیگر به بارگزاری های پیاپی از فضای sram داخل رجیستر های I/O نیازی نیست .

                  3- اگر از توابع بزرگ با متغیر هایی که عمر کوتاهی دارند استفاده مکینید میتواند استفاده از یک سری محدوده های از پیش تعیین شده ه اشغال کمتر فضای sram کمک کند. oo:

                  4- محاسبه فضای cstack , rstack با یک تخمین قابل قبول و تنظیم این دو فضای برای اشغال کمترین فضای حافظه sram .

                  5-Do not waste space for the IDATA0 and UDATA0 segments unless you are using
                  tiny variables (Linker File). oo:
                  من اینو رو هم اصلا نفهمیدم


                  18. یک لیست کنترل برای دیباگ کردن برنامه ای که نوشته اید :

                  1- مطمئن شوید که فضای CSTACK اندازه کافی بزرگ هست .

                  2- مطمئن شوید که فضای RSTACK به اندازه کافی بزرگ هست .

                  3- اگر از حافظه خارجی استفاده نموده اید مطمئن شوید که رابط آن را فعال کردید و اگر هم استفاده نمیکنید مطمئن شوید که غیر فعال باشد.

                  4-اگر در برنامه یک تابعی دارید که از طریق یک متغیر سراسری با روتین یک وقفه در ارتباط هست مطمئن شوید که آن متغیر به فرم volatile تعریف شده باشد چرا که در این صورت میتوان تضمین کرد که در هر بار که این متغیر مورد استفاده قرار میگیرد این متغیر از داخل فضای RAM خوانده شده است نه از داخل رجیسترها عمومی یا فضای استک که این مسئله از خراب شدن احتمالی اطلاعات آن متغیر در رفت و برگشتهایش در مکان های مختلف برنامه میان روتین وقفه و تابع جلوگیری می کند.

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

                  دیدگاه


                    #10
                    پاسخ : بررسی APPNOTE های ATMEL

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


                    | = OR
                    & = AND
                    ~ = NOT
                    ^ = XOR
                    << = SHIFT LEFT
                    >> = SHIFT RIGHT

                    0 OR 0 = 0
                    0 OR 1 = 1
                    1 OR 0 = 1
                    1 OR 1 = 1

                    0 AND 0 = 0
                    0 AND 1 = 0
                    1 AND 0 = 0
                    1 AND 1 = 1

                    0 XOR 0 = 0
                    0 XOR 1 = 1
                    1 XOR 0 = 1
                    1 XOR 1 = 0

                    0 NOT = 1
                    1 NOT = 0

                    x |= 0x01; // set bit 0

                    // Anything OR 0 is itself and anything OR 1 is 1

                    x &= 0x01; // clear bit 0

                    // Anything AND 1 is itself and anything AND 0 is 0

                    x ^= 0x01; // flip bit 0

                    // Anything XOR 0 is itself and anything XOR 1 changes to its opposite (flips)

                    if (x & 0x01) // Nonzero if the bit indicated by 0x01 is set
                    {
                    }

                    (0x01 << 2) = 00000100

                    (0x01 << 7) = 10000000

                    // Macros

                    #define b_get(p,m) ((p) & (m))
                    #define b_set(p,m) ((p) |= (m))
                    #define b_clear(p,m) ((p) &= ~(m))
                    #define b_flip(p,m) ((p) ^= (m))
                    #define b_write(c,p,m) (c ? b_set(p,m) : b_clear(p,m))
                    #define bit(n) (0x01 << (n))
                    #define longbit(n) ((unsigned long)0x00000001 << (n))

                    b_set(x, 0x01) // set a bit
                    b_set(x, bit(5)) // set bit 5
                    b_clear(x, bit(6)) // clear bit 6 with bitmask
                    b_flip(x, bit(0) // flip bit 0

                    if(b_get(x, bit(3))) // check bit 3
                    {
                    }

                    if(b_get(x, bit(4))) // Set or clear a bit based on bit 4
                    {
                    b_set(y, bit(0))
                    } else {
                    b_clear(y, bit(0))
                    }

                    b_write(b_get(x, bit(4)), y, bit(0)); // same as above as a macro

                    // If you are using an unsigned long (32 bit) variable x, and have to change a bit, use the macro LONGBIT which creates un unsigned long mask. Otherwise, using the BIT() macro, the compiler will truncate the value to 16-bits.

                    // Another set of macros from AVR035 Efficient C Coding for AVR:

                    #define SETBIT(ADDRESS,BIT) (ADDRESS |= (1<<BIT))
                    #define CLEARBIT(ADDRESS,BIT) (ADDRESS &= ~(1<<BIT))
                    #define FLIPBIT(ADDRESS,BIT) (ADDRESS ^= (1<<BIT))
                    #define CHECKBIT(ADDRESS,BIT) (ADDRESS & (1<<BIT))

                    #define SETBITMASK(x,y) (x |= (y))
                    #define CLEARBITMASK(x,y) (x &= (~y))
                    #define FLIPBITMASK(x,y) (x ^= (y))
                    #define CHECKBITMASK(x,y) (x & (y))

                    #define VARFROMCOMB(x, y) x
                    #define BITFROMCOMB(x, y) y

                    #define C_SETBIT(comb) SETBIT(VARFROMCOMB(comb), BITFROMCOMB(comb))
                    #define C_CLEARBIT(comb) CLEARBIT(VARFROMCOMB(comb), BITFROMCOMB(comb))
                    #define C_FLIPBIT(comb) FLIPBIT(VARFROMCOMB(comb), BITFROMCOMB(comb))
                    #define C_CHECKBIT(comb) CHECKBIT(VARFROMCOMB(comb), BITFROMCOMB(comb))



                    منبع : http://d474.net/notes/notes-on-c-for-avr-micros.html

                    دیدگاه


                      #11
                      پاسخ : بررسی APPNOTE های ATMEL


                      AVR034

                      Mixing C and Assembly Code with
                      IAR Embedded Workbench for AVR

                      دیدگاه


                        #12
                        پاسخ : بررسی APPNOTE های ATMEL

                        1.تبادل متغیر بین توابع زبان c و کدهای اسمبلی :

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


                        1) رجیسترهای چرک نویس (Scratch Registers) رجیسترهایی هستند که در جریان فراخوانی توابع و وقفه ها ذخیره نمی شوند .
                        2) رجیسترهای محلی (Local Register) رجیسترهایی هستند که در جریان فراخوانی توابع و وفقه ها در Data Stack ذخیره میشوند .
                        3) رجیستر Y به عنوان اشاره گر به محل آغازین Data Stack در حافظه SRAM استفاده می شود .

                        4) رجیستر های چرک نویس (Scratch Registers) برای تبادل داده های ورودی به و برگشتی از توابع استفاده میشوند .

                        زمانی که یک تابع فراخوانی می شود مقادیر ورودی تابع در رجیسترهای R16-R23 متناسب با اندازه مقادیر قرار میگیرند . و زمانی هم که یک تابع یک مقدار را برمیگرداند آن مقدار در داخل رجیستر های R16-R19 متناسب با اندازه مقداربرگشتی از تابع قرار می گیرد از این رو میتوان نتیجه گرفت حداکثر خروجی یک تابع 4 بایت می باشد .

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


                        مثالی از یک تابع به زبان C :

                        int get_port(unsigned char temp, int num)

                        همانطور که میبینید تابع فوق در کل 3 بایت ورودی و 2 بایت هم خروجی دارد طبق جدول فوق در هنگام فراخوانی تابع فوق پارامتر تک بایتی temp در رجیستر R16 و پارارمتر 2 بایتی num در رجیستر های R20:R21 قرار میگیرند . همچنین مقدار خروجی تابع که دو بایتی هست در داخل رجیستر های R16:R17 قرار میگیرد .

                        اما اگر تابعی که فراخوانی می شود بیشتر از دو پارامتر ورودی داشته باشد ، دو پارامتر اول مطابق جدول فوق میان تابع و رجیستر های محلی مبادله شده و مابقی پارامتر ها هم میان تابع و DATA STACK مبادله میشوند میگیرند .
                        اگر متغیر های ورودی تابعی که فراخوانی میشود به شکل "struct or union" باشند یک اشاره گر این ساختار داده وردوی را بین تابع و DATA STACK مبادله می کند .
                        و اما اگر تابع هیچ پارامتری به عنوان ورودی نداشته باشد پس نیازی به استفاده از هیچ کدام یک از رجیستر های محلی را ندارد و از طرفی برای حفظ مقدار این رجیستر ها از تغییرات احتمالی در تابع مقدار آن ها را در داخل DATA STACK پوش می نماید همچنین مقدار خروجی تابع نیز در داخل رجیسترهای R16-R19 متناسب با اندازه مقدار برگشتی قرار میگیرد .


                        پایان قسمت اول

                        دیدگاه


                          #13
                          پاسخ : بررسی APPNOTE های ATMEL


                          2. فراخوانی توابع اسمبلی در برنامه نوشته شده به زبان C :

                          مثال 1 تابعی بدون پارامتر ورودی و بدون مقدار برگشتی :
                          کد مربوط به تابع اصلی در زبان c :

                          #include <ioavr.h>
                          #include <inavr.h>
                          extern void get_port(void);/* Function prototype for asm function */
                          void main(void)
                          {
                          DDRD = 0x00;/* Initialization of the I/O ports*/
                          DDRB = 0xFF;
                          while(1)/* Infinite loop*/
                          {
                          get_port();/* Call the assembler function */
                          }
                          }


                          کد مربوط به تابع get_port به زبان اسمبلی :


                          NAME get_port
                          #include "ioavr.h" ; The #include file must be within the module
                          PUBLIC get_port ; Declare symbols to be exported to C function



                          RSEG CODE ; This code is relocatable, RSEG
                          get_port NOP
                          in R16,PIND ; Read in the pind value

                          out PORTB,R16 ; Output the data to the port register
                          ret ; Return to the main function
                          END


                          شبیه سازی مثال 1 با پرتئوس :


                          مثال 2 : تابعی با یک پارامتر ورودی یک بایتی و یک مقدار برگشتی یک بایتی :
                          در این مثال نیز تابع زبان C یک تابع اسمبلی را فرامیخواند پارامتر تک بایتی mask به پارامتر ورودی به تابع اسمبلی منتقل میشود مقدار این پارامتر به محض فراخوانی تابع در رجیستر R16 بارگذاری شده و در انتهای تابع اسمبلی مقدار برگشتی در داخل رجیستر R16 قرار گرفته و سپس داخل متغیر محلی value بار گذاری میشود .
                          کد مربوط به تابع اصلی به زبان c :


                          #include <ioavr.h>
                          unsigned char get_port(unsigned char mask); /*Function prototype for asm function */
                          __C_task void main(void){
                          DDRB=0xFF;
                          while(1){ /* Infinite loop*/
                          unsigned char value, temp; /* Decalre local variables*/
                          temp = 0x0F;
                          value = get_port(temp); /* Call the assembler function */
                          if(value==0x01)PORTB=~(PORTB); /* Invert value on Port B */
                          }
                          }


                          کد مربوط به تابع get_port به زبان اسمبلی :


                          NAME get_port
                          #include "ioavr.h" ; The #include file must be within the module
                          PUBLIC get_port ; Declare symbols to be exported to C function



                          RSEG CODE ; This code is relocatable, RSEG
                          get_port: ; Label, start execution here
                          in R17,PIND ; Read in the pinb value
                          eor R16,R17 ; XOR value with mask(in R16) from main()
                          swap R16 ; Swap the upper and lower nibble
                          rol R16 ; Rotate R16 to the left
                          brcc ret0 ; Jump if the carry flag is cleared
                          ldi r16,0x01 ; Load 1 into R16, return value
                          ret ; Return
                          ret0: clr R16 ; Load 0 into R16, return value
                          ret ; Return
                          END


                          تصویری از دیباگ مثال فوق :



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

                          دیدگاه


                            #14
                            پاسخ : بررسی APPNOTE های ATMEL

                            3. فراخوانی توابع C در برنامه نوشته شده به زبان اسمبلی :

                            فرض کنیم که در یک برنامه نوشته شده به زبان اسمبلی تابع ()rand از داخل کتابخانه استاندارد C فراخوانی میشود و یک عدد به صورت رندم بوسیله این تابع روی PORTB به نمایش در میآید . تابع rand() یک مقدار 2 بایتی را برمیگرداند که در مثال زیر 8 بیت پایین این 16 بیتی بر روی PORTB به نمایش در می آید .


                            NAME get_port
                            #include "ioavr.h" ; The #include file must be within the module
                            EXTERN rand, max_val ; External symbols used in the function
                            PUBLIC get_port ; Symbols to be exported to C function
                            RSEG CODE ; This code is relocatable, RSEG
                            get_port: ; Label, start execution here
                            clr R16 ; Clear R16
                            sbis PIND,0 ; Test if PIND0 is 0
                            rcall rand ; Call RAND() if PIND0 = 0
                            out PORTB,R16 ; Output random value to PORTB
                            lds R17,max_val ; Load the global variable max_val
                            cp R17,R16 ; Check if number higher than max_val
                            brlt nostore ; Skip if not
                            sts max_val,R16 ; Store the new number if it is higher
                            nostore:
                            ret ; Return
                            END




                            4. نوشتن توابع (روتین) اینتراپت در زبان اسمبلی :


                            توابع اینتراپت را میتوان به زبان اسمبلی نوشت . توابع اینتراپت نه پارامتر ورودی دارند و نه مقدار برگشی .
                            از آنجا که که یک اینتراپت می تونواند در هر مکانی از برنامه رخ بدهد در زمان وقوع یک اینتراپت باید تمام رجیسترهای عمومی که مورد استعمال واقع شده اند در DATA STACK ذخیره شوند .


                            Care must be taken when assembler code is placed at the interrupt vector adresses to
                            avoid problems with the interrupt functions in C.


                            مثال: در مثال زیر وقفه خارجی INT0 حساس به لبه بالا رونده در زبان C فعال شده و همچنین وقفه سراسری نیز فعال شده اما روتین وقفه آن به زبان اسمبلی نوشته شده است .

                            تابع اصلی به زبان C :

                            #include "ioavr.h"
                            #include "ina90.h"
                            void main( void )
                            {
                            // External Interrupt(s) initialization
                            // INT0 Mode: Rising Edge
                            GICR|=0x40;
                            MCUCR=0x03;
                            MCUCSR=0x00;
                            GIFR=0x40;
                            __enable_interrupt();
                            while(1);
                            }


                            روتین وقفه اینتراپت صفر :


                            NAME EXT_INT0
                            #include "ioavr.h"
                            extern c_int0
                            COMMON INTVEC(1) ; Code in interrupt vector segment
                            ORG INT0_vect ; Place code at interrupt vector
                            RJMP c_int0 ; Jump to assembler interrupt function
                            ENDMOD
                            ;The interrupt vector code performs a jump to the function c_int1:
                            NAME c_int0
                            #include "ioavr.h"
                            PUBLIC c_int0 ; Symbols to be exported to C function
                            RSEG CODE ; This code is relocatable, RSEG
                            c_int0:
                            cli
                            st -Y,R16 ; Push used registers on stack
                            in R16,SREG ; Read status register
                            st -Y,R16 ; Push Status register
                            in R16,PIND ; Load in value from port D
                            com R16 ; Invert it
                            out PORTB,R16 ; Output inverted value to port B
                            ld R16,Y+ ; Pop status register
                            out SREG,R16 ; Store status register
                            ld R16,Y+ ; Pop Register R16
                            reti
                            END


                            همانطور که در روتین وقفه مشاهده می کنید در ابتدای روتین وقفه سراسری با صفر شدن بیت I در رجیستر SREG غیر فعال شده سپس رجیستر R16 در داخل فضای DATA STAK پوش شده بعد از آن رجیستر موقعیت SREG در داخل R16 بارگذاری شده و پس از آن دوباره مقدار رجیستر R16 داخل فضای DATA STACK پوش شده در واقع این مقدار رجیستر SREG هست که پوش می شود سپس مقدار رجیستر PIND داخل R16 بارگذاری شده بعد مقدار R16 معکوس شده سپس داخل رجیستر PORTB ریخته میشود در انتهای روتین نیز آخیرین مقدار پوش شده در DATA STACK مقدار ابتدایی رجیستر SREG می باشد داخل R16 پوپ شده و سپس مقدار R16 داخل رجیستر SREG بارگذاری گشته و سپس مقدار ابتدایی R16 که یکی مانده به آخرین مقدار پوش شده در فضای DATA STACK میباشد بار دیگر در داخل R16 پوپ میشود و در انتها نیز وقفه سراسری با یک کردن بیت I در رجیستر SREG فعال میشود.

                            تصویر شبیه ساز پرتئوس قبل از اعمال سیگنال به پایه اینتراپت خارجی 0 :



                            تصویر شبیه ساز پرتئوس بعد از اعمال سیگنال به پایه اینتراپت خارجی 0 :




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

                            دیدگاه


                              #15
                              پاسخ : بررسی APPNOTE های ATMEL

                              5. دسترسی به متغیرهای سراسری در زبان اسمبلی :
                              در مثال زیر در تابع اصلی به زبان C یک متغیر سراسری با نام max_val تعریف شده است برای اینکه بتوان از این متغیر در زبان اسمبلی استفاده نمود باید به صورت "EXTERN max_val" تعریف شود . در توابع اسمبلی برای دسترسی به متغیر های سراسری از دستورات اسمبلی : (LDS (Load Direct from SRAM و
                              (STS (STore Direct to SRAM استفاده میشود .

                              کد مربوط به تابع اصلی در زبان C :


                              #include "ioavr.h"
                              #include "ina90.h"
                              char max_val;
                              void get_port(void); /* Function prototype for assembler function */
                              __C_task void main(void)
                              {
                              DDRB = 0xFF; /* Set port B as output */
                              DDRD = 0xFF; /* Set port D as output */
                              while(1) /* Infinite loop */
                              {
                              get_port(); /* Call assembly code function */
                              }
                              }


                              کد مربوط به تابع get_port در زبان اسمبلی :


                              NAME get_port
                              #include "ioavr.h" ; The #include file must be within the module
                              EXTERN rand, max_val ; External symbols used in the function
                              PUBLIC get_port ; Symbols to be exported to C function
                              RSEG CODE ; This code is relocatable, RSEG
                              get_port: ; Label, start execution here
                              clr R16 ; Clear R16
                              sbis PINC,0 ; Test if PIND0 is 0
                              rcall rand ; Call RAND() if PIND0 = 0
                              out PORTB,R16 ; Output random value to PORTB
                              lds R17,max_val ; Load the global variable max_val
                              cp R17,R16 ; Check if number higher than max_val
                              brlt nostore ; Skip if not
                              sts max_val,R16 ; Store the new number if it is higher
                              lds R17,max_val
                              out PORTD,R17
                              nostore:
                              ret ; Return
                              END


                              تصویری از محیط دیباگر برای دیباگ مثال فوق :




                              پایان قسمت سوم


                              پایان AVR034

                              دیدگاه

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