PICC 代码优化技巧.docx

上传人:b****8 文档编号:30519534 上传时间:2023-08-16 格式:DOCX 页数:40 大小:34.49KB
下载 相关 举报
PICC 代码优化技巧.docx_第1页
第1页 / 共40页
PICC 代码优化技巧.docx_第2页
第2页 / 共40页
PICC 代码优化技巧.docx_第3页
第3页 / 共40页
PICC 代码优化技巧.docx_第4页
第4页 / 共40页
PICC 代码优化技巧.docx_第5页
第5页 / 共40页
点击查看更多>>
下载资源
资源描述

PICC 代码优化技巧.docx

《PICC 代码优化技巧.docx》由会员分享,可在线阅读,更多相关《PICC 代码优化技巧.docx(40页珍藏版)》请在冰豆网上搜索。

PICC 代码优化技巧.docx

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

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 医学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1