註: 本文為中文翻譯,原文請見底下網址:
http://www.mikroe.com/eng/chapters/view/11/appendix-a-programming-a-microcontroller/
TOC Introduction Ch. 1 Ch. 2 Ch. 3 Ch 4. Ch. 5 Ch. 6 Ch. 7 Ch. 8 Ch. 9 App. A App. B App. C
Appendix A: Programming a Microcontroller
微控制器和人類透過稱為組合語言 (Assembly Language) 的語言媒介溝通。Assembly 這個字彙本身並沒有什麼深刻的意義,它只是對應其它語言的名稱,例如英語或法語。更確切地說,組合語言只不過是個過渡方案 (passing solution)。為了讓微控制器理解以組合語言寫成的程式,程式必須編譯為 0 和 1 的語言。組合語言和組譯器的意義是不一樣的,前者指的是一套規則,用來撰寫給微控制器的程式,而後者指的是個人電腦上的一支程式,用來把組合語言的程式翻譯為 0 和 1 的語言。編譯過的程式又稱為機械碼。"程式 (Program)” 是儲存在電腦硬碟上的一個資料檔 (或在微控制器的記憶體裏,如果有載入的話),而且是根據組合語言或其它程式語言的規則撰寫而成的。組合語言是人類可以理解的,因為它有有意義的英文字母字彙和符號組成。讓我們看看指令 "RETURN" 這個例子,如其名稱所示,這個指令用來讓微控制器從某個副程式返回。在機械碼中,同一個指令是由微控制認識的 14 位元的 0 和 1 組成。所以組合語言的指令都同樣編譯成對應的 0 和 1。儲存編譯過後的程式的資料檔稱為 "執行檔 (executive file)",即 "HEX檔"。這個名稱來自 16 進制的資料檔表示方式,而且其副檔名為 "hex",例如 "probe.hex"。在檔案產生之後,HEX 檔會被載入到微控制器,使用 programmer 載入。組合語言的程式可以使用任何可以建立 ASCII 文字檔的程式撰寫,或者也可以使用如 MPLAB 的特殊工作環境撰寫,這部份稍後會討論。
組合語言的元素 (ELEMENTS OF ASSEMBLY LANGUAGE)
以組合語言寫成的一支程式,由許多元素組成,這些元素在程式編譯為執行檔時會有不同的解譯。這些元素的使用需要嚴格的規則,而且在撰寫程式的時候必須特別注意,以避免錯誤。
組合語言語法 (ASSEMBLY LANGUAGE SYMTAX)
如前述,必須注意特定的規則以使程式順利編譯成 HEX 執行檔。必修的規則,解釋了運算式序列 (sequence of expressions) 如何放在一起以形成陳述式 (statements),陳述式構成一支組合語言程式,這個規則稱之為語法。語法只有幾條:
● 程式的每一行最多包含 255 個字元;
● 程式的每一行必須以符號、標記 (label)、助憶符號 (mnemonics) 或組譯指示 (directive) 開始;
● 程式中跟在 ";" 符號的文字代表註解,組譯器會略過 (不會編譯); 及
● 程式一行中的所有元素 (標記、指令等) 必須以至少 1 個空白隔開。為了整潔的目的,通常會使用 TAB 字元取代空白字元隔開元素,所以在程式中很容易界定標記、組譯指示 (directives) 的欄位。
標記 (LABELS)
標記 (label) 代表 ROM 或 RAM 某個位址的文字表示。每個標記必須從第一欄開始,使用英文字母或 "_" 符號開始,而且最多可以包含 32 個字元。此外,它很容易使用:
在呼叫副程式或在用跳躍 (jump) 指令時,使用標記而非 16 位元的位址便足矣。程式中也必須把同名的標記寫在副程式的起點或跳躍指令的目的地。通常的規則是,標記會使用好辨識的名稱。
在編譯程式的期間,組譯器會自動以對應的位址來取代標記。
註解
註解通常是為了讓程式更清楚容易理解由 programmer 撰寫的解釋文字。沒有必要註解每一行。當 3 或 4 行程式碼共同完成某件高階的工作時,最好為這群程式碼寫一行註解。因此,註解是需要的時候才加的,而且必須以 ";" 開始。加到組合語言程式碼的註解不會被編譯成機械碼。
指令
指令是由製造商為每個微控制器定義的。因此,它是由使用者按照指令的使用規則運用。指令的寫法又稱為指令語法。在底下的例子中,"movlp" 和 "goto" 指令會被 PIC16F887 視為錯誤的指令,因為它們的寫法不正確。
運算元 (OPERANDS)
運算元是跟在指令後的一個值 (參數),以助憶符號為名影響指令的運作。運算元可以是一個暫存器、一個變數、一個定字或者是一個記憶體位址。
組譯指示 (DIRECTIVES)
不像在編譯後寫到晶片記憶體的指令,組譯指示是組合語言的指令,而且不會直接影響微控制器的運作。有些組譯指示在每支程式中都必須用到,而有些只在為了促進或加強運作的時候才使用。組譯指示寫於指令欄。必須注意的規則是每行程式只允許一個組譯指示。
本節只涵蓋少數幾個常用的組譯指示。要描述 MPLAB 認識的所有 directives 肯定會花很多時間和紙張。不管如何,MPLAB 組譯器認識的所有組譯指示的完整列表可在說明文件 (Help) 中獲得。
處理器組譯指示
這個組譯指示必須寫在每支程式的開頭。它定義了程式所用的微控制器種類。例如:
EQU 組譯指示
這個組譯指示用來以符號取代數值。以這種方式,記憶體的特定位置便有了名字。例如。
這表示記憶體位址 25 (16進制) 被指定了 "MAXIMUM" 這個名稱,程式中每個出現 "MAXIMUM" 的標記將被組譯器解釋為位址 25 (MAXIMUM = H’25’)。這個組譯指示大部份寫在程式的前頭。
ORG 組譯指示
這個組譯指示指定之後的程式應該放在什麼地方。例如:
這支程式從 0x100 這個位置開始。含有資料的表格將儲存於 1024(1000h) 這個位置。
END 組譯指示
每支程式都必須使用這個組譯指示結束。當遇到這個組譯指示時,組譯器會立即停止編譯。例如:
#INCLUDE 組譯指示
這個組譯指示的名稱已完全說明了它的目的。在編譯期間,它會使組譯器得以使用電腦硬碟上的資料檔。例如:
CBLOCK and ENDC 組譯指示
程式會用到的所有變數 (它們的名稱和位址) 必須在程式前頭定義,因為這個原因,其實不必要在程式中指定每個變數的位址。相反的,只要使用 CBLOCK 組譯指示指定第一個變數的位址並在之後列出所有變數就足夠了。編譯器會照著它們條列的順序自動指定對應的位址給這些變數。最後,ENDC 組譯指示表示 CBLOCK 的結束。
IF, ENDIF and ELSE 組譯指示
這些組譯指示用來建立所謂的條件式區塊。每個區塊以 IF 組譯指示為始,並以 ENDIF 或 ELSE 為止。跟在 IF 組譯指示後的陳述式或符號 (位於小括號中) 代表決定程式哪部份要被編譯:
● 如果陳述式為真或符號的值為 1,組譯器會編譯寫在 ELSE 或 ENDIF 之前的所有指令; 及
● 如果陳述式為假或符號的值為 0,只有寫在 ELSE 或 ENDIF 之後的指令才會被編譯。
範例 1:
如果程式釋出的版本超過第 3 版 (陳述式為真) 那麼 "Table 2" subroutine 和延伸的指令會被執行。如果括號中的陳述式為假 (VERSION<3),呼叫副程式的兩個指令會被略過,並且也因此將不會被編譯。
範例 2:
如果 "Model" 為 1,那麼 IF 之後的兩個指令會被編譯 (介於 ELSE 和 ENDIF 之間的所有指令會被略過);如果 "Model" 為 0,那麼介於 IF 和 ELSE 之間的指令會被略過,而在 ELSE 之後的指令則會被編譯。
BANKSEL 組譯指示
為了存取 SFR 暫存器,有必要使用 STATUS 暫存器的 RP0 和 RP1 位元選擇 RAM 裏面恰當的 bank。這個組譯指示用於這種情況,由於 "inc" 資料檔裏包含了所有暫存器以及其位址,組譯器知道哪個暫存器對應到哪個 bank。在遇到這個組譯指示後,組譯器會為特定的暫存器選擇適當的 RP0 和 RP1 位元的設定。例如:
如何撰寫一支程式的範例
底下的範例說明了一支以組合語言寫成的簡單程式長相為何。
除了組合語言正常的規則外,在撰寫程式的時候,還有一些不成文的規則應該要注意。其中一個是要在程式開頭寫一些文字,註明程式名稱、用途、版本、發行日期、適用的微控制器種類,以及 programmer 的名字。由於這些資訊對組譯器而言不重要,所以都是以分號 ";" 開始,可以寫在新的一行或寫在某個指令之後。
在寫完這個一般的註解外,便是使用 PROCESSOR 組譯指示選擇微控制器的時候,跟著是另一個組譯指示,會將 PIC16877 內部全部暫存器的定義包含到程式中。這些定義沒有什麼,只不過可以用來定址 port B 或其它暫存器,而非讓程式使用 06h 位址,這會讓程式變得清楚易讀。
為了讓微控制器順利運作,必須定義諸如振盪器種類、watchdog 的狀態以及內部重置電路等一些參數。定義的方法是利用下列組譯指示:
當所有必要的元素都定義好,便可以開始撰寫程式。首先最重要的,必須指定微控制器上電後開始執行的位址 (ORG 0x00),以及中斷發生時程式要繼續執行的位址 (ORG 0x04)。由於這支程式很簡單,使用指令 "goto Main" 就足以指引微控制器到程式的進入點。下一個指令選擇記憶體 bank 1 以存取 TRISB 暫存器並把 port B設置為輸出 (banksel TRISB)。主程式選擇記憶體 bank 0 並將 port B 所有腳位設定為邏輯 1 後結束 (movlw 0xFF, movwf PORTB)。
有必要建立一個迴圈以免程式因為錯誤而 "迷路 (getting lost)" 。為了這個目的,當微控制器上電後有個無窮迴圈會不斷的執行。
每支程式都需要 "END" 指令以告訴組譯器沒有需要編譯的程式了。
編譯程式產生的資料檔
以組合語言寫成的程式編譯後的結果為資料檔。最重要和最常用的資料檔為:
● 可執行的資料檔 (Program_Name.HEX);
● 錯誤資料檔 (Program_Name.ERR);及
● 報表資料檔 (Program_Name.LST)。
第一個檔案包含將被載入到微控制器編譯過的程式。它的內容對 programmer 而言沒有什麼重要的資訊,所以這裡不會討論。第二個檔案包含程式撰寫過程中產生的錯誤,在編譯過程中編譯器偵測到的錯誤。
第三個檔案對 programmer 來說最有用。它包含了很多關於指令和變數在晶片記憶體中的位址。在每個報表資料檔最後面有一張符號對照表,該表包含了程式裏用到的所有符號。報表資料檔其它有用的資訊是記憶體使用映照圖 (memory usage map) 和錯誤統計資訊,位於報表資料檔的最後面。
巨集與副程式
程式通常會重覆使用相同的一串指令。組合語言是非常荷求的。Programmer 必須對微小的細節非常小心,因為只要有一個錯誤的指令或標記名稱,就可以導致程式運作不正確或可能完全不能執行。因此,若把運作正常的一串指令當成一個陳述式 (statement) 使用,絕對不會比較冗長無味,也不容易出差錯。為了實現這個想法,得用到巨集和副程式。
巨集
巨集包含了 programmer 定義的一串指令。它是利用 macro 組譯指示定義的,macro 組譯指示為巨集命名並視需要定義參數。巨集必須在使用它之前定義好。一旦定義好巨集,就可以在程式裏使用巨集。當遇到巨集名稱時,組譯器會使用恰當的一串指令取而代之並處理它們,就好像這串指令出現在程式中一樣。有很多不同用途的巨集指令可供使用,可以去除程式的重覆工作,也可以簡化程式的撰寫、閱讀與理解。巨集最簡純的使用可能是為了簡化重覆使用的一串指令。讓我們以 Global Interrupt Enable 與 SFR 的 bank 選擇為例。
底下的例子顯示 4 個巨集。前兩個巨集用來選擇 banks,第三個是啟用中斷,而第四個則是停用中斷。
以這種方式定義的巨集會儲存在一個特別的資料檔裏,資料檔副檔名為 INC,代表 INCLUDE 資料檔。如所見,這四個巨集沒有參數,然而,巨集可以視需要包含參數。
底下的例子顯示包含參數的巨集。如果對應的 TRIS 暫存器被設為 1 (bank 1),則腳位會被設置為輸入,否則的話腳位會被設置為輸出。
帶有參數的巨集可以下列方式呼叫:
在呼叫這個巨集的時候,第一個參數 TRISB 會被取代成為巨集定義中的 arg1。同樣的,數字 7 會被參數 arg2取代,而且會產生下列程式碼:
顯而易見的,第一眼可以看到,透過巨集,程式變得比較易讀而且有彈性。巨集主要的缺點是它佔用比較多的記憶體空間,因為程式裏的每個巨集呼叫會被預先定義的程式碼取代 (展開巨集)。頻繁使用巨集的事實是,程式展開後會變得非常長而複雜。
假若巨集之中有標記,它們必須使用 local 組譯指示定義為本地端的標記。上例中的巨集會呼叫副程式 (call label),當 STATUS 暫存器的 Carry 位元為 1 的時候,否則,會執行後其後的指令。
副程式
副程式 (Subroutine) 包含一串指令,以標記開始 (subroutine 的名稱) 並以 return 或 retlw 指令結束。相較於巨集,主要的不同點是程式不會以副程式的程式碼取代,而是跳到副程式裏執行之。每當執行到 call Subroutine_name 時,程式就會跳到副程式裏執行。當指令返回後,程式會離開副程式並從它離開主程式的地方繼續執行。副程式可以定義在呼叫之前或之後。
如所見,關於巨集,輸入與輸出的參數是非常重要的,關於副程式,它不能定義參數。然而,在主程式預先定義的變數可以當作副程式的參數。
一個合乎邏輯的事順順序如下:定義變數、呼叫使用該變數的副程式,最後在執行完副程式後讀取變數的內容。
底下的例子對兩個 2 個位元組 (2-byte) 的變數 ARG1 和 ARG2 進行加法運算,並且把結果搬到 RES 變數。在使用 2 個位元組的變數時,必須定義它們的高位元組和低位元組。程式非常的簡單。它首先讓 ARG1 和 ARG2 變數的低元組相加,之後再加高位元組。如果低位元相加之後超過 255 (一個 byte 的最大值),那麼就會加 1 到 RESH 變數。
簡而言之
巨集與副程式的主要差異是巨集在編譯後會在程式中展開其程式碼 (使得 programmer 可以少打一些字)。巨集也可以有參數,而副程式使用比較少的記憶體,但不能有參數。
MPLAB
MPLAB 是一個 Windows 套裝軟體,不但方便程式的撰寫而且也方便程式的開發。把它當成 PC 上標準程式語言的開發環境是最好的說法。MPLAB 簡化了很多工作,在 IDE 環境出現之前,這些工作很多參數從前都是在命令列上執行的。然而,青菜蘿蔔,各有所好,有些 programmer 還是偏好標準的編輯器和命令列的編譯器。MPLAB 的每支工具都很易懂,為了以防萬一,還是有說明文件可供參考。
安裝 MPLAB
MPLAB 由幾個部份組成:
● 用來將屬於同一個專案的檔案分成群組的程式 (Project Manager);
● 用來產生與處理文字的程式 (Text Editor); 及
● 用來模擬程式載入到微控制器後的運作模擬器 (simulator)。
此外,還內建用來將軟體燒錄到 PIC 微控制器的燒錄程式 (programmer),諸如 PICStart Plus 和 ICD (In Circuit Debugger)。由於不是本書的主題,所以只把它們當成選項來提。
為了開始使用 MPLAB,你的 PC 應該要有:
● 屬於 486 或更高等級相容於 PC 的電腦;
● 任何 Windows 作業系統;
● VGA 圖形介面卡;
● 8 MB 記憶體 (建議 32 MB);
● 200 MB 硬碟可用空間; 及
● 滑鼠一只。
首先是 MPLAB 的安裝,應當把 MPLAB CD 裏的資料檔複雜到硬碟。安裝的過程跟大部份 Windows 程式的安裝是一樣的。首先會出現一個歡迎視窗,然後是可供選擇的選項,最後開始安裝。會有個訊息視窗通知程式已成功安裝完畢並且準備就緒。你準備好了嗎?
安裝前的準備工作
● 啟動 Microsoft Windows;
● 把 CD 插入 CD-ROM;
● 點開始並選擇執行;
● 點瀏覽並選擇 CD-ROM 硬碟機;及
● 找出在 CD-ROM 上的 MPLAB 資料夾。
一切就緒,可以開始安裝了。下列的圖片說明安裝的步驟。
在這個圖示上點一下啟動安裝程序...
有件事正在發生…這張圖表示安裝程序剛剛啟動了!
下個視窗裏有 "歡迎" 這兩個字。需要解釋嗎?
事實上,程式提醒關閉所有作用中的程式,以免與安裝程序發生衝突。當然了,按 Next!
在繼續之前,你必須同意 MPLAB 的軟體許可 (software license)。選擇 "I accept" 並按 Next。
類似其它程式,MPLAB 應當裝到一個資料夾。可以是硬碟上的任何一個資料夾。如果不需要改變,選擇預設的資料夾並按 Next。
另一個軟體許可視窗,以及另一個資料夾路徑的選擇….按下一步,下一步….
耐心點!
終於!這就是你等待已久的。按 Finish 後,電腦會重新啟動。一切全部搞定!
按下桌面上的 MPLAB 圖示以啟動程式並開始學習使用它。
如所見,MPLAB 跟大部份的 Windows 程式很像。除了工作區域 (working area),有選單 (包含 File, Edit 等選項), 工具列 (包含不同的圖示) 和視窗下方的狀態列。類似 Windows 程式,為了更容易存取與加速操作,工具列上有大部份常用選項的捷徑。事實上,這些捷徑就位於選單下方。換句話說,工具列上所有的選項也都包含於選單裏。
建立專案
跟著底下的步驟以準備一支用來載入到微控制器的程式:
1. 建立一個專案;
2. 撰寫一支程式;及
3. 編譯程式。
為了建立一個專案,必須點選單上的 "Project",然後點 "Project Wizard",歡迎視窗就會出現。
在目前的情況下,是 PIC16F887 微控制器。
在最後,專案會有個名稱,該名稱表示其它的目的和程式內容。專案應當搬到想放的資料夾,資料夾最好跟 PIC微控制器有關 (見下圖)。
專案裏的文件不一定要在 MPLAB 裏寫成。使用其它程式寫好的文件也可以加到專案中。以目前的例子而言,並沒有這類文件。直接按下一步。
按下 Finish 以完成建立專案的程序。Summary 視窗顯示了專案的各項參數。
寫一支新的程式
在建立好專案後,會出現下圖的視窗:
下一步是寫一支程式。點 File>New 建立一份新文件。Text Editor 會出現在 MPLAB 環境上。
使用 File>Save As 把文件儲存在 D:\PIC projects 資料夾,並命名為 "Blink.asm" 以表示這支程式將當作發光二極體的範例。顯然你可以隨心所欲,把檔案放在想放的地方,放在想放的磁碟機上。使用常見的資料夾存放所有專案是很有概念的。
範例:
在建立 "Blink.asm" 之後,必須在 "Proba.mcw" 視窗的 "Source Files" 上按右鍵把它加到專案,在這之後,會出現有兩個選項的小視窗,選擇第一個選項 "Add Files"。
點 "Add Files" 選項後會啟動開啟檔案的視窗,見下圖。
點選 "Blink.asm" 以便把 Blink.asm 加到專案中。
程式撰寫範例
前述程序沒完成之前,沒辦法開始撰寫程式。底下這支程式說明專案的編譯。
程式可以在 Text Editor 裏編寫,也可以使用 Copy/Paste 從其它地方剪貼過來。之後,必須透過 Project>Build All 把程式編譯成可執行的 HEX 檔案形式。此時會跳出 output window,視窗中最後一行是最重要的,因為它告訴我們編譯成功與否。顯然地,"BUILD SUCCEEDED" 訊息表示沒有發生錯誤,而且編譯已成功完成。
萬一發生錯誤,必須切到 output window,在錯誤訊息上點兩下,這會自動切換到組合語言程式,直接跳到發生錯誤的那一行程式碼。
模擬器 (SIMULATOR)
模擬器 (Simulator) 是 MPLAB 環境的一部份,它提供對微控制器的運作更深入的理解。一般來說,模疑器是用來模仿真實人生或假設情況,以供學習了解系統是如何運作。透過模擬器,也可以監視變數、暫存器和腳位狀態等。坦白說,對所有程式而言,模擬器不一定重要。如果程式很簡單 (如我們的例子),模擬器就不是那麼重要,因為把 port B 腳位設定為邏輯 1 一點都不複雜。然而,對於包含計時器、不同狀況和請求 (尤其是數學運算) 等比較複雜的程式,模擬器可能就很有用。如其名稱所示,模擬器代表模擬微控制器的運作。就像微控制器,模擬器會一行一行的執行指令,而且會不斷的更新所有暫存器的狀態。以這種方式,使用者便可以觀察程式的執行。在撰寫完程式後,在拿到實際環境執行前,使用者應該先在模擬器裏測試程式。不幸地,模擬器是常被 programmer 忽略的工具中其中一個,因為它的角色還有缺乏高品質的模擬器的關係。
模擬器的啟動方法是點 DEBUGGER>SELECT TOOL>MPLAB SIM,如下圖所示。會出現幾個與模擬器相關的圖示。它們的意義如下:
以全速執行程式。在這個例子中,模擬器會以全速執行程式,直到按下底下的圖示為止。
暫停程式的執行。程式可以以單步(Step by step)或全速再繼續執行。
以隨意的速度執行程式。執行速度要在Debugger/Settings/Animation/Realtime Updates對話方塊中設定。
啟動單步執行 (Step-by-step)。指令會一個接一個執行。再者,點這個圖示允許你跳進副程式或巨集裏。
重置 (reset) 微控制器。點下這個圖示,program counter 會從頭開始。
如同真實的環境,應該做的第一件事是使用 DEBUGGER>RESET 或按下圖示重置 (reset) 微控制器。重置的結果是,一條綠色的線會落在程式的開頭,而 program counter PCL 會被清除為 0 。參考下圖的 Special Function Registers 視窗。
除了 SFRs 外,最好一併深入看看 File Registers。點下 VIEW>FILE REGISTERS 選項後,就會跳出含有這些暫存器的視窗。
如果程式裏有變數,觀察這些變數也很好。點 VIEW>WATCH 選項後,就會跳出含有程式變數的視窗 (Watch Window)。
在感興趣的變數和暫存器都出現在模擬器工作區域之後,就可以開始進行模擬。下一個命令是 Step into 或 Step over,這取決於你要跳進副程式與否。同樣的命令也可以用 keyboard 輸入 – 按 <F7> 或 <F8> 鍵 (一般來說,所有重要的命令在鍵盤上都有對應的按鍵)。
沒有留言:
張貼留言
注意:只有此網誌的成員可以留言。