1.PICC安裝:
PICC編譯器可以直接掛接在MPLAB-IDE集成開發平台下,實現一體化的編譯連接和原代碼調試。使用MPLAB-IDE內的調試工具ICE2000 、ICD2 和軟件模擬器都可以實現原代碼級的程序調試,非常方便。
首先必須在你的計算機中安裝 PICC 編譯器。安裝成功后可以進入IDE ,選擇菜單項Project Æ Set Language Tool Locations… ,打開語言工具掛接設置對話框。在對話框中選擇“HI-TECH PICC Toolsuite”欄,展開可執行文件組“Executable”后,列出了將被MPLAB-IDE 后台調用的編譯器所用到的所有可執行文件,其中有匯編編譯器“PICC Assembler ”、C 原程序編譯器“PICC Compiler”和連接定位程序“PICC Linker”。同時在此列表中還顯示了對應的可執行程序名,請注意在這里都是“PICC.EXE”。用鼠標分別點擊選中這三項可執行文件,觀察對話框下面“Location ”一欄中顯示的文件路徑,用“Browse…”按紐,從計算機中已經安裝的 PICC編譯器文件夾中選擇PICC.EXE 文件。
實際上PICC.EXE 只是一個調度管理程序,它會按照所輸入的文件擴展名自動調用對應的編譯器和連接器,用戶要注意的是C 語言原程序擴展名用“.c ”,匯編原程序用“.as”即可。用C 語言編程的好處是可以實現模塊化編程。程序編寫者應盡量把相互獨立的控制任務用多個獨立的C 原程序文件實現,如果程序量較大,一般不要把所有的代碼寫在一個文件內。
基於PICC編譯環境編寫PIC 單片機程序的基本方式和標准C 程序類似,程序一般由以下幾個主要部分組成:
z 在程序的最前面用#include 預處理指令引用包含頭文件,其中必須包含一個編譯器提供的“pic.h ”文件,實現單片機內特殊寄存器和其它特殊符號的聲明;
z 用“__CONFIG ”預處理指令定義芯片的配置位;
z 聲明本模塊內被調用的所有函數的類型,PICC將對所調用的函數進行嚴格的類型匹配檢查;
z 定義全局變量或符號替換;
z 實現函數(子程序),特別注意 main 函數必須是一個沒有返回的死循環。

