PICC 代码优化技巧.docx
《PICC 代码优化技巧.docx》由会员分享,可在线阅读,更多相关《PICC 代码优化技巧.docx(40页珍藏版)》请在冰豆网上搜索。
PICC代码优化技巧
常用优化技巧:
•要减少bank切换,把在不同bank里的变量放到一起。
•在初始化代码里,在程序的开头,注意初始化的顺序-一开始所有的变量放在bank0,然后放bank1,接着bank2,bank3。
•在初始化代码里-可能有些变量不需要初始化。
•在可能的地方,掉换操作数的顺序来使编译器避免多余使用W寄存器或临时位置。
•对于数学运算,表达式里的变量尽量要在同一个bank里,以避免过多的bank切换。
•如果可能,尽可能地采用字节byte运算代替字word运算。
•如果可能,对于数组元素的访问尽量采用指针而不是用下标索引。
注意在一个小的循环里使用指针时,管理循环多出的代码抵销了使用指针节省下来的代码,所以使用两种方法差不多。
•一系列的:
if
elseif
elseif...通常会比case语句产生更小的代码。
•在switch–case里,改变常量为有顺序的数据,不要有间隔。
•依靠bank切换必需的:
Dependingonthebankswitchingrequired:
var=value1;
if(!
flag)
var=value2;
产生更理想的代码:
if(flag)
var=value1;
else
var=value2;
只是要确认在该代码执行时不要在中断里使用这个var。
•清零,递增,以及递减一个字节byte是单指令的操作。
给一个字节赋值需要两条指令(value->W,andW->byte).
•只要可能,尽量使用bits代替unsignedchars。
置位Bitsets,清零clears,以及位测试跳转等都是单条指令。
因为不能在函数里申明位变量,你可以全局声明位变量。
•调用函数会产生一些管理代码。
尝试着用一些宏marcro代替你的一些小一点的函数。
•如果堆栈空间允许,大块的重复代码应该由函数及函数调用来替代。
•当前逻辑的优化。
我还只是刚接到一个固定要求的项目,于是我尝试着把代码写得非常灵活。
当我快接近项目结束时,我发现一些弹性代码不再需要了,这样可以删除它来节省代码。
优化提示1:
Signedvs.Unsigned变量
比较使用signed和unsigned变量的汇编代码,你会发现在比较有符号signed变量时会多出一些指令。
结论1:
尽可能地使用unsigned的int或char。
优化提示2:
基于字节Byte的循环Loops
这里有两块代码,它们做的完全是同样的事情。
但是其中一个要完成得快25%,并且使用更小的RAM空间,你能挑出是哪一个吗?
unsignedchari;
for(i=0;i<250;i++)do_func();//executesdo_func()250times,in3.25ms
for(i=250;i!
=0;i--)do_func();//executesdo_func()250times,in2.5ms
要找出这个,我们来看一下产生的汇编代码
for(i=0;i<250;i++)do_func();
//executes250timesin3251cy
161701B8clrf0x38
1618260Fcall0x60F
16190AB8incf0x38
161A3008movlw0xFA
161B0238subwf0x38,W
161C1C03btfss0x3,0x0
161D2E18goto0x618
for(i=250;i!
=0;i--)do_func();
//executes250timesin2502cy
16213008movlw0xFA
162200B8movwf0x38
1623260Fcall0x60F
16240BB8decfsz0x38
16252E23goto0x623
结论2:
如果可能,让你的循环递减到零。
检查一个ram变量是否为零会更快一些。
但是,注意在递增循环里,do_func()要早一个时钟周期被调用。
如果你希望进入函数的速度快些,可选择递增循环。
优化提示3:
IntegerTimeoutLoops
如果你想查询一个端口,或者在timeout“时间到”之前执行一个函数一定的次数,你需要一个定时循环timeoutloop.
unsignedinttimeout;
#definehibyte(x)((unsignedchar)(x>>8))
#definelobyte(x)((unsignedchar)(x&0xff))
//theoptimizertakescareofusingthehi/locorrectbyteofinteger
•Loopstoavoidwithtimeouts:
320000to380000cyclesfor20000iterations.
for(timeout=0;timeout<20000;timeout++)do_func();//380011cycles
for(timeout=20000;timeout!
=0;timeout--)do_func();//320011cycles|
•Bestloopforatimeout:
295000cyclesfor20000iterations.
//wewanttoexecutedo_func()approx.20000timesbeforetimingout
timeout=(20000/0x100)*0x100;//keepslobyte(timeout)==0,whichspeedsupassignments
for(;hibyte(timeout)!
=0;timeout--)do_func();//295704cycles
Noticethefeaturesoftheloopshownabove.
1.它在每个循环里只测试整型数的高位字节。
2.它检查这个字节是否到零,所以很快。
3.当初始化timeout变量时,它又一个好处是:
汇编代码清零一个ram变量只要一条指令,而赋值则需要两条指令。
结论3:
•尽可能地采用递减到零的循环,因为检查ram变量是否为零更简单。
•在延时循环里只查询整型数的高位字节,这要快一些。
•给整型数赋值时,对ram变量清零要比赋一个数值要快一些。
优化提示4:
使用内部定时器的Timeout定时循环
当然,使用芯片片内定时器并检查中断的方式是最快的定时循环。
它通常会比用延时循环快70%左右。
//setuptmr0tosetflagT0IFhighwhenitrollsover
while(RA0==0&&!
T0IF);//waituntilportgoeshigh
结论4:
•尽可能地使用内建的定时器及中断标志。
优化提示5:
Case语句
慢而且效率低
c=getch();
switch(c)
{
case'A':
{
dosomething;
break;
}
case'H':
{
dosomething;
break;
}
case'Z':
{
dosomething;
break;
}
}快且高效率
c=getch();
switch(c)
{
case0:
{
dosomething;
break;
}
case1:
{
dosomething;
break;
}
case2:
{
dosomething;
break;
}
}
Hi-TechC的优化器会尽可能地把switch语句变成计算偏移的goto。
结论5:
•尽可能地在case语句里使用连续的数字。
优化提示6:
Hi-TechC里的除法
如果你使用Hi-TechC,在你程序的任何位置有任何数学除法的运算,就将会使用到bank0里13到23个字节的空间,以及一些EPROM/Flash程序空间。
尽管变量不在bank0,这一样会发生。
Occurrence
Anymathematicaldivisionatallintheentireprogramusingavariableoftype'long',evenifallvariablesdonotresideinbank0.
RAMusage
23bytesinbank0
ROM/flashusage
large,ithastoincludeldivroutines
Fix/Explanation
Usecombinationsofbitshiftsie:
x=x*6isreplacedbyx1=x;x2=x;x=x1<<2+x2<<1
Occurrence
Anymathematicaldivisionatallintheentireprogramusingavariableoftype'unsignedint',evenifallvariablesdonotresideinbank0.
RAMusage
13bytesinbank0
ROM/flashusage
large,ithastoincludeldivroutines
Fix/Explanation
Usecombinationsofbitshifts
Occurrence
Anymathematicaldivisioninvolvingadivisorthatisapowerof2,ie:
x=x/64;
RAMusage
none
ROM/flashusage
low
Fix/Explanation
Usecombinationsofbitshifts
Occurrence
Anymathematicaldivisioninvolvingadivisorthatisnotapowerof2,ie:
x=x/65;
RAMusage
none
ROM/flashusage
high
Fix/Explanation
makeyourdivisorsapowerof2,ie:
2^5=32.
结论6:
如果可能,尽量让C编译器使用简单的移位来做除法,且除数是2的幂。
除数是2的幂,例如,256=2^8,可以被C编译器优化为移位操作。
如果你在程序里没有使用任何除法运算,则可以节省bank0里的23个字节和一部分程序空间-ldiv()程序。
----
注:
本组文章主要为网友CYPOK的论著,由我们收集整理,特对CYPOK表示感
谢!
1、PICC和MPLAB集成
PICC和MPLAB集成:
PICC有自己的文本编辑器,不过是DOS风格的,看来PICC的工程师要专业冷到酷底了...
大家大可不必用它,如果你没什么癖好的话,你不会不用UltraEdit吧?
1:
建立你的工作目录:
建议在C盘根目录下建立一个以A开头的文件夹做为工作目录.因为你会发现它总是在你查找文件时候第
一个跳入你眼中.
2:
MPLAB调用PICC.(以MPLAB5.7版本为例子)
启动MPLAB.在Project-->InstallLanguageTool:
LanguageSuite----->hi-techpicc
ToolName---->PICCCompiler
Executable---->c:
hi-picinpicc.exe(假如你的PICC是默认安装的)
选Command-line
最后OK.
上面这步只需要设定一次,除非你重新安装了MPLAB.
3:
创建你的项目文件:
(假如你实现用EDIT编辑好了一个叫AA.C的C代码文件)
Project-->NewProject-->FileName--->myc(假如我们把项目文件取名字叫MYC.PJT)
右边窗口当然要选择中你的工作目录.然后OK.
4:
设定你的PICC工作参数:
Project-->EditProject
上面4个栏目就用默认的,空的也就让它空着,无所谓的.
需要修改的是:
DevelopmentMode---->选择你的PIC型号.当然要选择MplabSIMSimulator
让你可以用软件仿真.
LanguageToolSuite--->HI-TECHPICC
上面的步骤,你可能会遇见多个提示条,不要管它,一路确定.
下面是PICC编译器的选择项:
双击ProjectFiles窗口里面的MYC.HEX,出现一个选择拦目.命令很多,大家可以看PICC文本编
辑器里面的HELP,里面有详细说明.
下面就推荐几个常用也是建议用的:
Generatedebuginfo以及下面的2项.
Produceassemblerlistfile
就在它们后面打勾即可,其它的不要管,除非你有特殊要求.
5:
添加你的C代码文件:
当进行了前面几步后,按AddNode找到AA.C文件就OK了.
6:
编译C代码:
最简单的一步:
直接按下F10.
编译完后,会出现各种调试信息.C代码对应的汇编代码就是工作目录里面的AA.IST,用EDIT
打开可以看见详细的对比.
7:
其它,要是一切都没问题,那么你就可以调试和烧片了,和以往操作无异.
2、如何从汇编转向PICC
首先要求你要有C语言的基础。
PICC不支持C++,这对于习惯了C++的朋友还得翻翻C语言的书。
C
代码的头文件一定要有#include,它是很多头文件的集合,C编译器在pic.h中根据你的芯片自动栽
入相应的其它头文件。
这点比汇编好用。
载入的头文件中其实是声明芯片的寄存器和一些函数。
顺便摘抄
一个片段:
staticvolatileunsignedcharTMR0@0x01;
staticvolatileunsignedcharPCL@0x02;
staticvolatileunsignedcharSTATUS@0x03;
可以看出和汇编的头文件中定义寄存器是差不多的。
如下:
TMR0EQU0X01;
PCLEQU0X02;
STATUSEQU0X03;
都是把无聊的地址定义为大家公认的名字。
一:
怎么附值?
如对TMR0附值,汇编中:
MOVLW200;
MOVWFTMR0;
当然得保证当前页面在0,不然会出错。
C语言:
TMR0=200;//无论在任何页面都不会出错。
可以看出来C是很直接了当的。
并且最大好处是操作一个寄存器时候,不用考虑页面的问题。
一切由
C自动完成。
二:
怎么位操作?
汇编中的位操作是很容易的。
在C中更简单。
C的头文件中已经对所有可能需要位操作的寄存器的每
一位都有定义名称:
如:
PORTA的每一个I/O口定义为:
RA0、RA1、RA2。
。
。
RA7。
OPTION的每一位定义为:
PS0、
PS1、PS2、PSA、T0SE、T0CS、INTEDG、RBPU。
可以对其直接进行运算和附值。
如:
RA0=0;
RA2=1;
在汇编中是:
BCFPORTA,0;
BSFPORTA,2;
可以看出2者是大同小异的,只是C中不需要考虑页面的问题。
三:
内存分配问题:
在汇编中定义一个内存是一件很小心的问题,要考虑太多的问题,稍微不注意就会出错。
比如16位的
运算等。
用C就不需要考虑太多。
下面给个例子:
16位的除法(C代码):
INTX=5000;
INTY=1000;
INTZ=X/Y;
而在汇编中则需要花太多精力。
给一个小的C代码,用RA0控制一个LED闪烁:
#include
voidmain()
{
intx;
CMCON=0B111;//掉A口比较器,要是有比较器功能的话。
ADCON1=0B110;//掉A/D功能,要是有A/D功能的话。
TRISA=0;//RA口全为输出。
loop:
RA0=!
RA0;
for(x=60000;--x;){;}//延时
gotoloop;
}
说说RA0=!
RA0的意思:
PIC对PORT寄存器操作都是先读取----修改----写入。
上句的含义是程序先
读RA0,然后取反,最后把运算后的值重新写入RA0,这就实现了闪烁的功能。
3、浅谈PICC的位操作
由于PIC处理器对位操作是最高效的,所以把一些BOOL变量放在一个内存的位中,既可以达到运算
速度快,又可以达到最大限度节省空间的目的。
在C中的位操作有多种选择。
*********************************************
如:
charx;x=x|0B00001000;/*对X的4位置1。
*/
charx;x=x&0B11011111;/*对X的5位清0。
*/
把上面的变成公式则是:
#definebitset(var,bitno)(var|=1<#definebitclr(var,bitno)(var&=~(1<则上面的操作就是:
charx;bitset(x,4)
charx;bitclr(x,5)
*************************************************
但上述的方法有缺点,就是对每一位的含义不直观,最好是能在代码中能直观看出每一位代表的意思,
这样就能提高编程效率,避免出错。
如果我们想用X的0-2位分别表示温度、电压、电流的BOOL值可以
如下:
unsignedcharx@0x20;/*象汇编那样把X变量定义到一个固定内存中。
*/
bittemperature@(unsigned)&x*8+0;/*温度*/
bitvoltage@(unsigned)&x*8+1;/*电压*/
bitcurrent@(unsigned)&x*8+2;/*电流*/
这样定义后X的位就有一个形象化的名字,不再是枯燥的1、2、3、4等数字了。
可以对X全局修改,
也可以对每一位进行操作:
char=255;
temperature=0;
if(voltage)......
*****************************************************************
还有一个方法是用C的struct结构来定义:
如:
structcypok{
temperature:
1;/*温度*/
voltage:
1;/*电压*/
current:
1;/*电流*/
none:
4;
}x@0x20;
这样就可以用
x.temperature=0;
if(x.current)....
等操作了。
**********************************************************
上面的方法在一些简单的设计中很有效,但对于复杂的设计中就比较吃力。
如象在多路工业控制上。
前端需要分别收集多路的多路信号,然后再设定控制多路的多路输出。
如:
有2路控制,每一路的前端信
号有温度、电压、电流。
后端控制有电机、喇叭、继电器、LED。
如果用汇编来实现的话,是很头疼的事
情,用C来实现是很轻松的事情,这里也涉及到一点C的内存管理(其实C的最大优点就是内存管理)。
采用如下结构:
unioncypok{
structout{
motor:
1;/*电机*/
relay:
1;/*继电器*/
speaker:
1;/*喇叭*/
led1:
1;/*指示灯*/
led2:
1;/*指示灯*/
}out;
structin{
none:
5;
temperature:
1;/*温度*/
voltage:
1;/*电压*/
current:
1;/*电流*/
}in;
charx;
};
unioncypokan1;
unioncypokan2;
上面的结构有什么好处呢?
细分了信号的路an1和an2;
细分了每一路的信号的类型(是前端信号in还是后端信号out):
an1.in;
an1.out;
an2.in;
an2.out;
然后又细分了每一路信号的具体含义,如:
an1.in.temperature;
an1.out.motor;
an2.in.voltage;
an2.out.led2;等
这样的结构很直观的在2个内存中就表示了2路信号。
并且可以极其方便的扩充。
如添加更多路的信号,只需要添加:
unioncypokan3;
unioncypokan4;
从上面就可以看出用C的巨大好处
4、PICC之延时函数和循环体优化。
很多朋友说C中不能精确控制延时时间,不能象汇编那样直观。
其实不然,对延时函数深入了解一下
就能设计出一个理想的框价出来。
一般的我们都用for(x=100;--x;){;}此句等同与x=100;while(--x){;};
或for(x=0;x<100;x++){;}。
来写一个延时函数。
在这里要特别注意:
X=100,并不表示只运行100个指令时间就跳出循环。
可以看看编译后的汇编:
x=100;while(--x){;}
汇编后:
movlw100
bcf3,5
bcf3,6
movwf_delay
l2decfsz_delay
gotol2
return
从代码可以看出总的指令是是303个,其公式是8+3*(X-1)。
注意其中循环周期是X-1是99个。
这
里总结的是x为char类型的循环体,当x为int时候,其中受X值的影响较大。
建议设计一个char类型的
循环体,然后再用一个循环体来调用它,可以实现精确的长时间的延时。
下面给出一个能精确控制延时的
函数,此函数的汇编代码是最简洁、最能精确控制指令时间的:
voiddelay(charx,chary){
charz;
do{
z=y;
do{;}while(--z);
}while(--x);
}
其指令时间为:
7+(3*(Y-1)+7)*(X-1)如果再加上函数调用的call指令、页面设定、传递参数
花掉的7个指令。
则是:
14+(3*(Y-1)+7)*(X-1)。
如果要求不是特别严格的延时,可以用这个函数:
voiddelay(){
unsignedintd=1000;
while(--d){;}
}
此函数在4M晶体下产生10003us的延时,也就是10MS。
如果把D改成2000,则是20003u