您的位置:首页 > 其它

使用 IAR for AVR 时需要注意的几个地方

2011-09-18 22:05 671 查看
在AVR所有的编译器中,IAR for AVR 是编译效率最高的编译器, 但是相对来说IAR for AVR的设置项也非常多,如果使用不当反而会出现很多莫名其妙的问题.

一. 关于堆栈的设置问题

GCC和IAR分配堆栈的方式不同,IAR先分配堆栈空间,相当于定义一个全局数组为堆栈空间,堆栈初始为堆栈空间最高地址;GCC不用先分配堆栈,自动把RAM剩余空间作为堆栈空间,堆栈初始为RAM最高地址。

初学者很容易忽视这个问题,造成程序跑飞而找不到问题的症结,我在用IAR For MSP430的时候没遇到过这个问题,因为MSP430的RAM比较大,IAR默认是80字节,足够一般程序使用。

但是使用IAR For SAM8的时候,有一个比较耗费堆栈的程序运行一段时间后出问题,由于要记录一个24小时的数组,而数组元素的值是在堆栈里改变的,所以,记录到一定时间以后,出现了堆栈不足的情况,初学者如果没有仿真器,是很难发现这个问题的,还好我用的OPENice i500仿真器在Debug的时候出现了堆栈不足的警告,我才意识到是这里问题。

IAR For SAM8默认堆栈是32字节,既然不够用,那么就要增大,但是设置到多少合适呢?

首先编译你的程序,看程序用了多少自己的RAM,





(原文件名:iar.JPG) 引用图片

在看看芯片的Datasheet,看看芯片总共有多大的RAM,





(原文件名:9428.JPG) 引用图片

好了现在你就知道剩余多少RAM了:208-142=66(Byte)

前面说过IAR的CSTACK,NEAR_HEAP和RSTACK是预先分配好的,占用存储空间是固定不变的,相当于定义了一个全局数组,GCC堆栈策略与IAR不同,堆栈大小不是预先分配好的,而是把SRAM里面剩余空间作为堆栈空间。

如果是GCC,那么编译器就会自动设置剩余的RAM为数据堆栈(和数据返回堆栈RSTACK,NEAR_HEAP等)。

在IAR里,STACK应该设置到多少呢?

在工程-》Options-》Linker-》List(不同的IAR版本会稍有不同),选择生成LIST文件,并包含stack选项





(原文件名:iar.JPG) 引用图片

在./Debug/list目录下,得到.map(可能是.lst等其他格式)文件,用记事本打开,找到以下内容:

****************************************

* *

* CALL GRAPH *

* *

****************************************

->Sub-tree of type: Interrupt function tree that does not make

: indirect calls

CSTACK

| Stack used (prev) : 00000000

01 int_T0_OV

| Stack used (prev) : 00000000

| + function block : 0000000C

......(省略N行)

01 main

| Stack used (prev) : 0000003A

| + function block : 00000000

<-Sub-tree of type: Function tree

| Stack used : 0000003A

找到最大的Stack used,我的就是0000003A,58个字节,这就是用到的最大的堆栈空间,保守一点,我设置成64字节,没有超过剩余RAM,再重新编译,运行,仿真器没有堆栈不足警告,程序也能正常运行了。

二. 关于代码优化

在用IAR开发C程序时,开发环境提供了一个优化的选项,有4档,即None/Low/Medium/High。但在实际应用时要注意,不是什么情况下都可以进行优化,有时在没有优化时运行的代码,优化后则不能正常运行了。(这个现象在用WINAVR软件开发时更严重,本人就是因为觉得WINAVR这个问题比较严重才转到IAR环境的)

例如,下面的程序是让LED闪烁:

#include <ioavr.h>

<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

// 定义一个宏操作

#define _BV(x) (1<<x)

// 定义LED对应的端口

#define LED 0

int main(void)

{

unsigned int k;

PORTB = _BV(LED); // 端口B的第0位置1

DDRB = _BV(LED); // 设置端口B的第0位为输出

while(1) // 进入无限循环

{

PORTB = PORTB & ~_BV(LED); // PB0<-0,点亮LED

for(k=0;k<60000;k++);

PORTB = PORTB | _BV(LED); // PB0<-1,熄灭LED

for(k=0;k<60000;k++);

}

}

