機(jī)電之家資源網(wǎng)
單片機(jī)首頁|單片機(jī)基礎(chǔ)|單片機(jī)應(yīng)用|單片機(jī)開發(fā)|單片機(jī)文案|軟件資料下載|音響制作|電路圖下載 |嵌入式開發(fā)
培訓(xùn)信息
贊助商
PIC單片機(jī)C語言編程教程
PIC單片機(jī)C語言編程教程
 更新時間:2008-7-26 17:03:52  點擊數(shù):3
【字體: 字體顏色

PIC 單片機(jī) C 語言編程簡介

用  C  語言來開發(fā)單片機(jī)系統(tǒng)軟件最大的好處是編寫代碼效率高、軟件調(diào)試直觀、維護(hù)升級方便、

代碼的重復(fù)利用率高、便于跨平臺的代碼移植等等,因此 C 語言編程在單片機(jī)系統(tǒng)設(shè)計中已得到越

來越廣泛的運(yùn)用。針對 PIC 單片機(jī)的軟件開發(fā),同樣可以用 C 語言實現(xiàn)。

但在單片機(jī)上用 C 語言寫程序和在 PC 機(jī)上寫程序絕對不能簡單等同,F(xiàn)在的 PC 機(jī)資

源十分豐富,運(yùn)算能力強(qiáng)大,因此程序員在寫 PC 機(jī)的應(yīng)用程序時幾乎不用關(guān)心編譯后的可

執(zhí)行代碼在運(yùn)行過程中需要占用多少系統(tǒng)資源,也基本不用擔(dān)心運(yùn)行效率有多高。寫單片機(jī)

的  C 程序最關(guān)鍵的一點是單片機(jī)內(nèi)的資源非常有限,控制的實時性要求又很高,因此,如

果沒有對單片機(jī)體系結(jié)構(gòu)和硬件資源作詳盡的了解,以筆者的愚見認(rèn)為是無法寫出高質(zhì)量實

用的 C 語言程序。這就是為什么前面所有章節(jié)中的的示范代碼全部用基礎(chǔ)的匯編指令實現(xiàn)

的原因,希望籍此能使讀者對  PIC  單片機(jī)的指令體系和硬件資源有深入了解,在這基礎(chǔ)之

上再來討論 C 語言編程,就有水到渠成的感覺。

本書圍繞中檔系列 PIC 單片機(jī)來展開討論,Microchip 公司自己沒有針對中低檔系列 PIC

單片機(jī)的 C 語言編譯器,但很多專業(yè)的第三方公司有眾多支持 PIC 單片機(jī)的 C 語言編譯器

提供,常見的有 Hitech、CCS、IAR、Bytecraft 等公司。其中筆者最常用的是 Hitech 公司的

PICC 編譯器,它穩(wěn)定可靠,編譯生成的代碼效率高,在用 PIC 單片機(jī)進(jìn)行系統(tǒng)設(shè)計和開發(fā)

的工程師群體中得到廣泛認(rèn)可。其正式完全版軟件需要購置,但在其網(wǎng)站上有限時的試用版

供用戶評估。另外,Hitech 公司針對廣大 PIC 的業(yè)余愛好者和初學(xué)者還提供了完全免費(fèi)的學(xué)

習(xí)版 PICC-Lite 編譯器套件,它的使用方式和完全版相同,只是支持的 PIC 單片機(jī)型號限制

在 PIC16F84、PIC16F877 和 PIC16F628 等幾款。這幾款 Flash 型的單片機(jī)因其所具備的豐富

的片上資源而最適用于單片機(jī)學(xué)習(xí)入門,因此筆者建議感興趣的讀者可從 PICC-Lite 入手掌

握 PIC 單片機(jī)的 C 語言編程。

在此列出幾個主要的針對 PIC 單片機(jī)的 C 編譯器相關(guān)連接網(wǎng)址,供讀者參考:

Hitech-PICC:       www.htsoft.com


IAR:www.iar.com

CCS:www.ccsinfo.com/picc.shtml

ByteCraft:www.bytecraft.com/mpccaps.html


本章將介紹 Hitech-PICC 編譯器的一些基本概念,由于篇幅所限將不涉及 C 語言的標(biāo)準(zhǔn)

語法和基礎(chǔ)知識介紹,因為在這些方面都有大量的書籍可以參考。重點突出針對  PIC  單片

機(jī)的特點而所需要特別注意的地方。

11.2

Hitech-PICC 編譯器

PICC 基本上符合 ANSI 標(biāo)準(zhǔn),除了一點:它不支持函數(shù)的遞歸調(diào)用。其主要原因是因

為 PIC 單片機(jī)特殊的堆棧結(jié)構(gòu)。在前面介紹 PIC 單片機(jī)架構(gòu)時已經(jīng)詳細(xì)說明了 PIC 單片機(jī)

中的堆棧是硬件實現(xiàn)的,其深度已隨芯片而固定,無法實現(xiàn)需要大量堆棧操作的遞歸算法;

另外在 PIC 單片機(jī)中實現(xiàn)軟件堆棧的效率也不是很高,為此,PICC 編譯器采用一種叫做“靜

態(tài)覆蓋”的技術(shù)以實現(xiàn)對    C  語言函數(shù)中的局部變量分配固定的地址空間。經(jīng)這樣處理后產(chǎn)

生出的機(jī)器代碼效率很高,按筆者實際使用的體會,當(dāng)代碼量超過 4K 字后,C 語言編譯出

的代碼長度和全部用匯編代碼實現(xiàn)時的差別已經(jīng)不是很大(<10%),當(dāng)然前提是在整個  C

代碼編寫過程中須時時處處注意所編寫語句的效率,而如果沒有對 PIC 單片機(jī)的內(nèi)核結(jié)構(gòu)、

各功能模塊及其匯編指令深入了解,要做到這點是很難的。

11.3

MPLAB-IDE 內(nèi)掛接 PICC

PICC 編譯器可以直接掛接在 MPLAB-IDE 集成開發(fā)平臺下,實現(xiàn)一體化的編譯連接和

原代碼調(diào)試。使用 MPLAB-IDE 內(nèi)的調(diào)試工具 ICE2000、ICD2 和軟件模擬器都可以實現(xiàn)原

代碼級的程序調(diào)試,非常方便。

首先必須在你的計算機(jī)中安裝PICC編譯器,無論是完全版還是學(xué)習(xí)版都可以和

MPLAB-IDE掛接。安裝成功后可以進(jìn)入IDE,選擇菜單項Project Set Language Tool

Locations…,打開語言工具掛接設(shè)置對話框,如圖 11-1 所示:

 

圖 11-1  MPLAB-IDE 語言工具設(shè)置對話框

在對話框中選擇“HI-TECH PICC Toolsuite”欄,展開可執(zhí)行文件組“Executable”后,

列出了將被  MPLAB-IDE   后臺調(diào)用的編譯器所用到的所有可執(zhí)行文件,其中有匯編編譯器

“PICC Assembler”、C 原程序編譯器“PICC Compiler”和連接定位程序“PICC Linker”。同

時在此列表中還顯示了對應(yīng)的可執(zhí)行程序名,請注意在這里都是“PICC.EXE”。用鼠標(biāo)分別

點擊選中這三項可執(zhí)行文件,觀察對話框下面“Location”一欄中顯示的文件路徑,用

“Browse…”按紐,從計算機(jī)中已經(jīng)安裝的 PICC 編譯器文件夾中選擇 PICC.EXE 文件。實

際上 PICC.EXE 只是一個調(diào)度管理程序,它會按照所輸入的文件擴(kuò)展名自動調(diào)用對應(yīng)的編譯

器和連接器,用戶要注意的是 C 語言原程序擴(kuò)展名用“.c”,匯編原程序用“.as”即可。

工具掛接完成后,在建立項目時可以選擇語言工具為“HI-TECH PICC”,具體步驟可以

參閱第三章 3.1.3 節(jié),此處不再重復(fù)。項目建立完成后可以加入 C 或匯編原程序,也可以加

入已有的庫文件或已經(jīng)編譯的目標(biāo)文件。最常見的是只加入 C 原程序。用 C 語言編程的好

處是可以實現(xiàn)模塊化編程。程序編寫者應(yīng)盡量把相互獨(dú)立的控制任務(wù)用多個獨(dú)立的 C 原程序文件實

現(xiàn),如果程序量較大,一般不要把所有的代碼寫在一個文件內(nèi)。

圖 11-2 列出的是筆者建立的一個項目中所有 C 原程序模塊,其中主控、數(shù)值計算、I2C 總線操

作、命令按鍵處理和液晶顯示驅(qū)動等不同的功能分別在不同的獨(dú)立的原程序模塊中實現(xiàn)。



圖 11-2  C 語言多模塊編程

11.4         PIC 單片機(jī)的 C 語言原程序基本框架

基于 PICC 編譯環(huán)境編寫 PIC 單片機(jī)程序的基本方式和標(biāo)準(zhǔn) C 程序類似,程序一般由以

下幾個主要部分組成:

&O1540;    在程序的最前面用#include 預(yù)處理指令引用包含頭文件,其中必須包含一個編譯器

提供的“pic.h”文件,實現(xiàn)單片機(jī)內(nèi)特殊寄存器和其它特殊符號的聲明;

&O1540;    用“__CONFIG”預(yù)處理指令定義芯片的配置位;

