视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
IAR代码优化
2025-09-28 02:16:44 责编:小OO
文档
                            AVR035:AVR单片机高效的C语言译码

特色:

     访问 I/O 存储器特定区域

     访问 映射I/O的存储器

     访问 Flash 数据

     访问 EEPROM 数据

     创造 EEPROM 数据文件

     变量和数据类型的有效使用

     位域和位屏蔽的使用

     宏和函数的使用

     18种方式缩减代码量

     5种方式缩减RAM需求

     调试程序的备忘目录(checklist)

     更新到支持IAR 版本2编译器

     

引序

C 已经逐步地成为编写各类微处理器高级语言.它在编写处理器程序中比汇编语言拥有更多优势:缩减开发时间,简化源程序的维护和移植以及代码的模块化.The penalty can be larger code size and as a result of that often reduced speed. (处罚可以扩大编码的大小,及作为速度减少的一种后果)减少这种不便,AVR的设计构造更加协调于由C编译器典型生成的有效编码和可执行指令.

IAR systems的这款C编译器先于AVR设计结构和指令设置的规格说明完成之前开发而成.编译器开发团队和AVR开发团队合作研发了一个适用于微处理器的生成高效、高性能代码的C编译器.

应用注解描述了如何利用AVR相比其他微处理器的构造设计优势以及相关开发工具来完成更多高效的C代码.

Architecture Tuned FOR C代码         

 AVR的32个8位工作寄存器是高效C译码的一个关键所在,每一个工作寄存器都具有传统累加器的功能. AVR可以同时访问Register File(寄存器文件)中任意2个操作数,并将数据送入ALU(算术逻辑单元)进行相关运算,结果存回到Register File.,整个过程只需要1个时钟周期.

当数据存储在32个工作寄存器的时候,每个运算指令

某些2个8-bits寄存器可以结合成1个16-bits指针来实现数据从数据存储器和程序存储器高效存取(双字节指针能够达到KB的寻址范围).甚至为达到更大的存储量,这个16-bits存储器指针结合一个8-bits寄存器便达到24-bits指针来寻址8M字节的数据.无需翻页.

取址模式             AVR 设计构造中有4个存储器指针被用来数据存取以及程序存储. Stack Pointer (堆栈指针) 用来存储函数调用返回之后的返回地址 . C编译器分配1个指针作为参数堆栈.剩余2个通用目的指针被C编译器分派完成程序的装载和数据的存储.

下面示例说明了指针是如何高效地用来完成C语言中典型的指针操作 

char  *pointer1 = &table[0]  ;

char  *pointer2 = &table[49]  ;

*pointer1++ = * - -pointer2 ;

生成汇编代码如下:

 LD    R16,-Z    ;  Pre-decrement Z pointer and load data  

 ST    X+, R16     ;  Store data and post inceement

四个指针地址模式和范例如下所示,当中的指针指令都为single-word(单字)指令执行周期为2个时钟.

1. 间接寻址: 数组和指针变量寻址

       *pointer = 0x00 ;

2. 带偏移量的间接寻址: 允许通过指向structrue(指令)第一个元素指针加上偏移量来访问结构体内部所有元素而不需要改变指针的值.同样适用于访问软件堆栈中的变量和数组单元.

3. 带后加偏移量的间接寻址:为更有效率地访问数组和指针变量,访问结束后添加偏移量(来改变指针指向)

      *pointer ++ = 0xFF ;

4. 带预加偏移量的间接寻址:  为更有效率地访问数组和指针变量,访问开始前添加偏移量        

      *- -pointer = 0xFF ;

该指针可同样用于访问Flash Program Memory(闪存程序存储器),除了用指针来间接寻址Flash Program Memory之外,数据存储器也能被直接寻址.下面给出访问整个数据存储器的双字指令.

支持16位/32位的变量    AVR指令设置中包含了几条特殊指令来处理16位数据.包括立即数与字相加/相减(ADIW,SBIW). 2条指令2个时钟周期完成的两个16位数据的算术运算以及比较.4条指令4个时钟周期则可以实现32位数据的运算和比较.比一些16位的处理器更加有效率.

AVR的C语言代码    

初始化堆栈指针  上电复位或是重起复位后,在任何功能被唤起之前堆栈指针必须重新设置.连接器命令文件决定了堆栈指针的位置尺寸.

