<meter id="pryje"><nav id="pryje"><delect id="pryje"></delect></nav></meter>
          <label id="pryje"></label>

          新聞中心

          EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > PIC單片機(jī) C編程技巧

          PIC單片機(jī) C編程技巧

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

          PICC和MPLAB集成:
          PICC有自己的文本編輯器,不過是DOS風(fēng)格的,看來PICC的工程師 要專業(yè)冷到酷底了...
          大家大可不必用它,如果你沒什么癖好的話,你不會不用UltraEdit 吧?
          1:建立你的工作目錄:
          建 議在C盤根目錄下建立一個以A開頭的文件夾做為工作目錄.因?yàn)槟銜l(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是默認(rèn)安裝的)
          選Command-line
          最后OK.
          上 面這步只需要設(shè)定一次,除非你重新安裝了MPLAB.
          3:創(chuàng)建你的項(xiàng)目文件:(假如你實(shí)現(xiàn)用EDIT編輯好了一個叫AA.C的C代碼文件)
          Project-->New Project-->File Name--->myc (假如我們把項(xiàng)目文件取名字叫MYC.PJT)
          右邊窗口當(dāng)然要選擇中你的 工作目錄.然后OK.
          4:設(shè)定你的PICC工作參數(shù):
          Project-->Edit Project
          上面4個欄目就用默認(rèn) 的,空的也就讓它空著,無所謂的.
          需要修改的是:
          Development Mode---->選擇你的PIC型號.當(dāng)然要選擇Mplab SIM Simulator
          讓你可以用軟件仿真.
          Language Tool Suite--->HI-TECH PICC
          上面的步驟,你可能會遇見多個提示條,不要管它,一路確定.
          下面是 PICC編譯器的選擇項(xiàng):
          雙擊Project Files 窗口里面的MYC.HEX,出現(xiàn)一個選擇攔目.命令很多,大家可以看PICC文本編
          輯 器里面的HELP,里面有詳細(xì)說明.
          下面就推薦幾個常用也是建議用的:
          Generate debug info 以及下面的2項(xiàng).
          Produce assembler list file
          就在它們后面打勾即可,其它的不要管,除非你有特殊要求.
          5:添加你的C代碼文件:
          當(dāng) 進(jìn)行了前面幾步后,按Add Node 找到AA.C文件就OK了.
          6:編譯C代碼:
          最簡單的一步:直接按下F10.
          編譯完后, 會出現(xiàn)各種調(diào)試信息.C代碼對應(yīng)的匯編代碼就是工作目錄里面的AA.IST,用EDIT
          打開可以看見詳細(xì)的對比.
          7:其它,要是一切都沒 問題,那么你就可以調(diào)試和燒片了,和以往操作無異.
          2、如何從匯編轉(zhuǎn)向PICC
          首先要求你要有C 語言的基礎(chǔ)。PICC 不支持C++,這對于習(xí)慣了C++的朋友還得翻翻C 語言的書。C
          代碼的頭文件一定要有#i nclude,它是很多頭文件的集合,C 編譯器在pic.h 中根據(jù)你的芯片自動栽
          入相應(yīng)的其它頭文件。這點(diǎn)比匯編 好用。載入的頭文件中其實(shí)是聲明芯片的寄存器和一些函數(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;
          都是把無聊的地址定義為大家公認(rèn)的名字。
          一: 怎么附值?
          如對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(jìn)行運(yùn)算和附值。
          如:
          RA0=0;
          RA2=1;
          在匯編中 是:
          BCF PORTA,0;
          BSF PORTA,2;
          可以看出2 者是大同小異的,只是C 中不需要考慮頁面的問題。
          三: 內(nèi)存分配問題:
          在匯編中定義一個內(nèi)存是一件很小心的問題,要考慮太多的問題,稍微不注意就會出錯。比如16 位的
          運(yùn)算等。用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,然后取反,最后 把運(yùn)算后的值重新寫入RA0,這就實(shí)現(xiàn)了閃爍的功能。
          3、淺談PICC 的位操作
          由于PIC 處理器對位操作是最高效的,所以把一些BOOL 變量放在一個內(nèi)存的位中,既可以達(dá)到運(yùn)算
          速度快,又可以達(dá)到最大限度節(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)
          *************************************************
          但 上述的方法有缺點(diǎn),就是對每一位的含義不直觀,最好是能在代碼中能直觀看出每一位代表的意思,
          這樣就能提高編程效率,避免出錯。如果我們想用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ù)字了。可以對X 全局修改,
          也可以對每一位進(jìn)行操作:
          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è)計(jì)中很有效,但對于復(fù)雜的設(shè)計(jì)中就比較吃力。如象在多路工業(yè)控制上。
          前端需要分別收集多路的多路信號,然后再設(shè)定控制多路的 多路輸出。如:有2 路控制,每一路的前端信
          號有溫度、電壓、電流。后端控制有電機(jī)、喇叭、繼電器、LED。如果用匯編來實(shí)現(xiàn)的話,是很頭疼的事
          情, 用C 來實(shí)現(xiàn)是很輕松的事情,這里也涉及到一點(diǎn)C 的內(nèi)存管理(其實(shí)C 的最大優(yōu)點(diǎn)就是內(nèi)存管理)。
          采用如下結(jié)構(gòu):
          union cypok{
          struct out{
          motor:1; /*電機(jī)*/
          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)有什么好處呢?
          細(xì)分了信號的路an1 和an2;
          細(xì) 分了每一路的信號的類型(是前端信號in 還是后端信號out):
          an1.in ;
          an1.out;
          an2.in;
          an2.out;
          然 后又細(xì)分了每一路信號的具體含義,如:
          an1.in.temperature;
          an1.out.motor;
          an2.in.voltage;
          an2.out.led2; 等
          這樣的結(jié)構(gòu)很直觀的在2 個內(nèi)存中就表示了2 路信號。并且可以極其方便的擴(kuò)充。
          如添加更多路的信號,只需要添加:
          union cypok an3;
          union cypok an4;
          從上面就可以看出用C 的巨大好處
          4、PICC 之延時函數(shù)和循環(huán)體優(yōu)化。
          很多朋友說C 中不能精確控制延時時間,不能象匯編那樣直觀。其實(shí)不然,對延時函數(shù)深入了解一下
          就能設(shè)計(jì)出一個 理想的框價出來。一般的我們都用for(x=100;--x;){;}此句等同與x=100;while(--x){;};
          或for(x=0; x<100;x++){;}。
          來寫一個延時函數(shù)。
          在這里要特別注意:X=100,并不表示只運(yùn)行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è)計(jì)一個char 類型的
          循環(huán)體,然后再用一個循環(huán)體來調(diào)用它,可以實(shí)現(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)。因?yàn)?br />這是由處理器硬件特性決定的。PICC 并不是一個很智能的C 編譯器,所以還是人腦才是第一的,掌握一些
          經(jīng)驗(yàn)對寫出高效,簡潔的代碼是有好處的。
          5、深入探討PICC之位操作
          一:用位操作來 做一些標(biāo)志位,也就是BOOL變量.可以簡單如下定義:
          bit a,b,c;
          PICC會自動安排一個內(nèi)存,并在此內(nèi)存中自動安排一位來對 應(yīng)a,b,c.由于我們只是用它們來簡單的
          表示一些0,1信息,所以我們不需要詳細(xì)的知道它們的地址\位究竟是多少,只管拿來就用好了.
          二: 要是需要用一個地址固定的變量來位操作,可以參照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系列上可能會出問題.
          還有就是表達(dá)起來復(fù)雜,你不覺得輸入代碼受累么?呵呵
          三:這也是一些常用手法:
          #define testbit(var, bit) ((var) & (1 <<(bit)))
          //測試某一位,可以做BOOL運(yùn)算
          #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)強(qiáng)制轉(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://www.ex-cimer.com/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%的達(dá)到匯編的程度的.另一個優(yōu)勢是可讀性高,代碼靈活.上面的幾個位操作方法任由你選,
          你不必 擔(dān)心會產(chǎn)生多余的代碼量出來.
          6、在PICC 中使用常數(shù)指針。
          常數(shù)指針使用非常靈活,可以給編程帶來很多便利。我測試過,PICC 也支持常數(shù)指針,并且也會自動
          分頁,實(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 默認(rèn)所有變量都是unsigned 類型的,哪怕你用了signed 變量。因?yàn)橛蟹栠\(yùn)算比無符號運(yùn)算耗資源,
          而且MCU 運(yùn)算一般不涉及有符號運(yùn)算。在PICC 后面加上-SIGNED_CHAR 后綴可以告訴PICC 把signed
          變量當(dāng)作有符號處理。
          在PICC 默認(rèn)的無符號運(yùn)算下看這樣的語句:
          char i;
          for(i=7;i>=0;i--){
          ; //中間語句
          }
          這樣的C 代碼看上去是沒有丁點(diǎn)錯誤的,但編譯后,問題出現(xiàn)了:
          movlw 7
          movwf i
          loop
          // 中間語句
          decf i //只是遞減,沒有判斷語句?。?!
          goto loop
          原因是當(dāng)i 是0 時候,條件還成立,還得循環(huán)一次,直到i 成負(fù)1 條件才不成立。而PICC 在默認(rèn)參數(shù)下是
          不能判斷負(fù)數(shù)的,所以編譯過程出現(xiàn)問題。那么采用這 樣的語句來驗(yà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
          再次驗(yàn)證了剛才的分析。
          在PICC 后面加上-SIGNED_CHAR 后綴,則第一個示例就正確編譯出來了,更證明了剛才的分析是正確的。
          代碼如下:
          movlw 7
          movwf i
          loop
          //中間語句
          decf i //遞減
          btfss i,7 //判斷i 的7 位來判斷是否為負(fù)數(shù)
          goto l94
          總結(jié):在PICC 無符號編譯環(huán)境下,對于遞減的for 語句的條件判斷語句不能是>=0 的形式。
          最后談?wù)凱ICC 的小竅門:
          在PICC 默認(rèn)的無符號環(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;
          }
          表面看上去, 一般會認(rèn)為下面的代碼編譯后要大一點(diǎn)點(diǎn),因?yàn)槎嗔薺[i-1]中的i-1。
          其實(shí)編譯后代碼量是一摸一樣的。
          原因如下:
          movlw 8 或7 //a 語句是7,b 語句是8
          movf i
          loop
          //a 語句在這里提取i 給j 數(shù)組
          //i 遞減判斷語句
          //b 語句在這里提取i 給j 數(shù)組
          goto loop
          可以看出只是代碼位置不同而已,并沒添加代碼量。b 語句同樣達(dá)到了從7 到0 的循環(huán)。
          小總結(jié):對于遞減到0 的for 語句推薦用>0 判斷語句來實(shí)現(xiàn),不會出現(xiàn)編譯錯誤的問題,并且不會增加代
          碼量,尤其對于數(shù)組操作的方面。
          另:對于PICC 或CCS,在其默認(rèn)的無符號編譯環(huán)境下,如果出現(xiàn)負(fù)數(shù)運(yùn)算就會出問題。
          如(-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í)行效率并不高效,這也是很多朋友認(rèn)為C 一定不能和匯編相比的認(rèn)識提供了
          說法。其實(shí)完全可以深入了解C 和匯編之間的關(guān)系,寫出非常高效的C 代碼,既有C 的便利,又有匯編的
          效率。首先對 for (i = 0; i < 8;
          i++) 做手術(shù),改成遞減的形式:for(i=8;i!=0;i--),因?yàn)镃PU 判斷一個數(shù)是否是0
          (只需要一個指令),比判斷一個數(shù)是多大來的快 (需要3 個指令)。再對value| = 0 x 01<value| = 0 x 01<
          仔細(xì)研究C 語言的位移操作,可以發(fā)現(xiàn)C 總是先把標(biāo)志位清0,然后再把此位移入字節(jié)中,也就是說,當(dāng)
          前移動進(jìn)字節(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ù),因?yàn)檫@些數(shù)都比
          較特殊,對于MCU 計(jì)算有很大好處。
          我們以128 次采樣為例:注:sampling()為外部采樣函數(shù)。
          unsigned int total;
          unsigned char i,val;
          for(i=0;i<128;i++){
          total+=sampling();
          }
          val=total/128;
          以 上代碼是很多場合都可以看見的,但是效率并不怎么樣,狂浪費(fèi)資源。
          結(jié)合C 和匯編的關(guān)系,再加上一些技巧,就可以寫出天壤之別的匯編級的C 代碼出來,首先分析128 這個
          數(shù)是0B10000000,發(fā)現(xiàn)其第7 位是1,其他低位全是0,那么就可以判斷第7 位的狀態(tài)來判斷是否到了128
          次采樣次數(shù)。在分析除以128 的運(yùn)算,上面的代碼用了除法運(yùn)算,浪費(fèi)了N 多資源,完全可以用右移的方
          法 來代替之,val=total/128 等同于val=(unsigned
          char)(total>>7);再觀察下 去:total>>7 還可以變通成
          (total<<1)>>8,先左移動一位,再右移動8 位,不就成了右移7 位了么?可知道位移1,4,8 的操作只需要
          一個指令哦。有上面的概驗(yàn)了,就可以寫出如下的代碼:
          unsigned int total;
          unsigned char i=0
          unsigned char val;
          while(!(i&0x80)){ //判斷i 第7 位,只需要一個指令。
          total+=sampling();
          i++;
          }
          val=(unsigned char)((total<<1)>>8); //幾個指令就代替了幾十個指令的除法運(yùn)算
          哈哈,發(fā)現(xiàn)什么?代碼量竟然 可以減少一大半,運(yùn)算速度可以提高幾倍。
          再回頭,就可以理解為什么采樣次數(shù)要用推薦的一些特殊值了。
          9、C 程序優(yōu)化
          對程序進(jìn)行 優(yōu)化,通常是指優(yōu)化程序代碼或程序執(zhí)行速度。優(yōu)化代碼和優(yōu)化速度實(shí)際上是一個予
          盾的統(tǒng)一,一般是優(yōu)化了代碼的尺寸,就會帶來執(zhí)行時間的增加,如果 優(yōu)化了程序的執(zhí)行速度,通常會帶
          來代碼增加的副作用,很難魚與熊掌兼得,只能在設(shè)計(jì)時掌握一個平衡點(diǎn)。
          一、程序結(jié)構(gòu)的優(yōu)化
          1、程 序的書寫結(jié)構(gòu)
          雖然書寫格式并不會影響生成的代碼質(zhì)量,但是在實(shí)際編寫程序時還是應(yīng)該尊循一定的書寫規(guī)則,一
          個書寫清晰、明了的程序,有利 于以后的維護(hù)。在書寫程序時,特別是對于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è)計(jì)語言,提供了十分完備的規(guī)范化流程控制結(jié)構(gòu)。因此在采用C 語言設(shè)計(jì)單
          片機(jī)應(yīng)用系統(tǒng)程序時,首先要注意盡可能采用結(jié)構(gòu)化的 程序設(shè)計(jì)方法,這樣可使整個應(yīng)用系統(tǒng)程序結(jié)構(gòu)清
          晰,便于調(diào)試和維護(hù)。于一個較大的應(yīng)用程序,通常將整個程序按功能分成若干個模塊,不同模塊完成不
          同 的功能。各個模塊可以分別編寫,甚至還可以由不同的程序員編寫,一般單個模塊完成的功能較為簡單,
          設(shè)計(jì)和調(diào)試也相對容易一些。在C 語言中,一個函數(shù)就可以認(rèn)為是一個模塊。所謂程序模塊化,不僅是要
          將整個程序劃分成若干個功能模塊,更重要的是,還應(yīng)該注意保持各個模塊之間變量 的相對獨(dú)立性,即保
          持模塊的獨(dú)立性,盡量少使用全局變量等。對于一些常用的功能模塊,還可以封裝為一個應(yīng)用程序庫,以
          便需要時可以直接調(diào) 用。但是在使用模塊化時,如果將模塊分成太細(xì)太小,又會導(dǎo)致程序的執(zhí)行效率變低(進(jìn)
          入和退出一個函數(shù)時保護(hù)和恢復(fù)寄存器占用了一些時間)。
          4、 定義常數(shù)
          在程序化設(shè)計(jì)過程中,對于經(jīng)常使用的一些常數(shù),如果將它直接寫到程序中去,一旦常數(shù)的數(shù)值發(fā)生
          變化,就必須逐個找出程序中所有的 常數(shù),并逐一進(jìn)行修改,這樣必然會降低程序的可維護(hù)性。因此,應(yīng)
          盡量當(dāng)采用預(yù)處理命令方式來定義常數(shù),而且還可以避免輸入錯誤。
          5、減少 判斷語句
          能夠使用條件編譯(ifdef)的地方就使用條件編譯而不使用if 語句,有利于減少編譯生成的代碼的長度。
          6、表達(dá)式
          對 于一個表達(dá)式中各種運(yùn)算執(zhí)行的優(yōu)先順序不太明確或容易混淆的地方,應(yīng)當(dāng)采用圓括號明確指定它
          們的優(yōu)先順序。一個表達(dá)式通常不能寫得太復(fù)雜,如果表 達(dá)式太復(fù)雜,時間久了以后,自己也不容易看得
          懂,不利于以后的維護(hù)。
          7、函數(shù)
          對于程序中的函數(shù),在使用之前,應(yīng)對函數(shù)的類型進(jìn)行 說明,對函數(shù)類型的說明必須保證它與原來定
          義的函數(shù)類型一致,對于沒有參數(shù)和沒有返回值類型的函數(shù)應(yīng)加上“void”說明。如果果需要縮短代碼的 長
          度,可以將程序中一些公共的程序段定義為函數(shù),在Keil 中的高級別優(yōu)化就是這樣的。如果需要縮短程序
          的執(zhí)行時間,在程序調(diào)試結(jié)束 后,將部分函數(shù)用宏定義來代替。注意,應(yīng)該在程序調(diào)試結(jié)束后再定義宏,
          因?yàn)榇蠖鄶?shù)編譯系統(tǒng)在宏展開之后才會報錯,這樣會增加排錯的難度。
          8、 盡量少用全局變量,多用局部變量。因?yàn)槿肿兞渴欠旁跀?shù)據(jù)存儲器中,定義一個全局變量,MCU 就
          少一個可以利用的數(shù)據(jù)存儲器空間,如果定義了太 多的全局變量,會導(dǎo)致編譯器無足夠的內(nèi)存可以分配。
          而局部變量大多定位于MCU 內(nèi)部的寄存器中,在絕大多數(shù)MCU 中,使用寄存器操作速度比數(shù)據(jù)存儲器快,
          指令也更多更靈活,有利于生成質(zhì)量更高的代碼,而且局部變量所的占用的寄存器和數(shù)據(jù)存儲器在不同的
          模 塊中可以重復(fù)利用。
          9、設(shè)定合適的編譯程序選項(xiàng)
          許多編譯程序有幾種不同的優(yōu)化選項(xiàng),在使用前應(yīng)理解各優(yōu)化選項(xiàng)的含義,然后選用最合適的一 種優(yōu)
          化方式。通常情況下一旦選用最高級優(yōu)化,編譯程序會近乎病態(tài)地追求代碼優(yōu)化,可能會影響程序的正確
          性,導(dǎo)致程序運(yùn)行出錯。因此應(yīng)熟悉 所使用的編譯器,應(yīng)知道哪些參數(shù)在優(yōu)化時會受到影響,哪些參數(shù)不
          會受到影響。
          在ICCAVR 中,有“Default”和“Enable Code Compression”兩個優(yōu)化選項(xiàng)。
          在CodeVisionAVR 中,“Tiny”和“small”兩種內(nèi)存模式。
          在IAR 中,共有7 種不同的內(nèi)存模式選項(xiàng)。
          在GCCAVR 中優(yōu)化選項(xiàng)更多,一不小心更容易選到不恰當(dāng)?shù)倪x項(xiàng)。
          二、代碼的優(yōu)化
          1、 選擇合適的算法和數(shù)據(jù)結(jié)構(gòu)
          應(yīng)該熟悉算法語言,知道各種算法的優(yōu)缺點(diǎn),具體資料請參見相應(yīng)的參考資料,有很多計(jì)算機(jī)書籍上
          都有介紹。將比較 慢的順序查找法用較快的二分查找或亂序查找法代替,插入排序或冒泡排序法用快速排
          序、合并排序或根排序代替,都可以大大提高程序執(zhí)行的效率。.選 擇一種合適的數(shù)據(jù)結(jié)構(gòu)也很重要,比如
          你在一堆隨機(jī)存放的數(shù)中使用了大量的插入和刪除指令,那使用鏈表要快得多。
          數(shù)組與指針具有十分密碼的 關(guān)系,一般來說,指針比較靈活簡潔,而數(shù)組則比較直觀,容易理解。對于大
          部分的編譯器,使用指針比使用數(shù)組生成的代碼更短,執(zhí)行效率更高。但是在 Keil 中則相反,使用數(shù)組比
          使用的指針生成的代碼更短。
          2、 使用盡量小的數(shù)據(jù)類型
          能夠使用字符型(char)定義的變量, 就不要使用整型(int)變量來定義;能夠使用整型變量定義的變量就
          不要用長整型(long int),能不使用浮點(diǎn)型(float)變量就不要使用浮點(diǎn)型變量。當(dāng)然,在定義變量后不要超過
          變量的作用范圍,如果超過變量的范圍賦值,C 編譯器并不報錯,但程序運(yùn)行結(jié)果卻錯了,而且這樣的錯
          誤很難發(fā)現(xiàn)。在ICCAVR 中,可以在Options 中設(shè)定使用printf 參數(shù),盡量使用基本型參數(shù)(%c、%d、%x、
          %X、%u 和%s 格式說明符),少用長整型參數(shù)(%ld、%lu、%lx 和%lX 格式說明符),至于浮點(diǎn)型的參數(shù)(%f)
          則盡量不要使用,其它C 編譯器也一樣。在其它條件不變的情況下,使用%f 參數(shù),會使生成的代碼的數(shù)量
          增 加很多,執(zhí)行速度降低。
          3、 使用自加、自減指令
          通常使用自加、自減指令和復(fù)合賦值表達(dá)式(如a-=1 及a+=1 等)都能夠生成高質(zhì)量的程序代碼,編譯器
          通常都能夠生成inc 和dec 之類的指令,而使用a=a+1 或a=a-1 之類的指令,有很多C 編譯器都會生成二到
          三個字節(jié)的指令。在AVR 單片適用的ICCAVR、GCCAVR、IAR 等C 編譯器以上幾種書寫方式生成的代
          碼 是一樣的,也能夠生成高質(zhì)量的inc 和dec 之類的的代碼。
          4、減少運(yùn)算的強(qiáng)度
          可以使用運(yùn)算量小但功能相同的表達(dá)式替換原來復(fù)雜的的 表達(dá)式。如下:
          (1)、求余運(yùn)算。
          a=a%8;
          可以改為:
          a=a&7;
          說明:位操作只需一個指令周期即 可完成,而大部分的C 編譯器的“%”運(yùn)算均是調(diào)用子程序來完成,代碼
          長、執(zhí)行速度慢。通常,只要求是求2n 方的余數(shù),均可使用位操作的方法來代替。
          (2)、平方運(yùn)算
          a=pow(a,2.0);
          可以改為:
          a=a*a;
          說 明:在有內(nèi)置硬件乘法器的單片機(jī)中(如51 系列),乘法運(yùn)算比求平方運(yùn)算快得多,因?yàn)?strong>浮點(diǎn)數(shù)的求平方
          是通過調(diào)用子程序來實(shí)現(xiàn)的,在自帶硬件乘法 器的AVR 單片機(jī)中,如ATMega163 中,乘法運(yùn)算只需2 個
          時鐘周期就可以完成。既使是在沒有內(nèi)置硬件乘法器的AVR 單片機(jī)中,乘法運(yùn)算的子程序比平方運(yùn)算的子
          程序代碼短,執(zhí)行速度快。
          如果是求3 次方,如:
          a=pow(a,3.0);
          更 改為:
          a=a*a*a;
          則效率的改善更明顯。
          (3)、用移位實(shí)現(xiàn)乘除法運(yùn)算
          a=a*4;
          b=b/4;
          可 以改為:
          a=a<<2;
          b=b>>2;
          說明:通常如果需要乘以或除以2n,都可以用移位的方法代替。在 ICCAVR 中,如果乘以2n,都可以生
          成左移的代碼,而乘以其它的整數(shù)或除以任何數(shù),均調(diào)用乘除法子程序。用移位的方法得到代碼比調(diào)用乘
          除 法子程序生成的代碼效率高。實(shí)際上,只要是乘以或除以一個整數(shù),均可以用移位的方法得到結(jié)果,如:
          a=a*9
          可以改為:
          a=(a<<3)+a
          5、 循環(huán)
          (1)、循環(huán)語
          對于一些不需要循環(huán)變量參加運(yùn)算的任務(wù)可以把它們放到循環(huán)外面,這里的任務(wù)包括表達(dá)式、函數(shù)的調(diào)用、
          指針運(yùn) 算、數(shù)組訪問等,應(yīng)該將沒有必要執(zhí)行多次的操作全部集合在一起,放到一個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--)
          ;
          }
          兩個函數(shù)的延時效果相似,但幾乎所有的C 編譯對后一種函數(shù)生成的代碼均比前一種代碼少1~3 個字節(jié),
          因?yàn)閹缀跛械腗CU 均有為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、查表
          在程序 中一般不進(jìn)行非常復(fù)雜的運(yùn)算,如浮點(diǎn)數(shù)的乘除及開方等,以及一些復(fù)雜的數(shù)學(xué)模型的插補(bǔ)運(yùn)算,
          對這些即消耗時間又消費(fèi)資源的運(yùn)算,應(yīng)盡量使用查表的 方式,并且將數(shù)據(jù)表置于程序存儲區(qū)。如果直接
          生成所需的表比較困難,也盡量在啟動時先計(jì)算,然后在數(shù)據(jù)存儲器中生成所需的表,后以在程序運(yùn)行直
          接 查表就可以了,減少了程序執(zhí)行過程中重復(fù)計(jì)算的工作量。
          7、其它
          比如使用在線匯編及將字符串和一些常量保存在程序存儲器中,均有利于優(yōu) 化。


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

          評論


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

          關(guān)閉
          看屁屁www成人影院,亚洲人妻成人图片,亚洲精品成人午夜在线,日韩在线 欧美成人 (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();