新聞中心

PIC單片機 C編程技巧

作者: 時間:2016-11-13 來源:網(wǎng)絡(luò) 收藏
1、PICC和MPLAB集成

PICC和MPLAB集成:
PICC有自己的文本編輯器,不過是DOS風(fēng)格的,看來PICC的工程師 要專業(yè)冷到酷底了...
大家大可不必用它,如果你沒什么癖好的話,你不會不用UltraEdit 吧?
1:建立你的工作目錄:
建 議在C盤根目錄下建立一個以A開頭的文件夾做為工作目錄.因為你會發(fā)現(xiàn)它總是在你查找文件時候第
一個跳入你眼中.
2:MPLAB調(diào)用 PICC.(以MPLAB5.7版本為例子)
啟動MPLAB.在Project-->Install Language Tool:
Language Suite----->hi-tech picc
Tool Name ---->PICC Compiler
Executable ---->c:hi-picinpicc.exe (假如你的PICC是默認安裝的)
選Command-line
最后OK.
上 面這步只需要設(shè)定一次,除非你重新安裝了MPLAB.
3:創(chuàng)建你的項目文件:(假如你實現(xiàn)用EDIT編輯好了一個叫AA.C的C代碼文件)
Project-->New Project-->File Name--->myc (假如我們把項目文件取名字叫MYC.PJT)
右邊窗口當(dāng)然要選擇中你的 工作目錄.然后OK.
4:設(shè)定你的PICC工作參數(shù):
Project-->Edit Project
上面4個欄目就用默認 的,空的也就讓它空著,無所謂的.
需要修改的是:
Development Mode---->選擇你的PIC型號.當(dāng)然要選擇Mplab SIM Simulator
讓你可以用軟件仿真.
Language Tool Suite--->HI-TECH PICC
上面的步驟,你可能會遇見多個提示條,不要管它,一路確定.
下面是 PICC編譯器的選擇項:
雙擊Project Files 窗口里面的MYC.HEX,出現(xiàn)一個選擇攔目.命令很多,大家可以看PICC文本編
輯 器里面的HELP,里面有詳細說明.
下面就推薦幾個常用也是建議用的:
Generate debug info 以及下面的2項.
Produce assembler list file
就在它們后面打勾即可,其它的不要管,除非你有特殊要求.
5:添加你的C代碼文件:
當(dāng) 進行了前面幾步后,按Add Node 找到AA.C文件就OK了.
6:編譯C代碼:
最簡單的一步:直接按下F10.
編譯完后, 會出現(xiàn)各種調(diào)試信息.C代碼對應(yīng)的匯編代碼就是工作目錄里面的AA.IST,用EDIT
打開可以看見詳細的對比.
7:其它,要是一切都沒 問題,那么你就可以調(diào)試和燒片了,和以往操作無異.
2、如何從匯編轉(zhuǎn)向PICC
首先要求你要有C 語言的基礎(chǔ)。PICC 不支持C++,這對于習(xí)慣了C++的朋友還得翻翻C 語言的書。C
代碼的頭文件一定要有#i nclude,它是很多頭文件的集合,C 編譯器在pic.h 中根據(jù)你的芯片自動栽
入相應(yīng)的其它頭文件。這點比匯編 好用。載入的頭文件中其實是聲明芯片的寄存器和一些函數(shù)。順便摘抄
一個片段:
static volatile unsigned char TMR0 @ 0x01;
static volatile unsigned char PCL @ 0x02;
static volatile unsigned char STATUS @ 0x03;
可以看出和匯編的頭文件中定義寄存器是差不多的。如下:
TMR0 EQU 0X01;
PCL EQU 0X02;
STATUS EQU 0X03;
都是把無聊的地址定義為大家公認的名字。
一: 怎么附值?
如對TMR0 附值,匯編中:
MOVLW 200;
MOVWF TMR0;
當(dāng)然得保證當(dāng)前頁面在0,不然會出 錯。
C 語言:
TMR0=200;//無論在任何頁面都不會出錯。
可以看出來C 是很直接了當(dāng)?shù)?。并且最大好處是操作一個寄存器時候,不用考慮頁面的問題。一切由
C 自動完成。
二:怎么位操作?
匯編中的位操作 是很容易的。在C 中更簡單。C 的頭文件中已經(jīng)對所有可能需要位操作的寄存器的每
一位都有定義名稱:
如:PORTA 的每一個I/O 口定義為:RA0、RA1、RA2。。。RA7。OPTION 的每一位定義為:PS0、
PS1、PS2 、PSA 、T0SE、T0CS、INTEDG 、RBPU??梢詫ζ渲苯舆M行運算和附值。
如:
RA0=0;
RA2=1;
在匯編中 是:
BCF PORTA,0;
BSF PORTA,2;
可以看出2 者是大同小異的,只是C 中不需要考慮頁面的問題。
三: 內(nèi)存分配問題:
在匯編中定義一個內(nèi)存是一件很小心的問題,要考慮太多的問題,稍微不注意就會出錯。比如16 位的
運算等。用C 就不需要考慮太多。下面給個例子:
16 位的除法(C 代碼):
INT X=5000;
INT Y=1000;
INT Z=X/Y;
而在匯編中則需要花太多精力。
給一個小的C 代碼,用RA0 控制一個LED 閃爍:
#i nclude
void main()
{
int x;
CMCON=0B111; //掉A 口比較器,要是有比較器功能的話。
ADCON1=0B110; //掉A/D 功能,要是有A/D 功能的話。
TRISA=0; //RA 口全為輸出。
loop:RA0=!RA0;
for(x=60000;--x;){;} //延時
goto loop;
}
說 說RA0=!RA0 的意思:PIC 對PORT 寄存器操作都是先讀取----修改----寫入。上句的含義是程序先
讀RA0,然后取反,最后 把運算后的值重新寫入RA0,這就實現(xiàn)了閃爍的功能。
3、淺談PICC 的位操作
由于PIC 處理器對位操作是最高效的,所以把一些BOOL 變量放在一個內(nèi)存的位中,既可以達到運算
速度快,又可以達到最大限度節(jié)省空間的目的。在C 中的位操作有多種選擇。
*********************************************
如:char x;x=x|0B00001000; /*對X 的4 位置1。*/
char x;x=x&0B11011111; /*對X 的5 位清0。*/
把上面的變成公式則是:
#define bitset(var,bitno)(var |=1<#define bitclr(var,bitno)(var &=~(1<則上面的操作就是:char x;bitset(x,4)
char x;bitclr(x,5)
*************************************************
但 上述的方法有缺點,就是對每一位的含義不直觀,最好是能在代碼中能直觀看出每一位代表的意思,
這樣就能提高編程效率,避免出錯。如果我們想用X 的0-2 位分別表示溫度、電壓、電流的BOOL 值可以
如下:
unsigned char x @ 0x20; /*象匯編那樣把X 變量定義到一個固定內(nèi)存中。*/
bit temperature@ (unsigned)&x*8+0; /*溫度*/
bit voltage@ (unsigned)&x*8+1; /*電壓*/
bit current@ (unsigned)&x*8+2; /*電流 */
這樣定義后X 的位就有一個形象化的名字,不再是枯燥的1、2、3、4 等數(shù)字了??梢詫 全局修改,
也可以對每一位進行操作:
char=255;
temperature=0;
if(voltage)......
*****************************************************************
還 有一個方法是用C 的struct 結(jié)構(gòu)來定義:
如:
struct cypok{
temperature:1; /*溫度*/
voltage:1; /*電壓*/
current:1; /*電流*/
none:4;
}x @ 0x20;
這樣就可以用
x.temperature=0;
if(x.current)....
等 操作了。
**********************************************************
上面 的方法在一些簡單的設(shè)計中很有效,但對于復(fù)雜的設(shè)計中就比較吃力。如象在多路工業(yè)控制上。
前端需要分別收集多路的多路信號,然后再設(shè)定控制多路的 多路輸出。如:有2 路控制,每一路的前端信
號有溫度、電壓、電流。后端控制有電機、喇叭、繼電器、LED。如果用匯編來實現(xiàn)的話,是很頭疼的事
情, 用C 來實現(xiàn)是很輕松的事情,這里也涉及到一點C 的內(nèi)存管理(其實C 的最大優(yōu)點就是內(nèi)存管理)。
采用如下結(jié)構(gòu):
union cypok{
struct out{
motor:1; /*電機*/
relay:1; /*繼電器*/
speaker:1; /*喇叭*/
led1:1; /*指示燈*/
led2:1; /*指示燈*/
}out;
struct in{
none:5;
temperature:1; /*溫度*/
voltage:1; /*電壓*/
current:1; /*電流*/
}in;
char x;
};
union cypok an1;
union cypok an2;
上面的結(jié)構(gòu)有什么好處呢?
細分了信號的路an1 和an2;
細 分了每一路的信號的類型(是前端信號in 還是后端信號out):
an1.in ;
an1.out;
an2.in;
an2.out;
然 后又細分了每一路信號的具體含義,如:
an1.in.temperature;
an1.out.motor;
an2.in.voltage;
an2.out.led2; 等
這樣的結(jié)構(gòu)很直觀的在2 個內(nèi)存中就表示了2 路信號。并且可以極其方便的擴充。
如添加更多路的信號,只需要添加:
union cypok an3;
union cypok an4;
從上面就可以看出用C 的巨大好處
4、PICC 之延時函數(shù)和循環(huán)體優(yōu)化。
很多朋友說C 中不能精確控制延時時間,不能象匯編那樣直觀。其實不然,對延時函數(shù)深入了解一下
就能設(shè)計出一個 理想的框價出來。一般的我們都用for(x=100;--x;){;}此句等同與x=100;while(--x){;};
或for(x=0; x<100;x++){;}。
來寫一個延時函數(shù)。
在這里要特別注意:X=100,并不表示只運行100 個指令時間就跳出循環(huán)。
可 以看看編譯后的匯編:
x=100;while(--x){;}
匯編后:
movlw 100
bcf 3,5
bcf 3,6
movwf _delay
l2 decfsz _delay
goto l2
return
從代碼可以看出 總的指令是是303 個,其公式是8+3*(X-1)。注意其中循環(huán)周期是X-1 是99 個。這
里總結(jié)的是x 為char 類型的循環(huán)體,當(dāng)x 為int 時候,其中受X 值的影響較大。建議設(shè)計一個char 類型的
循環(huán)體,然后再用一個循環(huán)體來調(diào)用它,可以實現(xiàn)精確的長時間的延時。下 面給出一個能精確控制延時的
函數(shù),此函數(shù)的匯編代碼是最簡潔、最能精確控制指令時間的:
void delay(char x,char y){
char z;
do{
z=y;
do{;}while(--z);
}while(--x);
}
其 指令時間為:7+(3*(Y-1)+7)*(X-1)如果再加上函數(shù)調(diào)用的call 指令、頁面設(shè)定、傳遞參數(shù)
花掉的7 個指令。則是:14+(3*(Y-1)+7)*(X-1)。如果要求不是特別嚴(yán)格的延時,可以用這個函數(shù):
void delay(){
unsigned int d=1000;
while(--d){;}
}
此函數(shù)在4M 晶體下產(chǎn)生10003us 的延時,也就是10MS。如果把D 改成2000,則是20003us,以此類
推。有朋友不明白,為什么不用while(x--)后減量,來控制 設(shè)定X 值是多少就循環(huán)多少周期呢?現(xiàn)在看看編
譯它的匯編代碼:
bcf 3,5
bcf 3,6
movlw 10
movwf _delay
l2
decf _delay
incfsz _delay,w
goto l2
return
可 以看出循環(huán)體中多了一條指令,不簡潔。所以在PICC 中最好用前減量來控制循環(huán)體。
再談?wù)勥@樣的語句:
for(x=100;--x;) {;}和for(x=0;x<100;x++){;}
從字面上看2 者意思一樣,但可以通過匯編查看代碼。后者代碼雍長,而前者就很好的匯編出了簡潔的代
碼。所以在PICC 中最好用前者的形式來寫循環(huán)體,好的C 編譯器會自動把增量循環(huán)化為減量循環(huán)。因為
這是由處理器硬件特性決定的。PICC 并不是一個很智能的C 編譯器,所以還是人腦才是第一的,掌握一些
經(jīng)驗對寫出高效,簡潔的代碼是有好處的。
5、深入探討PICC之位操作
一:用位操作來 做一些標(biāo)志位,也就是BOOL變量.可以簡單如下定義:
bit a,b,c;
PICC會自動安排一個內(nèi)存,并在此內(nèi)存中自動安排一位來對 應(yīng)a,b,c.由于我們只是用它們來簡單的
表示一些0,1信息,所以我們不需要詳細的知道它們的地址\位究竟是多少,只管拿來就用好了.
二: 要是需要用一個地址固定的變量來位操作,可以參照PIC.H里面定義寄存器.
如:用25H內(nèi)存來定義8個位變量.
static volatile unsigned char myvar @ 0x25;
static volatile bit b7 @ (unsigned)&myvar*8+7;
static volatile bit b6 @ (unsigned)&myvar*8+6;
static volatile bit b5 @ (unsigned)&myvar*8+5;
static volatile bit b4 @ (unsigned)&myvar*8+4;
static volatile bit b3 @ (unsigned)&myvar*8+3;
static volatile bit b2 @ (unsigned)&myvar*8+2;
static volatile bit b1 @ (unsigned)&myvar*8+1;
static volatile bit b0 @ (unsigned)&myvar*8+0;
這樣即可以對MYVAR操作,也可以對B0--B7直接位操作.
但不好的是,此招在 低檔片子,如C5X系列上可能會出問題.
還有就是表達起來復(fù)雜,你不覺得輸入代碼受累么?呵呵
三:這也是一些常用手法:
#define testbit(var, bit) ((var) & (1 <<(bit)))
//測試某一位,可以做BOOL運算
#define setbit(var, bit) ((var) |= (1 << (bit))) //把某一位置1
#define clrbit(var, bit) ((var) &= ~(1 << (bit))) //把某一位清0
付上一段代碼,可 以用MPLAB調(diào)試觀察
#i nclude
#define testbit(var, bit) ((var) & (1 <<(bit)))
#define setbit(var, bit) ((var) |= (1 << (bit)))
#define clrbit(var, bit) ((var) &= ~(1 << (bit)))
char a,b;
void main(){
char myvar;
myvar=0B10101010;
a=testbit(myvar,0);
setbit(myvar,0);
a=testbit(myvar,0);
clrbit(myvar,5);
b=testbit(myvar,5);
if(!testbit(myvar,3))
a=255;
else
a=100;
while(1){;}
}
四: 用標(biāo)準(zhǔn)C的共用體來表示:
#i nclude
union var{
unsigned char byte;
struct {
unsigned b0:1, b1:1, b2:1, b3:1, b4:1, b5:1, b6:1, b7:1;
} bits;
};
char a,b;
void main(){
static union var myvar;
myvar.byte=0B10101010;
a=myvar.bits.b0;
b=myvar.bits.b1;
if(myvar.bits.b7)
a=255;
else
a=100;
while(1){;}
}
五: 用指針轉(zhuǎn)換來表示:
#i nclude
typedef struct {
unsigned b0:1, b1:1, b2:1, b3:1, b4:1, b5:1, b6:1, b7:1;
} bits; //先定義一個變量的位
#define mybit0 (((bits *)&myvar)->b0) //取myvar
的地址(&myvar)強制轉(zhuǎn)換成 bits 類型的指針
#define mybit1 (((bits *)&myvar)->b1)
#define mybit2 (((bits *)&myvar)->b2)
#define mybit3 (((bits *)&myvar)->b3)
#define mybit4 (((bits *)&myvar)->b4)
#define mybit5 (((bits *)&myvar)->b5)
#define mybit6 (((bits *)&myvar)->b6)
#define mybit7 (((bits *)&myvar)->b7)
char myvar;
char a,b;
void main(){
myvar=0B10101010;
a=mybit0;
b=mybit1;
if(mybit7)
a=255;
else
a=100;
while(1){;}
}

[NextPage]

本文引用地址:http://m.butianyuan.cn/article/201611/316158.htm六:五的方法還是煩瑣,可以用粘貼符號的形式來簡化它.
#i nclude
typedef struct {
unsigned b0:1, b1:1, b2:1, b3:1, b4:1, b5:1, b6:1, b7:1;
} bits;
#define _paste(a,b) a##b
#define bitof(var,num) (((bits *)&(var))->_paste(b,num))
char myvar;
char a,b;
void main(){
a=bitof(myvar,0);
b=bitof(myvar,1);
if(bitof(myvar,7))
a=255;
else
a=100;
while(1){;}
}
有 必要說說#define _paste(a,b) a##b 的意思:
此語句是粘貼符號的意思,表示把b 符號粘貼到a 符號之后.
例子 中是
a=bitof(myvar,0);--->(((bits
*)& (myvar))->_paste(b,0))--->(((bits *)&(var))->b0)
可以看出 來,_paste(b,0)的作用是把0 粘貼到了b 后面,成了b0 符號.
總結(jié):C語言的優(yōu)勢是能直接對低層硬件操作,代碼可以非常非常接近 匯編,上面幾個例子的位操作代碼
是100%的達到匯編的程度的.另一個優(yōu)勢是可讀性高,代碼靈活.上面的幾個位操作方法任由你選,
你不必 擔(dān)心會產(chǎn)生多余的代碼量出來.
6、在PICC 中使用常數(shù)指針。
常數(shù)指針使用非常靈活,可以給編程帶來很多便利。我測試過,PICC 也支持常數(shù)指針,并且也會自動
分頁,實在是一大喜事。
定義一個指向8 位RAM 數(shù)據(jù)的常數(shù)指針(起始為0x00):
#define DBYTE ((unsigned char volatile *) 0)
定義一個指向16 位RAM 數(shù)據(jù)的常數(shù)指針(起始為0x00):
#define CWORD ((unsigned int volatile *) 0)
((unsigned char volatile *) 0)中的0 表示指向RAM 區(qū)域的起始地址,可以靈活修改它。
DBYTE[x]中的x 表示偏移量。
下面是一段代碼1:
char a1,a2,a3,a4;
#define DBYTE ((unsigned char volatile *) 0)
void main(void){
long cc=0x89abcdef;
a1=DBYTE[0x24];
a2=DBYTE[0x25];
a3=DBYTE[0x26];
a4=DBYTE[0x27];
while(1);
}
2:
char a1,a2,a3,a4;
#define DBYTE ((unsigned char volatile *) 0)
void pp(char y){
a1=DBYTE[y++];
a2=DBYTE[y++];
a3=DBYTE[y++];
a4=DBYTE[y];
}
void main(void){
long cc=0x89abcdef;
char x;
x=&cc;
pp(x);
while(1);
}
3:
char a1,a2,a3,a4;
#define DBYTE ((unsigned char volatile *) 0)
void pp(char y){
a1=DBYTE[y++];
a2=DBYTE[y++];
a3=DBYTE[y++];
a4=DBYTE[y];
}
void main(void){
bank1 static long cc=0x89abcdef;
char x;
x=&cc;
pp(x);
while(1);
}
7、 PICC 關(guān)于unsigned 和 signed 的幾個關(guān)鍵問題!
unsigned 是表示一個變量(或常數(shù))是無符號類型。signed 表示有符號。它們表示數(shù)值范圍不一樣。
PICC 默認所有變量都是unsigned 類型的,哪怕你用了signed 變量。因為有符號運算比無符號運算耗資源,
而且MCU 運算一般不涉及有符號運算。在PICC 后面加上-SIGNED_CHAR 后綴可以告訴PICC 把signed
變量當(dāng)作有符號處理。
在PICC 默認的無符號運算下看這樣的語句:
char i;
for(i=7;i>=0;i--){
; //中間語句
}
這樣的C 代碼看上去是沒有丁點錯誤的,但編譯后,問題出現(xiàn)了:
movlw 7
movwf i
loop
// 中間語句
decf i //只是遞減,沒有判斷語句?。?!
goto loop
原因是當(dāng)i 是0 時候,條件還成立,還得循環(huán)一次,直到i 成負1 條件才不成立。而PICC 在默認參數(shù)下是
不能判斷負數(shù)的,所以編譯過程出現(xiàn)問題。那么采用這 樣的語句來驗證:
char i;
i=7;
while(1){
i--;
//中間語句
if(i==0)break; //告訴PICC 以判斷i 是否是0 來作為條件
}
編譯后代碼正確:
movlw 7
movwf i
loop
// 中間語句
decfsz i //判斷是否是0
goto loop
再編譯這樣的語句:(同樣循環(huán)8 次)
for(i=8;i>0;i--){
;
}
movlw 8
movwf i
loop
decfsz i //同上編譯的代碼。
goto loop
再次驗證了剛才的分析。
在PICC 后面加上-SIGNED_CHAR 后綴,則第一個示例就正確編譯出來了,更證明了剛才的分析是正確的。
代碼如下:
movlw 7
movwf i
loop
//中間語句
decf i //遞減
btfss i,7 //判斷i 的7 位來判斷是否為負數(shù)
goto l94
總結(jié):在PICC 無符號編譯環(huán)境下,對于遞減的for 語句的條件判斷語句不能是>=0 的形式。
最后談?wù)凱ICC 的小竅門:
在PICC 默認的無符號環(huán)境下,對比如下代碼:
a 語句:
char i,j[8];
i=7;
while(1){
j[i]=0;
i--;
if(i==0)break;
}
b 語句:
char i,j[8];
for(i=8;i>0;i--){
j[i-1]=0;
}
表面看上去, 一般會認為下面的代碼編譯后要大一點點,因為多了j[i-1]中的i-1。
其實編譯后代碼量是一摸一樣的。
原因如下:
movlw 8 或7 //a 語句是7,b 語句是8
movf i
loop
//a 語句在這里提取i 給j 數(shù)組
//i 遞減判斷語句
//b 語句在這里提取i 給j 數(shù)組
goto loop
可以看出只是代碼位置不同而已,并沒添加代碼量。b 語句同樣達到了從7 到0 的循環(huán)。
小總結(jié):對于遞減到0 的for 語句推薦用>0 判斷語句來實現(xiàn),不會出現(xiàn)編譯錯誤的問題,并且不會增加代
碼量,尤其對于數(shù)組操作的方面。
另:對于PICC 或CCS,在其默認的無符號編譯環(huán)境下,如果出現(xiàn)負數(shù)運算就會出問題。
如(-100)+50 等,所以在編寫代碼時候要特別小心?。?!
8、 用PICC 寫高效的位移操作。
在許多模擬串行通信中需要用位移操作。
以1-W 總線的讀字節(jié)為例,原廠的代碼是:
unsigned char read_byte(void)
{
unsigned char i;
unsigned char value = 0;
for (i = 0; i < 8; i++)
{
if(read_bit()) value| = 0 x 01<// reads byte in, one byte at a time and then
// shifts it left
delay(10); // wait for rest of timeslot
}
return(value);
}
雖 然可以用,但編譯后執(zhí)行效率并不高效,這也是很多朋友認為C 一定不能和匯編相比的認識提供了
說法。其實完全可以深入了解C 和匯編之間的關(guān)系,寫出非常高效的C 代碼,既有C 的便利,又有匯編的
效率。首先對 for (i = 0; i < 8;
i++) 做手術(shù),改成遞減的形式:for(i=8;i!=0;i--),因為CPU 判斷一個數(shù)是否是0
(只需要一個指令),比判斷一個數(shù)是多大來的快 (需要3 個指令)。再對value| = 0 x 01<value| = 0 x 01<
仔細研究C 語言的位移操作,可以發(fā)現(xiàn)C 總是先把標(biāo)志位清0,然后再把此位移入字節(jié)中,也就是說,當(dāng)
前移動進字節(jié)的位一定是0。那么,既然已經(jīng)是0 了,我們就只剩下一個步驟:判斷總線狀態(tài)是否是高來
決定是否改寫此位,而不需要判斷總線是低的情況。于是改寫如下代碼:
for(i=8;i!=0;i--){
value>>=1; //先右移一位,value 最高位一定是0
if(read_bit()) value|=0x80; //判斷總線狀態(tài),如果是高,就把value 的最高位置1
}
這樣一來,整個代碼變得極其高效,編譯后根本就是匯編級的代碼。再舉一個例 子:
在采集信號方面,經(jīng)常是連續(xù)采集N 次,最后求其平均值。
一般的,無論是用匯編或C,在采集次數(shù)上都推薦用8,16,32、64、 128、256 等次數(shù),因為這些數(shù)都比
較特殊,對于MCU 計算有很大好處。
我們以128 次采樣為例:注:sampling()為外部采樣函數(shù)。
unsigned int total;
unsigned char i,val;
for(i=0;i<128;i++){
total+=sampling();
}
val=total/128;
以 上代碼是很多場合都可以看見的,但是效率并不怎么樣,狂浪費資源。
結(jié)合C 和匯編的關(guān)系,再加上一些技巧,就可以寫出天壤之別的匯編級的C 代碼出來,首先分析128 這個
數(shù)是0B10000000,發(fā)現(xiàn)其第7 位是1,其他低位全是0,那么就可以判斷第7 位的狀態(tài)來判斷是否到了128
次采樣次數(shù)。在分析除以128 的運算,上面的代碼用了除法運算,浪費了N 多資源,完全可以用右移的方
法 來代替之,val=total/128 等同于val=(unsigned
char)(total>>7);再觀察下 去:total>>7 還可以變通成
(total<<1)>>8,先左移動一位,再右移動8 位,不就成了右移7 位了么?可知道位移1,4,8 的操作只需要
一個指令哦。有上面的概驗了,就可以寫出如下的代碼:
unsigned int total;
unsigned char i=0
unsigned char val;
while(!(i&0x80)){ //判斷i 第7 位,只需要一個指令。
total+=sampling();
i++;
}
val=(unsigned char)((total<<1)>>8); //幾個指令就代替了幾十個指令的除法運算
哈哈,發(fā)現(xiàn)什么?代碼量竟然 可以減少一大半,運算速度可以提高幾倍。
再回頭,就可以理解為什么采樣次數(shù)要用推薦的一些特殊值了。
9、C 程序優(yōu)化
對程序進行 優(yōu)化,通常是指優(yōu)化程序代碼或程序執(zhí)行速度。優(yōu)化代碼和優(yōu)化速度實際上是一個予
盾的統(tǒng)一,一般是優(yōu)化了代碼的尺寸,就會帶來執(zhí)行時間的增加,如果 優(yōu)化了程序的執(zhí)行速度,通常會帶
來代碼增加的副作用,很難魚與熊掌兼得,只能在設(shè)計時掌握一個平衡點。
一、程序結(jié)構(gòu)的優(yōu)化
1、程 序的書寫結(jié)構(gòu)
雖然書寫格式并不會影響生成的代碼質(zhì)量,但是在實際編寫程序時還是應(yīng)該尊循一定的書寫規(guī)則,一
個書寫清晰、明了的程序,有利 于以后的維護。在書寫程序時,特別是對于While、for、do…while、if…elst、
switch…case 等語句或這些語句嵌套組合時,應(yīng)采用“縮格”的書寫形式,
2、標(biāo)識符
程序中使用的用戶標(biāo)識符除要遵循標(biāo)識符的命名規(guī)則以外,一般不要用代 數(shù)符號(如a、b、x1、y1)作
為變量名,應(yīng)選取具有相關(guān)含義的英文單詞(或縮寫)或漢語拼音作為標(biāo)識符,以增加程序的可讀性,如:
count、 number1、red、work 等。
3、程序結(jié)構(gòu)
C 語言是一種高級程序設(shè)計語言,提供了十分完備的規(guī)范化流程控制結(jié)構(gòu)。因此在采用C 語言設(shè)計單
片機應(yīng)用系統(tǒng)程序時,首先要注意盡可能采用結(jié)構(gòu)化的 程序設(shè)計方法,這樣可使整個應(yīng)用系統(tǒng)程序結(jié)構(gòu)清
晰,便于調(diào)試和維護。于一個較大的應(yīng)用程序,通常將整個程序按功能分成若干個模塊,不同模塊完成不
同 的功能。各個模塊可以分別編寫,甚至還可以由不同的程序員編寫,一般單個模塊完成的功能較為簡單,
設(shè)計和調(diào)試也相對容易一些。在C 語言中,一個函數(shù)就可以認為是一個模塊。所謂程序模塊化,不僅是要
將整個程序劃分成若干個功能模塊,更重要的是,還應(yīng)該注意保持各個模塊之間變量 的相對獨立性,即保
持模塊的獨立性,盡量少使用全局變量等。對于一些常用的功能模塊,還可以封裝為一個應(yīng)用程序庫,以
便需要時可以直接調(diào) 用。但是在使用模塊化時,如果將模塊分成太細太小,又會導(dǎo)致程序的執(zhí)行效率變低(進
入和退出一個函數(shù)時保護和恢復(fù)寄存器占用了一些時間)。
4、 定義常數(shù)
在程序化設(shè)計過程中,對于經(jīng)常使用的一些常數(shù),如果將它直接寫到程序中去,一旦常數(shù)的數(shù)值發(fā)生
變化,就必須逐個找出程序中所有的 常數(shù),并逐一進行修改,這樣必然會降低程序的可維護性。因此,應(yīng)
盡量當(dāng)采用預(yù)處理命令方式來定義常數(shù),而且還可以避免輸入錯誤。
5、減少 判斷語句
能夠使用條件編譯(ifdef)的地方就使用條件編譯而不使用if 語句,有利于減少編譯生成的代碼的長度。
6、表達式
對 于一個表達式中各種運算執(zhí)行的優(yōu)先順序不太明確或容易混淆的地方,應(yīng)當(dāng)采用圓括號明確指定它
們的優(yōu)先順序。一個表達式通常不能寫得太復(fù)雜,如果表 達式太復(fù)雜,時間久了以后,自己也不容易看得
懂,不利于以后的維護。
7、函數(shù)
對于程序中的函數(shù),在使用之前,應(yīng)對函數(shù)的類型進行 說明,對函數(shù)類型的說明必須保證它與原來定
義的函數(shù)類型一致,對于沒有參數(shù)和沒有返回值類型的函數(shù)應(yīng)加上“void”說明。如果果需要縮短代碼的 長
度,可以將程序中一些公共的程序段定義為函數(shù),在Keil 中的高級別優(yōu)化就是這樣的。如果需要縮短程序
的執(zhí)行時間,在程序調(diào)試結(jié)束 后,將部分函數(shù)用宏定義來代替。注意,應(yīng)該在程序調(diào)試結(jié)束后再定義宏,
因為大多數(shù)編譯系統(tǒng)在宏展開之后才會報錯,這樣會增加排錯的難度。
8、 盡量少用全局變量,多用局部變量。因為全局變量是放在數(shù)據(jù)存儲器中,定義一個全局變量,MCU 就
少一個可以利用的數(shù)據(jù)存儲器空間,如果定義了太 多的全局變量,會導(dǎo)致編譯器無足夠的內(nèi)存可以分配。
而局部變量大多定位于MCU 內(nèi)部的寄存器中,在絕大多數(shù)MCU 中,使用寄存器操作速度比數(shù)據(jù)存儲器快,
指令也更多更靈活,有利于生成質(zhì)量更高的代碼,而且局部變量所的占用的寄存器和數(shù)據(jù)存儲器在不同的
模 塊中可以重復(fù)利用。
9、設(shè)定合適的編譯程序選項
許多編譯程序有幾種不同的優(yōu)化選項,在使用前應(yīng)理解各優(yōu)化選項的含義,然后選用最合適的一 種優(yōu)
化方式。通常情況下一旦選用最高級優(yōu)化,編譯程序會近乎病態(tài)地追求代碼優(yōu)化,可能會影響程序的正確
性,導(dǎo)致程序運行出錯。因此應(yīng)熟悉 所使用的編譯器,應(yīng)知道哪些參數(shù)在優(yōu)化時會受到影響,哪些參數(shù)不
會受到影響。
在ICCAVR 中,有“Default”和“Enable Code Compression”兩個優(yōu)化選項。
在CodeVisionAVR 中,“Tiny”和“small”兩種內(nèi)存模式。
在IAR 中,共有7 種不同的內(nèi)存模式選項。
在GCCAVR 中優(yōu)化選項更多,一不小心更容易選到不恰當(dāng)?shù)倪x項。
二、代碼的優(yōu)化
1、 選擇合適的算法和數(shù)據(jù)結(jié)構(gòu)
應(yīng)該熟悉算法語言,知道各種算法的優(yōu)缺點,具體資料請參見相應(yīng)的參考資料,有很多計算機書籍上
都有介紹。將比較 慢的順序查找法用較快的二分查找或亂序查找法代替,插入排序或冒泡排序法用快速排
序、合并排序或根排序代替,都可以大大提高程序執(zhí)行的效率。.選 擇一種合適的數(shù)據(jù)結(jié)構(gòu)也很重要,比如
你在一堆隨機存放的數(shù)中使用了大量的插入和刪除指令,那使用鏈表要快得多。
數(shù)組與指針具有十分密碼的 關(guān)系,一般來說,指針比較靈活簡潔,而數(shù)組則比較直觀,容易理解。對于大
部分的編譯器,使用指針比使用數(shù)組生成的代碼更短,執(zhí)行效率更高。但是在 Keil 中則相反,使用數(shù)組比
使用的指針生成的代碼更短。
2、 使用盡量小的數(shù)據(jù)類型
能夠使用字符型(char)定義的變量, 就不要使用整型(int)變量來定義;能夠使用整型變量定義的變量就
不要用長整型(long int),能不使用浮點型(float)變量就不要使用浮點型變量。當(dāng)然,在定義變量后不要超過
變量的作用范圍,如果超過變量的范圍賦值,C 編譯器并不報錯,但程序運行結(jié)果卻錯了,而且這樣的錯
誤很難發(fā)現(xiàn)。在ICCAVR 中,可以在Options 中設(shè)定使用printf 參數(shù),盡量使用基本型參數(shù)(%c、%d、%x、
%X、%u 和%s 格式說明符),少用長整型參數(shù)(%ld、%lu、%lx 和%lX 格式說明符),至于浮點型的參數(shù)(%f)
則盡量不要使用,其它C 編譯器也一樣。在其它條件不變的情況下,使用%f 參數(shù),會使生成的代碼的數(shù)量
增 加很多,執(zhí)行速度降低。
3、 使用自加、自減指令
通常使用自加、自減指令和復(fù)合賦值表達式(如a-=1 及a+=1 等)都能夠生成高質(zhì)量的程序代碼,編譯器
通常都能夠生成inc 和dec 之類的指令,而使用a=a+1 或a=a-1 之類的指令,有很多C 編譯器都會生成二到
三個字節(jié)的指令。在AVR 單片適用的ICCAVR、GCCAVR、IAR 等C 編譯器以上幾種書寫方式生成的代
碼 是一樣的,也能夠生成高質(zhì)量的inc 和dec 之類的的代碼。
4、減少運算的強度
可以使用運算量小但功能相同的表達式替換原來復(fù)雜的的 表達式。如下:
(1)、求余運算。
a=a%8;
可以改為:
a=a&7;
說明:位操作只需一個指令周期即 可完成,而大部分的C 編譯器的“%”運算均是調(diào)用子程序來完成,代碼
長、執(zhí)行速度慢。通常,只要求是求2n 方的余數(shù),均可使用位操作的方法來代替。
(2)、平方運算
a=pow(a,2.0);
可以改為:
a=a*a;
說 明:在有內(nèi)置硬件乘法器的單片機中(如51 系列),乘法運算比求平方運算快得多,因為浮點數(shù)的求平方
是通過調(diào)用子程序來實現(xiàn)的,在自帶硬件乘法 器的AVR 單片機中,如ATMega163 中,乘法運算只需2 個
時鐘周期就可以完成。既使是在沒有內(nèi)置硬件乘法器的AVR 單片機中,乘法運算的子程序比平方運算的子
程序代碼短,執(zhí)行速度快。
如果是求3 次方,如:
a=pow(a,3.0);
更 改為:
a=a*a*a;
則效率的改善更明顯。
(3)、用移位實現(xiàn)乘除法運算
a=a*4;
b=b/4;
可 以改為:
a=a<<2;
b=b>>2;
說明:通常如果需要乘以或除以2n,都可以用移位的方法代替。在 ICCAVR 中,如果乘以2n,都可以生
成左移的代碼,而乘以其它的整數(shù)或除以任何數(shù),均調(diào)用乘除法子程序。用移位的方法得到代碼比調(diào)用乘
除 法子程序生成的代碼效率高。實際上,只要是乘以或除以一個整數(shù),均可以用移位的方法得到結(jié)果,如:
a=a*9
可以改為:
a=(a<<3)+a
5、 循環(huán)
(1)、循環(huán)語
對于一些不需要循環(huán)變量參加運算的任務(wù)可以把它們放到循環(huán)外面,這里的任務(wù)包括表達式、函數(shù)的調(diào)用、
指針運 算、數(shù)組訪問等,應(yīng)該將沒有必要執(zhí)行多次的操作全部集合在一起,放到一個init 的初始化程序中
進行。
(2)、延時函數(shù):
通常 使用的延時函數(shù)均采用自加的形式:
void delay (void)
{
unsigned int i;
for (i=0;i<1000;i++)
;
}
將其改為自減延時函數(shù):
void delay (void)
{
unsigned int i;
for (i=1000;i>0;i--)
;
}
兩個函數(shù)的延時效果相似,但幾乎所有的C 編譯對后一種函數(shù)生成的代碼均比前一種代碼少1~3 個字節(jié),
因為幾乎所有的MCU 均有為0 轉(zhuǎn)移的指令,采用后一種方式能夠生成這類指令。
在 使用while 循環(huán)時也一樣,使用自減指令控制循環(huán)會比使用自加指令控制循環(huán)生成的代碼更少1~3 個字
母。
但是在循環(huán)中有通過循環(huán)變 量“i”讀寫數(shù)組的指令時,使用預(yù)減循環(huán)時有可能使數(shù)組超界,要引起注意。
(3)while 循環(huán)和do…while 循環(huán)
用while 循環(huán)時有以下兩種循環(huán)形式:
unsigned int i;
i=0;
while (i<1000)
{
i++;
// 用戶程序
}
或:
unsigned int i;
i=1000;
do
i--;
//用戶程序
while (i>0);
在這兩種循環(huán)中,使用do…while 循環(huán)編譯后生成的代碼的長度短于while 循環(huán)。
6、查表
在程序 中一般不進行非常復(fù)雜的運算,如浮點數(shù)的乘除及開方等,以及一些復(fù)雜的數(shù)學(xué)模型的插補運算,
對這些即消耗時間又消費資源的運算,應(yīng)盡量使用查表的 方式,并且將數(shù)據(jù)表置于程序存儲區(qū)。如果直接
生成所需的表比較困難,也盡量在啟動時先計算,然后在數(shù)據(jù)存儲器中生成所需的表,后以在程序運行直
接 查表就可以了,減少了程序執(zhí)行過程中重復(fù)計算的工作量。
7、其它
比如使用在線匯編及將字符串和一些常量保存在程序存儲器中,均有利于優(yōu) 化。


關(guān)鍵詞: PIC單片機C編程技

評論


技術(shù)專區(qū)

關(guān)閉