访问I/O存储器单元   用C语言可以方便地访问AVR I/O存储器.所有I/O存储器声明在一个通常名为“ioxxxx.h”头文件中,(xxxx是AVR 产品代号).下面的代码例子显示如何访问I/O单元.C代码行下面是生成相应的汇编代码行.

#include            /* 包括象征名的头文件 */

_C_task void main(void) 

{

  Char temp ;                /* 声名一个局部变量 */

 

 /* 读写一个I/O口寄存器 */

  temp = PIND  ;           /* 读PIND引脚数据到局部变量temp */

 //  IN   R16,LOW(16)      ; 读I/O存储器

 

  TCCR0 = 0x4F ;           /*将数值写入一个I/O单元*/

 //   LDI  R17,79           ;  载入立即数

 //   OUT  LOW(51),R17     ;  写 I/O存储器

 

/* 置单独位和清单独位 */

 PORTB | =  {1<//   SBI   LOW(24),LOW(2) ; I/O置位

/* 置位和清屏蔽位 */

 DDRD | =  0x0C            /* 置DDRD位2和位3 */

//    IN    R17,LOW(17)      ; 读I/O寄存器

//   ORI    R17,LOW(12)      ; R17内容‘或’立即数

//   OUT   LOW(17),R17      ;  写I/O寄存器

 ACSR &= ~(0x0c)            ;   /* 清ACSR中位2和位3 */

//    IN    R17,LOW(8)      ;  读I/O寄存器

//   ANDI  R17,LOW(243)    ;   R17内容‘与’立即数

//   OUT   LOW(8),R17      ; 写I/O寄存器

/* 测试单独位是否已经置位或是清零了 */

if (USR & (1<{

PORTB | = (1<//    SBIC  LOW(11),LOW(6)   ;   test direct on I/O

//    SBIC  LOW(24),LOW(0)   ;

While( ! (SPSR & (1<//   ?0003: SBIS  LOW(14),LOW(6) ; test direct on I/O

//         RJMP  ?0003

/* 测试I/O寄存器是否等同于一个屏蔽位 */

If ( UDR & 0xF3 )               /* 检查UDR 寄存器 ‘与’ 0xF3后非零为真 */

{

}

//      IN   R16,LOW(12)   ;   读I/O寄存器

//      ANDI  R16,LOW(243)  ;   R16内容‘与’立即数

//      BREQ   ?0008        ;   相等跳转到?0008

// ?0008:    

/*  置位和清I/O寄存器位可以用‘宏’声明 */

# define SETBIT(ADDRESS,BIT) (ADDRESS |= (1<# define CLEARBIT(ADDRESS,BIT) (ADDRESS &= ~(1</* ‘宏’指令检查I/O单元的单独位 */

#define CHECKBIT((ADDRESS,BIT) (ADDRESS & (1</* 用法示例 */

If (CHECHBIT (PORTD,PIND1) )      /* 检查PIND1引脚是否置位 */

{

   CLEARBIT(PORTD,PIND1) ;      /* 清PIND1引脚 */

}

if (! (CHECKBIT(PORTD,PIND1) ) )   /* 检查PIND1引脚是否已经清零 */

{

  SETBIT(PORTD,PIND1);          /* 置位PIND1引脚 */

}

访问存储器映射I/O       一些AVR单片机包含一个外部数据存储器接口.这些接口能被用来访问外部RAM,EEPROM(可擦可编程程序存储器),或者被用来访问存储器映射I/O .下面的示例显示了如何声名、写、读存储器映射I/O

#include < io8515.h>

#define reg ( *(char) *) 0x8004    /* 声名一个存储器映射I/O 地址 */

_C_task void main( void )

{

   char   temp ; 

         reg  =  0x05 ;           /* 写值作为存储器映射I/O地址*/

         temp =  reg  ;           /* 读存储器映射I/O 地址 */

      }

     

      如果访问连续的存储器映射地址.最有效的访问方式是声名一个常量指针然后添加

一个偏移量来完成转移.下面的示例说明了如何按照上述所说来访问存储器映射I/O.每条指令生成的汇编代码以斜体表示.

/* 定义存储器映射地址 */

#define  data         0x0003

#define  address_hige  0x0002

#define  address_low   0x0001

_C_task void main ( void )

{

   /*  存储器映射起始地址 */

  unsigned char *pointer = (unsigned char *) 0x0800 ;

 //    LDI    R30,LOW(0)       ;初始化Z指针

 //    LDI    R31,8

*(pointer + address_low) |= 0x40  ;  /* 读出并修改一个地址 */

//    LDD   R18,Z+1         ;  装载变量 

//    ORI    R18,LOW()    ;  ‘或’立即数后送回R18

//    STD   Z+1,R18         ;   存回

*(pointer + address_high) = 0x00 ;  /*写入一个地址 */

//   STD  Z+2,R30          ; 存0

          PORTC = *(pointer + data ) ;  /* 读一个地址 * /

        //   LDD   R16, Z+3        ;  装载变量

        //   OUT   LOW(21) ,R16   ;   输出到端口

      }

      注意Z指针要在访问存储器之前初始化,同时指令LDD和STD (装载和存储偏移量)被用来访问数据.LDD和STD都是STD都是单字指令,执行周期为2个时钟.指针只装载一次,存储器映射I/O单元能被声明为局部变量,说明这些变量单元能通过硬件修改,即使通过优化代码行为,访问也不会被移除

访问EEPROM数据    AVR内置EEPROM数据能够在普通操作方式下读写.便于IAR编译器读写EEPROM数据的宏指令包含在“ina90.h”头文件中.下面的宏在正常情况下被定义为读写EEPROM:

#define _EEGET(VAR,ADR)        /* 从EEPROM的地址ADR中读取数到变量 VAR中 */

{

  While ( EECR & 0x02 )      ;    /* 检查EEPROM是否可以读写 */

   EEAR =  (ADR)          ;    /*  写EEPROM地址寄存器 */

   EECR |=  0x01            ;   /*  设置EEPROM读使能 */

   (VAR) =  EEDR          ;    /*  在下一个时钟周期将EEPROM数据读出送入变量VAR中 */

}

# define _EEPUT(ADR,VAL)       /*  写VAL变量到EEPROM地址ADR中 */

{

  While(EECR & 0x02)           /*  检查EEPROM是否可以读写 */

   EEAR =  (ADR)           ;    /*  写EEPROM地址寄存器 */

   EEDR =  (VAL)            ;    /*  写EEPROM数据寄存器 */

   EECR |=  0x04             ;   /*  置位EEMWE 表示写使能   */

   EECR |=  0x02             ;   /* 写入数据 */

 }

(译者注:写EEPROM操作步骤如下,等待EEWE为0,把EEPROM地址写到EEAR,数据写到EEDR,置位EEMWE,在置位EEMWE为’1”的4个时钟周期内向EEWE写入‘1’表示写完成) 

预定义‘宏’来完成对EEPROM的读写操作代码如下

# include

# incude

 #define EE_ADDRESS 0x010       /*  定义EEPROM地址常量 */

 _C_task void main(void)

{

   char  temp ;

  _EEGET(temp,EE_ADDRESS) ;    /* 读EEPROM中地址为EE_ADDRESS数据 */

  Temp +=UDR       ;           /*   将UART数据添加到temp变量中 */

  _EEPUT (EE_ADDRESS,temp) ;   /*  将数据写入到地址为EE_ADDRESS的EEPROM中 */

  }

 注意如果中断使能,必须在写EEPROM中关闭中断使能位来确保写操作没有超时.如果程序中包含了访问EEPROM而进入中断响应的程序段,在读EEPROM之前必须先关闭中断使能来避免EEPROM地址寄存器的错误.

变量和数据类型

           AVR是8位微处理器,16位和32位的变量的使用会被在必须使用的地方.下面的示例说明了一个循环记数器分别用8位和16位单元变量的代码量

 unsigned char count8 = 5       ; /* 声明一个变量分配一个数值 */

 //   LDI   R16,5            ; 初始化变量

 do

{ }while(--count8)          ;  减循环计数器

//  ?0004: DEC  R16      ;  减一

//        BRNE ?0004     ;  如果不等则跳转到?0004

unsigned int count16 = 6        ; /* 声明一个变量分配一个数值 */

//         LDI   R24,LOW(6)   ; 初始化低字节

//         LDI   R25,0         ; 初始化高字节

 do

{ }while(--count16)          ;  减循环计数器

//  ?0004:  SBIW  R24,LWRD(1)  ; 字减立即数

//         BRNE  ?0004         ; 如果不等则跳转到?0004

变量和代码量

8位变量代码量为6个字节,16位的变量代码量为8字节.

各种变量的有高效使用    一个C源程序可以分成许多执行不同功能模块的函数.函数通过参数来接收数据并(将处理结果做为)返回数据.函数内部的声名变量被称为局部变量(local variable),函数外部的声名变量被称位全局变量(global variable).局部变量需要在函数被调用之前被保存的时候,必须声名为静态局部变量.

声明在函数外部的全局变量被分配到静态数据存储器(SRAM)某一单元.静态数据存储器用来保存全局变量而不能做他用.因此造成SRAM单元的浪费.而使用过多的全局变量会使程序代码可读性和修改性降低.

局部变量的优越性在于声明的时候才被分配到寄存器中,在函数调用过程中一直保存直到函数调用结束,or until it is not referenced further.全局变量必须从SRAM中装载到工作寄存器后才能被访问.

下面示例了2者在代码量以及执行速度上的区别.

  char  global         ;     /* 全局变量 */

 _C_task void main(void)

{

  char  local ;              /*  局部变量 */

  global -=  45    ;        /*  全局变量减去45 */

//   LDS  R16,LWRW(global)  ;  从SRAM装载全局变量到工作寄存器R16

//   SUBI  R16,LOW(45)      ;  减去立即数

//   STS  LWRD(global),R16  ;  数据存回到先前调用的SRAM单元

  local - = 34 ;                  /*  局部变量减去45 */

 //    SUBI   R16,LOW(34)     ;/* 直接在工作寄存器R16中完成减操作 */

 }

注意到 LDS和STS双字指令(可直接从SRAM装载)用来访问静态数据存储器中的变量,执行周期都为2个时钟周期.

    变量名

代码量(字节)

执行周期
全局变量  10    5
局部变量   2    1
函数被调用开始时,一个静态局部变量被载进一个工作寄存器后会在函数调用结束后存入SRAM单元.因此在函数内部访问变量的次数多于一次的情况下,使用静态局部变量会生成比使用全局变量更加高效的代码.

为了全局变量的使用. C语言普遍采用函数调用时通过参数来传递自变量并且调用完成则返回处理结果的方式.工作寄存器R16~R23中最多有2个参数能够同时成为自变量传递进入函数(参数类型可为字符、整型、长整型、单精度、双精度不限).而多于2个参数或是复杂的数据类型(比如数组和结构体)的情况则可放入软件堆栈或是 Passed between functions as pointers to SRAM locations (通过指向SRAM单元的功能)

需要采用全局变量的时候,将其合并在合适的造体中.从而使C编译器间接的赋于它们地址.下面的示例说明了全局变量在结构体内外分别生成代码的比较.

 typedef  struct

{

    Char sec  ;

}t;

t  global           /* 声名一个结构全局变量 */

char min  ;

_C_task void main(void)

{

   t *time = &global ;

//     LDI      R30,LOW(globle)    ; 初始化Z指针低字节

//     LDI      R31,(global>>8)     ;  初始化Z指针高字节

if (++time-> sec = =60)

{

//     LDD      R16,Z+2           ; 带偏移量载入

//     INC       R16               ; (R16)←(R16)+1

//     STD       Z+2,R16          ; 带偏移量存储

//     CPI       R16,LOW(60)      ;  (R16)与立即数比较

//     BRNE     ?0005 

}

if ( ++min = =60)

{

//     LDS      R16,LWRD(min)     ;

//     INC      R16    ;            (R16)←(R16)+1

//     STS    LWRD(min),R16     ;   数据存入SRAM

//     CPI      R16,LOW(60)      ;

//     BRNE    ?0005             ;

 }

}

当访问作为结构体的全局变量时,C编译器自动采用Z指针,并且通过LDD/RD指令(装载和存储偏移量)来访问数据. 当全局变量在结构体外被访问的时候,C编译器通过LDS和STS(装载和存储地址到SRAM)/.

代码量的区别如下

   变量

   代码量(字节)

结构变量       10
非结构变量       14
上述代码量并不包括Z指针初始化所需的4个字节代码量,如果结构体包含2个或是更多的成员时,在结构体中使用全局变量将会更加高效.

优化全局标志位       

大多数的应用中会需要一些全局标志位来控制程序流向.在AVR中

使用这些置入全局变量中的标志位会比较低效.因为测试之前,这些全局变量将被载入到存储器中.优化这些用来测试的标志位即可以置入指定的寄存器或是放置在一个未使用的I/O单元.

 注意:  只有在 IAR Complier V2选择设置里,才有完成将全局变量置入指定的寄存器的功能设置.

当某些外围设备没被使用的时候, 未使用的I/O单元同样可以被用来存储全局变量.例如,如果UART没有使用波特率数据寄存器(EEDR)并且EEPROM的地址寄存器(EEAR)也没使用,那么就可以被用来存储全局变量.

   I/O存储器的访问非常的有效,地址低于0x1F的I/O口存储器特别适合于位访问.地址高于0x1F的存储器虽然没那么有效率,但还是强于使用全局SRAM变量。

   下面的例子说明分别使用SRAM变量,I/O存储器区或是一个指定的寄存器来放置全局标志而生成的代码量

          

      Type  struct Bitfield { //Bitfield 结构体

             Unsigned char bit0:1;

             Unsigned char bit1:1;

             Unsigned char bit2:1;

}bitfield;

bitfield  global_flag             //

           global_flag.bit1 = 1 ;

      //    LDI      R30,LOW(global_flag)

      //    LDI      R31,( global_flag >>8)

      //    LD       R16,Z

      //   ORI       R16,0x02

      //    ST        Z,R16

        代码量:10字节

      注意bitfield不能做为寄存器变量。

       _no_init_regvar unsigned char reg_flag@ 15 ; // 全局寄存器 R15

       reg_flag | = 0x01 ;

       //        SET 

       //        BLD     R15,0 

         代码量:4字节

 

新的AVR产品增加了free I/O 寄存器单元优势,从ATmega169,ATmega48/88/168到ATtiny2313上面的AVR都包含了通用I/O寄存器,都有低于0x1F和高于0x1F limit.这些寄存器可被用来作位任意的free I/O 寄存器.并且能够让你的设计提高性能和减少代码量。

       行动

SRAM数据

Free_I/O>0x1F 

寄存器文件数据Free_I/O<0x1F

   设置/清单独位

  10     6      4       2
     测试单独位

   6      4      2       2
设置/清多位

  10      6      4       6
与立即数比较    6      4      4       6
  

  下面示例显示操作单独位时用free I/O单元作为标志变量会比较高效,而使用指定的寄存器频繁地访问变量同样比较有效。注意放置全局变量的锁存器了编译器的优化代码效率。在复杂的程序中指定越多的寄存器放置全局变量时,那将会增大代码量。

位域和位屏蔽      为了节约宝贵的数据存储单元,有必要将几个单独位标志放入一个字节。这样字节内的各个位标志的普通用法就如同使用(官方指定的)存放状态位的字节一样。在一个字节内,即可以存放位域又可以存放位屏蔽。下面是一个使用位域和位屏蔽而声明的一个状态字节。

 /* 指定位的宏,注意到和定义I/O的宏比较类似 */

 #define  SETBIT(x,y)       (x |= (y) )          /* 在X字节中置位y位 */

 #define  CLEARBIT(x,y)   (x &= (~y) )        /* 在X字节中清位y位 */

 #define  CHECKBIT(x,y)   (x & (y) )          /* 在X字节中查位y位 */

/* 定义状态位屏蔽常量 */

#define  RETRANS      0x01                  /*  位0 :转发标志 */

#define  WRITEFLAG   0x02                   /*  位1 :可写标志位 */

#define  EMPTY        0x04                   /*  位2 :缓冲器空闲标志位 */

#define  FULL          0x08                   /*  位3:缓冲器满标志位 */

_C_task void main ( void )

{

  Char  status     ;                          /* 声名一个状态字节 */

  

  CLEARBIT(status,RETRANS) ;                /* 清转发标志并置可写标志 */

  CLEARBIT(status,WRITEFLAG) ;  

  

/* 确保转发标志已被清除 */

If (!(CLEARBIT(status,RETRANS)))

{

  SETBIT(status , WRITEFLAG) ;

}

}

 

函数内使用声明为局部变量的状态变量,C编译器能有效地处理屏蔽状态位。除此之外也可采用未使用的I/O单元来存储位屏蔽和位域.

位域能被定义在SRAM和I/O单元中.寄存器中的全局变量同样允许简单变量,为方便使用寄存器全局变量中的位指令,使用位屏蔽。

ANSI标准并不定义位域如何封装入字节,在一种编译器中

一个位置入某一个字节的最高位(MSB)而在另一种编译器中,却放置在最低位(LSB).用户自行控制的屏蔽位放置在变量内部。

全局变量的初始化        复位后重新启动后的执行代码都初始为全局变量。全局变量如果不初始数值都被初始为0.

  例如:

             Unsigned char global_counter = 100  ;

             Unsigned int  global_flags         ;       //所有全局变量都被初始化位0 

          在正常情况下,并不必须在初始程序中初始化全局变量。为得到更高效的代码量,所有的全局变量必须在声名的时候被初始化。也可以把所有变量初始化在一个特定程序中,这样初始化代码在开启时可被移除.

访问FLASH存储器        一个正常定义常量方式:

                     Const  char  max = 127 ;

                  启动时将存储在flash存储器的变量内容复制到SRAM中并一直在剩余程序的执行过程中保持。这会浪费SRAM空间。为了节约SRAM空间,常量能被存储在flash中并在需要时载出。

                   Flash  char  max = 127  ;

                   Flash  char string[]=  “this string is stored in flash “ ;

                   _C_task void main (void)

                  {

                    Char  flash *flashpointer ;          声明一个 存于flash的指针

                    Flashpointer = &string[0]  ;          分配指针到flash单元

                    UDR = *flashpointer  ;            从flash单元中读取数据送入UART

                   }

                 注意: IAR 编译器 V2  使用_ _flash代替flash 

                 如后一个例子,存储在flash内的字符串能够被直接地访问或是通过指向flash程序存储器指针来访问。对于 IAR C 编译器来说, 存在专门处理一些字符串的特殊库程序.细节参见”IAR 编译器使用手册”

中断程序          当进入一个中断子程序时,所有中断程序中使用的寄存器单元都被推进堆栈.为了减少代码量以及优化处理速度,中断程序(长度)尽量短而且最好不调用其他函数.原因是当中断程序访问一个外部的函数时,编译器将把所有寄存器单元堆入堆栈.

函数返回几个值     在某些程序里,全局变量被用来返回(函数处理后)的几个数值.就AVR来说,一个函数最多返回4个字节的返回值in the registers.下面示例一些将几个变量并入一个返回值的技巧.

                    例如:

                           Unsigned int read_io(void)

                   { 

                        Return (PIND<<8 | TCNT0) ; //  功能是返回2个值

                   }

                    //          IN  R17,0x10     //第一个8字节值移进R17 

                    //          IN  R16,0x32     //第二个值读进寄存器

                    //          RET

                    来自于调用函数

                  

                    _C_task void main (void )

                   {

                       Unsigned  int  temp  ; //     声明一个临时变量

                      

                        Temp = sum( )       ; // 从函数中读取返回值

                        PORTB= temp>>8   ; //  使用高字节

                        TCNT0 = temp & 00FF ; //  使用低字节

                    }

                     //         CALL      sum      // 调用sum函数

                     //         OUT     0x18,R17   // 使用高字节

                     //         OUT     0x32,R16   // 使用低字节

               最小的AVR产品含小于256 字节的SRAM,其中tiny存储模式可被用在很多实际情况中.

                当使用tiny存储模式时,所有SRAM中的变量以8字节指针代替16字节指针来访问.这样在装载指针数据时可减少代码量.注意到8位指针能寻址160字节的RAM+寄存器文件以及I/O存储器.

 为了确信编译器使用到所有的地址空间,链接文件在SRAM顶部配置了RSTACK 和CSTACK.

 控制程序流向

  主函数         主函数一般包含程序的主要循环部分.在大多数程序中,没有其他函数被称为主函数,并且不需要预先设置任何寄存器来进入主函数. 因而只有主函数能被声名为C_task .这样可以减少堆栈空间和代码量.

      _C_task void main (void)                   /* 声名主函数做为C_task */

     {

     }

     注意:   IAR 编译器 版本V2 使用_C_task 来代替版本中的C_task .

      死循环采用for(;;){}的结构将会更加有效率

       For ( ; ; )

      {

           /* 这是一个死循环 */

       }

         //       ?0001 : RJMP ?0001  ;    跳到?0001 

     Do {}while(表达式) 循环比 while{} 和for {表达式1; 表达式2; 表达式3}

通常生成更加高效的代码.下面示例显示了do {} while 循环的生成代码.

  

   Char  counter = 100 ;        /* 声名一个循环计数变量 */

   //          LDI  R16,100      ; 初始化变量

   Do

{

} while( - -counter)

//   ?0004: DEC R16        ;自减

//     BRNE  ?0004       ; 不等则转入分支

预减变量作为循环计数器将会让代码最有效.预减和后增也很高效因为分支程序取决于减后的标志位判断.

宏和函数   函数内部集合成少于或等于3~4行的汇编代码在一些程序中用宏处理会更加高效.编译时宏名将会被实际的宏所定义的代码取代.对于非常小型的函数,用各种宏指令代替访问一个函数会使

编译器生成更少的代码和提高程序执行速度.

   下面示例说明一个任务用函数的方式和宏的方式执行方法.

 

_C_task void main ( void )

{

  UDR = read_and_convert ()  ;       读出数据送到UART发送

     }  

      /* 从引脚读数据然后传送到 ASCII */            

     Char  read_and_convert( void )

{

  Return (PINB+ 0x48 ) ;             /*  以ASCII形式返回值 */

}

/* 用宏的方式来做同样的任务 */

#define  read_and_convert (PINB+0x48)

The code with function assemble into the following code 

 Main:

    //           RCALL  read_and_convert  ; 访问函数

    //              OUT  LOW(12),R16     ;写到I/O存储器

    

    Read_and_convert:

    //             IN   R16,LOW(22)     ; 读I/O 存储器

    //           SUBI    R16,LOW(184)    ; 添加48到(R16)

    //            RET                    ;

   The  CODE with macro assemble into this code :

  

   Main:

    //            IN   R16,LOW(22)     ;

    //           SUBI  R16,low(184)     ;

    //            OUT  LOW(12),R16    ; 

   变量

    代码量(字节)

  执行周期(时钟周期)

    函数

       10           10
     宏

        6           3
18种提示来减少代码量:

1.用完整代码优化优化

2.任何恰当的时候采用局部变量

3.如果适合的话,尽量使用最小的数据类型

4.如果一个非局部变量只在一个函数内部被引用,就声名为静态变量

5.将非局部变量合并到一个结构中whenever natural (任何自然的).这增加了没有指针重载间接地址的可能性.

6.用带偏移量的指针或声明结构体来访问存储器映射I/O口

7.用for ( ; ; ){}来 死循环

8.用do{} while (表达式) 如果合适的话

9.用递减循环计数器和预减方式,如果合适的话

10.直接访问I/O存储器 (例如不用指针时)

11.如果不从程序中任何地方调用就把主函数声明为C_task

12.用宏代替函数来完成任务在生成时能减少2-3行的汇编代码

13.减少中断向量区的代码量to what is actually needed by the application.(给实际上需要申请的代码量)

                            还可以自动连接所有的代码段到一个声明中

14.代码重用is intra-modular模的内部 .集合一些功能函数于一个模块内以便于增加代码使用频度.

15.一些程序中,高速的优化结果源自于更少的代码量而不是完整的代码优化.  Compile on a module by module basis to investigate what give the best result/ 根据调查什么会给一个最好的结果我们必须在模具上编辑。

16.optimize C_startup to not initialize unused segment (i.e.,IDATA0 or IDATA1 if all vgariables are tiny or small ) 最优化C 并不会优化没有用过的片段(如IDATAO 或者IDATAI 如果所有的   很少或很小。

17.如果合适的话,避免在中断程序中调用外部函数

18.使用最小的possible存储模式

 5种提示来减少RAM的需求  

1.所有的常量和字母应该放置在FLASH中通过使用FLASH 关键字

2.避免使用全局变量如果the variables are local in nature 变量在自然界中是局部的。 变量.这也节约了代码空间.局部变量能够动态的从堆栈中分派出来并且在函数结束使用时被释放.

3.if  using large functions with variable with a  limited lifetime within the function, the use of subscopes can be beneficial 如果在有限的时间里利用变量大的功能,SUBSCOPE 的利用将会受益。

4.获得软件堆栈代码量good estimate (好的评估)并且返回 堆栈(连接器文件)

5.不要把空间浪费在IDATA0 和 UDATA0 段 除非你使用tiny变量(连接器文件)

 下载本文

显示全文
专题