&O1540;    聲明本模塊內(nèi)被調(diào)用的所有函數(shù)的類型,PICC 將對所調(diào)用的函數(shù)進(jìn)行嚴(yán)格的類型

匹配檢查;

&O1540;    定義全局變量或符號替換;

&O1540;    實現(xiàn)函數(shù)(子程序),特別注意 main 函數(shù)必須是一個沒有返回的死循環(huán)。

 

下面的例 11-1 為一個 C 原程序的范例,供大家參考。

#include          //包含單片機(jī)內(nèi)部資源預(yù)定義

#include “pc68.h”       //包含自定義頭文件

//定義芯片工作時的配置位

__CONFIG (HS & PROTECT & PWRTEN & BOREN & WDTDIS);

//聲明本模塊中所調(diào)用的函數(shù)類型

void SetSFR(void);

void Clock(void);

void KeyScan(void);

void Measure(void);

void LCD_Test(void);

void LCD_Disp(unsigned char);

//定義變量

unsigned char second, minute, hour;

bit flag1,flag2;

//函數(shù)和子程序

void main(void)

{

  SetSFR();

  PORTC =  0x00;

  TMR1H +=  TMR1H_CONST;

  LED1  = LED_OFF;

 

 

  LCD_Test();

 

 

  //程序工作主循環(huán)

  while(1) {

     asm(“clrwdt”); 

     Clock();  

     KeyScan();

     Measure();

     SetSFR(); 

  }

}

//清看門狗

//更新時鐘

//掃描鍵盤

//數(shù)據(jù)測量

//刷新特殊功能寄存器

11.5

PICC 中的變量定義


例 11-1  C 語言原程序框架舉例

11.5.1     PICC 中的基本變量類型


PICC 遵循 Little-endian  標(biāo)準(zhǔn),多字節(jié)變量的低字節(jié)放在存儲空間的低地址,高字節(jié)放

在高地址。

11.5.2     PICC 中的高級變量

基于表 11-1 的基本變量,除了 bit 型位變量外,PICC 完全支持?jǐn)?shù)組、結(jié)構(gòu)和聯(lián)合等復(fù)

合型高級變量,這和標(biāo)準(zhǔn)的 C 語言所支持的高級變量類型沒有什么區(qū)別。例如:

數(shù)組:unsigned int data[10];

結(jié)構(gòu):struct commInData {

         unsigned char  inBuff[8];

         unsigned char  getPtr, putPtr;

      };

聯(lián)合:union int_Byte {

         unsigned char  c[2];

         unsigned int i;

      };

例 11-2  C 語言高級變量舉例

11.5.3     PICC 對數(shù)據(jù)寄存器 bank 的管理

 

為了使編譯器產(chǎn)生最高效的機(jī)器碼,PICC 把單片機(jī)中數(shù)據(jù)寄存器的 bank 問題交由編程

員自己管理,因此在定義用戶變量時你必須自己決定這些變量具體放在哪一個 bank 中。如

果沒有特別指明,所定義的變量將被定位在 bank0,例如下面所定義的這些變量:

unsigned char buffer[32];

bit flag1,flag2;

float val[8];

 

 

除了 bank0 內(nèi)的變量聲明時不需特殊處理外,定義在其它 bank 內(nèi)的變量前面必須加上

相應(yīng)的 bank 序號,例如:

bank1 unsigned char buffer[32];        //變量定位在 bank1 中


bank2 bit flag1,flag2;

bank3 float  val[8];


//變量定位在 bank2 中

//變量定位在 bank3 中


 

 

中檔系列 PIC 單片機(jī)數(shù)據(jù)寄存器的一個 bank 大小為 128 字節(jié),刨去前面若干字節(jié)的特

殊功能寄存器區(qū)域,在 C 語言中某一 bank 內(nèi)定義的變量字節(jié)總數(shù)不能超過可用 RAM 字節(jié)

數(shù)。如果超過 bank 容量,在最后連接時會報錯,大致信息如下:

Error[000]   : Can't find 0x12C words for psect rbss_1 in segment BANK1

 

 

連接器告訴你總共有 0x12C(300)個字節(jié)準(zhǔn)備放到 bank1 中但 bank1 容量不夠。顯然,只

有把一部分原本定位在 bank1 中的變量改放到其它 bank 中才能解決此問題。

 

雖然變量所在的 bank 定位必須由編程員自己決定,但在編寫原程序時進(jìn)行變量存取操

作前無需再特意編寫設(shè)定 bank 的指令。C 編譯器會根據(jù)所操作的對象自動生成對應(yīng) bank 設(shè)

定的匯編指令。為避免頻繁的 bank 切換以提高代碼效率,盡量把實現(xiàn)同一任務(wù)的變量定位

在同一個 bank 內(nèi);對不同 bank 內(nèi)的變量進(jìn)行讀寫操作時也盡量把位于相同 bank 內(nèi)的變量

歸并在一起進(jìn)行連續(xù)操作。

11.5.4     PICC 中的局部變量

 

PICC 把所有函數(shù)內(nèi)部定義的 auto 型局部變量放在 bank0。為節(jié)約寶貴的存儲空間,它

采用了一種被叫做“靜態(tài)覆蓋”的技術(shù)來實現(xiàn)局部變量的地址分配。其大致的原理是在編譯

器編譯原代碼時掃描整個程序中函數(shù)調(diào)用的嵌套關(guān)系和層次,算出每個函數(shù)中的局部變量字

節(jié)數(shù),然后為每個局部變量分配一個固定的地址,且按調(diào)用嵌套的層次關(guān)系各變量的地址可

以相互重疊。利用這一技術(shù)后所有的動態(tài)局部變量都可以按已知的固定地址地進(jìn)行直接尋

址,用 PIC 匯編指令實現(xiàn)的效率最高,但這時不能出現(xiàn)函數(shù)遞歸調(diào)用。PICC 在編譯時會嚴(yán)

格檢查遞歸調(diào)用的問題并認(rèn)為這是一個嚴(yán)重錯誤而立即終止編譯過程。

既然所有的局部變量將占用 bank0 的存儲空間,因此用戶自己定位在 bank0 內(nèi)的變量字

節(jié)數(shù)將受到一定的限制,在實際使用時需注意。

 

11.5.5     PICC 中的位變量

 

bit 型位變量只能是全局的或靜態(tài)的。PICC 將把定位在同一 bank 內(nèi)的 8 個位變量合并

成一個字節(jié)存放于一個固定地址。因此所有針對位變量的操作將直接使用  PIC  單片機(jī)的位

操作匯編指令高效實現(xiàn)。基于此,位變量不能是局部自動型變量,也無法將其組合成復(fù)合型

高級變量。

 

PICC 對整個數(shù)據(jù)存儲空間實行位編址,0x000 單元的第 0 位是位地址 0x0000,以此后

推,每個字節(jié)有 8 個位地址。編制位地址的意義純粹是為了編譯器最后產(chǎn)生匯編級位操作指

令而用,對編程人員來說基本可以不管。但若能了解位變量的位地址編址方式就可以在最后

程序調(diào)試時方便地查找自己所定義的位變量,如果一個位變量 flag1 被編址為 0x123,那么

實際的存儲空間位于:

字節(jié)地址=0x123/8 = 0x24

位偏移      =0x123%8 = 3

即 flag1 位變量位于地址為 0x24 字節(jié)的第 3 位。在程序調(diào)試時如果要觀察 flag1 的變化,必

須觀察地址為 0x24 的字節(jié)而不是 0x123。

 

PIC  單片機(jī)的位操作指令是非常高效的。因此,PICC 在編譯原代碼時只要有可能,對

普通變量的操作也將以最簡單的位操作指令來實現(xiàn)。假設(shè)一個字節(jié)變量  tmp  最后被定位在

地址 0x20,那么


tmp |= 0x80

tmp &= 0xf7


=>   bsf

=>   bcf


0x20,7

0x20,3


if (tmp&0xfe)


=>   btfsc 0x20,0


即所有只對變量中某一位操作的 C 語句代碼將被直接編譯成匯編的位操作指令。雖然編程

時可以不用太關(guān)心,但如果能了解編譯器是如何工作的,那將有助于引導(dǎo)我們寫出高效簡介

的 C 語言原程序。

 

在有些應(yīng)用中需要將一組位變量放在同一個字節(jié)中以便需要時一次性地進(jìn)行讀寫,這一

功能可以通過定義一個位域結(jié)構(gòu)和一個字節(jié)變量的聯(lián)合來實現(xiàn),例如:

 

union {

   struct  {

      unsigned  b0:  1;

      unsigned  b1:  1;

      unsigned  b2:  1;

      unsigned  b3:  1;

      unsigned  b4:  1;

      unsigned  b5:  1;

      unsigned   : 2; //最高兩位保留

   } oneBit;

   unsigned char allBits;

} myFlag;

例 11-3      定義位變量于同一字節(jié)

需要存取其中某一位時可以

myFlag.oneBit.b3=1; //b3 位置 1

一次性將全部位清零時可以

myFlag.allBits=0;          //全部位變量清 0

 

 

當(dāng)程序中把非位變量進(jìn)行強(qiáng)制類型轉(zhuǎn)換成位變量時,要注意編譯器只對普通變量的最低

位做判別:如果最低位是 0,則轉(zhuǎn)換成位變量 0;如果最低位是 1,則轉(zhuǎn)換成位變量 1。而標(biāo)

準(zhǔn)的 ANSI-C 做法是判整個變量值是否為 0。另外,函數(shù)可以返回一個位變量,實際上此返

