PC指针运行到第一个函数的时候(rIn = sensor ();),rIn=99;rout=2.5;
PC指针运行到第二个函数的时候( rOut = PIDCalc (&sPID,rIn );)rIn=99;rout=99;
PC指针运行到第三个函数的时候(rOut=actuator ( rOut );)rIn=99;rout=2;
在中间时刻rout值出现很大的误差这是为啥呢?
第一反应是KEIL代码优化的结果。KEIL默认优化等级是8级,我们选择0级,也就是不优化。
看图片,在第二个函数的时候,rout没有出现99的值,这才是对的。
你也可以在rout变量定义前加入Volatile关键字,告诉编译器,每次重新从变量地址中读取数值,而不是数值副本,或者说是数据的寄存器,也就是让编译器不要优化这段代码。我在程序添加了t=1,2,3,这几句,你可以自己试试看看没有Volatile关键字,单步执行,会出现什么情况。
你也可以使用全局变量,这样编译器也不会优化rout 了。
具体原因请看下面分析,我摘抄了几段网上文章,我认为你可以看懂的啊。
http://www.cnblogs.com/yc_sunniwell/archive/2010/06/24/1764231.html
volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。
下面举例说明。在DSP开发中,经常需要等待某个事件的触发,所以经常会写出这样的程序:
short flag;
void test()
{
do1();
while(flag==0);
do2();
}
这段程序等待内存变量flag的值变为1(怀疑此处是0,有点疑问,)之后才运行do2()。变量flag的值由别的程序更改,这个程序可能是某个硬件中断服务程序。例如:如果某个按钮按下的话,就会对DSP产生中断,在按键中断程序中修改flag为1,这样上面的程序就能够得以继续运行。但是,编译器并不知道flag的值会被别的程序修改,因此在它进行优化的时候,可能会把flag的值先读入某个寄存器,然后等待那个寄存器变为1。如果不幸进行了这样的优化,那么while循环就变成了死循环,因为寄存器的内容不可能被中断服务程序修改。为了让程序每次都读取真正flag变量的值,就需要定义为如下形式:
volatile short flag;
需要注意的是,没有volatile也可能能正常运行,但是可能修改了编译器的优化级别之后就又不能正常运行了。因此经常会出现debug版本正常,但是release版本却不能正常的问题。所以为了安全起见,只要是等待别的程序修改某个变量的话,就加上volatile关键字。
volatile的本意是“易变的”
由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。
比如:
static int i=0;
int main(void)
{
...
while (1)
{
if (i) do_something();
}
}
/* Interrupt service routine. */
void ISR_2(void)
{
i=1;
}
程序的本意是希望ISR_2中断产生时,在main当中调用do_something函数,但是,由于编译器判断在main函数里面没有修改过i,因此可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致do_something永远也不会被调用。如果变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。
一般说来,volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。
http://www.chsi.com.cn/xy/com/200906/20090610/25512445.html
volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改。
用volatile关键字声明的变量i每一次被访问时,执行部件都会从i相应的内存单元中取出i的值。
没有用volatile关键字声明的变量i在被访问的时候可能直接从cpu的寄存器中取值(因为之前i被访问过,也就是说之前就从内存中取出i的值保存到某个寄存器中),之所以直接从寄存器中取值,而不去内存中取值,是因为编译器优化代码的结果(访问cpu寄存器比访问ram快的多)。
以上两种情况的区别在于被编译成汇编代码之后,两者是不一样的。之所以这样做是因为变量i可能会经常变化,保证对特殊地址的稳定访问。
volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
使用该关键字的例子如下:
int volatile nVint;
当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。
例如:
volatile int i=10;
int a = i;
...
//其他代码,并未明确告诉编译器,对i进行过操作
int b = i;
volatile 指出 i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在b中,而不是重新从i里面读。这样一来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。
注意,在vc6中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编代码,测试有无volatile关键字,对程序最终代码的影响:
首先,用classwizard建一个win32 console工程,插入一个voltest.cpp文件,输入下面的代码:
#i nclude <stdio.h>
void main()
{
int i=10;
int a = i;
printf("i= %dn",a);
//下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf("i= %dn",b);
}
然后,在调试版本模式运行程序,输出结果如下:
i = 10
i = 32
然后,在release版本模式运行程序,输出结果如下:
i = 10
i = 10
输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。
下面,我们把 i的声明加上volatile关键字,看看有什么变化:
#i nclude <stdio.h>
void main()
{
volatile int i=10;
int a = i;
printf("i= %dn",a);
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf("i= %dn",b);
}
分别在调试版本和release版本运行程序,输出都是:
i = 10
i = 32
这说明这个关键字发挥了它的作用!
http://hi.baidu.com/chcwdpwnofeqtze/item/5685770d6c544131a3332a93,也是一篇很好的文章,读者自己看,
如果链接不可以打开,你google,按照下图片。
最后附上KEIL中优化级别的意思。
Keil C51中的优化级别及优化作用
级别 说明
0 常数合并:编译器预先计算结果,尽可能用常数代替表达式。包括运行地址计算。
优化简单访问:编译器优化访问8051系统的内部数据和位地址。
跳转优化:编译器总是扩展跳转到最终目标,多级跳转指令被删除。
1 死代码删除:没用的代码段被删除。
拒绝跳转:严密的检查条件跳转,以确定是否可以倒置测试逻辑来改进或删除。
2 数据覆盖:适合静态覆盖的数据和位段被确定,并内部标识。BL51连接/定位器可以通过
全局数据流分析,选择可被覆盖的段。
3 窥孔优化:清除多余的MOV指令。这包括不必要的从存储区加载和常数加载操作。
当存储空间或执行时间可节省时,用简单操作代替复杂操作。
4 寄存器变量:如有可能,自动变量和函数参数分配到寄存器上。为这些变量保留的存储区 就省略了。
优化扩展访问:IDATA、XDATA、PDATA和CODE的变量直接包含在操作中。在多数时间没必 要使用中间寄存器。
局部公共子表达式删除:如果用一个表达式重复进行相同的计算,则保存第一次计算结 果,后面有可能就用这结果。多余的计算就被删除。
Case/Switch优化:包含SWITCH和CASE的代码优化为跳转表或跳转队列。
5 全局公共子表达式删除:一个函数内相同的子表达式有可能就只计算一次。中间结果保存 在寄存器中,在一个新的计算中使用。
简单循环优化:用一个常数填充存储区的循环程序被修改和优化。
6 循环优化:如果结果程序代码更快和有效则程序对循环进行优化。
7 扩展索引访问优化:适当时对寄存器变量用DPTR。对指针和数组访问进行执行速度和代 码大小优化。
8 公共尾部合并:当一个函数有多个调用,一些设置代码可以复用,因此减少程序大小 。
9 公共块子程序:检测循环指令序列,并转换成子程序。Cx51甚至重排代码以得到更大的循 环序列。
文章评论(0条评论)
登录后参与讨论