最近在用Hitech PICC開發PIC16F877A,遇到不少Compiler上的問題,
這是在網路上發現的,想要好好了解PICC的編譯器,不妨參考這篇文章。
文章內容:
目前,Microchip公司生產的PIC系列單片機以其低成本、低功耗、高性能、開發速度快且一次性用戶可編程等優點迅速佔領了國內市場,成為國內銷售量最大的單片機。但國內介紹其C語言開發工具的書籍和文章卻比較少,在開發過程中給廣大程式師帶來了許多困難和不便。
Microchip公司沒有針對中低檔系列PIC單片機的C語言編譯器,但很多專業的第三方公司提供眾多支援PIC單片機的C語言編譯器,常見的有Hitech、CCS、IAR以及Bytecraft等公司。Hitech公司的PICC編譯器穩定可靠,編譯生成的代碼效率高,在用PIC單片機進行系統設計和開發的工程師群體中得到廣泛認可。因此,本文主要以HiTech PICC為基礎,介紹PIC的C語言的基本特點。
1 HiTech PICC語言的特點
PICC基本上符合ANSI標準,但是不支援函數的遞迴調用,其主要原因是PIC單片機特殊的堆疊結構。PIC單片機中的堆疊是硬體實現的,其深度已隨晶片固定,無法實現需要大量堆疊操作的遞迴演算法;另外在PIC單片機中實現軟體堆疊的效率也不是很高。為此,PICC編譯器採用一種“靜態覆蓋”技術,以實現對C語言函數中的局部變數分配固定的位址空間。經這樣處理後產生出的機器代碼效率很高。當代碼量超過4KB後,C語言編譯出的代碼長度與全部用彙編代碼實現的差別已經不是很大(<10%),當然前提是在整個C代碼編寫過程中需時時注意所編寫語句的效率。
2 PICC中的變數
PICC中的變數類型和標準C語言一樣,這裏不再重複。為了使編譯器產生最高效的機器碼,PICC把單片機中資料寄存器的bank交由編程員自己管理,因此在定義用戶變數時必須自己決定這些變數具體放在哪一個bank中。如果沒有特別指明,所定義的變數將被定位在bank0。定義在其他bank內的變數前面必須加上相應的bank序號,例如:
bank1 unsigned char temp;//變數定位在bank1中
中檔系列PIC單片機資料寄存器的一個bank大小為128B,除前面若干位元組的特殊功能寄存器區域,在C語言中某一bank內定義的變數位元組總數不能超過可用RAM位元組數。如果超過bank容量,在最後連接時會報錯,大致資訊如下:
Error[000]:Can’t find 0x12C words for psect rbss_1 in segmentBANK1
鏈結器提示,總共有0x12c(300)位元組準備放到bank1中,但bank1容量不夠。雖然變數所在的bank定位必須由編程員自己決定,但編寫根源程式時在進行變數存取操作前無需再特意編寫設定bank的指令。C編譯器會根據所操作的物件自動生成對應bank設定的彙編指令。為避免頻繁的bank切換以提高代碼效率,儘量把實現同一任務的變數定位在同一個bank內;對不同bank內的變數進行讀寫操作時也儘量把位於相同bank內的變數歸併在一起進行連續操作。
bit型位元變數只能是全局的或靜態的。PICC將把定位在同一bank內的8個位變數合併成一個位元組存放於一個固定位址。PICC對整個資料存儲空間實行位元編址,0x000單元第0位元位元位址是0x0000,以此類推,每個位元組有8個位位址。如果一個位變數flag1被編址為0x123,那麼實際的存儲空間位於:
位元組位址=0x123/8 = 0x24
位偏移=0x123%8 = 3
即flag1位元變數位於位址為0x24位元組的第3位元。在程式調試時如果要觀察flag1的變化,必須觀察位址為0x24的位元組而不是0x123。PICC在編譯原代碼時只要有可能,對普通變數的操作也將以最簡單的位元元操作指令來實現。假設一個位元組變數tmp最後被定位在位址0x20,那麼
tmp | =0x80=>bsf 0x20.7
另外,函數可以返回一個位變數,返回的位元變數將存放於單片機的進位位中返回。
3 PICC中的指標
3.1 指向RAM的指標
PICC在編譯C根源程式時,將指向RAM的指標操作最終用FSR來實現間接定址。FSR能夠直接連續定址的範圍是256B,所以一個指標可以同時覆蓋2個bank的存儲區域(bank0/1或bank2/3,一個bank區域是128 B)。要覆蓋最大512B的內部資料存儲空間,在定義指標時必須明確指定該指標適用的定址區域。例如:
unsigned char *pointer0; //定義覆蓋bank0/1的指針
bank2 char *pointer1;//定義覆蓋bank2/3的指針
既然定義的指標有明確的bank適用區域,在對指標變數賦值時就必須實現類型匹配,否則將產生錯誤,例如:
unsigned char *pointer0; //定義指向bank0/1的指標
bank2 unsigned char buff[8];//定義bank2/3中的一個緩衝區
程式語句:
pointer() =buff;//錯誤!試圖將bank2內的變數位址賦給指向bank0/1的指標
若出現此類錯誤的指標操作,PICC在最後鏈結時會告知類似於下麵的資訊:
Fixup oveRFlow in expression (…)
3.2 指向ROM常數的指標
如果一組變數是已經被定義在ROM區的常數,那麼指向其的指標可以這樣定義:
const unsigned char company[]="software"
3.3 指向函數的指標
因為在PIC單片機這一特定的架構上實現函數指標調用的效率不高,因此,除非特殊演算法的需要,建議大家儘量不要使用函數指標。
4 PICC中的副程式和函數
中檔系列的PIC單片機程式空間有分頁的概念,但用C語言編程時基本不用過多關心代碼的分頁問題。因為所有函數或副程式調用時的頁面設定(如果代碼超過一個頁面)都由編譯器自動生成的指令實現。
4.1 函數的代碼長度限制
PICC決定了C根源程式中的一個函數經編譯後生成的機器碼一定會放在同一個程式頁面內。中檔系列PIC單片機的一個程式頁面的長度是2KB,用C語言編寫的任何一個函數最後生成的代碼不能超過2KB。如果為實現特定的功能確實要連續編寫很長的程式,這時就必須把這些連續的代碼拆分成若干函數,以保證每個函數最後編譯出的代碼不超過一個頁面空間。
4.2 調用層次的控制
PIC單片機採用硬體堆疊,所以編程時函數的調用層次會受到一定限制。一般PIC系列的中檔單片機硬體堆疊深度為8級。程式師必須自己控制副程式調用時的嵌套深度以符合這一限制要求。PICC在最後編譯鏈結成功後可以生成一個鏈結定位映射檔(*.map),在此檔中有詳細的函數調用嵌套指示圖“call graph”,有些函數調用是編譯時自動加入的庫函數,這些函數調用從C根源程式中無法直接看出,但在嵌套指示圖上則一目了然。
5 C語言和組合語言混合編程
單片機的一些特殊指令操作在標準的C語言語法中沒有直接對應的描述,例如PIC單片機的清看門狗指令“clrwdt”和休眠指令“sleep”;單片機系統強調的是控制的即時性,為了實現這一要求,有時必須用彙編指令實現部分代碼以提高程式運行的效率。在C程式中嵌入彙編指令有2種方法。
① 如果只需要嵌入少量幾條彙編指令,PICC提供了一個類似於函數的語句:
asm("clrwdt");
這是在C根源程式中直接嵌入彙編指令的最直接最容易的方法。
② 如果需要編寫一段連續的彙編指令,PICC支援另外的一種語法描述:用“#asm”來開始彙編指令段,用“#endasm”結束。例如:
#asm
CDIS:MOVLW 4 ;清顯示子程序
MOVWF COUNT ;共四位顯示
CDIS1:MOVLW 0FEH ;顯示為"灰"的段碼
CALL xmit ;顯示子程序
DECFSZ count
GOTO CDIS
#endasm
5.1 彙編指令定址C語言定義的總體變數
所有C語言中定義的符號在編譯後將自動在前面添加下劃線“_”。因此,若要在彙編指令中定址C語言定義的各類變數,一定要在變數前加上“_”符號,例如上例中的count是在C語言中定義的無符號總體變數,在組合語言中只需在其前面加上“_”符號就可進行訪問了。另外,對於C語言中定義的多位元組總體變數,例如C語言中的如下定義:
int advalue;
在組合語言裏訪問時就得分位元組訪問,例如:
asm(“movf_advalue+0.0”);//把advalue低位元組中的數送到w裏
asm(“rRF_advalue+1”)//把advalue高位元組中的數左移一位
5.2 彙編指令定址C函數的局部變數
前面已經提到,PICC對自動型局部變數(包括函數調用時的入口參數)採用一種“靜態覆蓋”技術,對每一個變數確定一個固定位址(位於bank0),嵌入的彙編指令對其定址時只需採用資料寄存器的直接定址方式即可,因此關鍵是要知道這些局部變數的定址符號。建議讀者先編寫一小段C代碼,其中有最簡單的局部變數操作指令,把此源代碼編譯成對應的PICC彙編指令;查看C編譯器生成的彙編指令是如何定址這些局部變數的,自己編寫的行內彙編指令就採用同樣的定址方式。
相對於組合語言,用C語言編程的優勢是毋庸置疑的:開發效率大大提高、人性化的語句指令及模組化的程式易於日常管理和維護、程式在不同平臺間移植方便。所以既然使用C語言編程,就應該儘量避免嵌入彙編指令或編寫彙編指令模組檔。例如:
count1=8;
while(count1>0)
{
asm("rlf adv")
count1;
}
變數的迴圈右移操作用C語言實現非常不方便,PIC單片機已有對應的移位元元操作彙編指令,因此用嵌入彙編的形式實現效率最高。對移位元次數的控制,實際上變數count1的遞減判零也可以直接用彙編指令實現,這樣可節約代碼,但用標準C語言描述更直觀、更易於維護。
6 注意事項
① 既然所有的局部變數將佔用bank0的存儲空間,因此用戶自己定位在bank0內的變數位元組數將受到一定的限制,在實際使用時需注意。
② 當程式中把非位元變數進行強制類型轉換成位元變數時,要注意編譯器只對普通變數的最低位元做判別:若最低位元是0,則轉換成位元變數0;若最低位元是1,則轉換成位元變數1。
③ 由於PIC系列單片機的內部資源十分有限,所以在允許的條件下應儘量使用無符號字元型變數,以節約空間。
④ PICC對絕對定位的變數不保留位址空間,例如:
unsigned char advalue @ 0x20;//advalue定位在位址0x20,相當於組合語言中的虛擬指令
advalue EQU 20H
所以請讀者慎用。
⑤ 儘量使用總體變數進行參數傳遞,使用總體變數最大的好處是定址直觀,只需在C語言定義的變數名前增加一個下劃線符即可在彙編語句中定址;使用總體變數進行參數傳遞的效率也比形參高。
⑥ 對於多位元組變數(如int型、float型變數等)PICC遵循Little endian標準,即低位元組放在存儲空間的低位元址,高位元組放在高位址,編程時需注意。
結語
一般C語言產生的代碼是比較繁瑣的,所以要寫出高品質、實用的C語言程式,就必須對單片機體系結構和硬體資源作詳盡的瞭解。用C語言開發PIC系列單片機系統軟體具有編寫代碼效率高、軟體調試直觀、維護升級方便、代碼的重複利用率高、便於跨平臺的代碼移植等優點,因此C語言編程在單片機系統設計中的應用必將越來越廣泛。
參考文獻
[1]蔡純潔,邢武.PIC 16/17單片機原理和應用[M].合肥:中國科學技術大學出版社,1997.
[2]Hitech Software.PICC ANSI C Compiler User’s Guide,2002.</P>
留言列表