回的位變量將存放于單片機(jī)的進(jìn)位位中帶出返回。

 

11.5.6     PICC 中的浮點數(shù)

 

PICC 中描述浮點數(shù)是以 IEEE-754 標(biāo)準(zhǔn)格式實現(xiàn)的。此標(biāo)準(zhǔn)下定義的浮點數(shù)為 32 位長,

在單片機(jī)中要用 4 個字節(jié)存儲。為了節(jié)約單片機(jī)的數(shù)據(jù)空間和程序空間,PICC 專門提供了

一種長度為 24 位的截短型浮點數(shù),它損失了浮點數(shù)的一點精度,但浮點運(yùn)算的效率得以提

高。在程序中定義的 float 型標(biāo)準(zhǔn)浮點數(shù)的長度固定為 24 位,雙精度 double 型浮點數(shù)一般

也是 24 位長,但可以在程序編譯選項中選擇 double 型浮點數(shù)為 32 位,以提高計算的精度。

 

一般控制系統(tǒng)中關(guān)心的是單片機(jī)的運(yùn)行效率,因此在精度能夠滿足的前提下盡量選擇

24 位的浮點數(shù)運(yùn)算。

 

11.5.7     PICC 中變量的絕對定位

 

首先必須強(qiáng)調(diào),在用  C  語言寫程序時變量一般由編譯器和連接器最后定位,在寫程序

之時無需知道所定義的變量具體被放在哪個地址(除了 bank 必須聲明)。

 

真正需要絕對定位的只是單片機(jī)中的那些特殊功能寄存器,而這些寄存器的地址定位在

PICC 編譯環(huán)境所提供的頭文件中已經(jīng)實現(xiàn),無需用戶操心。編程員所要了解的也就是 PICC

是如何定義這些特殊功能寄存器和其中的相關(guān)控制位的名稱。好在 PICC 的定義標(biāo)準(zhǔn)基本上

按照芯片的數(shù)據(jù)手冊中的名稱描述進(jìn)行,這樣就秉承了變量命名的一貫性。一個變量絕對定

位的例子如下:

unsigned char tmpData @ 0x20; //tmpData 定位在地址 0x20

千萬注意,PICC 對絕對定位的變量不保留地址空間。換句話說,上面變量 tmpData 的

地址是 0x20,但最后 0x20 處完全有可能又被分配給了其它變量使用,這樣就發(fā)生了地址沖

突。因此針對變量的絕對定位要特別小心。從筆者的應(yīng)用經(jīng)驗看,在一般的程序設(shè)計中用戶

自定義的變量實在是沒有絕對定位的必要。

 

如果需要,位變量也可以絕對定位。但必須遵循上面介紹的位變量編址的方式。如果一

個普通變量已經(jīng)被絕對定位,那么此變量中的每個數(shù)據(jù)位就可以用下面的計算方式實現(xiàn)位變

量指派:

unsigned char tmpData @ 0x20; //tmpData 定位在地址 0x20

bit tmpBit0 @ tmpData*8+0;      //tmpBit0 對應(yīng)于 tmpData 第 0 位

bit tmpBit1 @ tmpData*8+1;      //tmpBit0 對應(yīng)于 tmpData 第 1 位

bit tmpBit2 @ tmpData*8+2;      //tmpBit0 對應(yīng)于 tmpData 第 2 位

 

 

如果 tmpData 事先沒有被絕對定位,那就不能用上面的位變量定位方式。

 

11.5.8     PICC 的其它變量修飾關(guān)鍵詞

 

&O1540;    extern  —  外部變量聲明

 

如果在一個 C  程序文件中要使用一些變量但其原型定義寫在另外的文件中,那么在本

文件中必須將這些變量聲明成“extern”外部類型。例如程序文件 code1.c 中有如下定義:


bank1 unsigned char var1, var2;  


//定義了 bank1 中的兩個變量


在另外一個程序文件 code2.c 中要對上面定義的變量進(jìn)行操作,則必須在程序的開頭定義:

extern bank1 unsigned char var1, var2;       //聲明位于 bank1 的外部變量

 

 

&O1540;    volatile  — 易變型變量聲明

 

PICC  中還有一個變量修飾詞在普通的  C  語言介紹中一般是看不到的,這就是關(guān)鍵詞

“volatile”。顧名思義,它說明了一個變量的值是會隨機(jī)變化的,即使程序沒有刻意對它進(jìn)

行任何賦值操作。在單片機(jī)中,作為輸入的 IO 端口其內(nèi)容將是隨意變化的;在中斷內(nèi)被修

改的變量相對主程序流程來講也是隨意變化的;很多特殊功能寄存器的值也將隨著指令的運(yùn)

行而動態(tài)改變。所有這種類型的變量必須將它們明確定義成“volatile”類型,例如:

volatile unsigned char STATUS @ 0x03;

volatile bit commFlag;

 

 

“volatile”類型定義在單片機(jī)的      C 語言編程中是如此的重要,是因為它可以告訴編譯

器的優(yōu)化處理器這些變量是實實在在存在的,在優(yōu)化過程中不能無故消除。假定你的程序定

義了一個變量并對其作了一次賦值,但隨后就再也沒有對其進(jìn)行任何讀寫操作,如果是非

volatile  型變量,優(yōu)化后的結(jié)果是這個變量將有可能被徹底刪除以節(jié)約存儲空間。另外一種

情形是在使用某一個變量進(jìn)行連續(xù)的運(yùn)算操作時,這個變量的值將在第一次操作時被復(fù)制到

中間臨時變量中,如果它是非 volatile 型變量,則緊接其后的其它操作將有可能直接從臨時

變量中取數(shù)以提高運(yùn)行效率,顯然這樣做后對于那些隨機(jī)變化的參數(shù)就會出問題。只要將其

定義成 volatile 類型后,編譯后的代碼就可以保證每次操作時直接從變量地址處取數(shù)。

 

&O1540;    const —  常數(shù)型變量聲明

 

如果變量定義前冠以“const”類型修飾,那么所有這些變量就成為常數(shù),程序運(yùn)行過

程中不能對其修改。除了位變量,其它所有基本類型的變量或高級組合變量都將被存放在程

序空間(ROM 區(qū))以節(jié)約數(shù)據(jù)存儲空間。顯然,被定義在 ROM 區(qū)的變量是不能再在程序

中對其進(jìn)行賦值修改的,這也是“const”的本來意義。實際上這些數(shù)據(jù)最終都將以“retlw”

的指令形式存放在程序空間,但 PICC 會自動編譯生成相關(guān)的附加代碼從程序空間讀取這些

常數(shù),編程員無需太多操心。例如:

const unsigned char name[]=”This is a demo”;  //定義一個常量字符串

如果定義了  “const”類型的位變量,那么這些位變量還是被放置在 RAM 中,但程序

不能對其賦值修改。本來,不能修改的位變量沒有什么太多的實際意義,相信大家在實際編

程時不會大量用到。

 

&O1540;    persistent  —   非初始化變量聲明

 

按照標(biāo)準(zhǔn)  C  語言的做法,程序在開始運(yùn)行前首先要把所有定義的但沒有預(yù)置初值的變

量全部清零。PICC 會在最后生成的機(jī)器碼中加入一小段初始化代碼來實現(xiàn)這一變量清零操

作,且這一操作將在 main 函數(shù)被調(diào)用之前執(zhí)行。問題是作為一個單片機(jī)的控制系統(tǒng)有很多

變量是不允許在程序復(fù)位后被清零的。為了達(dá)到這一目的,PICC  提供了“persistent”修飾

詞以聲明此類變量無需在復(fù)位時自動清零,編程員應(yīng)該自己決定程序中的那些變量是必須聲

明成“persisten”類型,而且須自己判斷什么時候需要對其進(jìn)行初始化賦值。例如:

persistent unsigned char hour,minute,second;  //定義時分秒變量

經(jīng)常用到的是如果程序經(jīng)上電復(fù)位后開始運(yùn)行,那么需要將 persistent 型的變量初始化,

如果是其它形式的復(fù)位,例如看門狗引發(fā)的復(fù)位,則無需對 persistent 型變量作任何修改。

PIC  單片機(jī)內(nèi)提供了各種復(fù)位的判別標(biāo)志,用戶程序可依具體設(shè)計靈活處理不同的復(fù)位情

形。

 

11.5.9     PICC 中的指針

 

PICC 中指針的基本概念和標(biāo)準(zhǔn) C 語法沒有太多的差別。但是在 PIC 單片機(jī)這一特定的

架構(gòu)上,指針的定義方式還是有幾點需要特別注意。

 

&O1540;    指向 RAM 的指針

 

如果是匯編語言編程,實現(xiàn)指針尋址的方法肯定就是用 FSR 寄存器,PICC 也不例外。

為了生成高效的代碼,PICC 在編譯 C 原程序時將指向 RAM 的指針操作最終用 FSR 來實現(xiàn)

間接尋址。這樣就勢必產(chǎn)生一個問題:FSR 能夠直接連續(xù)尋址的范圍是 256 字節(jié)(bank0/1

或 bank2/3),要覆蓋最大 512 字節(jié)的內(nèi)部數(shù)據(jù)存儲空間,又該如何讓定義指針?PICC 還是

將這一問題留給編程員自己解決:在定義指針時必須明確指定該指針?biāo)m用的尋址區(qū)域,例

如:

unsigned char *ptr0; //①定義覆蓋 bank0/1 的指針

