資料來源: Microchip文件DS33014G
(MPASM User's Guide with MPLINK and MPLIB)第一部份MPASM第五章Directive
Language.
註一: 假指令是不分大小寫的. 也就是說
cblock 無論是寫成CBLOCK, cblock, Cblock都會被Mpasm視為相同的假指令.
註二: 因為我還沒設計過多檔連結的案子. 所以歸類為 Object
File類別的指令,
只是我從Help檔翻譯而得,我不敢確定一定沒有誤譯.
(除了UDATA, IDATA, CODE指令外,其它都已經組譯確認過,
翻譯後的意思應該沒有跑掉.)
被歸類為CONTROL的指令:
CONSTANT 用來宣告常數,
語法
constant < label > [= < expr >, ... ,< label > [= < expr >] ]#DEFINE 定義字串的替代,
語法
#define < name > [ [(< arg >, ... ,< arg >)] < value >]
除了constant指令之外,其實define也可以用來定義常數. 例如
#define MaxPeriod 250
但是用#define來定義常數,其實並不是很恰當.
(容易混淆,以至於降低程式的可讀性.) #define 可以用來
定義一些symbol, 達成 條件式編譯的效果. 舉例來說
#define Testing
ifdef Testing
< instructions for Testing >
endif
END 結束程式,
語法
End (放在end指令後面的東西,都不會被編譯.)
EQU 與constant類似,可用來定義常數(從Help直接翻譯而來),
語法
< label > equ < expr >
這個指令實際上,可以用來宣告變數所在的位址.
(CBLOCK, ENDC也可以宣告變數位址) 例如
counter1 equ 0x20
W_Temp equ 0x21
也可以定義常數, 例如
b7 equ .7
#INCLUDE 引入常數(,暫存器...)的定義檔,
語法
include << include_file >>
include "< include_file >"
這個指令非常非常重要.
Microchip已經把每一個MCU的特殊暫存器都定義好了.
連MCU不存在的RAM的位置都幫你定義完畢.(用__BADRAM指令),
請參考各MCU的inc檔. 例如p12c671.inc定義了
__MAXRAM H'FF'
__BADRAM H'06'-H'09', H'0D'-H'1D'
__BADRAM H'86'-H'89', H'8D', H'90'-H'9E', H'C0'-H'EF'
所以你不需要自行定義S.F.R.的名稱.類似下列的定義列
都不必自己寫了. (S.F.R.是Special File Register的縮寫.)
STATUS equ H'0003'
FSR equ H'0004'
這可以讓你的程式開頭的定義區減少一大堆瑣碎的定義.
(筆者註: 別人寫好的工具檔或原始碼,尤其是Microchip寫的,
我們最好充分利用, 這樣才可以像 牛頓說的
踏在巨人的肩膀上. 讓自己可以看得更遠.)
ORG 設定程式碼的位址,
語法
< label > org < expr >
不只是程式開頭需要這個指令,
在跨頁,比較大容量的MCU裡面,這個指令更是必備的.
(參考MPASM的Directive的Example1)
PROCESSOR 設定處理器的種類 (此指令很少用,因為list p=處理器名稱,就可以取代這個指令了),
語法
processor < processsor_type >
RADIX 指定預設的數字基底,
語法 radix < default_radix >
數字基底共有Hex, Dec, Oct三種. 請依照你的習慣來設定數字基底.
SET 定義變數,
語法
< label > set < expr >
假指令SET和EQU的差別是, 被SET所定義的label可以用SET重新設定,
但EQU設定過的label,卻不可以重新設定.
#UNDEFINE 取消字串的取代,
語法
#undefine < label >
注意:要取消字串取代的label,必須在取消前已經用#define定義過.
VARIABLE 將某Symbol宣告為變數.
語法
variable < label > [= < expr >, ... ,< label > [= < expr >] ]
假指令constant和variable所定義的symbol,只是用來幫助組譯器,
並不會被存在MCU的暫存器裡面.
(所以用constant和variable所定義的symbol,
在watch window內是看不到數值的.)
被歸類為CONDITIONAL ASSEMBLY的指令:
IF 如果條件成立,之後的程式碼將被組譯,
ELSE 當IF所列的條件不成立,則ELSE之後的程式馬會被組譯
ENDIF 結束條件式組譯.
以上三指令的語法大致如下
If 條件
條件成立時,所要組譯的程式碼
else
條件不成立時,所要組譯的程式碼
endif
注意: else和之後的條件不成立所要組譯的程式碼可以省略.
這三個指令非常有用. 比如說,你要控制外部的LED,可以是
正邏輯推動(輸出High則LED亮, 輸出Low則LED熄), 也可以是
負邏輯(輸出low則LED亮..),
根據不同的外部的電路,搭配不同的推動邏輯. 在這個情況下,
你可以使用if這種條件式組譯的指令,讓程式更具有彈性.
WHILE (條件)
條件成立時,組譯此處的指令
ENDW 結束While迴圈
這一組指令,我知道意思,但卻不曉得該拿來做什麼.
懂的人不妨舉個例子,解釋一下. 謝謝!
(很好奇,Microchip當初設計這組假指令的目的是什麼呀!?)
IFDEF 若Symbol已經定義了,則組譯之後的程式碼,
語法
ifdef < label >
當< label >已定義,將被組譯的程式碼 endif這一組指令,在開發階段使用16F87X, 16F62X之類的Flash MCU, 但是成品將換用比較便宜的16C7X, 16CR5X之類的OTP或ROM. 這種情況下,非常好用. ***使用flash type來開發最終IC是OTP或ROM的時候, 可以參考Application Note #TB033)***
IFNDEF 若Symbol不曾定義,則組譯之後的程式碼,
語法 Ifndef < label >
當< label >未定義,將被組譯的程式碼 endif
被歸類為DATA的指令:
__MAXRAM 定義RAM位址的最大值(兩條底線相連),
語法
__maxram < expr >
__BADRAM 定義不存在的RAM位址(兩條底線相連),
語法
__badram < expr >
指令__BADRAM必須在__MAXRAM宣告後才可以使用.
這兩個指令可以避免我們用到不存在的RAM,但我們可以
引入Mplab內建的p12c508.inc, p12c508a.inc, …等inc檔,來達到同樣的效果.
這是因為那些inc檔都已經把不存在的RAM用__MAXRAM和__BADRAM
定義完畢了,
所以我們只須用include指令,把需要的inc檔引入.
就可以保證我們不會用到不存在的RAM.
__MAXROM 定義ROM的最大位址(兩條底線相連),
語法
__maxrom < expr >
__BADROM 定義不存在的ROM位址(兩條底線相連),
語法
__badrom < expr >[-< expr >] [, < expr >[-< expr >]]
指令__BADROM必須在__MAXROM宣告後才可以使用.
程式開頭的LIST P=???? 可以定義我們的程式,使用了????
這個型號的MCU,如果程式寫的太大了,組譯器在組譯時
就會警告我們. 所以這兩個指令似乎是派不上用場.
CBLOCK 定義一個常數區塊(Define a block of constants) ENDC 結束常數區塊,
語法
cblock [< expr >] < label >[:< increment >][,< label >[:< increment >]] endc
這一組假指令,可以用來定義存於RAM的變數的位址.
尤其是變數的個數很多的時候.
如果固定使用equ來宣告變數的位址,很容易出現keyin錯誤,
造成有的RAM還空著,有的卻已經被重複定義了
(兩個以上的變數使用同一個位址),
改用cblock和endc假指令,
就可以有效地消除 equ宣告變數位址可能造成的問題. 請看例子
cblock 0x20
Temp1, Temp2, TempW1:2, Counter1, Counter2
CounterW1:2, Counter3
endc
就會組譯出下列結果:
Temp1位址=0x20, Temp2位址=0x21, TempW1位址=0x22,
Counter1位址=0x24, Counter2位址=0x25, CounterW1位址=0x26
Counter3位址=0x28
__CONFIG 設定燒錄的設定值,
語法
__config < expr > 或 __config < addr >, < expr >
這個指令非常好用,可以免去燒錄時手動設定Osc, MCLR…等設定值.
***筆者註: 建議使用inc檔內所規定的常數來設定燒錄的設定值.
這樣可以提高程式的易讀性.
(可參考mplab\template樣板程式碼,和各個inc檔)***
__IDLOCS 設定辨識碼(ID),
語法
__idlocs < expr >
ID可以用來辨識程式的名稱和版次.
以方便IC程式的種類和版次的追蹤和確認.
DA 在Program Memory內儲存資料(14-bit數字代表兩個7-bit ASCII字元),
語法
[< label >] da < expr > [, < expr2 >, ..., < exprn >]
例 DA "abcdef"將在Program Memory內儲存30E2 31E4 32E6.
(a=110-0001, b=110-0010,結合成14bit,就變成30E2,依此類推,
cd變成31E4, ef變成32E6. 注意.Microchip範例說明是錯的,多了3380.
例 DA "12345678" ,0將在Program Memory內儲存18B2 19B4 1AB6 1BB8 0000.
(1=011-0001, 2=011-0010,結合成14bit,就變成18B2,依此類推,
34變成19B4, 56變成1AB6, 78變成1BB8. 注意Microchip範例說明是錯的,少了1BB8.
例 DA 0xFFFF將在Program Memory內儲存3FFF.
(超過14-bit的部分都被刪除)
DATA 宣告數字,文字資料(若配合IDATA假指令,DATA可用來宣告資料的初始值),
語法
[< label >] data < expr >[,< expr >, ... ,< expr >]
DA是把資料截斷成2個7bit,塞入14bit內;
DATA是直接把多餘的位元直接刪除,
比如說用在14bit長度的MCU,只保留14bit.
(類似DW,但用在PIC18CXX這種16bit長的MCU,
則DW和DATA的組譯結果上下位元組是相反的,.請看例子
data "test A,B,C",12,4,0x5678
組譯後得到 3465 3374 2041 2C42 2C43 0012 0004 1678
(編譯時出現Message[303] Program word too large.
Truncated to core size. (7465) (7374) (5678)
就是"te","st",0x5678太長了,被刪成3465,3374,1678.)
DB 宣告1 Byte的資料(若配合IDATA假指令,DB可用來宣告資料的初始值),
語法
[< label >] db < expr >[,< expr >, ... ,< expr >]
DA是把資料截斷成2個7bit,塞入14bit內;
DB卻是直接保存8bit(=1Byte)的資料,但因為14bit的前段只有6bit,
所以存在前面的資料,只保留6bit.請看例子
Db 0x0f,0x89, 0x0f, '1', 0x0f, '3', 0xff, 'A', 0x0f, 't', '\n'
組譯後得到 0F89 0F31 0F33 3F41 0F74 0A00
(編譯時出現Message[303] Program word too large.
Truncated to core size. (FF41)就是0xff太長了,被刪成3F.('A'=41),
最後一個0A00是'\n'(=0A)構成的.不成對的資料末端補0.)
DW 宣告1 Word的資料(若配合IDATA假指令,DW可用來宣告資料的初始值),
語法
[< label >] dw < expr >[,< expr >, ... ,< expr >]
DW和DB用法相似,只是資料長度不同;
在PIC18CXX等16-bit長度的MCU, DW可以保留16bit長度的資料,
但用於14bit的MCU,則資料只能保留14bit.請看例子
DW 0x6f, 0x89ee, 0x0fdd, '1', 0xff07, 't', '\n'
組譯後得到 006F 09EE 0FDD 0031 3F07 0074 000A
(編譯時出現Message[303] Program word too large.
Truncated to core size. (89EE)和(FF07),如組譯結果,
被刪成09EE和3F07.只保留14bit(=core size).)
DE 宣告EEPROM的資料內容(8-bit長,和da指令的長度14bit不同),
語法
[< label >] de < expr >[,< expr >, ... ,< expr >]
請注意, PIC18CXXX的EEPROM起始位址是0xF00000,
其它的PICmicro則是0x2100. (請參考MPASM Assembler Help)
這個指令可以在燒程式時一併把資料燒到EEPROM裡面.
DT 定義表格(將組譯產生一串RETLW指令, RETLW回傳的資料就定義在dt後面的表示式裡面),
語法
[< label >] dt < expr >[,< expr >, ... ,< expr >]
這個指令可以提高程式的易讀性,比如說,你想在LCD顯示
I am a student. 如果你用RETLW一個一個寫,
勢必增加閱讀的困擾,也使程式的維護修改更困難.
FILL 將記憶體填滿特定數值或指令,
語法
[< label >] fill < expr >, < count >
在Microchip的seminar講義中, 建議記憶體不要留空.
如果能把剩餘的記憶體統統填滿nop或goto reset指令,
就可以在程式誤迷失到剩餘的記憶體時,
因為goto reset而強制程式reset,免去當機的危險.
***此指令只是預防萬一. 要避免當機,
應該從良好的軟體和硬體下手,
千萬不要以為有了fill指令和watchdog,
就不必注意程式的運作流程是否合理.***
RES 保留記憶體空間,
語法
[< label >] res < mem_units >
若用在可重置程式碼位址(relocatable code)的場合,
此指令將保留資料儲存空間;
若用在不可重置程式碼位址(non-relocatable code)的場合,
< label >將被指定為記憶體位址. 例如
buffer res 64 ;保留64個儲存空間.
歸類為LISTING的指令:
ERROR 在Build Results和Absolute Listing視窗內 都會產生一個使用者自訂的錯誤訊息,
語法
error "< text_string >"
搭配if指令,我們可以設訂產生錯誤訊息的條件,
使組譯器在這些自訂的條件成立時,產生錯誤訊息
來提醒我們,某些我們不希望發生的情況已經發生了. 請看例子:
cfl_jge macro file,con,jump_to
if file < 0x20 ;if file register falls in S.F.R range
error "The first parameter of macro cfl_jge must be >= 0x20"
endif
movlw con&0xff
subwf file,w
btfsc STATUS,C
goto jump_to
endm
如果使用cfl_jge巨集時的參數值file小於0x20,則錯誤訊息
The first parameter of macro cfl_jge must be >= 0x20就會
出現在Build Results和Absolute Listing視窗內
MESSG 類似ERROR,但只是出現MESSAGE訊息,
語法
messg "< message_text >"
這個指令和if搭配,可以用來偵測巨集的參數是否引用失當…
ERRORLEVEL 設定訊息的等級,
語法
errorlevel 0|1|2|< +- >< msgnum >
0使messages, warnings, errors都會被列出來. 1使warnings, errors都會被列出來. (messages不列) 2使errors都會被列出來. (warnings, messages不列) +< msgnum >使編號msgnum的message被列出來, -< msgnum >使編號msgnum的message不被列孕X來. 關於message的編號,請參考DS33014G的Appendix C.
LIST 根據選項的on-off,使Listing檔和組譯程序出現對應的變化,
語法
list [< option >[, ... ,< option >]]
選項有很多個.簡述如下:
b=nnn 設定Tab的間隔大小.(預設值為8.也就是一個Tab間隔8個空格.)
c=nnn 設定欄位的寬度.(預設值132.)
f=
NOLIST 不產生listing檔案,
語法
Nolist
為了除錯,通常我們都會設定"產出list檔",若因硬碟空間不足,
我們可以加入nolist指令,就不會產生list檔了.
PAGE 使list檔換頁,
語法
Page
這個換頁指令,可以使list檔段落更分明,使程式報表更容易閱讀.
SPACE 在list檔插入空白行,
語法
space [< expr >]
產生
SUBTITLE 設定list檔每頁的開頭的副標題,
語法
subtitle "< sub_text >"
TITLE 設定list檔每頁的開頭的標題,
語法
title "< title_text >"
歸類為MACRO的指令:
MACRO 宣告巨集的定義,
語法
< label > macro [< arg >, ... ,< arg >]
ENDM 結束巨集的定義,
語法
Endm
巨集的名稱必須明確易懂,
太複雜的首字縮寫名稱,會造成看不懂巨集的功能,
進而減低使用巨集的頻率.
(請參考Microchip的巨集定義,領略巨集的威力.)
EXITM 跳出巨集,
語法
exitm
LOCAL 在巨集內宣告局部變數,
語法
local < label > [,< label >]
EXPAND使巨集在list檔中,做展開顯示,
語法
expand
NOEXPAND 使巨集在list檔中不做展開顯示,
語法
Noexpand
如果想要詳細了解巨集被組譯為那些指令,就不該使用noexpand指令.
因為下達此指令,會使得巨集指令都不做展開顯示,
只會顯示單純的巨集指令.
歸類為OBJECT FILE的指令:
BANKISEL 根據label所在的RAM bank來切換bank. (設定間接RAM的指標(STATUS的IRP位元)),
語法
bankisel < label >
注意,這個指令只在使用間接定址(FSR, INDF)時,才會用到.
使用本指令來切換間接RAM的頁次,可提高程式的易讀性.例如
VARIABLE Var1=0x192
movlw Var1
movwf FSR
bankisel Var1
movwf INDF
如果MCU本身的RAM沒有bank2(3), 則BANKISEL Var1不會組譯出
任何指令碼,而且會出現訊息,提醒你該MCU是不必使用BANKISEL指令的.
如果Var1位於bank0(1), 則BANKISEL Var1 會組譯為bcf STATUS,IRP
如果Var1位於bank2(3), 則BANKISEL Var1 會組譯為bsf STATUS,IRP
(此例的Var1=0x192在bank3,所以bankisel Var1將譯為bsf STATUS,IRP)
BANKSEL 根據label所在的RAM bank來切換bank (設定RAM的指標(STATUS的RP1, RP0兩個位元)),
語法 banksel < label >
這個指令可以減少自己切換RAM bank的麻煩.
使用本指令來換頁,可提高程式的易讀性.
CODE 宣告目的檔(Object File)的程式節區的開始,
語法
[< label >] code [< ROM address >]
適當的加上label, 可以提高易讀性. 例如
RESET code 0x1FF
Goto START
這樣,很容易知道0x1FF存放了RESET的程式碼.
(Object File不允許ORG指令)
CODE_PACK 在可執行碼區塊填入位元組的資料,
語法
[< label >] code_pack [< ROM address >]
例如
bytes CODE_PACK H'01FF'
DB 1, 2, 3 ; at addresses 0x1FF, 0x200, 0x201
DB 4 ; at address 0x202
DB 5 ; at address 0x203
EXTERN 宣告標記(變數或副程式)是在其它檔案內宣告的,
語法
extern < label > [ ,< label >]
指令External所宣告的標記,必須在其它檔案內
宣告為global(公用標記). 例如,
extern Function1, Function2
:
call Function1
call Function2
GLOBAL 宣告一個標記(變數或副程式名稱)給其它檔案使用,
語法
global < label > [, < label > …]
指令GLOBAL宣告的標記,可以給其它檔案使用,但其他檔案
必須搭配EXTERN指出要引用的標記並不是在本身宣告定義的.
(利用Edit project把會用到的檔案都列入,而且要設定用來link的
工具程式(通常是直接使用Microchip提供的mplink))
例如
udata
Var1 res 1
Var2 res 1
Global Var1, Var2
Code
AddThree
Global AddThree
Addlw 3
Return
IDATA 宣告有初值的RAM的資料區段的開始,
語法
[< label >] idata [< RAM address >]
搭配RES指令(初值0)、DB指令(以Byte為單位)、
或DW指令(以Word為單位)可以規劃RAM空間,
並且設定初始值給變數使用(從設定的RAM address開始). 例如
idata
LimitL dw 0
LimitH dw D'300' ;D'300'=012C,存為2C 01(低位元組先存)
Gain dw D'5'
Flags res 0
String db "Hi there!"
PAGESEL 切換到label所在的頁,
語法
pagesel < label >
這個指令可以減少自己設定頁次(跨頁)的麻煩.
若程式大到需要跨頁.最好使用本指令來換頁,以提高程式的易讀性.
UDATA 宣告未初始化的RAM的資料區段的開始,
語法
[< label >] udata [< RAM address >]
搭配RES指令後,可以規劃RAM空間給變數使用
(從設定的RAM address開始). 例如
udata
Var1 res 1
Double res 2
UDATA_ACS (限用於PIC18CXX)宣告 未初始化的RAM的資料區段的開始,
語法
[< label >] udata_acs [< RAM address >]
這個指令可以規劃RAM空間讓變數使用(必須和RES指令搭配). 例如
udata_acs
Var1 res 1
Double res 2
UDATA_OVR 宣告可重複使用的RAM的資料區段的開始,
語法
[< label >] udata_ovr [< RAM address >]
擁有相同名稱(udata_ovr指令前的label相同)的資料節區,
使用同一塊RAM. 例如
Temps udata_ovr
Temp1 res 1
Temp2 res 1
Temp3 res 1
Temps udata_ovr
LongTemp1 res 2 ;變數LongTemp1和變數Temp1、Temp2使用相同的RAM位址.
LongTemp2 res 2 ;變數LongTemp2和變數Temp3使用相同的1Byte RAM位址,
但LongTemp2多用了一個Byte,則是單獨使用的.
UDATA_SHR 宣告全部BANK都共享的RAM的資料區段的開始,
語法
[< label >] udata_shr [< RAM address >]
這個指令,只能用在有共享RAM的MCU,比如說,16F876/877的
70-7F(在bank0) =F0-FF(在bank1) =170-17F(在bank2) =1F0-1FF(在bank3),
這裡有16個RAM就可以讓UDATA_SHR指令拿去定義. 例如
Temps udata_shr
Temp1 res 1
Temp2 res 1
Temp3 res 1