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

          新聞中心

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


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

          評(píng)論


          技術(shù)專(zhuān)區(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); })();