bank2 unsigned char *ptr1;  //②定義覆蓋 bank2/3 的指針

bank3 unsigned char *ptr2;  //③定義覆蓋 bank2/3 的指針

上面定義了三個指針變量,其中①指針沒有任何 bank 限定,缺省就是指向 bank0 和 bank1;

②和③一個指明了 bank2,另一個指明了 bank3,但實際上兩者是一樣的,因為一個指針可

以同時覆蓋兩個 bank 的存儲區(qū)域。另外,上面三個指針變量自身都存放在 bank0 中。我們

將在稍后介紹如何在其它 bank 中存放指針變量。

 

既然定義的指針有明確的 bank 適用區(qū)域,在對指針變量賦值時就必須實現(xiàn)類型匹配,

下面的指針賦值將產(chǎn)生一個致命錯誤:


unsigned char *ptr0;

bank2 unsigned char buff[8];

程序語句:


//定義指向 bank0/1 的指針

//定義 bank2 中的一個緩沖區(qū)


ptr0 = buff;  //錯誤!試圖將 bank2 內(nèi)的變量地址賦給指向 bank0/1 的指針

若出現(xiàn)此類錯誤的指針操作,PICC 在最后連接時會告知類似于下面的信息:

Fixup overflow in expression (...)

 

 

同樣的道理,若函數(shù)調(diào)用時用了指針作為傳遞參數(shù),也必須注意 bank 作用域的匹配,

而這點往往容易被忽視。假定有下面的函數(shù)實現(xiàn)發(fā)送一個字符串的功能:

void SendMessage(unsigned char *);

那么被發(fā)送的字符串必須位于 bank0 或 bank1 中。如果你還要發(fā)送位于 bank2 或 bank3 內(nèi)的

字符串,必須再另外單獨(dú)寫一個函數(shù):

void SendMessage_2(bank2 unsigned char *);

這兩個函數(shù)從內(nèi)部代碼的實現(xiàn)來看可以一模一樣,但傳遞的參數(shù)類型不同。

 

按筆者的應(yīng)用經(jīng)驗體會,如果你看到了“Fixup overflow”的錯誤指示,幾乎可以肯定

是指針類型不匹配的賦值所至。請重點檢查程序中有關(guān)指針的操作。

 

&O1540;    指向 ROM 常數(shù)的指針

 

如果一組變量是已經(jīng)被定義在 ROM 區(qū)的常數(shù),那么指向它的指針可以這樣定義:


const unsigned char company[]=”Microchip”;

const unsigned char *romPtr;

程序中可以對上面的指針變量賦值和實現(xiàn)取數(shù)操作:

romPtr      = company;  //指針賦初值

data = *romPtr++;  //取指針指向的一個數(shù),然后指針加 1


//定義 ROM 中的常數(shù)

//定義指向 ROM 的指針


反過來,下面的操作將是一個錯誤,因為該指針指向的是常數(shù)型變量,不能賦值。

*romPtr = data; //往指針指向的地址寫一個數(shù)

&O1540;    指向函數(shù)的指針

 

單片機(jī)編程時函數(shù)指針的應(yīng)用相對較少,但作為標(biāo)準(zhǔn) C 語法的一部分,PICC 同樣支持

函數(shù)指針調(diào)用。如果你對編譯原理有一定的了解,就應(yīng)該明白在  PIC  單片機(jī)這一特定的架

構(gòu)上實現(xiàn)函數(shù)指針調(diào)用的效率是不高的:PICC 將在 RAM 中建立一個調(diào)用返回表,真正的

調(diào)用和返回過程是靠直接修改 PC 指針來實現(xiàn)的。因此,除非特殊算法的需要,建議大家盡

量不要使用函數(shù)指針。

 

&O1540;    指針的類型修飾

 

前面介紹的指針定義都是最基本的形式。和普通變量一樣,指針定義也可以在前面加上

特殊類型的修飾關(guān)鍵詞,例如“persistent”、“volatile”等?紤]指針本身還要限定其作用域,

因此 PICC 中的指針定義初看起來顯得有點復(fù)雜,但只要了解各部分的具體含義,理解一個

指針的實際用圖就變得很直接。

 

㈠ bank 修飾詞的位置含義

前面介紹的一些指針有的作用于 bank0/1,有的作用于 bank2/3,但它們本身的存放位置

全部在 bank0。顯然,在一個程序設(shè)計中指針變量將有可能被定位在任何可用的地址空間,

這時,bank 修飾詞出現(xiàn)的位置就是一個關(guān)鍵,看下面的例子:

//定義指向 bank0/1 的指針,指針變量為于 bank0 中

unsigned char *ptr0;

//定義指向 bank2/3 的指針,指針變量為于 bank0 中

bank2 unsigned char *ptr0; 

//定義指向 bank2/3 的指針,指針變量為于 bank1 中

bank2 unsigned char * bank1 ptr0; 

從中可以看出規(guī)律:前面的 bank 修飾詞指明了此指針的作用域;后面的 bank 修飾詞定義了

此指針變量自身的存放位置。只要掌握了這一法則,你就可以定義任何作用域的指針且可以

將指針變量放于任何 bank 中。

 

㈡ volatile、persistent 和 const 修飾詞的位置含義

如果能理解上面介紹的 bank 修飾詞的位置含義,實際上 volatile、persistent 和 const 這

些關(guān)鍵詞出現(xiàn)在前后不同位置上的含義規(guī)律是和 bank 一詞相一致的。例如:

//定義指向 bank0/1 易變型字符變量的指針,指針變量位于 bank0 中且自身為非易變型

volatile unsigned char *ptr0;

//定義指向 bank2/3 非易變型字符變量的指針,指針變量位于 bank1 中且自身為易變型

bank2 unsigned char * volatile bank1 ptr0;

//定義指向 ROM 區(qū)的指針,指針變量本身也是存放于 ROM 區(qū)的常數(shù)

const unsigned char * const ptr0;

亦即出現(xiàn)在前面的修飾詞其作用對象是指針?biāo)柑幍淖兞;出現(xiàn)在后面的修飾詞其作用對象

就是指針變量自己。


11.6


PICC 中的子程序和函數(shù)

 

中檔系列的 PIC 單片機(jī)程序空間有分頁的概念,但用 C 語言編程時基本不用太多關(guān)心


代碼的分頁問題。因為所有函數(shù)或子程序調(diào)用時的頁面設(shè)定(如果代碼超過一個頁面)都由

編譯器自動生成的指令實現(xiàn)。

 

11.6.1     函數(shù)的代碼長度限制

 

PICC 決定了 C 原程序中的一個函數(shù)經(jīng)編譯后生成的機(jī)器碼一定會放在同一個程序頁面

內(nèi)。中檔系列的 PIC 單片機(jī)其一個程序頁面的長度是 2K 字,換句話說,用 C 語言編寫的任

何一個函數(shù)最后生成的代碼不能超過 2K 字。一個良好的程序設(shè)計應(yīng)該有一個清晰的組織結(jié)

構(gòu),把不同的功能用不同的函數(shù)實現(xiàn)是最好的方法,因此一個函數(shù) 2K 字長的限制一般不會

對程序代碼的編寫產(chǎn)生太多影響。如果為實現(xiàn)特定的功能確實要連續(xù)編寫很長的程序,這時

就必須把這些連續(xù)的代碼拆分成若干函數(shù),以保證每個函數(shù)最后編譯出的代碼不超過一個頁

面空間。

 

11.6.2     調(diào)用層次的控制

 

中檔系列 PIC 單片機(jī)的硬件堆棧深度為 8 級,考慮中斷響應(yīng)需占用一級堆棧,所

有函數(shù)調(diào)用嵌套的最大深度不要超過 7 級。編程員必須自己控制子程序調(diào)用時的嵌套深

度以符合這一限制要求。

 

PICC 在最后編譯連接成功后可以生成一個連接定位映射文件(*.map),在此文件

中有詳細(xì)的函數(shù)調(diào)用嵌套指示圖“call graph”,建議大家要留意一下。其信息大致如下

(取自于一示范程序的編譯結(jié)果):

Call graph:

*_main size 0,0 offset 0

     _RightShift_C

*    _Task size 0,1 offset 0

     lwtoft

     ftmul size 0,0 offset 0

         ftunpack1

         ftunpack2

     ftadd size 0,0 offset 0

         ftunpack1

         ftunpack2

         ftdenorm

例 11-4  C 函數(shù)調(diào)用層次圖

上面所舉的信息表明整個程序在正常調(diào)用子程序時嵌套最多為兩級(沒有考慮中斷)。因為

main  函數(shù)不可能返回,故其不用計算在嵌套級數(shù)中。其中有些函數(shù)調(diào)用是編譯代碼時自動

加入的庫函數(shù),這些函數(shù)調(diào)用從 C 原程序中無法直接看出,但在此嵌套指示圖上則一目了

然。

 

11.6.3     函數(shù)類型聲明

 

PICC 在編譯時將嚴(yán)格進(jìn)行函數(shù)調(diào)用時的類型檢查。一個良好的習(xí)慣是在編寫程序代碼

前先聲明所有用到的函數(shù)類型。例如:

void Task(void);

unsigned char Temperature(void);

void BIN2BCD(unsigned char);

void TimeDisplay(unsigned char, unsigned char);

這些類型聲明確定了函數(shù)的入口參數(shù)和返回值類型,這樣編譯器在編譯代碼時就能保證生成

 