在没有优化时,或者优化级别为Low或Medium时,可以看出LED是在闪烁,或者用示波器可以测出,PB0输出的是一个频率较低的方波。

但如果我们选了优化级别为High,这时就看不出来LED在闪烁了,或者用示波器可以测出,PB0输出的是一个频率较高的方波。

从C语言是看不出任何问题的,只能从编译后的汇编语言找问题。首先看一下在优化级别为Medium时,main函数对应的机器码,如下图所示:



从汇编代码中,我们可以看出,当对PB0端口操作后,程序进行了延时,(R25,R24)寄存器对作为变量k,进行了加1运算,和判断大小。

但如果我们选择了优化级别为High时,main函数的代码就变成如下图所示:



很明显看出,当对PB0端口设置为0后,紧接的下一条指令就是设置1的指令,然后又跳转回来,中间的延时完全被优化掉了,当然运行结果就是在PB0端口上看到一个频率较高的方波了。

如果我们确实需要对代码优化,可又需要这样的软件延时,那应该怎么办呢?答案是只要将上面的变量k定义成volatile类型即可,修改后的程序如下:

#include <ioavr.h>

// 定义一个宏操作

#define _BV(x) (1<<x)

// 定义LED对应的端口

#define LED 0

int main(void)

{

volatile unsigned int k;

PORTB = _BV(LED); // 端口B的第0位置1

DDRB = _BV(LED); // 设置端口B的第0位为输出

while(1) // 进入无限循环

{

PORTB = PORTB & ~_BV(LED); // PB0<-0,点亮LED

for(k=0;k<60000;k++);

PORTB = PORTB | _BV(LED); // PB0<-1,熄灭LED

for(k=0;k<60000;k++);

}

}

设置优化级别为High时,main函数对应的汇编代码如下:





从汇编代码中,我们可以清楚地看见,当进行了PB0端口操作后,程序调用Subroutine0和Subroutine1分别完成加1运算和大小判别,从而实现了软件延时。

总之,当遇到优化后与优化前代码实现的功能不一样时,不妨从汇编代码进行分析一下。这样有利于解决问题。

三. 关于IO端口位操作

类似于keil c ,在iar avr中也可以直接进行位操作,这一点要比icc avr好.

比如: PORTA_Bit0 就表示端口a ,0号引脚. 其它类似,直接用PORTA_Bit0进行写或读操作即可.

PORTA_Bit0=1;

if(PORTA_Bit0)

{

......

}

也可以用宏定义将各个引脚定义成特定名字.如:

#define LCD_WR PORTA_Bit0

四. 关于变量储存位置

如何把常数数组定义在flash 空间?

法一:unsigned char __flash temptab[] = {1,2,3,4,5};

法二:__flash unsigned char temptab[] = {1,2,3,4,5};

法三:#pragma type_attribute=__flash

unsigned char temptab[]={1,2,3,4,5};

法四:const unsigned char temptab[]={1,2,3,4,5};

注:第三种方式用#pragma说明后,下面的定义的变量将都在FLASH空间了,用于定义一批FLASH变量,但实际上一般只能作为常量使用了.

五.关于内存模型

AVR 微控制器的其中一个特点是它有一种存储器访问方法均衡了“cheap access limited to small memory areas”与“more expensive accessmethods that can access any location in memory”。

在AVR_IAR C/C++编译器中,通过选择某种存储模式(memory model),可设置一些访问方法为默认的存储器访问方法(default memory accessmethod)。共有三种可用的存储模式——Tiny,Small 和Large。你的处理器选项决定了哪些模式可以使用。如果你不指定一种存储模式,则编译器自动设定-v0、-v1、-v2、-v3、-v5 选项下的默认方法为Tiny,-v4 和-v6 选项下的访问方法为Small。



六. 关于生成文件格式的设置



如图,在linker -> outpu ->other中可设置相应的输出文件格式.

比如要生成bin格式,选择raw-binary就可以了, 如果是要生成hex格式,那么可以选intel-extern ,不过这个时候文件扩展名是*.a90,可以把"Override default " 打钩,然后修改后缀名为hex就行了.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: