<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>

新聞中心

EEPW首頁(yè) > 嵌入式系統 > 設計應用 > PIC單片機 C編程技巧

PIC單片機 C編程技巧

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

PICC和MPLAB集成:
PICC有自己的文本編輯器,不過(guò)是DOS風(fēng)格的,看來(lái)PICC的工程師 要專(zhuān)業(yè)冷到酷底了...
大家大可不必用它,如果你沒(méi)什么癖好的話(huà),你不會(huì )不用UltraEdit 吧?
1:建立你的工作目錄:
建 議在C盤(pán)根目錄下建立一個(gè)以A開(kāi)頭的文件夾做為工作目錄.因為你會(huì )發(fā)現它總是在你查找文件時(shí)候第
一個(gè)跳入你眼中.
2:MPLAB調用 PICC.(以MPLAB5.7版本為例子)
啟動(dòng)MPLAB.在Project-->Install Language Tool:
Language Suite----->hi-tech picc
Tool Name ---->PICC Compiler
Executable ---->c:hi-picinpicc.exe (假如你的PICC是默認安裝的)
選Command-line
最后OK.
上 面這步只需要設定一次,除非你重新安裝了MPLAB.
3:創(chuàng )建你的項目文件:(假如你實(shí)現用EDIT編輯好了一個(gè)叫AA.C的C代碼文件)
Project-->New Project-->File Name--->myc (假如我們把項目文件取名字叫MYC.PJT)
右邊窗口當然要選擇中你的 工作目錄.然后OK.
4:設定你的PICC工作參數:
Project-->Edit Project
上面4個(gè)欄目就用默認 的,空的也就讓它空著(zhù),無(wú)所謂的.
需要修改的是:
Development Mode---->選擇你的PIC型號.當然要選擇Mplab SIM Simulator
讓你可以用軟件仿真.
Language Tool Suite--->HI-TECH PICC
上面的步驟,你可能會(huì )遇見(jiàn)多個(gè)提示條,不要管它,一路確定.
下面是 PICC編譯器的選擇項:
雙擊Project Files 窗口里面的MYC.HEX,出現一個(gè)選擇攔目.命令很多,大家可以看PICC文本編
輯 器里面的HELP,里面有詳細說(shuō)明.
下面就推薦幾個(gè)常用也是建議用的:
Generate debug info 以及下面的2項.
Produce assembler list file
就在它們后面打勾即可,其它的不要管,除非你有特殊要求.
5:添加你的C代碼文件:
當 進(jìn)行了前面幾步后,按Add Node 找到AA.C文件就OK了.
6:編譯C代碼:
最簡(jiǎn)單的一步:直接按下F10.
編譯完后, 會(huì )出現各種調試信息.C代碼對應的匯編代碼就是工作目錄里面的AA.IST,用EDIT
打開(kāi)可以看見(jiàn)詳細的對比.
7:其它,要是一切都沒(méi) 問(wèn)題,那么你就可以調試和燒片了,和以往操作無(wú)異.
2、如何從匯編轉向PICC
首先要求你要有C 語(yǔ)言的基礎。PICC 不支持C++,這對于習慣了C++的朋友還得翻翻C 語(yǔ)言的書(shū)。C
代碼的頭文件一定要有#i nclude,它是很多頭文件的集合,C 編譯器在pic.h 中根據你的芯片自動(dòng)栽
入相應的其它頭文件。這點(diǎn)比匯編 好用。載入的頭文件中其實(shí)是聲明芯片的寄存器和一些函數。順便摘抄
一個(gè)片段:
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;
都是把無(wú)聊的地址定義為大家公認的名字。
一: 怎么附值?
如對TMR0 附值,匯編中:
MOVLW 200;
MOVWF TMR0;
當然得保證當前頁(yè)面在0,不然會(huì )出 錯。
C 語(yǔ)言:
TMR0=200;//無(wú)論在任何頁(yè)面都不會(huì )出錯。
可以看出來(lái)C 是很直接了當的。并且最大好處是操作一個(gè)寄存器時(shí)候,不用考慮頁(yè)面的問(wèn)題。一切由
C 自動(dòng)完成。
二:怎么位操作?
匯編中的位操作 是很容易的。在C 中更簡(jiǎn)單。C 的頭文件中已經(jīng)對所有可能需要位操作的寄存器的每
一位都有定義名稱(chēng):
如:PORTA 的每一個(gè)I/O 口定義為:RA0、RA1、RA2。。。RA7。OPTION 的每一位定義為:PS0、
PS1、PS2 、PSA 、T0SE、T0CS、INTEDG 、RBPU??梢詫ζ渲苯舆M(jìn)行運算和附值。
如:
RA0=0;
RA2=1;
在匯編中 是:
BCF PORTA,0;
BSF PORTA,2;
可以看出2 者是大同小異的,只是C 中不需要考慮頁(yè)面的問(wèn)題。
三: 內存分配問(wèn)題:
在匯編中定義一個(gè)內存是一件很小心的問(wèn)題,要考慮太多的問(wèn)題,稍微不注意就會(huì )出錯。比如16 位的
運算等。用C 就不需要考慮太多。下面給個(gè)例子:
16 位的除法(C 代碼):
INT X=5000;
INT Y=1000;
INT Z=X/Y;
而在匯編中則需要花太多精力。
給一個(gè)小的C 代碼,用RA0 控制一個(gè)LED 閃爍:
#i nclude
void main()
{
int x;
CMCON=0B111; //掉A 口比較器,要是有比較器功能的話(huà)。
ADCON1=0B110; //掉A/D 功能,要是有A/D 功能的話(huà)。
TRISA=0; //RA 口全為輸出。
loop:RA0=!RA0;
for(x=60000;--x;){;} //延時(shí)
goto loop;
}
說(shuō) 說(shuō)RA0=!RA0 的意思:PIC 對PORT 寄存器操作都是先讀取----修改----寫(xiě)入。上句的含義是程序先
讀RA0,然后取反,最后 把運算后的值重新寫(xiě)入RA0,這就實(shí)現了閃爍的功能。
3、淺談PICC 的位操作
由于PIC 處理器對位操作是最高效的,所以把一些BOOL 變量放在一個(gè)內存的位中,既可以達到運算
速度快,又可以達到最大限度節省空間的目的。在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)
*************************************************
但 上述的方法有缺點(diǎn),就是對每一位的含義不直觀(guān),最好是能在代碼中能直觀(guān)看出每一位代表的意思,
這樣就能提高編程效率,避免出錯。如果我們想用X 的0-2 位分別表示溫度、電壓、電流的BOOL 值可以
如下:
unsigned char x @ 0x20; /*象匯編那樣把X 變量定義到一個(gè)固定內存中。*/
bit temperature@ (unsigned)&x*8+0; /*溫度*/
bit voltage@ (unsigned)&x*8+1; /*電壓*/
bit current@ (unsigned)&x*8+2; /*電流 */
這樣定義后X 的位就有一個(gè)形象化的名字,不再是枯燥的1、2、3、4 等數字了??梢詫 全局修改,
也可以對每一位進(jìn)行操作:
char=255;
temperature=0;
if(voltage)......
*****************************************************************
還 有一個(gè)方法是用C 的struct 結構來(lái)定義:
如:
struct cypok{
temperature:1; /*溫度*/
voltage:1; /*電壓*/
current:1; /*電流*/
none:4;
}x @ 0x20;
這樣就可以用
x.temperature=0;
if(x.current)....
等 操作了。
**********************************************************
上面 的方法在一些簡(jiǎn)單的設計中很有效,但對于復雜的設計中就比較吃力。如象在多路工業(yè)控制上。
前端需要分別收集多路的多路信號,然后再設定控制多路的 多路輸出。如:有2 路控制,每一路的前端信
號有溫度、電壓、電流。后端控制有電機、喇叭、繼電器、LED。如果用匯編來(lái)實(shí)現的話(huà),是很頭疼的事
情, 用C 來(lái)實(shí)現是很輕松的事情,這里也涉及到一點(diǎn)C 的內存管理(其實(shí)C 的最大優(yōu)點(diǎn)就是內存管理)。
采用如下結構:
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;
上面的結構有什么好處呢?
細分了信號的路an1 和an2;
細 分了每一路的信號的類(lèi)型(是前端信號in 還是后端信號out):
an1.in ;
an1.out;
an2.in;
an2.out;
然 后又細分了每一路信號的具體含義,如:
an1.in.temperature;
an1.out.motor;
an2.in.voltage;
an2.out.led2; 等
這樣的結構很直觀(guān)的在2 個(gè)內存中就表示了2 路信號。并且可以極其方便的擴充。
如添加更多路的信號,只需要添加:
union cypok an3;
union cypok an4;
從上面就可以看出用C 的巨大好處
4、PICC 之延時(shí)函數和循環(huán)體優(yōu)化。
很多朋友說(shuō)C 中不能精確控制延時(shí)時(shí)間,不能象匯編那樣直觀(guān)。其實(shí)不然,對延時(shí)函數深入了解一下
就能設計出一個(gè) 理想的框價(jià)出來(lái)。一般的我們都用for(x=100;--x;){;}此句等同與x=100;while(--x){;};
或for(x=0; x<100;x++){;}。
來(lái)寫(xiě)一個(gè)延時(shí)函數。
在這里要特別注意:X=100,并不表示只運行100 個(gè)指令時(shí)間就跳出循環(huán)。
可 以看看編譯后的匯編:
x=100;while(--x){;}
匯編后:
movlw 100
bcf 3,5
bcf 3,6
movwf _delay
l2 decfsz _delay
goto l2
return
從代碼可以看出 總的指令是是303 個(gè),其公式是8+3*(X-1)。注意其中循環(huán)周期是X-1 是99 個(gè)。這
里總結的是x 為char 類(lèi)型的循環(huán)體,當x 為int 時(shí)候,其中受X 值的影響較大。建議設計一個(gè)char 類(lèi)型的
循環(huán)體,然后再用一個(gè)循環(huán)體來(lái)調用它,可以實(shí)現精確的長(cháng)時(shí)間的延時(shí)。下 面給出一個(gè)能精確控制延時(shí)的
函數,此函數的匯編代碼是最簡(jiǎn)潔、最能精確控制指令時(shí)間的:
void delay(char x,char y){
char z;
do{
z=y;
do{;}while(--z);
}while(--x);
}
其 指令時(shí)間為:7+(3*(Y-1)+7)*(X-1)如果再加上函數調用的call 指令、頁(yè)面設定、傳遞參數
花掉的7 個(gè)指令。則是:14+(3*(Y-1)+7)*(X-1)。如果要求不是特別嚴格的延時(shí),可以用這個(gè)函數:
void delay(){
unsigned int d=1000;
while(--d){;}
}
此函數在4M 晶體下產(chǎn)生10003us 的延時(shí),也就是10MS。如果把D 改成2000,則是20003us,以此類(lèi)
推。有朋友不明白,為什么不用while(x--)后減量,來(lái)控制 設定X 值是多少就循環(huán)多少周期呢?現在看看編
譯它的匯編代碼:
bcf 3,5
bcf 3,6
movlw 10
movwf _delay
l2
decf _delay
incfsz _delay,w
goto l2
return
可 以看出循環(huán)體中多了一條指令,不簡(jiǎn)潔。所以在PICC 中最好用前減量來(lái)控制循環(huán)體。
再談?wù)勥@樣的語(yǔ)句:
for(x=100;--x;) {;}和for(x=0;x<100;x++){;}
從字面上看2 者意思一樣,但可以通過(guò)匯編查看代碼。后者代碼雍長(cháng),而前者就很好的匯編出了簡(jiǎn)潔的代
碼。所以在PICC 中最好用前者的形式來(lái)寫(xiě)循環(huán)體,好的C 編譯器會(huì )自動(dòng)把增量循環(huán)化為減量循環(huán)。因為
這是由處理器硬件特性決定的。PICC 并不是一個(gè)很智能的C 編譯器,所以還是人腦才是第一的,掌握一些
經(jīng)驗對寫(xiě)出高效,簡(jiǎn)潔的代碼是有好處的。
5、深入探討PICC之位操作
一:用位操作來(lái) 做一些標志位,也就是BOOL變量.可以簡(jiǎn)單如下定義:
bit a,b,c;
PICC會(huì )自動(dòng)安排一個(gè)內存,并在此內存中自動(dòng)安排一位來(lái)對 應a,b,c.由于我們只是用它們來(lái)簡(jiǎn)單的
表示一些0,1信息,所以我們不需要詳細的知道它們的地址\位究竟是多少,只管拿來(lái)就用好了.
二: 要是需要用一個(gè)地址固定的變量來(lái)位操作,可以參照PIC.H里面定義寄存器.
如:用25H內存來(lái)定義8個(gè)位變量.
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系列上可能會(huì )出問(wèn)題.
還有就是表達起來(lái)復雜,你不覺(jué)得輸入代碼受累么?呵呵
三:這也是一些常用手法:
#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調試觀(guān)察
#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){;}
}
四: 用標準C的共用體來(lái)表示:
#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){;}
}
五: 用指針轉換來(lái)表示:
#i nclude
typedef struct {
unsigned b0:1, b1:1, b2:1, b3:1, b4:1, b5:1, b6:1, b7:1;
} bits; //先定義一個(gè)變量的位
#define mybit0 (((bits *)&myvar)->b0) //取myvar
的地址(&myvar)強制轉換成 bits 類(lèi)型的指針
#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://dyxdggzs.com/article/201611/316158.htm六:五的方法還是煩瑣,可以用粘貼符號的形式來(lái)簡(jiǎn)化它.
#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){;}
}
有 必要說(shuō)說(shuō)#define _paste(a,b) a##b 的意思:
此語(yǔ)句是粘貼符號的意思,表示把b 符號粘貼到a 符號之后.
例子 中是
a=bitof(myvar,0);--->(((bits
*)& (myvar))->_paste(b,0))--->(((bits *)&(var))->b0)
可以看出 來(lái),_paste(b,0)的作用是把0 粘貼到了b 后面,成了b0 符號.
總結:C語(yǔ)言的優(yōu)勢是能直接對低層硬件操作,代碼可以非常非常接近 匯編,上面幾個(gè)例子的位操作代碼
是100%的達到匯編的程度的.另一個(gè)優(yōu)勢是可讀性高,代碼靈活.上面的幾個(gè)位操作方法任由你選,
你不必 擔心會(huì )產(chǎn)生多余的代碼量出來(lái).
6、在PICC 中使用常數指針。
常數指針使用非常靈活,可以給編程帶來(lái)很多便利。我測試過(guò),PICC 也支持常數指針,并且也會(huì )自動(dòng)
分頁(yè),實(shí)在是一大喜事。
定義一個(gè)指向8 位RAM 數據的常數指針(起始為0x00):
#define DBYTE ((unsigned char volatile *) 0)
定義一個(gè)指向16 位RAM 數據的常數指針(起始為0x00):
#define CWORD ((unsigned int volatile *) 0)
((unsigned char volatile *) 0)中的0 表示指向RAM 區域的起始地址,可以靈活修改它。
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 的幾個(gè)關(guān)鍵問(wèn)題!
unsigned 是表示一個(gè)變量(或常數)是無(wú)符號類(lèi)型。signed 表示有符號。它們表示數值范圍不一樣。
PICC 默認所有變量都是unsigned 類(lèi)型的,哪怕你用了signed 變量。因為有符號運算比無(wú)符號運算耗資源,
而且MCU 運算一般不涉及有符號運算。在PICC 后面加上-SIGNED_CHAR 后綴可以告訴PICC 把signed
變量當作有符號處理。
在PICC 默認的無(wú)符號運算下看這樣的語(yǔ)句:
char i;
for(i=7;i>=0;i--){
; //中間語(yǔ)句
}
這樣的C 代碼看上去是沒(méi)有丁點(diǎn)錯誤的,但編譯后,問(wèn)題出現了:
movlw 7
movwf i
loop
// 中間語(yǔ)句
decf i //只是遞減,沒(méi)有判斷語(yǔ)句?。?!
goto loop
原因是當i 是0 時(shí)候,條件還成立,還得循環(huán)一次,直到i 成負1 條件才不成立。而PICC 在默認參數下是
不能判斷負數的,所以編譯過(guò)程出現問(wèn)題。那么采用這 樣的語(yǔ)句來(lái)驗證:
char i;
i=7;
while(1){
i--;
//中間語(yǔ)句
if(i==0)break; //告訴PICC 以判斷i 是否是0 來(lái)作為條件
}
編譯后代碼正確:
movlw 7
movwf i
loop
// 中間語(yǔ)句
decfsz i //判斷是否是0
goto loop
再編譯這樣的語(yǔ)句:(同樣循環(huán)8 次)
for(i=8;i>0;i--){
;
}
movlw 8
movwf i
loop
decfsz i //同上編譯的代碼。
goto loop
再次驗證了剛才的分析。
在PICC 后面加上-SIGNED_CHAR 后綴,則第一個(gè)示例就正確編譯出來(lái)了,更證明了剛才的分析是正確的。
代碼如下:
movlw 7
movwf i
loop
//中間語(yǔ)句
decf i //遞減
btfss i,7 //判斷i 的7 位來(lái)判斷是否為負數
goto l94
總結:在PICC 無(wú)符號編譯環(huán)境下,對于遞減的for 語(yǔ)句的條件判斷語(yǔ)句不能是>=0 的形式。
最后談?wù)凱ICC 的小竅門(mén):
在PICC 默認的無(wú)符號環(huán)境下,對比如下代碼:
a 語(yǔ)句:
char i,j[8];
i=7;
while(1){
j[i]=0;
i--;
if(i==0)break;
}
b 語(yǔ)句:
char i,j[8];
for(i=8;i>0;i--){
j[i-1]=0;
}
表面看上去, 一般會(huì )認為下面的代碼編譯后要大一點(diǎn)點(diǎn),因為多了j[i-1]中的i-1。
其實(shí)編譯后代碼量是一摸一樣的。
原因如下:
movlw 8 或7 //a 語(yǔ)句是7,b 語(yǔ)句是8
movf i
loop
//a 語(yǔ)句在這里提取i 給j 數組
//i 遞減判斷語(yǔ)句
//b 語(yǔ)句在這里提取i 給j 數組
goto loop
可以看出只是代碼位置不同而已,并沒(méi)添加代碼量。b 語(yǔ)句同樣達到了從7 到0 的循環(huán)。
小總結:對于遞減到0 的for 語(yǔ)句推薦用>0 判斷語(yǔ)句來(lái)實(shí)現,不會(huì )出現編譯錯誤的問(wèn)題,并且不會(huì )增加代
碼量,尤其對于數組操作的方面。
另:對于PICC 或CCS,在其默認的無(wú)符號編譯環(huán)境下,如果出現負數運算就會(huì )出問(wèn)題。
如(-100)+50 等,所以在編寫(xiě)代碼時(shí)候要特別小心?。?!
8、 用PICC 寫(xiě)高效的位移操作。
在許多模擬串行通信中需要用位移操作。
以1-W 總線(xiàn)的讀字節為例,原廠(chǎng)的代碼是:
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);
}
雖 然可以用,但編譯后執行效率并不高效,這也是很多朋友認為C 一定不能和匯編相比的認識提供了
說(shuō)法。其實(shí)完全可以深入了解C 和匯編之間的關(guān)系,寫(xiě)出非常高效的C 代碼,既有C 的便利,又有匯編的
效率。首先對 for (i = 0; i < 8;
i++) 做手術(shù),改成遞減的形式:for(i=8;i!=0;i--),因為CPU 判斷一個(gè)數是否是0
(只需要一個(gè)指令),比判斷一個(gè)數是多大來(lái)的快 (需要3 個(gè)指令)。再對value| = 0 x 01<value| = 0 x 01<
仔細研究C 語(yǔ)言的位移操作,可以發(fā)現C 總是先把標志位清0,然后再把此位移入字節中,也就是說(shuō),當
前移動(dòng)進(jìn)字節的位一定是0。那么,既然已經(jīng)是0 了,我們就只剩下一個(gè)步驟:判斷總線(xiàn)狀態(tài)是否是高來(lái)
決定是否改寫(xiě)此位,而不需要判斷總線(xiàn)是低的情況。于是改寫(xiě)如下代碼:
for(i=8;i!=0;i--){
value>>=1; //先右移一位,value 最高位一定是0
if(read_bit()) value|=0x80; //判斷總線(xiàn)狀態(tài),如果是高,就把value 的最高位置1
}
這樣一來(lái),整個(gè)代碼變得極其高效,編譯后根本就是匯編級的代碼。再舉一個(gè)例 子:
在采集信號方面,經(jīng)常是連續采集N 次,最后求其平均值。
一般的,無(wú)論是用匯編或C,在采集次數上都推薦用8,16,32、64、 128、256 等次數,因為這些數都比
較特殊,對于MCU 計算有很大好處。
我們以128 次采樣為例:注:sampling()為外部采樣函數。
unsigned int total;
unsigned char i,val;
for(i=0;i<128;i++){
total+=sampling();
}
val=total/128;
以 上代碼是很多場(chǎng)合都可以看見(jiàn)的,但是效率并不怎么樣,狂浪費資源。
結合C 和匯編的關(guān)系,再加上一些技巧,就可以寫(xiě)出天壤之別的匯編級的C 代碼出來(lái),首先分析128 這個(gè)
數是0B10000000,發(fā)現其第7 位是1,其他低位全是0,那么就可以判斷第7 位的狀態(tài)來(lái)判斷是否到了128
次采樣次數。在分析除以128 的運算,上面的代碼用了除法運算,浪費了N 多資源,完全可以用右移的方
法 來(lái)代替之,val=total/128 等同于val=(unsigned
char)(total>>7);再觀(guān)察下 去:total>>7 還可以變通成
(total<<1)>>8,先左移動(dòng)一位,再右移動(dòng)8 位,不就成了右移7 位了么?可知道位移1,4,8 的操作只需要
一個(gè)指令哦。有上面的概驗了,就可以寫(xiě)出如下的代碼:
unsigned int total;
unsigned char i=0
unsigned char val;
while(!(i&0x80)){ //判斷i 第7 位,只需要一個(gè)指令。
total+=sampling();
i++;
}
val=(unsigned char)((total<<1)>>8); //幾個(gè)指令就代替了幾十個(gè)指令的除法運算
哈哈,發(fā)現什么?代碼量竟然 可以減少一大半,運算速度可以提高幾倍。
再回頭,就可以理解為什么采樣次數要用推薦的一些特殊值了。
9、C 程序優(yōu)化
對程序進(jìn)行 優(yōu)化,通常是指優(yōu)化程序代碼或程序執行速度。優(yōu)化代碼和優(yōu)化速度實(shí)際上是一個(gè)予
盾的統一,一般是優(yōu)化了代碼的尺寸,就會(huì )帶來(lái)執行時(shí)間的增加,如果 優(yōu)化了程序的執行速度,通常會(huì )帶
來(lái)代碼增加的副作用,很難魚(yú)與熊掌兼得,只能在設計時(shí)掌握一個(gè)平衡點(diǎn)。
一、程序結構的優(yōu)化
1、程 序的書(shū)寫(xiě)結構
雖然書(shū)寫(xiě)格式并不會(huì )影響生成的代碼質(zhì)量,但是在實(shí)際編寫(xiě)程序時(shí)還是應該尊循一定的書(shū)寫(xiě)規則,一
個(gè)書(shū)寫(xiě)清晰、明了的程序,有利 于以后的維護。在書(shū)寫(xiě)程序時(shí),特別是對于While、for、do…while、if…elst、
switch…case 等語(yǔ)句或這些語(yǔ)句嵌套組合時(shí),應采用“縮格”的書(shū)寫(xiě)形式,
2、標識符
程序中使用的用戶(hù)標識符除要遵循標識符的命名規則以外,一般不要用代 數符號(如a、b、x1、y1)作
為變量名,應選取具有相關(guān)含義的英文單詞(或縮寫(xiě))或漢語(yǔ)拼音作為標識符,以增加程序的可讀性,如:
count、 number1、red、work 等。
3、程序結構
C 語(yǔ)言是一種高級程序設計語(yǔ)言,提供了十分完備的規范化流程控制結構。因此在采用C 語(yǔ)言設計單
片機應用系統程序時(shí),首先要注意盡可能采用結構化的 程序設計方法,這樣可使整個(gè)應用系統程序結構清
晰,便于調試和維護。于一個(gè)較大的應用程序,通常將整個(gè)程序按功能分成若干個(gè)模塊,不同模塊完成不
同 的功能。各個(gè)模塊可以分別編寫(xiě),甚至還可以由不同的程序員編寫(xiě),一般單個(gè)模塊完成的功能較為簡(jiǎn)單,
設計和調試也相對容易一些。在C 語(yǔ)言中,一個(gè)函數就可以認為是一個(gè)模塊。所謂程序模塊化,不僅是要
將整個(gè)程序劃分成若干個(gè)功能模塊,更重要的是,還應該注意保持各個(gè)模塊之間變量 的相對獨立性,即保
持模塊的獨立性,盡量少使用全局變量等。對于一些常用的功能模塊,還可以封裝為一個(gè)應用程序庫,以
便需要時(shí)可以直接調 用。但是在使用模塊化時(shí),如果將模塊分成太細太小,又會(huì )導致程序的執行效率變低(進(jìn)
入和退出一個(gè)函數時(shí)保護和恢復寄存器占用了一些時(shí)間)。
4、 定義常數
在程序化設計過(guò)程中,對于經(jīng)常使用的一些常數,如果將它直接寫(xiě)到程序中去,一旦常數的數值發(fā)生
變化,就必須逐個(gè)找出程序中所有的 常數,并逐一進(jìn)行修改,這樣必然會(huì )降低程序的可維護性。因此,應
盡量當采用預處理命令方式來(lái)定義常數,而且還可以避免輸入錯誤。
5、減少 判斷語(yǔ)句
能夠使用條件編譯(ifdef)的地方就使用條件編譯而不使用if 語(yǔ)句,有利于減少編譯生成的代碼的長(cháng)度。
6、表達式
對 于一個(gè)表達式中各種運算執行的優(yōu)先順序不太明確或容易混淆的地方,應當采用圓括號明確指定它
們的優(yōu)先順序。一個(gè)表達式通常不能寫(xiě)得太復雜,如果表 達式太復雜,時(shí)間久了以后,自己也不容易看得
懂,不利于以后的維護。
7、函數
對于程序中的函數,在使用之前,應對函數的類(lèi)型進(jìn)行 說(shuō)明,對函數類(lèi)型的說(shuō)明必須保證它與原來(lái)定
義的函數類(lèi)型一致,對于沒(méi)有參數和沒(méi)有返回值類(lèi)型的函數應加上“void”說(shuō)明。如果果需要縮短代碼的 長(cháng)
度,可以將程序中一些公共的程序段定義為函數,在Keil 中的高級別優(yōu)化就是這樣的。如果需要縮短程序
的執行時(shí)間,在程序調試結束 后,將部分函數用宏定義來(lái)代替。注意,應該在程序調試結束后再定義宏,
因為大多數編譯系統在宏展開(kāi)之后才會(huì )報錯,這樣會(huì )增加排錯的難度。
8、 盡量少用全局變量,多用局部變量。因為全局變量是放在數據存儲器中,定義一個(gè)全局變量,MCU 就
少一個(gè)可以利用的數據存儲器空間,如果定義了太 多的全局變量,會(huì )導致編譯器無(wú)足夠的內存可以分配。
而局部變量大多定位于MCU 內部的寄存器中,在絕大多數MCU 中,使用寄存器操作速度比數據存儲器快,
指令也更多更靈活,有利于生成質(zhì)量更高的代碼,而且局部變量所的占用的寄存器和數據存儲器在不同的
模 塊中可以重復利用。
9、設定合適的編譯程序選項
許多編譯程序有幾種不同的優(yōu)化選項,在使用前應理解各優(yōu)化選項的含義,然后選用最合適的一 種優(yōu)
化方式。通常情況下一旦選用最高級優(yōu)化,編譯程序會(huì )近乎病態(tài)地追求代碼優(yōu)化,可能會(huì )影響程序的正確
性,導致程序運行出錯。因此應熟悉 所使用的編譯器,應知道哪些參數在優(yōu)化時(shí)會(huì )受到影響,哪些參數不
會(huì )受到影響。
在ICCAVR 中,有“Default”和“Enable Code Compression”兩個(gè)優(yōu)化選項。
在CodeVisionAVR 中,“Tiny”和“small”兩種內存模式。
在IAR 中,共有7 種不同的內存模式選項。
在GCCAVR 中優(yōu)化選項更多,一不小心更容易選到不恰當的選項。
二、代碼的優(yōu)化
1、 選擇合適的算法和數據結構
應該熟悉算法語(yǔ)言,知道各種算法的優(yōu)缺點(diǎn),具體資料請參見(jiàn)相應的參考資料,有很多計算機書(shū)籍上
都有介紹。將比較 慢的順序查找法用較快的二分查找或亂序查找法代替,插入排序或冒泡排序法用快速排
序、合并排序或根排序代替,都可以大大提高程序執行的效率。.選 擇一種合適的數據結構也很重要,比如
你在一堆隨機存放的數中使用了大量的插入和刪除指令,那使用鏈表要快得多。
數組與指針具有十分密碼的 關(guān)系,一般來(lái)說(shuō),指針比較靈活簡(jiǎn)潔,而數組則比較直觀(guān),容易理解。對于大
部分的編譯器,使用指針比使用數組生成的代碼更短,執行效率更高。但是在 Keil 中則相反,使用數組比
使用的指針生成的代碼更短。
2、 使用盡量小的數據類(lèi)型
能夠使用字符型(char)定義的變量, 就不要使用整型(int)變量來(lái)定義;能夠使用整型變量定義的變量就
不要用長(cháng)整型(long int),能不使用浮點(diǎn)型(float)變量就不要使用浮點(diǎn)型變量。當然,在定義變量后不要超過(guò)
變量的作用范圍,如果超過(guò)變量的范圍賦值,C 編譯器并不報錯,但程序運行結果卻錯了,而且這樣的錯
誤很難發(fā)現。在ICCAVR 中,可以在Options 中設定使用printf 參數,盡量使用基本型參數(%c、%d、%x、
%X、%u 和%s 格式說(shuō)明符),少用長(cháng)整型參數(%ld、%lu、%lx 和%lX 格式說(shuō)明符),至于浮點(diǎn)型的參數(%f)
則盡量不要使用,其它C 編譯器也一樣。在其它條件不變的情況下,使用%f 參數,會(huì )使生成的代碼的數量
增 加很多,執行速度降低。
3、 使用自加、自減指令
通常使用自加、自減指令和復合賦值表達式(如a-=1 及a+=1 等)都能夠生成高質(zhì)量的程序代碼,編譯器
通常都能夠生成inc 和dec 之類(lèi)的指令,而使用a=a+1 或a=a-1 之類(lèi)的指令,有很多C 編譯器都會(huì )生成二到
三個(gè)字節的指令。在A(yíng)VR 單片適用的ICCAVR、GCCAVR、IAR 等C 編譯器以上幾種書(shū)寫(xiě)方式生成的代
碼 是一樣的,也能夠生成高質(zhì)量的inc 和dec 之類(lèi)的的代碼。
4、減少運算的強度
可以使用運算量小但功能相同的表達式替換原來(lái)復雜的的 表達式。如下:
(1)、求余運算。
a=a%8;
可以改為:
a=a&7;
說(shuō)明:位操作只需一個(gè)指令周期即 可完成,而大部分的C 編譯器的“%”運算均是調用子程序來(lái)完成,代碼
長(cháng)、執行速度慢。通常,只要求是求2n 方的余數,均可使用位操作的方法來(lái)代替。
(2)、平方運算
a=pow(a,2.0);
可以改為:
a=a*a;
說(shuō) 明:在有內置硬件乘法器的單片機中(如51 系列),乘法運算比求平方運算快得多,因為浮點(diǎn)數的求平方
是通過(guò)調用子程序來(lái)實(shí)現的,在自帶硬件乘法 器的AVR 單片機中,如ATMega163 中,乘法運算只需2 個(gè)
時(shí)鐘周期就可以完成。既使是在沒(méi)有內置硬件乘法器的AVR 單片機中,乘法運算的子程序比平方運算的子
程序代碼短,執行速度快。
如果是求3 次方,如:
a=pow(a,3.0);
更 改為:
a=a*a*a;
則效率的改善更明顯。
(3)、用移位實(shí)現乘除法運算
a=a*4;
b=b/4;
可 以改為:
a=a<<2;
b=b>>2;
說(shuō)明:通常如果需要乘以或除以2n,都可以用移位的方法代替。在 ICCAVR 中,如果乘以2n,都可以生
成左移的代碼,而乘以其它的整數或除以任何數,均調用乘除法子程序。用移位的方法得到代碼比調用乘
除 法子程序生成的代碼效率高。實(shí)際上,只要是乘以或除以一個(gè)整數,均可以用移位的方法得到結果,如:
a=a*9
可以改為:
a=(a<<3)+a
5、 循環(huán)
(1)、循環(huán)語(yǔ)
對于一些不需要循環(huán)變量參加運算的任務(wù)可以把它們放到循環(huán)外面,這里的任務(wù)包括表達式、函數的調用、
指針運 算、數組訪(fǎng)問(wèn)等,應該將沒(méi)有必要執行多次的操作全部集合在一起,放到一個(gè)init 的初始化程序中
進(jìn)行。
(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--)
;
}
兩個(gè)函數的延時(shí)效果相似,但幾乎所有的C 編譯對后一種函數生成的代碼均比前一種代碼少1~3 個(gè)字節,
因為幾乎所有的MCU 均有為0 轉移的指令,采用后一種方式能夠生成這類(lèi)指令。
在 使用while 循環(huán)時(shí)也一樣,使用自減指令控制循環(huán)會(huì )比使用自加指令控制循環(huán)生成的代碼更少1~3 個(gè)字
母。
但是在循環(huán)中有通過(guò)循環(huán)變 量“i”讀寫(xiě)數組的指令時(shí),使用預減循環(huán)時(shí)有可能使數組超界,要引起注意。
(3)while 循環(huán)和do…while 循環(huán)
用while 循環(huán)時(shí)有以下兩種循環(huán)形式:
unsigned int i;
i=0;
while (i<1000)
{
i++;
// 用戶(hù)程序
}
或:
unsigned int i;
i=1000;
do
i--;
//用戶(hù)程序
while (i>0);
在這兩種循環(huán)中,使用do…while 循環(huán)編譯后生成的代碼的長(cháng)度短于while 循環(huán)。
6、查表
在程序 中一般不進(jìn)行非常復雜的運算,如浮點(diǎn)數的乘除及開(kāi)方等,以及一些復雜的數學(xué)模型的插補運算,
對這些即消耗時(shí)間又消費資源的運算,應盡量使用查表的 方式,并且將數據表置于程序存儲區。如果直接
生成所需的表比較困難,也盡量在啟動(dòng)時(shí)先計算,然后在數據存儲器中生成所需的表,后以在程序運行直
接 查表就可以了,減少了程序執行過(guò)程中重復計算的工作量。
7、其它
比如使用在線(xiàn)匯編及將字符串和一些常量保存在程序存儲器中,均有利于優(yōu) 化。


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

評論


技術(shù)專(zhuān)區

關(guān)閉
国产精品自在自线亚洲|国产精品无圣光一区二区|国产日产欧洲无码视频|久久久一本精品99久久K精品66|欧美人与动牲交片免费播放
<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>