正確的機(jī)器碼。筆者在實際工作中有時碰到一些用戶聲稱發(fā)現(xiàn) C 編譯器生成了錯誤的代碼,

最后究其原因就是因為沒有事先聲明函數(shù)類型所致。

 

建議大家在編寫一個函數(shù)的原代碼時,立即將此函數(shù)的類型聲明復(fù)制到原文件的起始

處,見例 11-1;或是復(fù)制到專門的包含頭文件中,再在每個原程序模塊中引用。

 

11.6.4     中斷函數(shù)的實現(xiàn)

 

PICC 可以實現(xiàn) C 語言的中斷服務(wù)程序。中斷服務(wù)程序有一個特殊的定義方法:

void interrupt ISR(void);

其中的函數(shù)名“ISR”可以改成任意合法的字母或數(shù)字組合,但其入口參數(shù)和返回參數(shù)類型

必須是“void”型,亦即沒有入口參數(shù)和返回參數(shù),且中間必須有一個關(guān)鍵詞“interrupt”。

 

中斷函數(shù)可以被放置在原程序的任意位置。因為已有關(guān)鍵詞“interrupt”聲明,PICC 在

最后進(jìn)行代碼連接時會自動將其定位到 0x0004 中斷入口處,實現(xiàn)中斷服務(wù)響應(yīng)。編譯器也

會實現(xiàn)中斷函數(shù)的返回指令“retfie”。一個簡單的中斷服務(wù)示范函數(shù)如下:

void  interrupt  ISR(void) //中斷服務(wù)程序

{


   if (T0IE &&  T0IF) 

   {

T0IF = 0;

      //在此加入 TMR0 中斷服務(wù)

   }


//判 TMR0 中斷

 

 

//清除 TMR0 中斷標(biāo)志


   if (TMR1IE && TMR1IF)       //判 TMR1 中斷

   {


TMR1IF0;

      //在此加入 TMR1 中斷服務(wù)

   }

}


//清除 TMR1 中斷標(biāo)志

 

 

 

//中斷結(jié)束并返回


 

 

例 11-5  C 語言中斷函數(shù)舉例

 

 

PICC 會自動加入代碼實現(xiàn)中斷現(xiàn)場的保護(hù),并在中斷結(jié)束時自動恢復(fù)現(xiàn)場,所以編程

員無需象編寫匯編程序那樣加入中斷現(xiàn)場保護(hù)和恢復(fù)的額外指令語句。但如果在中斷服務(wù)程

序中需要修改某些全局變量時,是否需要保護(hù)這些變量的初值將由編程員自己決定和實施。

 

用 C 語言編寫中斷服務(wù)程序必須遵循高效的原則:

&O1540;    代碼盡量簡短,中斷服務(wù)強(qiáng)調(diào)的是一個“快”字。

&O1540;    避免在中斷內(nèi)使用函數(shù)調(diào)用。雖然 PICC 允許在中斷里調(diào)用其它函數(shù),但為了解決

遞歸調(diào)用的問題,此函數(shù)必須為中斷服務(wù)獨(dú)家專用。既如此,不妨把原本要寫在其

它函數(shù)內(nèi)的代碼直接寫在中斷服務(wù)程序中。

&O1540;    避免在中斷內(nèi)進(jìn)行數(shù)學(xué)運(yùn)算。數(shù)學(xué)運(yùn)算將很有可能用到庫函數(shù)和許多中間變量,就

算不出現(xiàn)遞歸調(diào)用的問題,光在中斷入口和出口處為了保護(hù)和恢復(fù)這些中間臨時變

量就需要大量的開銷,嚴(yán)重影響中斷服務(wù)的效率。

中檔系列 PIC 單片機(jī)的中斷入口只有一個,因此整個程序中只能有一個中斷服務(wù)函數(shù)。

 

11.6.5     標(biāo)準(zhǔn)庫函數(shù)

 

PICC 提供了較完整的 C 標(biāo)準(zhǔn)庫函數(shù)支持,其中包括數(shù)學(xué)運(yùn)算函數(shù)和字符串操作函數(shù)。

在程序中使用這些現(xiàn)成的庫函數(shù)時需要注意的是入口參數(shù)必須在 bank0 中。

 

如果需要用到數(shù)學(xué)函數(shù),則應(yīng)在程序前  “#include ”      包含頭文件;如果要使

用字符串操作函數(shù),就需要包含“#include ”頭文件。在這些頭文件中提供了函數(shù)

類型的聲明。通過直接查看這些頭文件就可以知道 PICC 提供了哪些標(biāo)準(zhǔn)庫函數(shù)。

 

C  語言中常用的格式化打印函數(shù)“printf/sprintf”用在單片機(jī)的程序中時要特別謹(jǐn)慎。

printf/sprintf 是一個非常大的函數(shù),一旦使用,你的程序代碼長度就會增加很多。除非是在

編寫試驗性質(zhì)的代碼,可以考慮使用格式化打印函數(shù)以簡化測試程序;一般的最終產(chǎn)品設(shè)計

都是自己編寫最精簡的代碼實現(xiàn)特定格式的數(shù)據(jù)顯示和輸出。本來,在單片機(jī)應(yīng)用中輸出的

數(shù)據(jù)格式都相對簡單而且固定,實現(xiàn)起來應(yīng)該很容易。

 

對于標(biāo)準(zhǔn) C 語言的控制臺輸入(scanf)/輸出(printf)函數(shù),PICC 需要用戶自己編寫

其底層函數(shù) getch()和 putch()。在單片機(jī)系統(tǒng)中實現(xiàn) scanf/printf 本來就沒什么太多意義,如

果一定要實現(xiàn),只要編寫好特定的  getch()和 putch()函數(shù),你就可以通過任何接口輸入或輸

出格式化的數(shù)據(jù)。


11.7

 

PICC 定義特殊區(qū)域值

 

PICC 提供了相關(guān)的預(yù)處理指令以實現(xiàn)在原程序中定義單片機(jī)的配置字和標(biāo)記單元。

 

11.7.1     定義工作配置字

 

在原程序中定義 PIC 單片機(jī)工作配置字的重要性在前面章節(jié)中已經(jīng)闡述。在用 PICC 寫


程序時同樣可以在 C 原程序中定義,具體方式如下:

__CONFIG (HS & UNPROTECT & PWRTEN & BORDIS & WDTEN);

上面的關(guān)鍵詞“__CONFIG”(注意前面有兩個下劃線符)專門用于是芯片配置字的設(shè)

定,后面括號中的各項配置位符號在特定型號單片機(jī)的頭文件中已經(jīng)定義(注意不是  pic.h

頭文件),相互之間用邏輯“與”操作符組合在一起。這樣定義的配置字信息最后將和程序

代碼一起放入同一個 HEX 文件。

 

在這里列出了適用于  16F7x  系列單片機(jī)配置位符號預(yù)定義,其它型號或系列的單片機(jī)

配置字定義方式類似,使用前查閱一下對應(yīng)的頭文件即可。

/*振蕩器配置*/


#define RC

#define HS


0x3FFF // RC 振蕩

0x3FFE // HS 模式

 

#define XT

#define LP

 

 

/*看門狗配置*/


 

0x3FFD // XT 模式

0x3FFC // LP 模式


#define WDTEN  0x3FFF //  看門狗打開


#define WDTDIS

 

 

/*上電延時定時器配置*/

#define PWRTEN


0x3FFB //  看門狗關(guān)閉

0x3FF7 //  上電延時定時器打開


#define PWRTDIS               0x3FFF    //     上電延時定時器關(guān)閉

/*低電壓復(fù)位配置*/

#define BOREN    0x3FFF //  低電壓復(fù)位允許


#define BORDIS

 

/*代碼保護(hù)配置*/


0x3FBF //  低電壓復(fù)位禁止


#define UNPROTECT  0x3FFF    //     沒有代碼保護(hù)

#define PROTECT               0x3FEF    //     程序代碼保護(hù)

 

例 11-6 頭文件預(yù)定義的配置信息符號

11.7.2     定義芯片標(biāo)記單元

PIC 單片機(jī)中的標(biāo)記單元定義可以用下面的__IDLOC(注意前面有兩個下劃線符)預(yù)處

理指令實現(xiàn),方法如下:

__IDLOC (1234);

其特殊之處是括號內(nèi)的值全部為 16 進(jìn)制數(shù),不需要用“0x”引導(dǎo)。這樣上面的定義就設(shè)定

了標(biāo)記單元內(nèi)容為 01020304。

11.8


MPLAB-IDE 中實現(xiàn) PICC 的編譯選項設(shè)置

 

在 11.3 節(jié)中已經(jīng)介紹了如何實現(xiàn) PICC 和 MPLAB-IDE 開發(fā)平臺的掛接。一旦項目建立


成功、程序編寫完成后即可以通過 MPLAB 環(huán)境下的項目管理工具實現(xiàn)程序的編譯、連接和

調(diào)試。它們的含義分別

是:


-項目維護(hù)(Make):MPLAB 檢查項目中的原程序文件,只編譯那些在上次編

譯后又被修改過的原程序,最后進(jìn)行連接;

-項目重建(Build All):項目中的所有原程序文件,不管是否有修改,都將被

重新編譯一次,最后進(jìn)行連接。

 

也可以通過 Project 菜單選擇“Make”或“Build All”實現(xiàn)項目編譯。不管采用何種方

式,在啟動編譯過程前一般都要設(shè)定一些編譯選項。