1 #include <pic.h> //包含單片機內部資源預定義 2 #include “pc68.h” //包含自定義頭文件 3 //定義芯片工作時的配置位 4 __CONFIG (HS & PROTECT & PWRTEN & BOREN & WDTDIS); 5 //聲明本模塊中所調用的函數類型 6 void SetSFR(void); 7 void Clock(void); 8 void KeyScan(void); 9 void Measure(void); 10 void LCD_Test(void); 11 void LCD_Disp(unsigned char); 12 //定義變量 13 unsigned char second, minute, hour; 14 bit flag1,flag2; 15 //函數和子程序 16 void main(void) 17 { 18 SetSFR(); 19 PORTC = 0x00; 20 TMR1H += TMR1H_CONST; 21 LED1 = LED_OFF; 22 23 LCD_Test(); 24 25 //程序工作主循環 26 while(1) { 27 asm(“clrwdt”); //清看門狗 28 Clock(); //更新時鍾 29 KeyScan(); //掃描鍵盤 30 Measure(); //數據測量 31 SetSFR(); //刷新特殊功能寄存器 32 } 33 }
2.PICC 中的變量定義:
bit 1 布爾型位變量,0 或1 兩種取值
char 8 有符號或無符號字符變量,PICC 缺省認定char 型變量為無符號數,但可以通過編譯選項改為有符號字節變量
unsigned char 8 無符號字符變量
short 16 有符號整型數
unsigned short 16 無符號整型數
int 16 有符號整型數
unsigned int 16 無符號整型數
long 32 有符號長整型數
unsigned long 32 無符號長整型數
float 24 浮點數
double 24 或32 浮點數,PICC 缺省認定double 型變量為24位長,但可以改變編譯選項改成32位
除了bit型位變量外,PICC完全支持數組、結構和聯合等復合型高級變量,這和標准的C 語言所支持的高級變量類型沒有什么區別。例如: 數組:unsigned int data[10]; 結構:struct commInData { unsigned char inBuff[8]; unsigned char getPtr, putPtr; }; 聯合:union int_Byte { unsigned char c[2]; unsigned int i; };
3.PICC對數據寄存器bank 的管理
PICC把單片機中數據寄存器的bank 問題交由編程員自己管理,因此在定義用戶變量時你必須自己決定這些變量具體放在哪一個bank 中。如
果沒有特別指明,所定義的變量將被定位在bank0。
例如下面所定義的這些變量: unsigned char buffer[32]; bit flag1,flag2; float val[8];
除了bank0 內的變量聲明時不需特殊處理外,定義在其它bank 內的變量前面必須加上相應的bank 序號,例如:
bank1 unsigned char buffer[32]; //變量定位在bank1中
bank2 bit flag1,flag2; //變量定位在bank2中
bank3 float val[8]; //變量定位在bank3中
如果超過bank 容量,在最后連接時會報錯,大致信息如下:(中檔PIC中一個數據寄存器128字節)
Error[000] : Can't find 0x12C words for psect rbss_1 in segment BANK1
連接器告訴你總共有0x12C (300 )個字節准備放到 bank1 中但 bank1 容量不夠。顯然,只有把一部分原本定位在bank1 中的變量改放到其它 bank 中才能解決此問題。
雖然變量所在的bank 定位必須由編程員自己決定,但在編寫原程序時進行變量存取操作前無需再特意編寫設定bank 的指令。C 編譯器會根據所操作的對象自動生成對應 bank 設定的匯編指令。為避免頻繁的bank 切換以提高代碼效率,盡量把實現同一任務的變量定位在同一個bank 內;對不同bank 內的變量進行讀寫操作時也盡量把位於相同 bank 內的變量歸並在一起進行連續操作。
PICC中把所有的函數內部定義的auto型局部變量放在bank0。為了節約寶貴的存儲空間,它采用了一種被叫做“靜態覆蓋”的技術來實現局部變量的地址分配。因此用戶自己定位在bank()內的變量字節數將受到一定的限制,在實際使用時需注意。
bit型為變量只能是全局的或靜態的。PICC將把定位在同一bank內的8個位變量合並成一個存放於一個固定地址。因此所有指針對為變量的操作將直接使用PIC單片機的位操作匯編指令高效實現。
在用C 語言寫程序時變量一般由編譯器和連接器最后定位,在寫程序之時無需知道所定義的變量具體被放在哪個地址(除了bank 必須聲明)。真正需要絕對定位的只是單片機中的那些特殊功能寄存器,而這些寄存器的地址定位在PICC編譯環境所提供的頭文件中已經實現,無需用戶操心。
unsigned char tmpData @ 0x20; //tmpData定位在地址0x20
上面變量 tmpData 的地址是0x20,但最后 0x20 處完全有可能又被分配給了其它變量使用,這樣就發生了地址沖突。因此針對變量的絕對定位要特別小心。從筆者的應用經驗看,在一般的程序設計中用戶自定義的變量實在是沒有絕對定位的必要。
如果需要,位變量也可以絕對定位。但必須遵循上面介紹的位變量編址的方式。如果一個普通變量已經被絕對定位,那么此變量中的每個數據位就可以用下面的計算方式實現位變量指派:
unsigned char tmpData @ 0x20; //tmpData定位在地址0x20
bit tmpBit0 @ tmpData*8+0; //tmpBit0對應於tmpData第0 位
bit tmpBit1 @ tmpData*8+1; //tmpBit0對應於tmpData第1 位
bit tmpBit2 @ tmpData*8+2; //tmpBit0對應於tmpData第2 位
如果tmpData 事先沒有被絕對定位,那就不能用上面的位變量定位方式。
4.PICC的其它變量修飾關鍵詞:
extern — 外部變量聲明。如果在一個C 程序文件中要使用一些變量但其原型定義寫在另外的文件中,那么在本文件中必須將這些變量聲明成“extern ”外部類型。
例如程序文件code1.c中有如下定義: bank1 unsigned char var1, var2; //定義了bank1中的兩個變量 在另外一個程序文件code2.c中要對上面定義的變量進行操作,則必須在程序的開頭定義: extern bank1 unsigned char var1, var2; //聲明位於bank1的外部變量
volatile — 易變型變量聲明。PICC 中還有一個變量修飾詞在普通的C 語言介紹中一般是看不到的,它說明了一個變量的值是會隨機變化的,即使程序沒有刻意對它進行任何賦值操作。“volatile”類型定義在單片機的 C 語言編程中是如此的重要,是因為它可以告訴編譯器的優化處理器這些變量是實實在在存在的,在優化過程中不能無故消除。
假定你的程序定義了一個變量並對其作了一次賦值,但隨后就再也沒有對其進行任何讀寫操作,如果是非volatile 型變量,優化后的結果是這個變量將有可能被徹底刪除以節約存儲空間。另外一種情形是在使用某一個變量進行連續的運算操作時,這個變量的值將在第一次操作時被復制到中間臨時變量中,如果它是非volatile型變量,則緊接其后的其它操作將有可能直接從臨時變量中取數以提高運行效率,顯然這樣做后對於那些隨機變化的參數就會出問題。只要將其定義成volatile 類型后,編譯后的代碼就可以保證每次操作時直接從變量地址處取數。
const — 常數型變量聲明。這些變量就成為常數,程序運行過程中不能對其修改。除了位變量(這些位變量還是被放置在 RAM中,但程序不能對其賦值修改。),其它所有基本類型的變量或高級組合變量都將被存放在程序空間(ROM區)以節約數據存儲空間。顯然,被定義在ROM區的變量是不能再在程序中對其進行賦值修改的,這也是“const”的本來意義。
persistent — 非初始化變量聲明。按照標准C 語言的做法,程序在開始運行前首先要把所有定義的但沒有預置初值的變量全部清零。PICC會在最后生成的機器碼中加入一小段初始化代碼來實現這一變量清零操作,且這一操作將在main 函數被調用之前執行。問題是作為一個單片機的控制系統有很多變量是不允許在程序復位后被清零的。為了達到這一目的,PICC 提供了“persistent ”修飾詞以聲明此類變量無需在復位時自動清零,編程員應該自己決定程序中的那些變量是必須聲明成“persisten ”類型,而且須自己判斷什么時候需要對其進行初始化賦值。
例如: persistent unsigned char hour,minute,second; //定義時分秒變量
5.PICC中的指針
PICC中指針的基本概念和標准C 語法沒有太多的差別。但是在 PIC 單片機這一特定的架構上,指針的定義方式還是有幾點需要特別注意。
1.指向RAM的指針
如果是匯編語言編程,實現指針尋址的方法肯定就是用FSR 寄存器,PICC也不例外。這樣就勢必產生一個問題:FSR 能夠直接連續尋址的范圍是256 字節(bank0/1或bank2/3),要覆蓋最大512 字節的內部數據存儲空間,又該如何讓定義指針?PICC還是將這一問題留給編程員自己解決:在定義指針時必須明確指定該指針所適用的尋址區域,例如:
unsigned char *ptr0; //①定義覆蓋bank0/1的指針 bank2 unsigned char *ptr1; //②定義覆蓋bank2/3的指針 bank3 unsigned char *ptr2; //③定義覆蓋bank2/3的指針 上面定義了三個指針變量,其中①指針沒有任何bank 限定,缺省就是指向bank0 和bank1;②和③一個指明了bank2,另一個指明了bank3,
但實際上兩者是一樣的,因為一個指針可以同時覆蓋兩個bank 的存儲區域。另外,上面三個指針變量自身都存放在 bank0 中。
我們將在稍后介紹如何在其它bank 中存放指針變量。
既然定義的指針有明確的bank 適用區域,在對指針變量賦值時就必須實現類型匹配,同樣的道理,若函數調用時用了指針作為傳遞參數,也必須注意bank 作用域的匹配,而這點往往容易被忽視。假定有下面的函數實現發送一個字符串的功能:
void SendMessage(unsigned char *);
那么被發送的字符串必須位於bank0 或bank1 中。如果你還要發送位於 bank2 或bank3 內的字符串,必須再另外單獨寫一個函數:
void SendMessage_2(bank2 unsigned char *);
這兩個函數從內部代碼的實現來看可以一模一樣,但傳遞的參數類型不同。
按筆者的應用經驗體會,如果你看到了“Fixup overflow”的錯誤指示,幾乎可以肯定是指針類型不匹配的賦值所至。請重點檢查程序中有關指針的操作。
2.指向ROM常數的指針
如果一組變量是已經被定義在ROM區的常數,那么指向它的指針可以這樣定義:
const unsigned char company[]=”Microchip”; //定義ROM 中的常數
const unsigned char *romPtr; //定義指向ROM 的指針
程序中可以對上面的指針變量賦值和實現取數操作:
romPtr = company; //指針賦初值
data = *romPtr++; //取指針指向的一個數,然后指針加1
反過來,下面的操作將是一個錯誤,因為該指針指向的是常數型變量,不能賦值。
*romPtr = data; // 往指針指向的地址寫一個數
3.指向函數的指針
單片機編程時函數指針的應用相對較少,但作為標准 C 語法的一部分,PICC同樣支持函數指針調用。如果你對編譯原理有一定的了解,就應該明白在PIC 單片機這一特定的架構上實現函數指針調用的效率是不高的:PICC 將在RAM中建立一個調用返回表,真正的調用和返回過程是靠直接修改PC指針來實現的。因此,除非特殊算法的需要,建議大家盡量不要使用函數指針。
㈠ bank 修飾詞的位置含義 前面介紹的一些指針有的作用於bank0/1,有的作用於 bank2/3,但它們本身的存放位置全部在bank0。顯然,在一個程序設計中指針變量將有
可能被定位在任何可用的地址空間,這時,bank 修飾詞出現的位置就是一個關鍵,看下面的例子: //定義指向bank0/1的指針,指針變量為於bank0中 unsigned char *ptr0; //定義指向bank2/3的指針,指針變量為於bank0中 bank2 unsigned char *ptr0; //定義指向bank2/3的指針,指針變量為於bank1中 bank2 unsigned char * bank1 ptr0; 從中可以看出規律:前面的 bank 修飾詞指明了此指針的作用域;后面的 bank 修飾詞定義了此指針變量自身的存放位置。只要掌握了這一法則,
你就可以定義任何作用域的指針且可以將指針變量放於任何bank 中。 ㈡ volatile、persistent 和const修飾詞的位置含義 如果能理解上面介紹的bank 修飾詞的位置含義,實際上 volatile 、persistent 和const這些關鍵詞出現在前后不同位置上的含義規律是和
bank 一詞相一致的。例如: //定義指向bank0/1易變型字符變量的指針,指針變量位於bank0中且自身為非易變型 volatile unsigned char *ptr0; //定義指向bank2/3非易變型字符變量的指針,指針變量位於bank1中且自身為易變型 bank2 unsigned char * volatile bank1 ptr0; //定義指向ROM 區的指針,指針變量本身也是存放於ROM 區的常數 const unsigned char * const ptr0; 亦即出現在前面的修飾詞其作用對象是指針所指處的變量;出現在后面的修飾詞其作用對象就是指針變量自己。
6.PICC 中的子程序和函數
中檔系列的PIC 單片機程序空間有分頁的概念,但用C 語言編程時基本不用太多關心代碼的分頁問題。因為所有函數或子程序調用時的頁面設定(如果代碼超過一個頁面)都由編譯器自動生成的指令實現。
函數的代碼長度限制,一個良好的程序設計應該有一個清晰的組織結構,把不同的功能用不同的函數實現是最好的方法,因此一個函數 2K字長的限制一般不會對程序代碼的編寫產生太多影響。如果為實現特定的功能確實要連續編寫很長的程序,這時就必須把這些連續的代碼拆分成若干函數,以保證每個函數最后編譯出的代碼不超過一個頁面空間。
調用層次的控制,中檔系列PIC 單片機的硬件堆棧深度為8 級,考慮中斷響應需占用一級堆棧,所有函數調用嵌套的最大深度不要超過7 級。編程員必須自己控制子程序調用時的嵌套深度以符合這一限制要求。
中斷函數的實現,PICC可以實現C 語言的中斷服務程序。中斷服務程序有一個特殊的定義方法:
void interrupt ISR(void);
其中的函數名“ISR ”可以改成任意合法的字母或數字組合,但其入口參數和返回參數類型必須是“void ”型,亦即沒有入口參數和返回參數,且中間必須有一個關鍵詞“interrupt ”。中斷函數可以被放置在原程序的任意位置。因為已有關鍵詞“interrupt ”聲明,PICC在最后進行代碼連接時會自動將其定位到0x0004中斷入口處,實現中斷服務響應。編譯器也會實現中斷函數的返回指令“retfie”。
一個簡單的中斷服務示范函數如下: void interrupt ISR(void) //中斷服務程序 { if (T0IE && T0IF) //判TMR0 中斷 { T0IF = 0; // 清除TMR0 中斷標志 //在此加入TMR0 中斷服務 } if (TMR1IE && TMR1IF) //判TMR1 中斷 { TMR1IF = 0; //清除TMR1 中斷標志 //在此加入TMR1 中斷服務 } } //中斷結束並返回
PICC會自動加入代碼實現中斷現場的保護,並在中斷結束時自動恢復現場,所以編程員無需象編寫匯編程序那樣加入中斷現場保護和恢復的額外指令語句。但如果在中斷服務程序中需要修改某些全局變量時,是否需要保護這些變量的初值將由編程員自己決定和實施。用C 語言編寫中斷服務程序必須遵循高效的原則:
1.代碼盡量簡短,中斷服務強調的是一個“快”字。2.避免在中斷內使用函數調用。3.避免在中斷內進行數學運算。
標准庫函數,PICC提供了較完整的C 標准庫函數支持,其中包括數學運算函數和字符串操作函數。前加“#include <math.h> ” 包含頭文件,“#include <string.h> ”頭文件。
PICC 定義特殊區域值:
1 定義工作配置字,在用 PICC寫程序時同樣可以在C 原程序中定義,具體方式如下:
__CONFIG (HS & UNPROTECT & PWRTEN & BORDIS & WDTEN);
上面的關鍵詞“__CONFIG ”(注意前面有兩個下划線符)專門用於是芯片配置字的設定,后面括號中的各項配置位符號在特定型號單片機的頭文件中已經定義(注意不是pic.h頭文件),相互之間用邏輯“與”操作符組合在一起。這樣定義的配置字信息最后將和程序代碼一起放入同一個HEX文件。
在這里列出了適用於16F7x 系列單片機配置位符號預定義,其它型號或系列的單片機配置字定義方式類似,使用前查閱一下對應的頭文件即可。 /*振盪器配置*/ #define RC 0x3FFF // RC 振盪 #define HS 0x3FFE // HS 模式 #define XT 0x3FFD // XT 模式 #define LP 0x3FFC // LP 模式 /*看門狗配置*/ #define WDTEN 0x3FFF // 看門狗打開 #define WDTDIS 0x3FFB // 看門狗關閉 /*上電延時定時器配置*/ #define PWRTEN 0x3FF7 // 上電延時定時器打開 #define PWRTDIS 0x3FFF // 上電延時定時器關閉 /*低電壓復位配置*/ #define BOREN 0x3FFF // 低電壓復位允許 #define BORDIS 0x3FBF // 低電壓復位禁止 /*代碼保護配置*/ #define UNPROTECT 0x3FFF // 沒有代碼保護 #define PROTECT 0x3FEF // 程序代碼保護 頭文件預定義的配置信息符號
定義芯片標記單元
PIC 單片機中的標記單元定義可以用下面的__IDLOC(注意前面有兩個下划線符)預處理指令實現,方法如下:
__IDLOC (1234);
其特殊之處是括號內的值全部為16進制數,不需要用“0x”引導。這樣上面的定義就設定了標記單元內容為01020304 。
C 和匯編混合編程
在C 原程序中直接嵌入匯編指令是最直接最容易的方法。如果只需要嵌入少量幾條的匯編指令,PICC提供了一個類似於函數的語句:
asm(“clrwdt”);
雙引號中可以編寫任何一條PIC 的標准匯編指令。例如:
for (;;) { asm("clrwdt"); //清看門狗 Task(); ClockRun(); asm("sleep"); //休眠 asm("nop"); //空操作延時 }
如果需要編寫一段連續的匯編指令,PICC支持另外一種語法描述:用“#asm”開始匯編指令段,用“#endasm ”結束。一句話:用了C 語言后,就不要再老想着用匯編。盡量使用全局變量進行參數傳遞。
類似於純匯編文件的代碼也可以在C 語言框架下實現,方法是基於C 標准語法定義所有的變量和函數名,包括需要傳遞的形式參數、返回參數和局部變量,但函數內部的指令基本用嵌入匯編指令編寫,只有最后的返回參數用C 語句實現。這樣做后函數的運行效率和純匯編編寫時幾乎一模一樣,但各參數的傳遞統一用C 標准實現,這樣管理和維護就比較方便。例如下面的實現一個字節變量的偶校驗位計算。
bit EvenParity(unsigned char data) { #asm swapf ?a_EvenParity+0,w //入口參數data 的尋址符為 ?a_EvenParity+0 xorwf ?a_EvenParity+0,f rrf ?a_EvenParity+0,w xorwf ?a_EvenParity+0,f btfsc ?a_EvenParity+0,2 incf ?a_EvenParity+0,f #endasm //至此,data 的最低位即為偶校驗位 if (data&0x01) return(1); else return(0); }