



版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
PIC單片機C語言編程講義奧
科
電
子
ェ
作
室(內(nèi)部資料)
2006年元月第一版第1章PIC單片機的C語言編程pic單片機c語言編程簡介用C語言來開發(fā)單片機系統(tǒng)軟件最大的好處是編寫代碼效率髙、軟件調(diào)試直觀、維護升級方便、代碼的重復(fù)利用率高、便于跨平臺的代碼移植等等,因此c語言編程在單片機系統(tǒng)設(shè)計中已得到越來越廣泛的運用。針對PIC單片機的軟件開發(fā),同樣可以用C語言實現(xiàn)。但在單片機上用C語言寫程序和在PC機上寫程序絕對不能簡單等同?,F(xiàn)在的PC機資源十分豐富,運算能力強大,因此程序員在寫PC機的應(yīng)用程序時幾乎不用關(guān)心編譯后的可執(zhí)行代碼在運行過程中需要占用多少系統(tǒng)資源,也基本不用擔(dān)心運行效率有多高。寫單片機的C程序最關(guān)鍵的一點是單片機內(nèi)的資源非常有限,控制的實時性要求又很高,因此,如果沒有對單片機體系結(jié)構(gòu)和硬件資源作詳盡的了解,以筆者的愚見認為是無法寫出髙質(zhì)量實用的C語言程序。這就是為什么前面所有章節(jié)中的的示范代碼全部用基礎(chǔ)的匯編指令實現(xiàn)的原因,希望籍此能使讀者對PIC單片機的指令體系和硬件資源有深入了解,在這基礎(chǔ)之上再來討論C語言編程,就有水到渠成的感覺。本講稿圍繞中檔系列PIC單片機來展開討論,Microchip公司自己沒有針對中低檔系列PIC單片機的C語言編譯器,但很多專業(yè)的第三方公司有眾多支持PIC單片機的C語言編譯器提供,常見的有Hitech、CCS、IAR、Bytecraft等公司。其中筆者最常用的是Hitech公司的PICC編譯器,它穩(wěn)定可靠,編譯生成的代碼效率高,在用PIC單片機進行系統(tǒng)設(shè)計和開發(fā)的工程師群體中得到廣泛認可。其正式完全版軟件需要購置,但在其網(wǎng)站上有限時的試用版供用戶評估。另外,Hitech公司針對廣大PIC的業(yè)余愛好者和初學(xué)者還提供了完全免費的學(xué)習(xí)版PICC-Lite編譯器套件,它的使用方式和完全版相同,只是支持的PIC單片機型號限制在PIC16F84、PIC16F877和PIC16F628等幾款。這幾款Flash型的單片機因其所具備的豐富的片上資源而最適用于單片機學(xué)習(xí)入門,因此筆者建議感興趣的讀者可從PICC-Lite入手掌握PIC單片機的C語言編程。在此列出兒個主要的針對PIC單片機的C編譯器相關(guān)連接網(wǎng)址,供讀者參考:Hitech-PICC:IAR:CCS:/picc.shtmlByteCraft:/mpccaps.html本章將介紹Hitech-PICC編譯器的ー些基本概念,由于篇幅所限將不涉及C語言的標(biāo)準(zhǔn)語法和基礎(chǔ)知識介紹,因為在這些方面都有大量的書籍可以參考。重點突出針對PIC單片機的特點而所需要特別注意的地方。Hitech-PICC編譯器PICC基本上符合ANSI標(biāo)準(zhǔn),除了一點:它不支持函數(shù)的遞歸調(diào)用。其主要原因是因為PIC單片機特殊的堆棧結(jié)構(gòu)。在前面介紹PIC單片機架構(gòu)時已經(jīng)詳細說明了PIC單片機中的堆棧是硬件實現(xiàn)的,其深度已隨芯片而固定,無法實現(xiàn)需要大量堆棧操作的遞歸算法;另外在PIC單片機中實現(xiàn)軟件堆棧的效率也不是很高,為此,PICC編譯器采用?種叫做“靜態(tài)覆蓋”的技術(shù)以實現(xiàn)對C語言函數(shù)中的局部變量分配固定的地址空間。經(jīng)這樣處理后產(chǎn)生出的機器代碼效率很髙,按筆者實際使用的體會,當(dāng)代碼量超過4K字后,C語言編譯出的代碼長度和全部用匯編代碼實現(xiàn)時的差別已經(jīng)不是很大(<10%),當(dāng)然前提是在整個C代碼編寫過程中須時時處處注意所編寫語句的效率,而如果沒有對PIC單片機的內(nèi)核結(jié)構(gòu)、各功能模塊及其匯編指令深入了解,要做到這點是很難的。MPLAB-IDE內(nèi)掛接PICCPICC編譯器可以直接掛接在MPLAB-IDE集成開發(fā)平臺下,實現(xiàn)一體化的編譯連接和原代碼調(diào)試。使用MPLAB-IDE內(nèi)的調(diào)試工具ICE200〇、ICD2和軟件模擬器都可以實現(xiàn)原代碼級的程序調(diào)試,非常方便。首先必須在你的計算機中安裝PICC編譯器,無論是完全版還是學(xué)習(xí)版都可以和MPLAB-IDE掛接。安裝成功后可以進入IDE,選擇菜單項ProjectSetLanguageToolLoeations...,打開語言工具掛接設(shè)置對話框,如圖所示:在對話框中選擇“HI-TECHPICCToolsuite"欄,展開可執(zhí)行文件組“Executable”后,列出了將被MPLAB-IDE后臺調(diào)用的編譯器所用到的所有可執(zhí)行文件,其中有匯編編譯器“PICCAssembler"、C原程序編譯器*'PICCCompiler"和連接定位程序“PICCLinker”。同時在此列表中還顯示了對應(yīng)的可執(zhí)行程序名,請注意在這里都是“PICC.EXE”。用鼠標(biāo)分別點擊選中這三項可執(zhí)行文件,觀察對話框下面“Location”ー欄中顯示的文件路徑,用“Browse…”按紐,從計算機中已經(jīng)安裝的PICC編譯器文件夾中選擇PICC.EXE文件。實際上PICC.EXE只是ー個調(diào)度管理程序,它會按照所輸入的文件擴展名自動調(diào)用對應(yīng)的編譯器和連接器,用戶要注意的是C語言原程序擴展名用“.c”,匯編原程序用“.as”即可。工具掛接完成后,在建立項目時可以選擇語言工具為“HI-TECHPICC”,具體步驟可以參閱第三章3エ3節(jié),此處不再重復(fù)。項目建立完成后可以加入C或匯編原程序,也可以加入已有的庫文件或已經(jīng)編譯的目標(biāo)文件。最常見的是只加入C原程序。用C語言編程的好處是可以實現(xiàn)模塊化編程。程序編寫者應(yīng)盡量把相互獨立的控制任務(wù)用多個獨立的C原程序文件實現(xiàn),如果程序量較大,一般不要把所有的代碼寫在ー個文件內(nèi)。圖2列出的是筆者建立的ー個項目中所有C原程序模塊,其中主控、數(shù)值計算、12c總線操作、命令按犍處理和液晶顯示驅(qū)動等不同的功能分別在不同的獨立的原程序模塊中實現(xiàn)。PC68.acv*.I忙1除-S—raFtl??><€6一e?lc><46ユれcKtejttchWJLc4?t4?rFi1?k
K?hObjertF&IabLibraryFilesPIC單片機的C語言原程序基本框架基于PICC編譯環(huán)境編寫PIC單片機程序的基本方式和標(biāo)準(zhǔn)C程序類似,程序一般由以下幾個主要部分組成:在程序的最前面用#include預(yù)處理指令引用包含頭文件,其中必須包含一個編譯器提供的“pic.h”文件,實現(xiàn)單片機內(nèi)特殊寄存器和其它特殊符號的聲明;用“_CONFIG”預(yù)處理指令定義芯片的配置位;聲明本模塊內(nèi)被調(diào)用的所有函數(shù)的類型,PICC將對所調(diào)用的函數(shù)進行嚴(yán)格的類型匹配檢查;定義全局變量或符號替換;實現(xiàn)函數(shù)(子程序),特別注意main函數(shù)必須是ー個沒有返回的死循環(huán)。下面的例1-1為ー個C原程序的范例,供大家參考。?include<pic.h>/,包含單片機內(nèi)部資源預(yù)定義/includeupc68.h-ル包含自定義頭文件//定義芯片工作時的配置位__CONFIG(HS&PROTECT&P加TEN&BOREN&加TDIS);//聲明本模塊中所調(diào)用的函數(shù)類型voidSetSFR(void);voidClock(void);voidKeyScan(void);voidMeasure(void);voidLCD_Test(void);voidLCD.Disp(unsignedchar);〃定義變量unsignedcharsecond,minute,hour;bitfIag1,fIag2;I!函數(shù)和子程序圖1-2C語言多模塊編程voidmain(void)(SetSFR();PORTC=0x00;TMR1H+=TMR1H_CONST;LED1?LED.OFF;LCD.Test();//程序工作主循環(huán)whiIe(1){asm(-clrwdt");//清看門狗Clock();//更新時鐘KeyScan()!”掃描鍵盤Measured;/ノ數(shù)據(jù)測量SetSFR();〃刷新特殊功能寄存器))例1-1C語言原程序框架舉例PICC中的變量定義PICC中的基本變量類型PICC支持的基本變量類型見表1-1:類型■(位數(shù))3-セ送b*it1布爾型位變量,?;?兩艸取值char8有符號或無符號字符変量,PKC缺省認定charSHE?為無符「?里”!?以通「川『米頂改為ハ衿り帽節(jié)變貴unsignedchar8無符號字符變量short16有符號整型數(shù)unsignedshort16無符號整型數(shù)int16有符號整型故unsignedint16無符號整里數(shù)long32有符號長整型數(shù)unsignedlong32無符號長整里數(shù)float24浮點數(shù)double24或32浮點數(shù),PKC缺省認定double型變?為24位長,但可以改変編譯選項改成32位表レ1PICC的基本變量:類型PICC遵循Little-endian標(biāo)準(zhǔn),多字節(jié)變量的低字節(jié)放在存儲空間的低地址,高字節(jié)放在高地址。PICC中的髙級變量基于表1-1的基本變量,除了bit型位變量外,PICC完全支持數(shù)組、結(jié)構(gòu)和聯(lián)合等復(fù)合型高級變量,這和標(biāo)準(zhǔn)的C語言所支持的髙級變量類型沒有什么區(qū)別。例如:數(shù)組:unsignedintdata[10];結(jié)構(gòu):structcommlnData{unsignedcharinBuff[8];unsignedchargetPtr,putPtr;1;聯(lián)合:unionint_Byte{unsignedcharc[2];unsignedinti;);例1-2C語言高級變量舉例PICC對數(shù)據(jù)寄存器bank的管理為了使編譯器產(chǎn)生最高效的機器碼,PICC把單片機中數(shù)據(jù)寄存器的bank問題交由編程員自己管理,因此在定義用戶變量時你必須自己決定這些變量具體放在哪ー個bank中。如果沒有特別指明,所定義的變量將被定位在bank。,例如下面所定義的這些變量:unsignedcharbuffer[32];bitfIag1,fIag2;fIoatval[8];除了bank。內(nèi)的變量聲明時不需特殊處理外,定義在其它bank內(nèi)的變量前面必須加上相應(yīng)的bank序號,例如:banklunsignedcharbuffer[32];ハ變量定位在bankl中bank2bitfIag1,fIag2; 變量定位在bank2中bank3fIoatval[8];ハ變量定位在bank3中中檔系列PIC單片機數(shù)據(jù)寄存器的ー個bank大小為128字節(jié),刨去前面若干字節(jié)的特殊功能寄存器區(qū)域,在C語言中某bank內(nèi)定義的變量字節(jié)總數(shù)不能超過可用RAM字節(jié)數(shù)。如果超過bank容量,在最后連接時會報錯,大致信息如下:Error[000]:Can,tfind0x12Cwordsforpsectrbss_1insegmentBANK1連接器告訴你總共有0xl2C(300)個字節(jié)準(zhǔn)備放到bankl中但bankl容量不夠。顯然,只有把一部分原本定位在bankl中的變量改放到其它bank中才能解決此問題。雖然變量所在的bank定位必須由編程員自己決定,但在編寫原程序時進行變量存取操作前無需再特意編寫設(shè)定bank的指令。C編譯器會根據(jù)所操作的對象自動生成對應(yīng)bank設(shè)定的匯編指令。為避免頻繁的bank切換以提高代碼效率,盡量把實現(xiàn)同一任務(wù)的變量定位在同一個bank內(nèi);對不同bank內(nèi)的變量進行讀寫操作時也盡量把位于相同bank內(nèi)的變量歸并在ー起進行連續(xù)操作。PICC中的局部變量PICC把所有函數(shù)內(nèi)部定義的auto型局部變量放在bank。。為節(jié)約寶貴的存儲空間,它采用了一種被叫做“靜態(tài)覆蓋”的技術(shù)來實現(xiàn)局部變量的地址分配。其大致的原理是在編譯器編譯原代碼時掃描整個程序中函數(shù)調(diào)用的嵌套關(guān)系和層次,算出每個函數(shù)中的局部變量字節(jié)數(shù),然后為每個局部變量分配?個固定的地址,且按調(diào)用嵌套的層次關(guān)系各變量的地址可以相互重福。利用這ー技術(shù)后所有的動態(tài)局部變量都可以按已知的固定地址地進行直接尋址,用PIC匯編指令實現(xiàn)的效率最高,但這時不能出現(xiàn)函數(shù)遞歸調(diào)用。PICC在編譯時會嚴(yán)格檢查遞歸調(diào)用的問題并認為這是一個嚴(yán)重錯誤而立即終止編譯過程。既然所有的局部變量將占用bank。的存儲空間,因此用戶自己定位在bank。內(nèi)的變量字節(jié)數(shù)將受到一定的限制,在實際使用時需注意。PICC中的位變量bit型位變量只能是全局的或靜態(tài)的。PICC將把定位在同一bank內(nèi)的8個位變量合并成一個字節(jié)存放于一個固定地址。因此所有針對位變量的操作將直接使用PIC單片機的位操作匯編指令高效實現(xiàn)?;诖?,位變量不能是局部自動型變量,也無法將其組合成復(fù)合型高級變量。PICC對整個數(shù)據(jù)存儲空間實行位編址,0x000単元的第0位是位地址0x0000,以此后推,每個字節(jié)有8個位地址。編制位地址的意義純粹是為了編譯器最后產(chǎn)生匯編級位操作指令而用,對編程人員來說基本可以不管。但若能了解位變量的位地址編址方式就可以在最后程序調(diào)試時方便地查找自己所定義的位變量,如果ー個位變量flagl被編址為0x123,那么實際的存儲空間位于:字節(jié)地址=0x123/8=0x24位偏移=0x123%8=3即flagl位變量位于地址為0x24字節(jié)的第3位。在程序調(diào)試時如果要觀察flagl的變化,必須觀察地址為0x24的字節(jié)而不是0x123。PIC單片機的位操作指令是非常高效的。因此,PICC在編譯原代碼時只要有可能,對普通變量的操作也將以最簡單的位操作指令來實現(xiàn)。假設(shè)ー個字節(jié)變量tmp最后被定位在地址0x20,那么tmp|?0x80=>bsf0x20,7tmp&=Oxf7=>bcf0x20,3if(tmp&Oxfe)=>btfsc0x20,0即所有只對變量中某一位操作的c語句代碼將被直接編譯成匯編的位操作指令。雖然編程時可以不用太關(guān)心,但如果能了解編譯器是如何工作的,那將有助于引導(dǎo)我們寫出高效簡介的C語言原程序。在有些應(yīng)用中需要將一組位變量放在同一個字節(jié)中以便需要時一次性地進行讀寫,這ー功能可以通過定義ー個位域結(jié)構(gòu)和?個字節(jié)變量的聯(lián)合來實現(xiàn),例如:union{struct(unsignedbO:1;unsignedbl:1;unsignedb2:1;unsignedb3:1;unsignedb4:1;unsignedb5:1;unsigned:2;//最高兩位保留}oneBit;unsignedcharalIBits;)myFIag;例1-3定義位變量于同一字w需要存取其中某一位時可以myFlag.oneBit.b3=1;//b3位置1一次性將全部位清零時可以myFIag.alIBits=0:〃全部位變量清〇當(dāng)程序中把非位變量進行強制類型轉(zhuǎn)換成位變量時,要注意編譯器只對普通變量的最低位做判別:如果最低位是0,則轉(zhuǎn)換成位變量0;如果最低位是1,則轉(zhuǎn)換成位變量1。而標(biāo)準(zhǔn)的ANSI-C做法是判整個變量值是否為0。另外,函數(shù)可以返回一個位變量,實際上此返回的位變量將存放于單片機的進位位中帶出返回。PICC中的浮點數(shù)PICC中描述浮點數(shù)是以IEEE-754標(biāo)準(zhǔn)格式實現(xiàn)的。此標(biāo)準(zhǔn)下定義的浮點數(shù)為32位長,在單片機中要用4個字節(jié)存儲。為了節(jié)約單片機的數(shù)據(jù)空間和程序空間,PICC專門提供了一種長度為24位的截短型浮點數(shù),它損失了浮點數(shù)的一點精度,但浮點運算的效率得以提高。在程序中定義的float型標(biāo)準(zhǔn)浮點數(shù)的長度固定為24位,雙精度double型浮點數(shù)一般也是24位長,但可以在程序編譯選項中選擇double型浮點數(shù)為32位,以提高計算的精度。一般控制系統(tǒng)中關(guān)心的是單片機的運行效率,因此在精度能夠滿足的前提下盡量選擇24位的浮點數(shù)運算。PICC中變量的絕對定位首先必須強調(diào),在用C語言寫程序時變量一般由編譯器和連接器最后定位,在寫程序之時無需知道所定義的變量具體被放在哪個地址(除了bank必須聲明)。真正需要絕對定位的只是單片機中的那些特殊功能寄存器,而這些寄存器的地址定位在PICC編譯環(huán)境所提供的頭文件中已經(jīng)實現(xiàn),無需用戶操心。編程員所要了解的也就是PICC是如何定義這些特殊功能寄存器和其中的相關(guān)控制位的名稱。好在PICC的定義標(biāo)準(zhǔn)基本上按照芯片的數(shù)據(jù)手冊中的名稱描述進行,這樣就秉承了變量命名的ー貫性。一個變量絕對定位的例子如下:unsignedchartmpData@0x20;//1mpData定位在地址Ox20千萬注意,PICC對絕對定位的變量不保留地址空間。換句話說,上面變量tmpData的地址是0x20,但最后0x20處完全有可能又被分配給了其它變量使用,這樣就發(fā)生了地址沖突。因此針對變量的絕對定位要特別小心。從筆者的應(yīng)用經(jīng)驗看,在ー?般的程序設(shè)計中用戶自定義的變量實在是沒有絕對定位的必要。如果需要,位變量也可以絕對定位。但必須遵循上面介紹的位變量編址的方式。如果?個普通變量已經(jīng)被絕對定位,那么此變量中的每個數(shù)據(jù)位就可以用下面的計算方式實現(xiàn)位變量指派:unsi gnedchar tmpData@0x20;//1mpData定位在地址0x20TOC\o"1-5"\h\zbit tmpBit0 @ tmpData'8+0; //1mpBi t0 對應(yīng)于tmpData 第〇 位bit tmpBit1 @ tmpData*8+1; //1mpBi t0 對應(yīng)于ImpData 第1 位bit tmpBit2 @ tmpData*8+2; //1mpBi t0 對應(yīng)于tmpData 第2 位如果tmpData事先沒有被絕對定位,那就不能用上面的位變量定位方式。PICC的其它變量修飾關(guān)鍵詞extern一外部變量聲明如果在ー個C程序文件中要使用ー些變量但其原型定義寫在另外的文件中,那么在本文件中必須將這些變量聲明成"extern”外部類型。例如程序文件codel.c中有如下定義:bank1unsignedcharvar1,var2;//定義了bankl中的兩個變量在另外一個程序文件code2.c中要對上面定義的變量進行操作,則必須在程序的開頭定義:externbanklunsignedcharvar1,var2;//聲明位于bankl的外部變量volatile—易變型變量聲明P1CC中還有一個變量修飾詞在普通的C語言介紹中一般是看不到的,這就是關(guān)鍵詞“volatile”。顧名思義,它說明了一個變量的值是會隨機變化的,即使程序沒有刻意對它進行任何賦值操作。在單片機中,作為輸入的IO端口其內(nèi)容將是隨意變化的;在中斷內(nèi)被修改的變量相對主程序流程來講也是隨意變化的:很多特殊功能寄存器的值也將隨著指令的運行而動態(tài)改變。所有這種類型的變量必須將它們明確定義成“volatile”類型,例如:volatiIeunsignedcharSTATUS@0x03;volatiIebitcommFIag:-volatile"類型定義在單片機的C語言編程中是如此的重要,是因為它可以告訴編譯器的優(yōu)化處理器這些變量是實實在在存在的,在優(yōu)化過程中不能無故消除。假定你的程序定義了?個變量并對其作了一次賦值,但隨后就再也沒有對其進行任何讀寫操作,如果是非volatile型變量,優(yōu)化后的結(jié)果是這個變量將有可能被徹底刪除以節(jié)約存儲空間。另外一種情形是在使用某一個變量進行連續(xù)的運算操作時,這個變量的值將在第?次操作時被復(fù)制到中間臨時變量中,如果它是非volatile型變量,則緊接其后的其它操作將有可能直接從臨時變量中取數(shù)以提髙運行效率,顯然這樣做后對于那些隨機變化的參數(shù)就會出問題。只要將其定義成volatile類型后,編譯后的代碼就可以保證每次操作時直接從變量地址處取數(shù)。const-常數(shù)型變量聲明如果變量定義前冠以“consビ類型修飾,那么所有這些變量就成為常數(shù),程序運行過程中不能對其修改。除了位變量,其它所有基本類型的變量或高級組合變量都將被存放在程序空間(ROM區(qū))以節(jié)約數(shù)據(jù)存儲空間。顯然,被定義在ROM區(qū)的變量是不能再在程序中對其進行賦值修改的,這也是“const”的本來意義。實際上這些數(shù)據(jù)最終都將以“retlw”的指令形式存放在程序空間,但PICC會自動編譯生成相關(guān)的附加代碼從程序空間讀取這些常數(shù),編程員無需太多操心。例如:constunsignedcharname[]="Thisisademo-;//定義ー個常量字符串如果定義了“const”類型的位變量,那么這些位變量還是被放置在RAM中,但程序不能對其賦值修改。本來,不能修改的位變量沒有什么太多的實際意義,相信大家在實際編程時不會大量用到。persistent—非初始化變量聲明按照標(biāo)準(zhǔn)C語言的做法,程序在開始運行前首先要把所有定義的但沒有預(yù)置初值的變量全部清零。PICC會在最后生成的機器碼中加入ー小段初始化代碼來實現(xiàn)這ー變量清零操作,且這ー操作將在main函數(shù)被調(diào)用之前執(zhí)行。問題是作為ー個單片機的控制系統(tǒng)有很多變量是不允許在程序復(fù)位后被清零的。為了達到這一目的,PICC提供了“persistent”修飾詞以聲明此類變量無需在復(fù)位時自動清零,編程員應(yīng)該自己決定程序中的那些變量是必須聲明成“persisten”類型,而且須自己判斷什么時候需要對其進行初始化賦值。例如:persistentunsignedcharhour,minute,second;//定義時分秒變量經(jīng)常用到的是如果程序經(jīng)上電復(fù)位后開始運行,那么需要將persistent型的變量初始化,如果是其它形式的復(fù)位,例如看門狗引發(fā)的復(fù)位,則無需對persistent型變量作任何修改。PIC單片機內(nèi)提供了各種復(fù)位的判別標(biāo)志,用戶程序可依具體設(shè)計靈活處理不同的復(fù)位情形。PICC中的指針PICC中指針的基本概念和標(biāo)準(zhǔn)C語法沒有太多的差別。但是在PIC單片機這ー特定的架構(gòu)上,指針的定義方式還是有幾點需要特別注意。指向RAM的指針如果是匯編語言編程,實現(xiàn)指針尋址的方法肯定就是用FSR寄存器,P1CC也不例外。為了生成高效的代碼,PICC在編譯C原程序時將指向RAM的指針操作最終用FSR來實現(xiàn)間接尋址。這樣就勢必產(chǎn)生一個問題:FSR能夠直接連續(xù)尋址的范圍是256字節(jié)(bankO/1或bank2/3),要覆蓋最大512字節(jié)的內(nèi)部數(shù)據(jù)存儲空間,又該如何讓定義指針?PICC還是將這一問題留給編程員自己解決:在定義指針時必須明確指定該指針?biāo)m用的尋址區(qū)域,例如:unsignedchar,ptr0;//①定義覆蓋bank。/1的指針bank2unsignedchar*ptr1;//②定義覆蓋bank2/3的指針bank3unsignedchar*ptr2;//③定義覆蓋bank2/3的指針上面定義了三個指針變量,其中①指針沒有任何bank限定,缺省就是指向bank。和bankl;②和③一個指明了bank2,另一個指明了bank3,但實際上兩者是ー樣的,因為ー個指針可以同時覆蓋兩個bank的存儲區(qū)域。另外,上面三個指針變量自身都存放在bank。中。我們將在稍后介紹如何在其它bank中存放指針變量。既然定義的指針有明確的bank適用區(qū)域,在對指針變量賦值時就必須實現(xiàn)類型匹配,下面的指針賦值將產(chǎn)生一個致命錯誤:unsignedchar,ptr0;//定義指向bank。/1的指針bank2unsignedcharbuff|8];//定義bank2中的ー個緩沖區(qū)程序語句:ptr0=buff:ツ錯誤!試圖將bank2內(nèi)的變量地址賦給指向bank。パ的指針若出現(xiàn)此類錯誤的指針操作,PICC在最后連接時會告知類似于下面的信息:FixupoverfIowinexpress!on(...)同樣的道理,若函數(shù)調(diào)用時用了指針作為傳遞參數(shù),也必須注意bank作用域的匹配,而這點往往容易被忽視。假定有下面的函數(shù)實現(xiàn)發(fā)送ー個字符串的功能:voidSendMessagefunsignedchar*);那么被發(fā)送的字符串必須位于bank?;騜ankl中。如果你還要發(fā)送位于bank2或bank3內(nèi)的字符串,必須再另外單獨寫一個函數(shù):voidSendMessage_2(bank2unsignedchar*);這兩個函數(shù)從內(nèi)部代碼的實現(xiàn)來看可以一模ー樣,但傳遞的參數(shù)類型不同。按筆者的應(yīng)用經(jīng)驗體會,如果你看至リ了‘‘Fixupoverflow”的錯誤指示,幾乎可以肯定是指針類型不匹配的賦值所至。請看點檢查程序中有關(guān)指針的操作。指向ROM常數(shù)的指針如果組變量是已經(jīng)被定義在ROM區(qū)的常數(shù),那么指向它的指針可以這樣定義:constunsignedcharcompany(]="Microchip';//定義ROM中的常數(shù)constunsignedchar*romPtr; 定義指向ROM的指針程序中可以對上面的指針變量賦值和實現(xiàn)取數(shù)操作:romPtr=company:ル指針賦初值data=*romPtr++:ツ取指針指向的ー個數(shù),然后指針加1反過來,下面的操作將是ー個錯誤,因為該指針指向的是常數(shù)型變量,不能賦值。,romPtr=data;〃往指針指向的地址寫ー個數(shù)指向函數(shù)的指針單片機編程時函數(shù)指針的應(yīng)用相對較少,但作為標(biāo)準(zhǔn)C語法的一部分,P1CC同樣支持函數(shù)指針調(diào)用。如果你對編譯原理有一定的了解,就應(yīng)該明白在PIC單片機這ー特定的架構(gòu)上實現(xiàn)函數(shù)指針調(diào)用的效率是不髙的:PICC將在RAM中建立一個調(diào)用返回表,真正的調(diào)用和返回過程是靠直接修改PC指針來實現(xiàn)的。因此,除非特殊算法的需要,建議大家盡量不要使用函數(shù)指針。指針的類型修飾前面介紹的指針定義都是最基本的形式。和普通變量一樣,指針定義也可以在前面加上特殊類型的修飾關(guān)鍵詞,例如"persistentM "volatile"等。考慮指針本身還要限定其作用域,因此PICC中的指針定義初看起來顯得有點復(fù)雜,但只要了解各部分的具體含義,理解ー個指針的實際用圖就變得很直接。㈠bank修飾詞的位置含義前面介紹的ー些指針有的作用于bank。ハ,有的作用于bank2/3,但它們本身的存放位置全部在bank。。顯然,在一個程序設(shè)計中指針變量將有可能被定位在任何可用的地址空間,這時,bank修飾詞出現(xiàn)的位置就是ー個關(guān)鍵,看下面的例子:〃定義指向bank0/1的指針,指針變量為「bank。中unsignedchar,ptr0;//定義指向bank2/3的指針,指針變量為丁bank。中bank2unsignedchar*ptr0;〃定義指向bank2/3的指針,指針變量為于bankl中bank2unsignedchar*banklptr0;從中可以看出規(guī)律:前面的bank修飾詞指明了此指針的作用域;后面的bank修飾詞定義了此指針變量自身的存放位置。只要掌握了這一法則,你就可以定義任何作用域的指針且可以將指針變量放于任何bank中。㈡volatile、persistent和const修飾詞的位置含義如果能理解上面介紹的bank修飾詞的位置含義,實際上volatile、persistent和const這些關(guān)鍵詞出現(xiàn)在前后不同位置上的含義規(guī)律是和bankー詞相一致的。例如://定義指向bankO/1易變型字符變量的指針,指針變量位于bank。中且自身為非易變型volatiIeunsignedchar,ptr0;//定義指向bank2/3非易變型字符變量的指針,指針變量位于bank1中且自身為易變型bank2unsignedchar*volatilebank1ptr0;//定義指向ROM區(qū)的指針,指針變量本身也是存放于ROM區(qū)的常數(shù)constunsignedchar*constptr0;亦即出現(xiàn)在前面的修飾詞其作用對象是指針?biāo)柑幍淖兞?出現(xiàn)在后面的修飾詞其作用對象就是指針變量自己。PICC中的子程序和函數(shù)中檔系列的PIC單片機程序空間有分頁的概念,但用C語言編程時基本不用太多關(guān)心代碼的分頁問題。因為所有函數(shù)或子程序調(diào)用時的頁面設(shè)定(如果代碼超過一個頁面)都由編譯器自動生成的指令實現(xiàn)。函數(shù)的代碼長度限制PICC決定了C原程序中的ー個函數(shù)經(jīng)編譯后生成的機器碼一定會放在同一個程序頁面內(nèi)。中檔系列的PIC單片機其ー個程序頁面的長度是2K字,換句話說,用C語言編寫的任何?個函數(shù)最后生成的代碼不能超過2K字。一個良好的程序設(shè)計應(yīng)該有一個清晰的組織結(jié)構(gòu),把不同的功能用不同的函數(shù)實現(xiàn)是最好的方法,因此一個函數(shù)2K字長的限制一般不會對程序代碼的編寫產(chǎn)生太多影響。如果為實現(xiàn)特定的功能確實要連續(xù)編寫很長的程序,這時就必須把這些連續(xù)的代碼拆分成若干函數(shù),以保證每個函數(shù)最后編譯出的代碼不超過ー個頁面空間。調(diào)用層次的控制中檔系列PIC單片機的硬件堆棧深度為8級,考慮中斷響應(yīng)需占用ー級堆棧,所有函數(shù)調(diào)用嵌套的最大深度不要超過7級。編程員必須自己控制子程序調(diào)用時的嵌套深度以符合這ー?限制要求。PICC在最后編譯連接成功后可以生成一個連接定位映射文件(*.map),在此文件中有詳細的函數(shù)調(diào)用嵌套指示圖“callgraph”,建議大家要留意一下。其信息大致如下(取自于一示范程序的編譯結(jié)果):CalIgraph:*_mainsize0,0offset0_RightShift_C*.Tasksize0,1offset0Iwtoftftmulsize0,0offset0ftunpack1ftunpack2ftaddsize0,0offset0ftunpack1ftunpack2ftdenorm例1<C函數(shù)調(diào)用層次圖上面所舉的信息表明整個程序在正常調(diào)用子程序時嵌套最多為兩級(沒有考慮中斷)。因為main函數(shù)不可能返回,故其不用計算在嵌套級數(shù)中。其中有些函數(shù)調(diào)用是編譯代碼時自動加入的庫函數(shù),這些函數(shù)調(diào)用從C原程序中無法直接看出,但在此嵌套指示圖上則ー目了然。函數(shù)類型聲明PICC在編譯時將嚴(yán)格進行函數(shù)調(diào)用時的類型檢查。ー個良好的習(xí)慣是在編寫程序代碼前先聲明所有用到的函數(shù)類型。例如:voidTask(void);unsignedcharTemperature(void);voidBlN2BCD(unsignedchar);void'FimeDisplay(unsignedchar,unsignedchar);這些類型聲明確定了函數(shù)的入口參數(shù)和返回值類型,這樣編譯器在編譯代碼時就能保證生成正確的機器碼。筆者在實際工作中有時碰到一些用戶聲稱發(fā)現(xiàn)C編譯器生成了錯誤的代碼,最后究其原因就是因為沒有事先聲明函數(shù)類型所致。建議大家在編寫一個函數(shù)的原代碼時,立即將此函數(shù)的類型聲明復(fù)制到原文件的起始處,見例1-1;或是復(fù)制到專門的包含頭文件中,再在每個原程序模塊中引用。中斷函數(shù)的實現(xiàn)PICC可以實現(xiàn)C語言的中斷服務(wù)程序。中斷服務(wù)程序有一個特殊的定義方法:voidinterruptISR(void);其中的函數(shù)名“ISR”可以改成任意合法的字母或數(shù)字組合,但其入口參數(shù)和返回參數(shù)類型必須是“void”型,亦即沒有入口參數(shù)和返回參數(shù),且中間必須有一個關(guān)鍵詞“interrupt”。中斷函數(shù)可以被放置在原程序的任意位置。因為已有關(guān)鍵詞“interrupビ聲明,PICC在最后進行代碼連接時會自動將其定位到0x0004中斷入口處,實現(xiàn)中斷服務(wù)響應(yīng)。編譯器也會實現(xiàn)中斷函數(shù)的返回指令“retfie”。ー個簡單的中斷服務(wù)示范函數(shù)如下:voidinterruptISR(void)ル中斷服務(wù)程序Iif(TOIE&&TOIF)I!判TMRO中斷ITOIF=0;〃清除TMRO中斷標(biāo)志“在此加入TMRO中斷服務(wù))if(TMR1IE&&TMR1IF)//判TMR1中斷(TMR11F=0;//淸除TMR1中斷標(biāo)志//在此加入TMR1中斷服務(wù)I)//中斷結(jié)束并返回例し5C語言中斷函數(shù)舉例PICC會自動加入代碼實現(xiàn)中斷現(xiàn)場的保護,并在中斷結(jié)朿時自動恢復(fù)現(xiàn)場,所以編程員無需象編寫匯編程序那樣加入中斷現(xiàn)場保護和恢復(fù)的額外指令語句。但如果在中斷服務(wù)程序中需要修改某些全局變量時,是否需要保護這些變量的初值將由編程員自己決定和實施。用C語言編寫中斷服務(wù)程序必須遵循高效的原則:代碼盡量簡短,中斷服務(wù)強調(diào)的是ー個“快”字。避免在中斷內(nèi)使用函數(shù)調(diào)用。雖然PICC允許在中斷里調(diào)用其它函數(shù),但為了解決遞歸調(diào)用的問題,此函數(shù)必須為中斷服務(wù)獨家專用。既如此,不妨把原本要寫在其它函數(shù)內(nèi)的代碼直接寫在中斷服務(wù)程序中。避免在中斷內(nèi)進行數(shù)學(xué)運算。數(shù)學(xué)運算將很有可能川到庫函數(shù)和許多中間變量,就算不出現(xiàn)遞歸調(diào)用的問題,光在中斷入口和出口處為了保護和恢復(fù)這些中間臨時變量就需要大量的開銷,嚴(yán)重影響中斷服務(wù)的效率。中檔系列PIC單片機的中斷入口只有一個,因此整個程序中只能有一個中斷服務(wù)函數(shù)。標(biāo)準(zhǔn)庫函數(shù)PICC提供了較完整的C標(biāo)準(zhǔn)庫函數(shù)支持,其中包括數(shù)學(xué)運算函數(shù)和字符串操作函數(shù)。在程序中使用這些現(xiàn)成的庫函數(shù)時需要注意的是入口參數(shù)必須在bank。中。如果需要用到數(shù)學(xué)函數(shù),則應(yīng)在程序前“include<math.h>”包含頭文件;如果要使用字符串操作函數(shù),就需要包含^include<string.h>"頭文件。在這些頭文件中提供了函數(shù)類型的聲明。通過直接查看這些頭文件就可以知道PICC提供了哪些標(biāo)準(zhǔn)庫函數(shù)。C語言中常用的格式化打印函數(shù)“printf/sprintf”用在單片機的程序中時要特別謹慎。printf/sprintf是ー個非常大的函數(shù),一旦使用,你的程序代碼長度就會增加很多。除非是在編寫試驗性質(zhì)的代碼,可以考慮使用格式化打印函數(shù)以簡化測試程序;一般的最終產(chǎn)品設(shè)計都是自己編寫最精簡的代碼實現(xiàn)特定格式的數(shù)據(jù)顯示和輸出。本來,在單片機應(yīng)用中輸出的數(shù)據(jù)格式都相對簡單而且固定,實現(xiàn)起來應(yīng)該很容易。對于標(biāo)準(zhǔn)C語言的控制臺輸入(scanf)/輸出(printf)函數(shù),PICC需要用戶自己編寫其底層函數(shù)getch()和putch。。在單片機系統(tǒng)中實現(xiàn)scanBprintf本來就沒什么太多意義,如果一定要實現(xiàn),只要編寫好特定的getch()和putch()函數(shù),你就可以通過任何接口輸入或輸出格式化的數(shù)據(jù)。PICC定義特殊區(qū)域值PICC提供了相關(guān)的預(yù)處理指令以實現(xiàn)在原程序中定義單片機的配置字和標(biāo)記單元。定義工作配置字在原程序中定義PIC單片機工作配置字的重要性在前面章節(jié)中已經(jīng)闡述。在用PICC寫程序時同樣可以在c原程序中定義,具體方式如下:__CONFIG(HS&UNPROTECT&PWRTEN&BORDIS&加TEN);上面的關(guān)鍵詞“—CONFIG”(注意前面有兩個下劃線符)專門用于是芯片配置字的設(shè)定,后面括號中的各項配置位符號在特定型號單片機的頭文件中已經(jīng)定義(注意不是pic.h頭文件),相互之間用邏輯“與”操作符組合在一起。這樣定義的配置字信息最后將和程序代碼ー起放入同一個HEX文件。在這里列出了適用于16F7X系列單片機配置位符號預(yù)定義,其它型號或系列的單片機配置字定義方式類似,使用前查閱一下對應(yīng)的頭文件即可。/?振蕩腓配置./defineRC0x3FFFIIRC振蕩defineHS0x3FFEIIHS模式defineXT0x3FFDIIXT模式defineLP0x3FFCIILP模式/.看門狗配置狗defineMITEN0x3FFFII看門狗打開/defineM)TDIS0x3FFBII看門狗關(guān)閉廠上電延時定時器配置’/definePWRTEN0x3FF7II上電延時定時器打開definePWRTDIS0x3FFFII上電延時定時器關(guān)閉/?低電壓復(fù)位配置”defineBOREN0x3FFF/Z低電樂復(fù)位允許defineBORDIS0x3FBFI!低電壓復(fù)位禁止/?代碼保護配置’/defineUNPROTECT0x3FFFI!沒有代碼保護definePROTECT0x3FEFI{程序代碼保護例1?6頭文件預(yù)定義的配置信息符號定義芯片標(biāo)記單元PIC單片機中的標(biāo)記單元定義可以用下面的—IDLOC(注意前面有兩個下劃線符)預(yù)處理指令實現(xiàn),方法如下:__1DLOC(1234);其特殊之處是括號內(nèi)的值全部為16進制數(shù),不需要用“Ox”引導(dǎo)。這樣上面的定義就設(shè)定了標(biāo)記單元內(nèi)容為〇1020304。MPLAB-IDE中實現(xiàn)PICC的編譯選項設(shè)置在1.3節(jié)中已經(jīng)介紹了如何實現(xiàn)PICC和MPLAB-IDE開發(fā)平臺的掛接。,旦項目建立成功、程序編寫完成后即可以通過MPLAB環(huán)境卜?的項目管理工具實現(xiàn)程序的編譯、連接和調(diào)試。對應(yīng)于整個項目編譯最常用的MPLAB快捷圖標(biāo)為“”和“”。它們的含義分別是:ー項目維護(Make):MPLAB檢査項目中的原程序文件,只編譯那些在上次編譯后又被修改過的原程序,最后進行連接;ー項目重建(BuildAll):項目中的所有原程序文件,不管是否有修改,都將被重新編譯一次,最后進行連接。也可以通過Project菜單選擇“Make”或“BuildAh”實現(xiàn)項目編譯。不管采用何種方式,在啟動編譯過程前一般都要設(shè)定一些編譯選項。選擇單片機型號在選擇PICC作為語言工具并建立了項目后,同樣通過菜單項ConfgureSelectDevice在MPLAB環(huán)境中選擇具體単片機型號。請回顧一下例1-1的代碼,我們在原程序ー開始使用了“#include<pic.h>”實現(xiàn)了相關(guān)單片機的ー些預(yù)定義符號的直接引用,但沒有具體指明是哪一個型號。實際上,“pic.h”頭文件只是ー個簡單的管理工具(條件判別),它會按照MPLAB所選擇的特定型號的單片機,把真正對應(yīng)的頭文件包含進來。有興趣者可以直接用文本編輯工具打開pic.h文件查看其是如何根據(jù)不同的単片機型號包含對應(yīng)的頭文件。這樣對編程員而言,程序中只需加上一句“#include<pic.h>”即可。PICC普通編譯選項(General)設(shè)定啟動編譯選項設(shè)定對話框。在使用PICC語言工具時對話框的內(nèi)容和用MPAMS匯編エ具相比完全不同。圖1-3為PICC編譯環(huán)境下普通選項設(shè)定的界面。在此界面中用戶唯?能改變的是編譯器查找頭文件時的指定路徑(IncludePath),實際上如果編譯器安裝沒有問題,在此界面中這些普通選項的設(shè)定無需任何改動,編譯器會自動到缺省認定的路徑中(編譯器安裝后的相關(guān)路徑)查找編譯所需的各類文件。PICC全局選項設(shè)定(PICCGlobal)全局選項將影響項目中所有C和匯編原程序的編譯,詳細的設(shè)定內(nèi)容見圖1-4。其中必須關(guān)注的有:CompileforMPLABICD:如果你準(zhǔn)備用ICD調(diào)試C語言編譯后的代碼,那么此項就必須打鉤選中。這樣編譯后的結(jié)果就能保證ICD本身使用的芯片資源(ー小部分的程序和數(shù)據(jù)空間)不被應(yīng)用程序所占用。Treat'char'assigned:為了提高編譯后的代碼效率,PICC缺省認定‘char’型變量也是無符號數(shù)。如果在設(shè)計中需要使用帶符號的‘char,型變量,此項就應(yīng)該被選中。Floatingpoint'double'width:同樣為了提高編譯后的代碼效率,PICC缺省認定'double'型的雙精度浮點數(shù)變量的實現(xiàn)長度為24位(等同于普通float型浮點數(shù))。在這里可以選擇使其長度達32位。這樣數(shù)值計算的精度將得到提高,但代碼長度將增加,計算速度也會降低,所以請在權(quán)衡利弊后作出你自己的決定。C編譯器選項設(shè)定(PICCCompiler)項目中所有的C原程序都將通過C編譯器編譯成機器碼,這些選項決定了C編譯器是如何工作的。所有選項又分為兩組:普通選項(General)和高級選項(Advanced),分別見圖1-5A和1-5B。C編譯器的普通選項最垂要的就是針對代碼優(yōu)化的設(shè)定。如果沒有特殊原因,應(yīng)該設(shè)定全局優(yōu)化級別為9級(最高級別優(yōu)化),同時使用匯編級優(yōu)化,這樣最終得到的代碼效率最高(長度和執(zhí)行速度兩方面)。按筆者的使用經(jīng)驗,僅從代碼長度去比較,使用最高級別優(yōu)化后代碼長度至少可以減少20%(2K字以上的程序)。而且PICC的優(yōu)化器相當(dāng)可靠,?般不會因為使用優(yōu)化從而使生成的程序出現(xiàn)錯誤。碰到的ー些問題也基本都是用戶編寫的原程序有漏洞所導(dǎo)致,例如一些變量應(yīng)該是volatile型但編程員沒有明確定義,在優(yōu)化前程序可以正常運行,一旦使用優(yōu)化,程序運行就出現(xiàn)異常。顯然,把出現(xiàn)的這些問題歸罪到編譯器是亳無道理的。使用優(yōu)化后可能對原程序級的調(diào)試帶來ー些不便之處。因PICC可能會重組編譯后的代碼,例如多處重復(fù)的代碼可能會改成同一個子程序調(diào)用以節(jié)約程序空間,這樣在調(diào)試過程中跟蹤原程序時可能會出現(xiàn)程序亂跳的現(xiàn)象,這基本是正常的。若為了強調(diào)更直觀的代碼調(diào)試過程,你可以將優(yōu)化級別降低甚至關(guān)閉所有優(yōu)化功能,這樣調(diào)試時程序的運行就可以按部就班了。C編譯器的高級選項設(shè)定基本都是針對診斷信息輸出的,和生成的代碼無關(guān)。用得相對較多的選項有:Generateassemblylistfile:編譯器生成C原程序的匯編列表文件(*.lst)。在此文件中列出了每一行C原代碼對應(yīng)的匯編指令,但這些都是優(yōu)化前的代碼。簡單的?條C語句被翻譯成匯編指令后可能有好幾條。有時匯編列表文件可以作為解決問題的輔助手段。如果你懷疑編譯器生成的代碼有錯誤,不妨先產(chǎn)生對應(yīng)的匯編列表文件,看看在優(yōu)化前一條C語句被編譯后的匯編碼到底是什么。Compiletoassemblyonly:這?選項的作用是把C原程序編譯成匯編指令文件(*.as),此時將不生成目標(biāo)文件,也不進行最后的連接定位。這ー選項在C和匯編混合編程時特別有用。通過解讀C程序?qū)?yīng)的匯編指令,可以掌握C程序中存取變量的具體方法,然后用在自己編寫的匯編指令中。我們將在稍后專門做介紹。連接器選項設(shè)定(PICCLinker)連接器PICCLinker的選項基本不用作太多的改變,在圖1-6的對話框中顯示了可設(shè)定的各類項目。其中有兩項有用的信息輸出可以考慮加以利用:Generatemapfile:生成連接定位映射文件。在此映射文件中詳細列出了所有程序用到的變量的具體物理地址;所有函數(shù)的入口地址;函數(shù)相互之間調(diào)用的層次關(guān)系和深度等。這些信息對于程序的調(diào)試將非常有用。此文件將以擴展名“*.map”的形式存放在同一個項目路徑下,需要時可以用任何文本編輯器打開觀察。Displaymemory-segmentusage:顯示詳細的內(nèi)存分配和使用情況報告。用戶可以了解到程序空間和數(shù)據(jù)存儲器空間資源分配的細節(jié)。下面列舉了在ー個項目編譯后實際的內(nèi)存使用信息,為方便理解筆者用‘ッ/”添加了一些注釋:PsectUsageMap:〃程序段定位表Psect|Contents|MemoryRangepowerupIPoweronresetcode|$0000?$0003intentry|Interruptserviceroutine|$0004-$0000intcode|Interruptserviceroutine|$000D-$0020TOC\o"1-5"\h\zintret|Interr upt serviceroutine | $002D - $0035initIInitiali zati oncode|$0036 - $0030end_init|Initializationcode|$003E-$0040clrtextIMemor ycl earingcode|$0041?$0047const3IStringsandconstantdata | $0048 - $0060constIStringsandconstantdata|$0061?$0071const2|Stringsandconstantdata|$0072-$0076text|Programandlibrarycode|$0576-$0582text|Programandlibrarycode|$0583-$07C7fIoat_te|Arithmeticroutinecode|$0708?$07FFrbss_0|Bank0RAMvariables|$0021-$0042temp|TemporaryRAMdata|$0043?$0047TOC\o"1-5"\h\znvram|PersistentRAMdata| $0048- $004Aintsave| Registerssaved on interrupt | $004B - $0040intsave| Registerssaved on interrupt | $007F - $007Fintsave_1 |Savedcopyof Wi nbank1 | $00FF- $00FFrbit_0|Bank0bitvariables|$0100-$0104config|User-programmedCONFIGbits|$2007-$2007MemoryUsageMap://存儲空間使用情況報告/Z程序空間代碼定位地址分布ProgramROM$0000-$0076$0077(19)wordsProgramROM$0576-$07FF$028A(650)words$0301(769)wordstotalProgramROM//bankO數(shù)據(jù)空間變量地址分布Bank0RAM$0021?$0040$0020(45)bytesBank0RAM$007F-$007F$0001(1)bytes$002E(46)bytestotalBank0RAM//bank!數(shù)據(jù)空間變量地址分布Bank1RAM$00FF-$00FF$0001(1)bytestotalBank1RAM//bankO數(shù)據(jù)空間位變量地址分布Bank0Bits$0100-$0104$0005(5)bitstotalBank0BitsII配置字地址ConfigData$2007-$2007$0001(1)wordstotalConfigDataProgramstatistics: 程序總體資源消耗統(tǒng)計TotalROMused769words(18.8%)“生成代碼字總數(shù)和程序空間使用率TotalRAMused48bytes(25.0%)”使用數(shù)據(jù)字直數(shù)和數(shù)據(jù)空間使用率例1-7編譯后程序使用的內(nèi)存信息匯編器選項設(shè)定(PICCAssembler)PICC環(huán)境提供了自己的匯編編譯器,它和Microchip公司提供的MPASM編譯器在原程序的語法表達方面要求稍有不同。另外,PICC的匯編編譯器要求輸入原程序文件的擴展名是“*.as”,而MPASM缺省認定的原程序以“*.asm”為擴展名。在基于PICC編譯環(huán)境下開發(fā)PIC單片機的C語言應(yīng)用程序時基本無需關(guān)心其匯編編譯器,除非是在混合語言編程時用匯編語言編寫完整的匯編原程序模塊文件。其編譯選項設(shè)定的對話框見圖1-7,最重要的是優(yōu)化使能控制項“Enableoptimization",一般情況ド應(yīng)該使用匯編器的優(yōu)化以節(jié)約程序空間。C和匯編混合編程有兩個原因決定了用C語言進行單片機應(yīng)用程序開發(fā)時使用匯編語句的必要性:單片機的ー些特殊指令操作在標(biāo)準(zhǔn)的C語言語法中沒有直接對應(yīng)的描述,例如PIC單片機的清看門狗指令"clrwdt”和休眠指令"sleep”;單片機系統(tǒng)強調(diào)的是控制的實時性,為了實現(xiàn)這ー要求,有時必須用匯編指令實現(xiàn)部分代碼以提高程序運行的效率。這樣,ー個項目中就會出現(xiàn)C和匯編混合編程的情形,我們在此討論一些混合編程的基本方法和技巧。嵌入行內(nèi)匯編的方法在C原程序中直接嵌入?yún)R編指令是最直接最容易的方法。如果只需要嵌入少量幾條的匯編指令,P1CC提供了一個類似于函數(shù)的語句:asm("cIrwdt");雙引號中可以編寫任何一條PIC的標(biāo)準(zhǔn)匯編指令。例如:for(;;){asm("clrwdt');ル清看門狗Task();ClockRun();asm("sleep");//休眠asm("nop");//空操作延時1例1-8逐行嵌入ガ編的方式如果需要編寫一段連續(xù)的匯編指令,PICC支持另外?種語法描述:用“#asm”開始匯編指令段,用“#endasm”結(jié)束。例如下面的一段嵌入?yún)R編指令實現(xiàn)了將0x20?0x7F間的RAM全部清零:#asmmovlw0x20movwf_FSRclrf_lNDFincf_FSR,fbtfss_FSR,7goto$-3#endasm例1-9整段嵌入?yún)R編的方式匯編指令尋址C語言定義的全局變量C語言中定義的全局或靜態(tài)變量尋址是最容易的,因為這些變量的地址已知且固定。按じ語言的語法標(biāo)準(zhǔn),所有C中定義的符號在編譯后將自動在前面添加一下劃線符“一”,因此,若要在匯編指令中尋址C語言定義的各類變量,一定要在變量前加上ー“一”符號,我們在上面例1-9中已經(jīng)體現(xiàn)了這ー變量引用的法則,因為FSR和INDF等所有特殊寄存器是以C語言語法定義的,因此匯編中需要對其尋址時前面必須添加下劃線。對于C語言中用戶自定義的全局變量,用行內(nèi)匯編指令尋址時也同樣必須加上']”,下面的例1-10說明了具體的引用方法:volatiIeunsignedchartmp;//定義位于bank。的字符型全局變量voidTest(void)/ノ測試程序(#asmI!開始行內(nèi)匯編clrf.STATUSル選擇bank。movlw0x10/ノ設(shè)定初值movwf_tmpハImp=0x10#endasm/Z結(jié)束行內(nèi)匯編if(tmp==0x10){〃開始C語言程序例1/0行內(nèi)匯編尋址C全局變量(位于bank。)上面的例子說明了匯編指令中尋址C語言所定義變量的基本方法。PICC在編譯處理嵌入的行內(nèi)匯編指令時將會原封不動地把這些指令復(fù)制成最后的機器碼。所有對C編譯器所作的優(yōu)化設(shè)定對這些行內(nèi)匯編指令而言將不起任何作用。編程員必須自己負責(zé)編寫最高效的匯編代碼,同時處理變量所在的bank設(shè)定。對于定義在其它bank中的變量,還必須在匯編指令中加以明確指示,見例1-1的代碼范例。volat iIe bank1 unsi gned char tmpBank1;ル定義位fbankl 的字符型全局變量volat iIe bank2 unsi gned char tmpBank2; //定義位于bank2 的字符型全局變量volat iIe bank3 unsi gned char tmpBank3;ル定義位于bank3的字符型全局變量voidTest(void)/ノ測試程序(#asmII開始行內(nèi)匯編bcf.STATUS,6//選擇banklbsf.STATUS,5movlw0x10/Z設(shè)定初值movwf_tmpBank1AOx80//1mpBank1^Ox10bsf_STATUS,6/ノ選擇bank2bcf.STATUS,5movlw0x2011設(shè)定初值movwf_tmpBanklA0x100Z/tmpBank2=0x20bsf_STATUS,6//選擇bank3bsf.STATUS,5movlw0x30/Z設(shè)定初值movwf_tmpBank1AOx180Z/tmpBank1=0x30#endasm//結(jié)束行內(nèi)匯編)例1」行內(nèi)匯編尋址C全局變量(非bank。變量)通過上面的代碼實例,我們可以掌握這樣ー個規(guī)律:在行內(nèi)匯編指令中尋址C語言定義的全局變量時,除了在尋址前設(shè)定正確的bankタト,在指令描述時還必須在變量上異或其所在bank的起始地址,實際上位于bank。的變量在匯編指令中尋址時也可以這樣理解,只是異或的是0x00,可以省略。如果你了解PIC單片機的匯編指令編碼格式,上面異或的bank起始地址是無法在真正的匯編指令中體現(xiàn)的,其目的純粹是為了告訴PICC連接器變量所在的bank,以便連接器進行bank類別檢查。匯編指令尋址C函數(shù)的局部變量前面已經(jīng)提到,PICC對自動型局部變量(包括函數(shù)調(diào)用時的入口參數(shù))采用ー種“靜態(tài)覆蓋”技術(shù)對每ー個變量確定一個固定地址(位于bank。),因此嵌入的匯編指令對其尋址時只需采用數(shù)據(jù)寄存器的直接尋址方式即可,唯一要考慮的是如何才能在編寫程序時知道這些局部變量的尋址符號(具體地址在最后連接后才能決定,編程時無需關(guān)心)。ー個最實用也是最可靠的方法是先編寫ー小段C代碼,其中有最簡單的局部變量操作指令,然后參考圖1-5(B)對話框選擇"Compiletoassemblyonly",把此C原代碼編譯成對應(yīng)的PICC匯編指令;查看C編譯器生成的匯編指令是如何尋址這些局部變量的,你自己編寫的行內(nèi)匯編指令就采用同樣的尋址方式。例如,例1-12的一小段C原代碼編譯出的匯編指令I(lǐng)IC原程序代碼voidTest(unsignedcharinVar1,inVar2)(unsignedchartmp1,tmp2;inVar1+?;inVar2--;tmp1=1;tmp2=2;)//編譯瑞生成的匯編指令_Test;」呷1assignedto?a_Test+0//1mp!的尋址符為?a_Test+O.Test$tmplset?a_Test;_tmp2assignedto?a_Test+1//1mp2的尋址符為?a_Test+1_Test$tmp2set?a_Test+1;_inVar1assignedto?a_Test+2//inVar1的尋?址符為?a_Test+2_Test$inVar1set?a_Test+2Iine44;_inVar1storedfromw//第一個字符型行參由W寄存器傳遞bcf3,5bcf3,6movwf?a_Test+2;ht16.c:43:unsignedchartmp1,tmp2;inet?a_Test+2Iine45;ht16.c:45:inVar2--;deef?_Test//行參inVar2的尋址符為?一TestIine46;ht16.c:46:tmp1=1;clrf?aTestincf?a_TestIine47;ht16.c:47:tmp2=2;movlw2movwf?a_Test+1Iine48;ht16.c:48:}return例1-12P1CC實現(xiàn)局部變量操作的尋址方式基于上面得到的PICC編譯后局部變量的尋址方式,我們在C語言程序中用嵌入?yún)R編指令時必須采樣同樣的尋址符以實現(xiàn)對應(yīng)變量的存取操作,見下面的例1-13。IIC原程序代碼voidTest(unsignedcharinVar1,inVar2)(unsignedchartmp1,tmp2;#asm//開始嵌入?yún)R編incf?a_Test+0,f//1mp1++;decf?a_Test+1,f/11mp2--;movlwOx10addwf?a_Test+2,f/1inVar1+=0x10;rrf?_Tesl,wハinVar2循環(huán)右移一位rrf?_Test,f#endasm/1結(jié)束嵌入?yún)R編)例I?13嵌入?yún)R編指令實現(xiàn)局部變量尋址操作如果局部變量為多字節(jié)形式組成,例如整型數(shù)、長整型等,必須按照PICC約定的存儲格式進行存取。前面已經(jīng)說明了PICC采用“Littleendian”格式,低字節(jié)放在低地址,髙字節(jié)放在高地址。下面的例1-14實現(xiàn)了一個整型數(shù)的循環(huán)移位,在C語言中沒有直接針對循環(huán)移位的語法操作,用標(biāo)準(zhǔn)C指令實現(xiàn)的效率較低。//16位整型數(shù)循環(huán)右移若干位unsignedintRR_Shift16(unsignedintvar,unsignedcharcount)(whiIe(count--)//移位次數(shù)控制(#asmI!開始嵌入?yún)R編rrf?_RR_Shift16+0,w//最低位送入Crrf?_RR_Shift16+1,f//var高字節(jié)右移1位,C移入最高位rrf?_RR_Shif116+0,f//var低字節(jié)右移1位#endasm//結(jié)束嵌入?yún)R編)return(var);II返回結(jié)果例1/4嵌入?yún)R編指令對多字節(jié)變量的操作1.9.4混合編程的ー些經(jīng)驗C和匯編語言混合編程可以使單片機應(yīng)用程序的開發(fā)效率和程序本身的運行效率達到最佳的配合。筆者從實際應(yīng)用中得到ー些經(jīng)驗供讀者一起分享。(一)慎用匯編指令相比于匯編語言,用C語言編程的優(yōu)勢是毋庸置疑的:開發(fā)效率大大提高、人性化的語句指令加上模塊化的程序易于日常管理和維護、程序在不同平臺間的移植方便。所以既然用了C語言編程,就盡量避免使用嵌入?yún)R編指令或整個地編寫匯編指令模塊文件。PICC已具備高效的優(yōu)化功能,如果在寫C原程序時就十分注意程序的編譯和運行效率問題,加上PICC的后道編譯優(yōu)化,最后得到的代碼效率不會比全部用匯編編寫的代碼差多少,尤其是程序量較大時。另外,PICC對數(shù)據(jù)存儲空間的利用率肯定比用戶人工定位變量時的利用率要高,同時還提供完整的庫函數(shù)支持。C語言的語法功能強大,能夠高效率地實現(xiàn)絕大部分控制和運算功能。因此,除了一些十分強調(diào)單片機運行時間的代碼或C語言沒有直接對應(yīng)的操作可以考慮用匯編指令實現(xiàn)外,其它部分都應(yīng)該用C語言編寫。以上面的例1-14進ー步說明,變量的循環(huán)右移操作用C語言實現(xiàn)非常不方便,PIC單片機已有對應(yīng)的移位操作匯編指令,因此用嵌入?yún)R編的形式實現(xiàn)效率最高。同時對移位次數(shù)的控制,本質(zhì)上說變量count的遞減判零也可以直接用匯編指令實現(xiàn),但這樣做節(jié)約不了多少代碼,用標(biāo)準(zhǔn)的C語言描述更直觀,更易于維護。一句話:用了C語言后,就不要再老想著用匯編。㈡盡量使用嵌入?yún)R編這和上面的慎用匯編指令的說法并不矛盾。如果確實需要用匯編指令實現(xiàn)部分代碼以提高運行效率,應(yīng)盡量使用行內(nèi)ガ編,避免編寫純匯編文件(*.as文件)。雖然PICC支持C和匯編原程序模塊存在于同一個項目中,但要編寫純匯編文件必須首先了解PICC特有的匯編語法結(jié)構(gòu)。Hitech公司提供了完整的文檔介紹其匯編器的使用方法,有興趣者可以從其網(wǎng)站上下載PICC的用戶使用手冊查看。筆者認為,類似于純匯編文件的代碼也可以在C語言框架下實現(xiàn),方法是基于C標(biāo)準(zhǔn)語法定義所有的變量和函數(shù)名,包括需要傳遞的形式參數(shù)、返回參數(shù)和局部變量,但函數(shù)內(nèi)部的指令基本用嵌入?yún)R編指令編寫,只有最后的返回參數(shù)用C語句實現(xiàn)。這樣做后函數(shù)的運行效率和純匯編編寫時幾乎一模ー樣,但各參數(shù)的傳遞統(tǒng)ー用C標(biāo)準(zhǔn)實現(xiàn),這樣管理和維護就比較方便。例如下面的例1-15實現(xiàn)ー個字節(jié)變量的偶校驗位計算。bitEvenParity(unsignedchardata)(#asmswapf?a_EvenPariIy+〇,w//入口參數(shù)daia的尋址符為?a_EvenParity+0xorwf?a_EvenParity+0,frrf?a_EvenParity+0,wxorwf?a_EvenParity+0,fbtfsc?a_EvenParity+0,2inet?a_EvenPariIy+0,f#endasm至此,data的最低位即為偶校驗位if(data&0x01)return(1);elsereturn(0);㈢盡量使用全局變量進行參數(shù)傳遞使用全局變量最大的好處是尋址宜觀,只需在C語言定義的變量名前增加一個下劃線符即可在匯編語句中尋址;使用全局變量進行參數(shù)傳遞的效率也比形參高。編寫單片機的C程序時不能死硬強求教科書上的模塊化編程而大量采用行參和局部變量的做法,在開發(fā)編程時應(yīng)視實際情況靈活變通,?切以最高的代碼效率為目標(biāo)。第2章PIC16F877的外圍功能模塊1.1.2簡單應(yīng)用實例該例用于令與PORTDロ相連的8個發(fā)光二極管前4個點亮,后4個熄滅。在調(diào)試程序前,應(yīng)使與PORTDロ相連的8位拔碼開關(guān)拔向相應(yīng)的位置。例1.1PORTD輸出#include<pic.h>main(){TRISD=0X00;/*TRISD寄存器被賦值,PORTD每一位都為輸出?/while(l);/?循環(huán)執(zhí)行點亮發(fā)光二極管的語句?/(PORTD=OXFO;/?向PORTD送數(shù)據(jù),點亮LED(由實驗?zāi)0?//*的設(shè)計決定相應(yīng)位置低時LED點亮)。*/1.2.1MSSP模塊SPI方式功能簡介下面是一段簡單的SPI初始化例程,用于利用SPI工作方式輸出數(shù)據(jù)的場合。例1.2SPI初始化程序/*spi初始化子程序*/voidSPIINIT0{PIR1=O;/?清除SP1中斷標(biāo)志?/SSPCON=0x30;/*SSPEN=1;CKP=0,FOSC/4*/SSPSTAT=0xC0;TRISC=0x00;/*SDO引腳為輸出,SCK引腳為輸出?/I1.2.3程序清單下面給出已經(jīng)在實驗板上調(diào)試通過的ー個程序,可作為用戶編制其它程序的參考。#include<picl687x.h>/*該程序用于在8個LED上依次顯示1?8等8個字符?/staticvolatileinttable[20]={0xc0,0xf9,0xa4,OxbO,0x99,0x92,0x82,0XD8,0x80,0x90,0x88,0x83,0xc6,Oxal,0x86,0x8e,0x7f,Oxbf,0x89,Oxff};volatileunsignedchardata;#define
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 深海探險起點:船舶租賃合同揭秘
- 飛行員培訓(xùn)合同合作意向范本
- 車險代理合同書樣本
- 企業(yè)員工培訓(xùn)合作協(xié)議合同
- 股權(quán)激勵實施合同協(xié)議
- 施工領(lǐng)域農(nóng)民工勞動合同模板
- 汽車購銷合同其一:條款解析
- 小學(xué)生心理課件
- 無線廣播電視傳輸中的信號傳輸信道分配考核試卷
- 天然氣儲層滲透性改善技術(shù)考核試卷
- 初三語文月考質(zhì)量分析
- 《天才少年維克多》
- CH:火花塞功能、結(jié)構(gòu)類型及檢測
- 信訪工作課件
- 物資盤點工作步驟與細則
- “中小學(xué)教師全員遠程培訓(xùn)”的實效性研究-以山西省J市為例的中期報告
- 工業(yè)旅游項目策劃
- 視頻監(jiān)控入門基礎(chǔ)教程視頻監(jiān)控系統(tǒng)
- 國家基本藥物臨床應(yīng)用指南1
- 自主選擇頂崗實習(xí)申請表
- 報驗申請表模板
評論
0/150
提交評論