11.8.1     選擇單片機(jī)型號

 

在選擇 PICC 作為語言工具并建立了項目后,同樣通過菜單項 Configure&O1616;Select Device

在 MPLAB 環(huán)境中選擇具體單片機(jī)型號。請回顧一下例 11-1 的代碼,我們在原程序一開始

使用了“#include ”實現(xiàn)了相關(guān)單片機(jī)的一些預(yù)定義符號的直接引用,但沒有具體指

明是哪一個型號。實際上,“pic.h”頭文件只是一個簡單的管理工具(條件判別),它會按照

MPLAB 所選擇的特定型號的單片機(jī),把真正對應(yīng)的頭文件包含進(jìn)來。有興趣者可以直接用

文本編輯工具打開 pic.h 文件查看其是如何根據(jù)不同的單片機(jī)型號包含對應(yīng)的頭文件。

 

這樣對編程員而言,程序中只需加上一句“#include ”即可。

 

11.8.2     PICC 普通編譯選項(General)設(shè)定

 

參考第三章  3.2.7 節(jié)的內(nèi)容和圖 3-20  的指示說明,啟動編譯選項設(shè)定對話框。在使用

PICC 語言工具時對話框的內(nèi)容和用 MPAMS 匯編工具相比完全不同。圖 11-3 為 PICC 編譯

環(huán)境下普通選項設(shè)定的界面。

 

在此界面中用戶唯一能改變的是編譯器查找頭文件時的指定路徑(Include Path),實際

上如果編譯器安裝沒有問題,在此界面中這些普通選項的設(shè)定無需任何改動,編譯器會自動

到缺省認(rèn)定的路徑中(編譯器安裝后的相關(guān)路徑)查找編譯所需的各類文件。

               

圖 11-3  PICC 普通選項設(shè)定                 圖 11-4  PICC 全局選項設(shè)定

11.8.3     PICC 全局選項設(shè)定(PICC Global)

 

全局選項將影響項目中所有 C 和匯編原程序的編譯,詳細(xì)的設(shè)定內(nèi)容見圖 11-4。其中

必須關(guān)注的有:

&O1540;    Compile for MPLAB ICD:如果你準(zhǔn)備用 ICD 調(diào)試 C 語言編譯后的代碼,那么此項

就必須打鉤選中。這樣編譯后的結(jié)果就能保證  ICD 本身使用的芯片資源(一小部

分的程序和數(shù)據(jù)空間)不被應(yīng)用程序所占用。

&O1540;    Treat ‘char’ as signed:為了提高編譯后的代碼效率,PICC 缺省認(rèn)定‘char’型變量也

是無符號數(shù)。如果在設(shè)計中需要使用帶符號的‘char’型變量,此項就應(yīng)該被選中。

&O1540;    Floating point ‘double’ width:同樣為了提高編譯后的代碼效率,PICC     缺省認(rèn)定

‘double’型的雙精度浮點數(shù)變量的實現(xiàn)長度為 24 位(等同于普通 float 型浮點數(shù))。

在這里可以選擇使其長度達(dá) 32 位。這樣數(shù)值計算的精度將得到提高,但代碼長度

將增加,計算速度也會降低,所以請在權(quán)衡利弊后作出你自己的決定。

 

11.8.4     C 編譯器選項設(shè)定(PICC Compiler)

 

項目中所有的 C 原程序都將通過 C 編譯器編譯成機(jī)器碼,這些選項決定了 C 編譯器是

如何工作的。所有選項又分為兩組:普通選項(General)和高級選項(Advanced),分別見

圖 11-5A 和 11-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)可靠,一般

0 onclick="javascript:window.open(this.src);" style="cursor:pointer;" onload="javascript:if(this.width>600)this.style.width='600px';" border="0" />0){if(image.width>=510){this.width=510;this.height=image.height*510/image.width;}}" border=0>                     0 onclick="javascript:window.open(this.src);" style="cursor:pointer;" onload="javascript:if(this.width>600)this.style.width='600px';" border="0" />0){if(image.width>=510){this.width=510;this.height=image.height*510/image.width;}}" border=0>

(A) 常用選項                                      (B)     高級選項

                       圖 11-5      C 編譯器選項設(shè)定

不會因為使用優(yōu)化從而使生成的程序出現(xiàn)錯誤。碰到的一些問題也基本都是用戶編寫的原程

序有漏洞所導(dǎo)致,例如一些變量應(yīng)該是 volatile 型但編程員沒有明確定義,在優(yōu)化前程序可

以正常運(yùn)行,一旦使用優(yōu)化,程序運(yùn)行就出現(xiàn)異常。顯然,把出現(xiàn)的這些問題歸罪到編譯器

是毫無道理的。

 

使用優(yōu)化后可能對原程序級的調(diào)試帶來一些不便之處。因 PICC 可能會重組編譯后的代

碼,例如多處重復(fù)的代碼可能會改成同一個子程序調(diào)用以節(jié)約程序空間,這樣在調(diào)試過程中

跟蹤原程序時可能會出現(xiàn)程序亂跳的現(xiàn)象,這基本是正常的。若為了強(qiáng)調(diào)更直觀的代碼調(diào)試

過程,你可以將優(yōu)化級別降低甚至關(guān)閉所有優(yōu)化功能,這樣調(diào)試時程序的運(yùn)行就可以按部就

班了。

C 編譯器的高級選項設(shè)定基本都是針對診斷信息輸出的,和生成的代碼無關(guān)。用得相對

較多的選項有:

&O1540;    Generate assembly list file:編譯器生成 C 原程序的匯編列表文件(*.lst)。在此文件

中列出了每一行  C  原代碼對應(yīng)的匯編指令,但這些都是優(yōu)化前的代碼。簡單的一

條  C 語句被翻譯成匯編指令后可能有好幾條。有時匯編列表文件可以作為解決問

題的輔助手段。如果你懷疑編譯器生成的代碼有錯誤,不妨先產(chǎn)生對應(yīng)的匯編列表

文件,看看在優(yōu)化前一條 C 語句被編譯后的匯編碼到底是什么。

&O1540;    Compile to assembly only:這一選項的作用是把&n, bsp;     C  原程序編譯成匯編指令文件

(*.as),此時將不生成目標(biāo)文件,也不進(jìn)行最后的連接定位。這一選項在 C 和匯

編混合編程時特別有用。通過解讀 C 程序?qū)?yīng)的匯編指令,可以掌握 C 程序中存

取變量的具體方法,然后用在自己編寫的匯編指令中。我們將在稍后專門做介紹。

11.8.5     連接器選項設(shè)定(PICC Linker)

 

連接器 PICC Linker 的選項基本不用作太多的改變,在圖 11-6 的對話框中顯示了可設(shè)定的各類

項目。其中有兩項有用的信息輸出可以考慮加以利用:

&O1540;    Generate map file:生成連接定位映射文件。在此映射文件中詳細(xì)列出了所有程

序用到的變量的具體物理地址;所有函數(shù)的入口地址;函數(shù)相互之間調(diào)用的層次關(guān)系和深度等。這

些信息對于程序的調(diào)試將非常有用。此文件將以擴(kuò)展名“*.map”的形式存放在同一個項目路徑

下,需要時可以用任何文本編輯器打開觀察。

 



圖 11-6  PICC 連接器選項設(shè)定

&O1540;    Display memory-segment usage:顯示詳細(xì)的內(nèi)存分配和使用情況報告。用戶可以

了解到程序空間和數(shù)據(jù)存儲器空間資源分配的細(xì)節(jié)。下面列舉了在一個項目編譯后實際的內(nèi)存使用

信息,為方便理解筆者用“//”添加了一些注釋:

Psect Usage Map:        //程序段定位表

Psect     |  Contents                    |  Memory Range

----------|------------------------------|--------------------

powerup   | Power on reset code          |  $0000 - $0003

intentry  | Interrupt service routine    | $0004 - $000C

intcode   | Interrupt service routine    | $000D - $002C

intret    | Interrupt service routine    | $002D - $0035

init      | Initialization code          |  $0036 - $003D

end_init  | Initialization code          | $003E - $0040

clrtext   | Memory  clearing  code       |  $0041 - $0047

const3    | Strings and constant data    | $0048 - $0060

const     | Strings and constant data    | $0061 - $0071

const2    | Strings and constant data    | $0072 - $0076

text      | Program and library code     | $0576 - $0582

text      | Program and library code     | $0583 - $07C7

float_te  | Arithmetic routine code      | $07C8 - $07FF

rbss_0    |  Bank 0  RAM variables       |  $0021 - $0042

temp      | Temporary RAM data           | $0043 - $0047

nvram     |  Persistent RAM data          |  $0048 - $004A

intsave   | Registers saved on interrupt | $004B - $004D

intsave   | Registers saved on interrupt | $007F - $007F

intsave_1 | Saved copy of W in bank 1    | $00FF - $00FF

rbit_0    |  Bank 0  bit variables       |  $0100 - $0104

config    | User-programmed CONFIG bits  | $2007 - $2007

Memory Usage Map:  

//程序空間代碼定位地址分布

//存儲空間使用情況報告

Program  ROM   $0000 -  $0076  $0077 (   119) words

Program  ROM   $0576 -  $07FF  $028A (   650) words

                               $0301 (   769)  words  total Program ROM 

//bank0 數(shù)據(jù)空間變量地址分布

Bank  0 RAM    $0021 -  $004D  $002D (    45) bytes

Bank  0 RAM    $007F -  $007F  $0001 (     1) bytes

                               $002E (    46) bytes  total Bank 0  RAM 

//bank1 數(shù)據(jù)空間變量地址分布

Bank 1 RAM    $00FF - $00FF    $0001 (     1) bytes total Bank 1 RAM  

//bank0 數(shù)據(jù)空間位變量地址分布

Bank 0 Bits   $0100 - $0104    $0005 (     5) bits  total Bank 0 Bits 

//配置字地址

Config Data   $2007 - $2007    $0001 (     1) words total Config Data 

Program statistics:

//程序總體資源消耗統(tǒng)計

Total ROM used      769 words (18.8%)        //生成代碼字總數(shù)和程序空間使用率

Total RAM used       48  bytes (25.0%)       //使用數(shù)據(jù)字節(jié)數(shù)和數(shù)據(jù)空間使用率

例 11-7        編譯后程序使用的內(nèi)存信息

11.8.6     匯編器選項設(shè)定(PICC Assembler)

PICC 環(huán)境提供了自己的匯編編譯器,它和 Microchip 公司提供的 MPASM 編譯器在原

程序的語法表達(dá)方面要求稍有不同。另外,PICC 的匯編編譯器要求輸入原程序文件的擴(kuò)展

名是“*.as”,而 MPASM 缺省認(rèn)定的原程序以“*.asm”為擴(kuò)展名。

在基于 PICC 編譯環(huán)境下開發(fā) PIC 單片機(jī)的 C 語言應(yīng)用程序時基本無需關(guān)心其匯編編譯

器,除非是在混合語言編程時用匯編語言編寫完整的匯編原程序模塊文件。其編譯選項設(shè)定

的對話框見圖 11-7,最重要的是優(yōu)化使能控制項“Enable optimization”,一般情況下應(yīng)該使

用匯編器的優(yōu)化以節(jié)約程序空間。

0 onclick="javascript:window.open(this.src);" style="cursor:pointer;" onload="javascript:if(this.width>600)this.style.width='600px';" border="0" />0){if(image.width>=510){this.width=510;this.height=image.height*510/image.width;}}" border=0>

圖 11-7  PICC 匯編器選項設(shè)定

11.9

C 和匯編混合編程

有兩個原因決定了用 C  語言進(jìn)行單片機(jī)應(yīng)用程序開發(fā)時使用匯編語句的必要性:單片


機(jī)的一些特殊指令操作在標(biāo)準(zhǔn)的 C 語言語法中沒有直接對應(yīng)的描述,例如 PIC 單片機(jī)的清

看門狗指令“clrwdt”和休眠指令“sleep”;單片機(jī)系統(tǒng)強(qiáng)調(diào)的是控制的實時性,為了實現(xiàn)這

一要求,有時必須用匯編指令實現(xiàn)部分代碼以提高程序運(yùn)行的效率。這樣,一個項目中就會

出現(xiàn) C 和匯編混合編程的情形,我們在此討論一些混合編程的基本方法和技巧。

11.9.1     嵌入行內(nèi)匯編的方法

在  C  原程序中直接嵌入?yún)R編指令是最直接最容易的方法。如果只需要嵌入少量幾條的

匯編指令,PICC 提供了一個類似于函數(shù)的語句:

asm(“clrwdt”);

雙引號中可以編寫任何一條 PIC 的標(biāo)準(zhǔn)匯編指令。例如:

for (;;) {

   asm("clrwdt");       //清看門狗

   Task();

   ClockRun();


   asm("sleep");

   asm("nop"); 

}


//休眠

//空操作延時

 

 

例 11-8       逐行嵌入?yún)R編的方式

如果需要編寫一段連續(xù)的匯編指令,PICC 支持另外一種語法描述:用“#asm”開始匯

編指令段,用“#endasm”結(jié)束。例如下面的一段嵌入?yún)R編指令實現(xiàn)了將       0x20~0x7F  間的

RAM 全部清零:

#asm

   MOVlw    0x20

   MOVwf    _FSR

   clrf     _INDF

   incf     _FSR,f

   btfss    _FSR,7

   goto     $-3

#endasm

例 11-9    整段嵌入?yún)R編的方式

11.9.2     匯編指令尋址 C 語言定義的全局變量

C 語言中定義的全局或靜態(tài)變量尋址是最容易的,因為這些變量的地址已知且固定。按

C 語言的語法標(biāo)準(zhǔn),所有 C 中定義的符號在編譯后將自動在前面添加一下劃線符“_”,因

此,若要在匯編指令中尋址 C 語言定義的各類變量,一定要在變量前加上一“_”符號,我

們在上面例 11-9 中已經(jīng)體現(xiàn)了這一變量引用的法則,因為 FSR 和 INDF 等所有特殊寄存器

是以 C 語言語法定義的,因此匯編中需要對其尋址時前面必須添加下劃線。

對于 C 語言中用戶自定義的全局變量,用行內(nèi)匯編指令尋址時也同樣必須加上“_”    ,

下面的例 11-10 說明了具體的引用方法:

volatile unsigned char tmp;          //定義位于 bank0 的字符型全局變量

void Test(void)

{

   #asm

clrf     _STATUS 

   MOVlw    0x10

   MOVwf    _tmp

#endasm

   if (tmp==0x10) {

      ;

}

}

//測試程序

//開始行內(nèi)匯編

//選擇 bank0

//設(shè)定初值

//tmp=0x10

//結(jié)束行內(nèi)匯編

//開始 C 語言程序


例 11-10       行內(nèi)匯編尋址 C 全局變量(位于 bank0)

上面的例子說明了匯編指令中尋址 C 語言所定義變量的基本方法。PICC 在編譯處理嵌

入的行內(nèi)匯編指令時將會原封不動地把這些指令復(fù)制成最后的機(jī)器碼。所有對 C 編譯器所

作的優(yōu)化設(shè)定對這些行內(nèi)匯編指令而言將不起任何作用。編程員必須自己負(fù)責(zé)編寫最高效的

匯編代碼,同時處理變量所在的 bank 設(shè)定。對于定義在其它 bank 中的變量,還必須在匯編

指令中加以明確指示,見例 11-11 的代碼范例。

volatile bank1 unsigned char tmpBank1;      //定義位于 bank1 的字符型全局變量

volatile bank2 unsigned char tmpBank2;      //定義位于 bank2 的字符型全局變量

volatile bank3 unsigned char tmpBank3;      //定義位于 bank3 的字符型全局變量

void Test(void)

{

   #asm

bcf      _STATUS,6

bsf      _STATUS,5

MOVlw   0x10

MOVwf    _tmpBank1^0x80

bsf      _STATUS,6

bcf      _STATUS,5

MOVlw   0x20

//測試程序

//開始行內(nèi)匯編

//選擇 bank1

//設(shè)定初值

//tmpBank1=0x10

//選擇 bank2

//設(shè)定初值


MOVwf    _tmpBank1^0x100         //tmpBank2=0x20

bsf      _STATUS,6

bsf      _STATUS,5

MOVlw   0x30


//選擇 bank3

//設(shè)定初值


MOVwf    _tmpBank1^0x180         //tmpBank1=0x30


}


#endasm


//結(jié)束行內(nèi)匯編

例 11-11       行內(nèi)匯編尋址 C 全局變量(非 bank0 變量)


通過上面的代碼實例,我們可以掌握這樣一個規(guī)律:在行內(nèi)匯編指令中尋址 C  語言定

義的全局變量時,除了在尋址前設(shè)定正確的 bank 外,在指令描述時還必須在變量上異或其

所在 bank 的起始地址,實際上位于 bank0 的變量在匯編指令中尋址時也可以這樣理解,只

是異或的是 0x00,可以省略。如果你了解 PIC 單片機(jī)的匯編指令編碼格式,上面異或的 bank

起始地址是無法在真正的匯編指令中體現(xiàn)的,其目的純粹是為了告訴 PICC 連接器變量所在

的 bank,以便連接器進(jìn)行 bank 類別檢查。

11.9.3     匯編指令尋址 C 函數(shù)的局部變量

前面已經(jīng)提到,PICC 對自動型局部變量(包括函數(shù)調(diào)用時的入口參數(shù))采用一種“靜

態(tài)覆蓋”技術(shù)對每一個變量確定一個固定地址(位于    bank0),因此嵌入的匯編指令對其尋

址時只需采用數(shù)據(jù)寄存器的直接尋址方式即可,唯一要考慮的是如何才能在編寫程序時知道

這些局部變量的尋址符號(具體地址在最后連接后才能決定,編程時無需關(guān)心)。一個最實

用也是最可靠的方法是先編寫一小段  C 代碼,其中有最簡單的局部變量操作指令,然后參

考圖 11-5(B)對話框選擇“Compile to assembly only”,把此 C 原代碼編譯成對應(yīng)的 PICC 匯

編指令;查看 C 編譯器生成的匯編指令是如何尋址這些局部變量的,你自己編寫的行內(nèi)匯

編指令就采用同樣的尋址方式。例如,例 11-12 的一小段 C 原代碼編譯出的匯編指令

//C 原程序代碼

void Test(unsigned char inVar1, inVar2) 

{

   unsigned char tmp1, tmp2;

inVar1++;

inVar2--;

tmp1 = 1;

tmp2 = 2;

}

//編譯器生成的匯編指令

_Test

;      _tmp1 assigned to ?a_Test+0      //tmp1 的尋址符為  ?a_Test+0


_Test$tmp1 set


?a_Test


;      _tmp2 assigned to ?a_Test+1      //tmp2 的尋址符為  ?a_Test+1


_Test$tmp2 set


?a_Test+1


;      _inVar1 assigned to ?a_Test+2     //inVar1 的尋址符為  ?a_Test+2

_Test$inVar1 set ?a_Test+2

44line


;_inVar1 stored from w  

bcf

bcf

MOVwf ?a_Test+2


//第一個字符型行參由 W 寄存器傳遞


;ht16.c: 43: unsigned char tmp1, tmp2;

incf

45line

;ht16.c: 45: inVar2--;


decf

46line

;ht16.c: 46: tmp1 = 1;

clrf

incf

47line

;ht16.c: 47: tmp2 = 2;

MOVlw 2

MOVwf ?a_Test+1

48line

;ht16.c: 48: }

return


//行參 inVar2 的尋址符為 ?_Test


例 11-12  PICC 實現(xiàn)局部變量操作的尋址方式

基于上面得到的 PICC 編譯后局部變量的尋址方式,我們在 C 語言程序中用嵌入?yún)R編指

令時必須采樣同樣的尋址符以實現(xiàn)對應(yīng)變量的存取操作,見下面的例 11-13。

//C 原程序代碼

void Test(unsigned char inVar1, inVar2) 

{

   unsigned char tmp1, tmp2;

#asm

//開始嵌入?yún)R編


incf

decf

MOVlw

addwf

rrf

0x10


?a_Test+0,f

?a_Test+1,f

?a_Test+2,f

?_Test,w


//tmp1++;

//tmp2--;

//inVar1 +=

//inVar2 循環(huán)右移一位

}


rrf

#endasm


?_Test,f

//結(jié)束嵌入?yún)R編


例 11-13     嵌入?yún)R編指令實現(xiàn)局部變量尋址操作

 

如果局部變量為多字節(jié)形式組成,例如整型數(shù)、長整型等,必須按照 PICC 約定的存儲

格式進(jìn)行存取。前面已經(jīng)說明了 PICC 采用“Little endian”格式,低字節(jié)放在低地址,高字

節(jié)放在高地址。下面的例 11-14 實現(xiàn)了一個整型數(shù)的循環(huán)移位,在 C 語言中沒有直接針對循

環(huán)移位的語法操作,用標(biāo)準(zhǔn) C 指令實現(xiàn)的效率較低。

//16 位整型數(shù)循環(huán)右移若干位

unsigned int RR_Shift16(unsigned int var, unsigned char count) 

{

}


while(count--)

{

   #asm 

   rrf  ?_RR_Shift16+0,w

   rrf  ?_RR_Shift16+1,f

   rrf  ?_RR_Shift16+0,f

#endasm

}

return(var);


//移位次數(shù)控制

//開始嵌入?yún)R編

//最低位送入 C

//var 高字節(jié)右移 1 位,C 移入最高位

//var 低字節(jié)右移 1 位

//結(jié)束嵌入?yún)R編

//返回結(jié)果


例 11-14      嵌入?yún)R編指令對多字節(jié)變量的操作

11.9.4     混合編程的一些經(jīng)驗

C  和匯編語言混合編程可以使單片機(jī)應(yīng)用程序的開發(fā)效率和程序本身的運(yùn)行效率達(dá)到

最佳的配合。筆者從實際應(yīng)用中得到一些經(jīng)驗供讀者一起分享。

㈠  慎用匯編指令

 

相比于匯編語言,用 C  語言編程的優(yōu)勢是毋庸置疑的:開發(fā)效率大大提高、人性化的

語句指令加上模塊化的程序易于日常管理和維護(hù)、程序在不同平臺間的移植方便。所以既然

用了 C 語言編程,就盡量避免使用嵌入?yún)R編指令或整個地編寫匯編指令模塊文件。PICC 已

具備高效的優(yōu)化功能,如果在寫 C 原程序時就十分注意程序的編譯和運(yùn)行效率問題,加上

PICC 的后道編譯優(yōu)化,最后得到的代碼效率不會比全部用匯編編寫的代碼差多少,尤其是

程序量較大時。另外,PICC 對數(shù)據(jù)存儲空間的利用率肯定比用戶人工定位變量時的利用率

要高,同時還提供完整的庫函數(shù)支持。C 語言的語法功能強(qiáng)大,能夠高效率地實現(xiàn)絕大部分

控制和運(yùn)算功能。因此,除了一些十分強(qiáng)調(diào)單片機(jī)運(yùn)行時間的代碼或 C 語言沒有直接對應(yīng)

的操作可以考慮用匯編指令實現(xiàn)外,其它部分都應(yīng)該用 C 語言編寫。

 

以上面的例 11-14 進(jìn)一步說明,變量的循環(huán)右移操作用 C 語言實現(xiàn)非常不方便,PIC 單

片機(jī)已有對應(yīng)的移位操作匯編指令,因此用嵌入?yún)R編的形式實現(xiàn)效率最高。同時對移位次數(shù)

的控制,本質(zhì)上說變量 count 的遞減判零也可以直接用匯編指令實現(xiàn),但這樣做節(jié)約不了多

少代碼,用標(biāo)準(zhǔn)的 C 語言描述更直觀,更易于維護(hù)。

 

一句話:用了 C 語言后,就不要再老想著用匯編。

 

㈡  盡量使用嵌入?yún)R編

 

這和上面的慎用匯編指令的說法并不矛盾。如果確實需要用匯編指令實現(xiàn)部分代碼以提

高運(yùn)行效率,應(yīng)盡量使用行內(nèi)匯編,避免編寫純匯編文件(*.as 文件)。

 

雖然 PICC 支持 C 和匯編原程序模塊存在于同一個項目中,但要編寫純匯編文件必須首

先了解 PICC 特有的匯編語法結(jié)構(gòu)。Hitech 公司提供了完整的文檔介紹其匯編器的使用方法,

有興趣者可以從其網(wǎng)站上下載 PICC 的用戶使用手冊查看。

 

筆者認(rèn)為,類似于純匯編文件的代碼也可以在 C 語言框架下實現(xiàn),方法是基于 C 標(biāo)準(zhǔn)

語法定義所有的變量和函數(shù)名,包括需要傳遞的形式參數(shù)、返回參數(shù)和局部變量,但函數(shù)內(nèi)

部的指令基本用嵌入?yún)R編指令編寫,只有最后的返回參數(shù)用 C 語句實現(xiàn)。這樣做后函數(shù)的

運(yùn)行效率和純匯編編寫時幾乎一模一樣,但各參數(shù)的傳遞統(tǒng)一用  C 標(biāo)準(zhǔn)實現(xiàn),這樣管理和

維護(hù)就比較方便。例如下面的例 11-15 實現(xiàn)一個字節(jié)變量的偶校驗位計算。

bit EvenParity(unsigned char data)

{

    #asm


    swapf   ?a_EvenParity+0,w

    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);

}


//入口參數(shù) data 的尋址符為 ?a_EvenParity+0


例 11-15  C 函數(shù)框架中使用嵌入?yún)R編指令

㈢  盡量使用全局變量進(jìn)行參數(shù)傳遞

 

使用全局變量最大的好處是尋址直觀,只需在 C 語言定義的變量名前增加一個下劃線

符即可在匯編語句中尋址;使用全局變量進(jìn)行參數(shù)傳遞的效率也比形參高。編寫單片機(jī)的 C

程序時不能死硬強(qiáng)求教科書上的模塊化編程而大量采用行參和局部變量的做法,在開發(fā)編程

時應(yīng)視實際情況靈活變通,一切以最高的代碼效率為目標(biāo)。

  • 上一篇: PICC18使用說明
  • 下一篇: 基于16F877A和DS18B20的測溫程序
  • 發(fā)表評論   告訴好友   打印此文  收藏此頁  關(guān)閉窗口  返回頂部
    熱點文章
     
    推薦文章
     
    相關(guān)文章
    網(wǎng)友評論:(只顯示最新5條。)
    關(guān)于我們 | 聯(lián)系我們 | 廣告合作 | 付款方式 | 使用幫助 | 機(jī)電之家 | 會員助手 | 免費(fèi)鏈接

    點擊這里給我發(fā)消息66821730(技術(shù)支持)點擊這里給我發(fā)消息66821730(廣告投放) 點擊這里給我發(fā)消息41031197(編輯) 點擊這里給我發(fā)消息58733127(審核)
    本站提供的機(jī)電設(shè)備,機(jī)電供求等信息由機(jī)電企業(yè)自行提供,該企業(yè)負(fù)責(zé)信息內(nèi)容的真實性、準(zhǔn)確性和合法性。
    機(jī)電之家對此不承擔(dān)任何保證責(zé)任,有侵犯您利益的地方請聯(lián)系機(jī)電之家,機(jī)電之家將及時作出處理。
    Copyright 2007 機(jī)電之家 Inc All Rights Reserved.機(jī)電之家-由機(jī)電一體化網(wǎng)更名-聲明
    電話:0571-87774297 傳真:0571-87774298
    杭州濱興科技有限公司提供技術(shù)支持

    主辦:杭州市高新區(qū)(濱江)機(jī)電一體化學(xué)會
    中國行業(yè)電子商務(wù)100強(qiáng)網(wǎng)站

    網(wǎng)站經(jīng)營許可證:浙B2-20080178-1