 |
 |
培訓(xùn)信息 |
|
|
|
 |
 |
贊助商 |
|
|
|
|
 |
 |
MPASM的偽指令 |
|
|
| MPASM的偽指令 |
| 作者:佚名 來源:不詳 錄入:Admin 更新時(shí)間:2008-7-26 17:09:16 點(diǎn)擊數(shù):4 |
【字體:
】 |
MPASM 的偽指令 3.2.3 MPASM 的偽指令 我們在第一章中已經(jīng)詳細(xì)介紹了中檔PIC 單片機(jī)的35 條指令,源程序的編寫主要就是 用這些基本的指令實(shí)現(xiàn)你的控制任務(wù)。但為了增加源程序的可讀性和可維護(hù)性,我們引入了 偽指令的概念。偽指令本身不會產(chǎn)生可執(zhí)行的匯編指令,但它們可以幫組“管理”你編寫的 程序,其實(shí)用性和必要性絕不亞于35 條正真的匯編指令。我們在此著重介紹最常用的幾種 偽指令。 􀁺 #i nclude 或 include #i nclude 偽指令的作用是把另外一個(gè)文件的內(nèi)容全部包含復(fù)制到本偽指令所在的位置。 被包含復(fù)制的文件可以是任何形式的文本文件,當(dāng)然文件中的內(nèi)容和語法結(jié)構(gòu)必須是 MPASM 能夠識別的。最經(jīng)常被“include”的是針對PIC 單片機(jī)內(nèi)部特殊功能寄存器定義的 包含頭文件, 在MPLAB 安裝后它們?nèi)糠旁诼窂健?C:\Program Files\MPLAB IDE\MCHIP_Tools”下,每一個(gè)型號的PIC 單片機(jī)都有一個(gè)對應(yīng)的預(yù)定義包含頭文件,擴(kuò)展 名是“.inc”。除了一些符號預(yù)定義文件,你也可以把現(xiàn)有的其它程序文件作為一個(gè)代碼模塊 直接“包含”進(jìn)來作為自己程序的一部分。見例3-01。 #i nclude <p16f877a.inc> 把預(yù)定義的PIC16F877A 寄存器符號包含到此處 #i nclude ”math.asm” ;把現(xiàn)有的程序文件包含進(jìn)來作為自己代碼的一部分 例3-01 請注意被包含文件的引用方式。一種是<>尖括號引用,這種引用意味著讓編譯器去默 認(rèn)的路徑下尋找該文件,MPASM 默認(rèn)的寄存器預(yù)定義文件存放路徑即為上面提及的 MPLAB 安裝后的目錄;另一種是””雙引號引用,這種引用方式的意思是指示編譯器從引號 中指定的全程文件路徑下尋找該文件。例3-01 中”math.asm”沒有指定路徑,即意味著在 當(dāng)前項(xiàng)目路徑下尋找math.asm 文件。如果編譯器找不到被包含的文件,將會有錯(cuò)誤信息告 知。 請?jiān)谀愕脑闯绦蛑斜M量用MPLAB 標(biāo)準(zhǔn)頭文件定義的寄存器符號。一來這些被定義的寄 存器符號和芯片數(shù)據(jù)手冊上的描述一一對應(yīng),理解起來即直觀又容易;二來如果用你自己定 義符號就缺乏一個(gè)大家能一起交流的標(biāo)準(zhǔn)平臺,其他人要解讀你的代碼時(shí)將費(fèi)時(shí)費(fèi)力。故例 3-01 中的首行#i nclude 包含引用偽指令可以說是PIC 單片機(jī)程序編寫時(shí)的標(biāo)準(zhǔn)必備。 􀁺 list list 偽指令可以設(shè)定程序編譯時(shí)的一些信息,例如所選單片機(jī)的型號,編譯時(shí)選擇的缺 省數(shù)制等。例如: list p=16f877a, r=DEC ;單片機(jī)型號為PIC16F877A,無特別指明的數(shù)字為十進(jìn)制數(shù) 例3-02 如果程序開發(fā)時(shí)使用項(xiàng)目管理的模式,則所有l(wèi)ist 偽指令可以描述的參數(shù)項(xiàng)都可以在項(xiàng) 目的設(shè)定選項(xiàng)中通過對話框的形式設(shè)定并保存。在此只需對list 偽指令稍作了解即可。 􀁺 __config 此偽指令的重要作用是把芯片的配置字設(shè)定在源程序中,請參閱2.5 節(jié)的詳細(xì)說明。建 議大家盡量用此偽指令把芯片的配置字寫在程序中。 􀁺 __idlocs PIC 單片機(jī)中有一處非常特殊的標(biāo)記單元。它獨(dú)立于任何其它存儲器,唯一的作用就是 作為一個(gè)標(biāo)記。此標(biāo)記值無法用軟件讀到,讀取和寫入的方法只有通過編程器實(shí)現(xiàn)。此標(biāo)記 值沒有讀保護(hù),你可以利用它存放程序的版本或日期等信息。如果需要,則可以用偽指令 __idloc 在程序中定義具體的值。 __idloc 0x1234 ;設(shè)定芯片的標(biāo)記值為0x1234,注意前面有兩個(gè)下劃線符 例3-03 和__config 偽指令定義的配置字一樣,用__idloc 定義的芯片標(biāo)記值在最后也會存放在 HEX 文件中,這就要求編程器能夠解析它。 􀁺 errorlevel errorlevel 的用途是控制編譯信息的輸出顯示。編譯器在編譯你的源程序時(shí)會提供很多 信息,有些信息是你必須要處理的,例如錯(cuò)誤信息(Error),只要有錯(cuò)誤信息存在,你的程 序?qū)⒂肋h(yuǎn)無法完成編譯;有些可能只需要關(guān)注,例如警告信息(Warning);也有一些可能你 根本就不感興趣,它們只是一些提示信息(Message)而已。注意出現(xiàn)警告和提示信息時(shí)將 不會中止編譯器的編譯工作,你的程序?qū)⒈痪幾g并最終產(chǎn)生HEX 文件。圖3-14 中顯示了一 個(gè)程序編譯后的各種信息實(shí)例,其中既有錯(cuò)誤信息,也有警告和提示信息。我們可以用 errorlevel 偽指令來控制輸出信息的級別,或刻意關(guān)閉/打開一些提示信息。 編譯信息的輸出顯示級別有三種,分別是0、1 和2。級別0 代表顯示所有信息,包括 各種錯(cuò)誤、警告和提示信息,如圖3-14 所示;級別1 代表顯示錯(cuò)誤和警告信息,忽略提示 信息;級別3 代表只顯示錯(cuò)誤信息而忽略警告和提示信息。在任何一個(gè)大的級別上還可以對 某些信息單獨(dú)設(shè)定顯示或關(guān)閉。每個(gè)信息都有一個(gè)識別標(biāo)號,見圖3-14 中信息項(xiàng)“[]”中的 數(shù)字,打開或關(guān)閉某類信息只需在errorlevel 偽指令中引用信息識別標(biāo)號,并在其前面用“+” 或“-”號,即代表打開或關(guān)閉這一類信息,例如: errorlevel 0, -302, -305 ;顯示所有信息,但不需要302 和305 這兩類提示信息 errorlevel 1, +305 ;顯示錯(cuò)誤和警告信息,但同時(shí)還要關(guān)注305 類的提示信息 圖3-14 例3-04 􀁺 #define / #undefine #define 的作用是定義常數(shù)符號,即用一個(gè)符號變量替換另一個(gè)符號串或變量。被替換 的可以是任意字母數(shù)字組成的符號但替換者本身不能是一個(gè)純數(shù)字。例如: #define DELAY_TIME 1000 ;定義常數(shù)符號,即用DELAY_TIME 符號代替1000 #define KEY1 PORTB,7 ;用KEY1 符號代替端口PORTB 的第7 引腳 例3-05 用#define 偽指令定義符號后,可使程序中的變量或指令變得更具實(shí)際意義,也使程序 變得更易維護(hù)。指令“btfss PORTB,7”和“btfss KEY1”在事先用了例3-05 中的#define 后 編譯的結(jié)果是一樣的,但明顯地后者看起來更容易理解,一看就知道這是在測試編號為 KEY1 的一個(gè)按鍵。而且如果你的硬件設(shè)計(jì)改動了KEY1 所接的單片機(jī)引腳,只要改動這一 處#define 重新定義引腳位置,程序的其它部分無需任何修改,再編譯一次即可得到更新后 的軟件代碼。一個(gè)好的編程習(xí)慣是事先把一些代表實(shí)際意義的變量、單片機(jī)的輸入輸出引腳 在硬件電路中的實(shí)際功能等用#define 偽指令定義成簡單直觀的符號名字,然后在程序中直 接用其符號名字而不用簡單機(jī)械的數(shù)字形式。替換的工作由編譯器在編譯時(shí)自動完成。它會 先掃描你的源程序代碼,把事先#define 的符號名改回成被替換的字符串,然后再繼續(xù)編譯 生產(chǎn)機(jī)器碼。 􀁺 equ equ 顧名思義是“等于”的意思,其作用和#define 偽指令有點(diǎn)類似,也是用一個(gè)符號名 字替換其它數(shù)字變量,但它只能替換立即數(shù)。如果要替換一個(gè)符號名字,則此符號名必須事 先用#define 或equ 偽指令已經(jīng)定義替換了一個(gè)立即數(shù)。例如: #define MyCount 0x70 ;定義MyCount 符號替換立即數(shù)0x70 w_temp equ 0x20 ;符號名w_temp 等于0x20 count1 equ MyCount ;符號名count1 等同于MyCount ;如果MyCount 沒有事先定義則會產(chǎn)生一個(gè)錯(cuò)誤 例3-06 在絕對定位的編程模式中equ 被經(jīng)常用于定義用戶自己的變量,即用一個(gè)符號名代替一 個(gè)固定的存儲單元地址,上例3-06 中的w_temp 定義即屬于此類。用equ 方式定義的符號在 匯編后可以生成相關(guān)的調(diào)試信息,可以通過各種變量觀察的方式顯示此符號所代表的內(nèi)存地 址處的數(shù)據(jù)內(nèi)容,但用#define 方式定義的符號則不能產(chǎn)生調(diào)試信息。要注意equ 偽指令本 身并沒有限定所定義的一定是一個(gè)變量地址,它只是一個(gè)簡單的符號和數(shù)字替換而已,其意 義必須和具體的指令結(jié)合才能確定,如下例3-07 中對符號w_temp 的理解。 w_temp equ 0x20 ;符號名w_temp 等于0x20 MOVlw 0x55 ;W=0x55 MOVwf w_temp ;把W 的值送給變量w_temp, (0x20 單元內(nèi)容=0x55) MOVf w_temp, w ;把w_temp 單元內(nèi)容送W, (W=0x55) MOVwf FSR ;把W 的內(nèi)容送FSR, (FSR=0x55) MOVlw w_temp ;把w_temp 所代表的立即數(shù)即地址值送給W, (W=0x20) MOVwf FSR ;讓FSR 指針指向w_temp, (FSR=0x20 而不是0x55) 例3-07 􀁺 cblock / endc 用equ 偽指令可以給一個(gè)符號變量分配一個(gè)地址。但在一個(gè)程序設(shè)計(jì)過程中往往需要定 義很多變量,你當(dāng)然可以給每一個(gè)變量逐個(gè)用equ 的方法分配一個(gè)地址空間。但如果變量很 多,這樣做就顯得非常麻煩,你必須自己安排每個(gè)變量的地址,小心不能出現(xiàn)地址重疊;若 要在已定義分配好的變量間插入新的變量,那就必須重新逐個(gè)安排隨后變量的地址等等。 cblock/endc 偽指令可以輕松解決有很多變量定義的場合出現(xiàn)的這些問題,我們把它叫作變 量塊連續(xù)定義。具體用法如下: cblock 偽指令聲明變量塊的起始地址,endc 偽指令聲明變量塊定義結(jié)束,cblock/endc 中間可以插入任意多的變量聲明。其地址編排由編譯器自動計(jì)算:第一個(gè)變量地址分配從起 始地址開始,然后按所聲明變量保留的字節(jié)數(shù)自動分配后面變量的地址,變量所需保留的字 節(jié)數(shù)用“:”加后面的數(shù)字表示,如果只有一個(gè)字節(jié)“:1”可以省略不寫。以例3-08 來說明: cblock 0x20 ;變量定義起始地址為0x20 w_temp ;w_temp 地址為0x20,占一個(gè)字節(jié) status_temp ;status_temp 地址為0x21,占一個(gè)字節(jié) buffer:8 ;buffer 的起始地址為0x22,并保留8 個(gè)字節(jié)單元 var1 ;var1 的地址為0x2a,占一個(gè)字節(jié) var2 ;var2 的地址為0x2b,占一個(gè)字節(jié) endc ;結(jié)束變量連續(xù)定義 例3-08 用cblock 方式定義的變量和用equ 方式定義的變量一樣在匯編后可以生成相關(guān)的調(diào)試 信息,可以通過各種變量觀察的方式顯示此符號所代表的內(nèi)存地址和其中的數(shù)據(jù)內(nèi)容,所以 實(shí)際編程時(shí)一般無需關(guān)心計(jì)算每個(gè)變量的具體地址。程序員要注意的用這種方式連續(xù)定義很 多變量時(shí)不要讓變量塊跨越所處bank 的邊界。你可以在cblock 中隨意插入新定義的變量, 或通過改變起始地址的方式使變量塊整個(gè)挪到其它內(nèi)存地址處,地址的更新由編譯器代勞。 􀁺 org org 用以定義程序代碼的起始地址,通過此偽指令你可以把程序定位到任何可用的程序 空間,它實(shí)現(xiàn)的是程序代碼絕對定位,如例3-09: org 0x0000 ;定義復(fù)位入口地址,以下指令從地址0x0000 開始 goto main ; org 0x0004 ;定義中斷入口地址,以下指令從地址0x0004 開始 MOVwf w_temp ;保存w ;... ;其它中斷服務(wù)代碼 org 0x0800 ;定義page1 的起始地址,以下指令代碼放在page1 Sub1 return 例3-09 只要你認(rèn)為代碼需要確定放在某一特定地址處,在程序的任何地方都可以用org 偽指令 重新定義存放的起始地址,且地址順序可以任意編排。但要注意的是若干個(gè)確定起始地址的 代碼塊不能相互重疊,否則編譯器會報(bào)錯(cuò),無法得到正確結(jié)果。若用可重定位方式開發(fā)指令 代碼時(shí)一般不能用org 偽指令絕對定位代碼。 􀁺 dt dt 的作用是定義表格數(shù)據(jù)。在第一章介紹基本匯編指令時(shí)已經(jīng)提到,PIC 單片機(jī)實(shí)現(xiàn)表 格定義的最基本指令是“retlw xx”,表格中的每一個(gè)字節(jié)數(shù)據(jù)都以指令“retlw”的形式出現(xiàn)。 若表格較大,就需要很多“retlw”指令,比較麻煩,可讀性也差。這時(shí)我們可以用此“dt” 偽指令替代“retlw”實(shí)現(xiàn)很多數(shù)據(jù)的表格定義。如例3-10: Table addwf PCL,f ;PC 相對尋址查表 dt 0 ;retlw 0 dt 1, 2, ’3’ ;retlw 1 ;retlw 2 ;retlw 0x33 (’3’的ASCII 碼) dt ”ABC” ;retlw ’A’ ;retlw ’B’ ;retlw ’C’ 例3-10 􀁺 de de 偽指令可以讓你在源程序中定義片內(nèi)EEPROM 的初值。毫無疑問,該條偽指令只適 用于那些內(nèi)含EEPROM 數(shù)據(jù)存儲器的單片機(jī),例如:PIC16F87x、PIC16F62x 等等。在中檔 PIC 單片機(jī)中,除了PIC16F7x 系列外,其它Flash 型的單片機(jī)都有片上EEPROM,只是字 節(jié)數(shù)多少的問題。你可以編寫代碼在程序運(yùn)行時(shí)來設(shè)定片內(nèi)EEPROM 數(shù)據(jù)區(qū)的初值,但此 EEPROM 區(qū)還可以在芯片編程燒寫時(shí)通過編程器對其設(shè)定初值。對編程器而言EEPROM 數(shù) 據(jù)區(qū)是程序空間的延伸,它有個(gè)特別的編程起始地址0x2100。基于這一前提,我們可以在 源程序中利用“org”和“de”偽指令定義片內(nèi)EEPROM 數(shù)據(jù)的初值,這樣最后得到的HEX 文件被燒入到單片機(jī)內(nèi)后,EEPROM 區(qū)就同時(shí)被特定數(shù)據(jù)所初始化?蠢3-11 的實(shí)例 org 0x2100 ;特殊的程序空間起始地址 ;編程器能識別此地址作為EEPROM 數(shù)據(jù)區(qū)的起始地址 de 0, 1, 2, 3 ;EEPROM 地址單元[0]=0, [1]=1, [2]=2, [3]=3 de ”ABCD” ;[4]=0x41, [5]=0x42, [6]=0x43, [7]=0x44 例3-11 按例3-11 所示的定義,芯片完成編程燒入后,其內(nèi)部EEPROM 區(qū)從0x00 單元開始被 分別初始化成0x00、0x01、0x02、0x03、0x41、0x42、0x43、0x44。其它未被初始化的EEPROM 單元全部是0xff。 要注意并不是所有的編程工具都能支持此法定義的EEPROM 初始值燒入。能直接掛接 在MPLAB 環(huán)境下的Microchip 原廠或兼容的編程工具都可以支持“de”偽指令定義的 EEPROM 初值燒入,但其它第三方生產(chǎn)的編程工具就不一定,使用前請咨詢編程器的生產(chǎn) 廠商。 􀁺 fill fill 偽指令可以實(shí)現(xiàn)對程序空間連續(xù)自動填充某一特定的指令數(shù)據(jù),被填充的可以是一 個(gè)立即數(shù)(實(shí)際肯定代表某一條指令),也可以是一條形象的匯編指令;旧显谝粋(gè)設(shè)計(jì) 中都有一些程序空間沒有寫上具體的指令編碼(空白處),在單片機(jī)正常運(yùn)行時(shí)這些地方的 指令是不會被執(zhí)行到的。但在有干擾的情況下程序跑飛正好落在這些非法指令處時(shí),就有必 要設(shè)置軟件陷阱捕捉這些非法跳轉(zhuǎn),讓程序恢復(fù)正常運(yùn)行。如果要程序員一個(gè)一個(gè)地址去分 析哪里有空的指令單元然后又用特殊指令一條一條填入,這是根本行不通的。fill 偽指令在 這時(shí)就派上用場了。 fill 0x0000, 5 ;從當(dāng)前地址處連續(xù)5 個(gè)程序字填成0x0000(NOP 指令) fill (goto $), NEXT_BLOCK-$ ;從當(dāng)前地址開始到標(biāo)號NEXT_BLOCK 前所有程序空間填上 ;goto $ (死循環(huán))指令 org 0x0800 NEXT_BLOCK 例3-12 請大家特別注意上例3-12 中第二行fill 偽指令的用法。在你自己的程序中也可以用同樣 的方法把所有未用到的程序空間填上“goto $”這樣一條死循環(huán)的指令。一旦單片機(jī)執(zhí)行過 程中非法跳到這些指令處時(shí)指令運(yùn)行就將被“俘獲”,停在那里直到看門狗復(fù)位,然后程序 從頭開始。這是軟件陷阱的最基本處理方法。若填充指令“goto 0x0000”直接跳轉(zhuǎn)到復(fù)位地 址處可能會有問題,因?yàn)間oto 指令執(zhí)行時(shí)必須和PCLATH 寄存器配合(跨頁跳轉(zhuǎn)的問題), 若PCLATH[4:3]不為00 就不能跳到復(fù)位地址0x0000 處。在程序跑飛非法跳轉(zhuǎn)到設(shè)定的陷阱 處時(shí)你又怎能保證PCLATH 中的頁面設(shè)定為正好指向第0 頁? 􀁺 end end 偽指令告訴匯編編譯器編譯工作到此為止,end 后面所有的信息,不管正確與否, 一概不管。絕大多數(shù)情形下你的程序的最后一行應(yīng)該是“end”。無論如何,end 必須出現(xiàn)在 程序中,不然編譯器會報(bào)錯(cuò),無法進(jìn)行編譯工作。 3.2.4 MPASM 內(nèi)的直接運(yùn)算符 為了使所編的程序理解更直觀,維護(hù)更方便,MPASM 匯編器允許你在程序的編寫過程 中直接以數(shù)學(xué)表達(dá)式的形式在指令中實(shí)現(xiàn)一些數(shù)字運(yùn)算的功能。千萬不要誤解成MPASM 可 以替你生成數(shù)學(xué)運(yùn)算的指令,那可是其它編譯器(例如C 編譯器)才能完成的工作。這里 講的數(shù)字運(yùn)算前提是所有參與運(yùn)算的操作數(shù)全部是明明白白的立即數(shù),如果是符號名字則必 須事先用#define 或equ 偽指令明確定義了的。整個(gè)運(yùn)算過程是由編譯器在掃描你的源程序 時(shí)進(jìn)行的,運(yùn)算結(jié)果也只能是一個(gè)確定的立即數(shù)。我們將在這里介紹幾種非常有用的運(yùn)算符。 􀁺 取當(dāng)前指令的地址值:$ 你可以在寫程序時(shí)給一條指令前加上一個(gè)標(biāo)號,然后直接引用該標(biāo)而得到此程序字的地 址。如果你的程序經(jīng)常需要用到指令的當(dāng)前地址或附近的地址值,這樣的標(biāo)號就需要寫很多 且不能重復(fù)。用“$”運(yùn)算符讓匯編器替你計(jì)算當(dāng)前指令所處的位置將有效地減輕你的這份 工作量。見例3-12 和3-13。 ;用語句標(biāo)號得到指令地址 Here goto Here ;跳轉(zhuǎn)到當(dāng)前地址,程序進(jìn)入死循環(huán) Delay decfsz count, f ;計(jì)數(shù)器減1 并判0 goto Delay ;跳轉(zhuǎn)到上一行重復(fù)循環(huán) ;用$運(yùn)算符得到指令地址而無需定義任何語句標(biāo)號 goto $ ;跳轉(zhuǎn)到當(dāng)前地址,程序進(jìn)入死循環(huán) decfsz count, f ;計(jì)數(shù)器減1 并判0 goto $-1 ;跳轉(zhuǎn)到(當(dāng)前地址-1)處,即上一行,重復(fù)循環(huán) 例3-13 􀁺 取16 位立即數(shù)的高低字節(jié):high 和low 一個(gè)16 位的立即數(shù)在8 位單片機(jī)中必須被拆解成高8 位一個(gè)字節(jié)(高字節(jié))和低8 位 一個(gè)字節(jié)(低字節(jié))才能用指令一條條處理,類似的處理在對兩字節(jié)變量賦立即數(shù)初值和基 于PC 相對跳轉(zhuǎn)查表前設(shè)定PCLATH 寄存器時(shí)經(jīng)常碰到。MPASM 提供了high 和low 兩個(gè)運(yùn) 算符分別計(jì)算一個(gè)立即數(shù)的高字節(jié)和低字節(jié)。我們看例3-14 的代碼實(shí)例: ;兩字節(jié)變量賦立即數(shù)初值 #define DELAY_TIME .1000 ;定義一個(gè)常數(shù)立即數(shù) MOVlw low(DELAY_TIME) ;取立即數(shù)的低字節(jié)值,經(jīng)編譯器計(jì)算將得到0xe8 MOVwf count ;賦給變量的低字節(jié) MOVlw high(DELAY_TIME) ;取立即數(shù)的高字節(jié)值,經(jīng)編譯器計(jì)算將得到0x03 MOVwf count+1 ;賦給變量的高字節(jié) ;查表前設(shè)定PCLATH 寄存器。關(guān)于PC 相對跳轉(zhuǎn)的概念詳見1.5.2 節(jié) MOVlw high(Table) ;取查找表入口地址的高字節(jié)值 MOVwf PCLATH ;設(shè)定PCLATH 寄存器 MOVf index,w ;取查表索引值 call Table ;調(diào)用查表子程序 例3-14 􀁺 加減乘除:+ - * / 實(shí)際上前面的很多代碼范例中都已經(jīng)說明了“+”、“-”運(yùn)算符的使用方法!*”和“/” 的運(yùn)算也類似。看下面例3-15 計(jì)算異步串行通訊波特率常數(shù)的方法。 ;高速異步通信波特率 BPS=Fosc/(16*(X+1)) ;故,波特率常數(shù)X = Fosc/(BPS*16) – 1 #define BPS .9600 ;定義工作波特率 #define Fosc .4000000 ;定義單片機(jī)工作振蕩頻率 4MHz ;... ;其它代碼 MOVlw Fosc/(BPS*.16) – 1 ;編譯器計(jì)算得到.25(10 進(jìn)制25) MOVwf SPBRG ;設(shè)定波特率定時(shí)寄存器 例3-15 程序中用了統(tǒng)一的計(jì)算公式后,在調(diào)試時(shí)只要簡單地改變前面的#define 語句定義新的 波特率或振蕩頻率值,然后重新編譯一次程序即實(shí)現(xiàn)了波特率設(shè)定代碼的更新,非常方便。 􀁺 移位運(yùn)算:>> 和 << “>>”運(yùn)算符把一個(gè)立即數(shù)算術(shù)右移若干位(高位補(bǔ)0),“<<”運(yùn)算符把一個(gè)立即數(shù)算 術(shù)左移若干位(低位補(bǔ)0)。 #define xxx 0x55 MOVlw xxx>>1 ;W=0x2a MOVlw xxx<<2 ;W=0x54 MOVlw 1<<7 ;W=0x80 例3-16 􀁺 立即數(shù)邏輯運(yùn)算: & | ^ “&”運(yùn)算符把一個(gè)立即數(shù)和另外一個(gè)立即數(shù)相“與”;“|”運(yùn)算符把一個(gè)立即數(shù)和另外 一個(gè)立即數(shù)相“或”;“^”運(yùn)算符把一個(gè)立即數(shù)和另外一個(gè)立即數(shù)相“異或”。例3-17 的代 碼利用異或運(yùn)算符“^”實(shí)現(xiàn)類似于C 語言“switch-case”功能的匯編代碼指令,注意例中 的VAL1、VAL2、VAL3 等判別值都是事先已經(jīng)定義的立即數(shù)而不是RAM 中的變量。 ;利用異或運(yùn)算實(shí)現(xiàn)類似于C 語言的switch-case 語句 MOVf switchVal, w ;取分支判斷值. switch (W) xorlw VAL1 ;W=W ^ VAL1 btfsc STATUS, Z ;判0 標(biāo)志 goto Case_VAL1 ;case VAL1: (原始W=VAL1) xorlw VAL1^VAL2 ;W=(W^VAL1)^(VAL1^VAL2) = W^VAL2 btfsc STATUS, Z ;判0 標(biāo)志 goto Case_VAL2 ;case VAL2: (原始W=VAL2) xorlw VAL2^VAL3 ;W=(W^VAL2)^(VAL2^VAL3) = W^VAL3 btfsc STATUS, Z ;判0 標(biāo)志 goto Case_VAL3 ;case VAL3: (原始W=VAL3) ;... ;其它c(diǎn)ase 情況判別 例3-17 3.2.5 MPASM 的宏指令 引入宏指令的目的也是為了增強(qiáng)程序的可讀性和易維護(hù)性。和偽指令不同的是,偽指令 所起的只是輔助性的作用,其本身不會直接產(chǎn)生真正的機(jī)器碼;但宏指令是真正的指令,它 實(shí)際上是若干條基本匯編指令的集合。為了編程方便,MPASM 已經(jīng)內(nèi)含了一些非常好用的 宏指令,用戶也可以自己編寫任意形式的宏指令。 3.2.5.1 MPASM 內(nèi)含的宏指令 MPASM 內(nèi)含的宏指令就象擴(kuò)充了的標(biāo)準(zhǔn)匯編指令一樣,其名字已作為MPLAB 的關(guān)鍵 詞而被保留。雖然經(jīng)過編譯器編譯后最終將變成真正的匯編指令機(jī)器碼,但某些宏指令的轉(zhuǎn) 換過程還是有其獨(dú)到之處。 􀁺 banksel banksel 和下面的pagesel 宏指令可以說是所有宏指令中最好用最有用的了。banksel 可 以幫助你非常方便地實(shí)現(xiàn)寄存器bank 的設(shè)定。你只需在banksel 后給它一個(gè)變量名或地址, 編譯器會自動按照變量地址所在的bank,自動生成設(shè)定STATUS 寄存器RP1:RP0 位的指令。 更聰明的是,編譯器知道你所選的芯片最多有幾個(gè)bank,它將用最少的指令完成bank 設(shè)定。 例如: ;芯片選擇PIC16F874A,RAM 共有2 個(gè)bank banksel TRISC ;設(shè)定TRISC 所在的bank (TRISC 在bank1) ;編譯后的機(jī)器碼 bsf STATUS, RP0 ;只生成1 條匯編代碼 ;芯片選擇PIC16F877A,RAM 共有4 個(gè)bank banksel TRISC ;設(shè)定TRISC 所在的bank (TRISC 在bank1) ;編譯后的機(jī)器碼 bsf STATUS, RP0 ;生成2 條匯編代碼 bcf STATUS, RP1 ; 例3-18 同樣的一條“banksel TRISC”指令,針對不同的芯片編譯器生成的匯編代碼可能不同。 兩個(gè)bank 的芯片只要用到RP0 一位即可實(shí)現(xiàn)bank 選擇,banksel 宏指令會轉(zhuǎn)換成一條匯編 指令;四個(gè)bank 的芯片則必須用RP1:RP0 兩位一起實(shí)現(xiàn)bank 選擇,故一條banksel 宏指令 將轉(zhuǎn)換成兩條匯編指令。用banksel 的好處是顯而易見的,你無需太多關(guān)心你準(zhǔn)備操作的寄 存器落在哪個(gè)bank 內(nèi),編譯器會知道這個(gè)寄存器的實(shí)際地址,然后替你生成相關(guān)的匯編代 碼以正確設(shè)定bank 位;需要時(shí)你可以隨意移動變量的定義地址而無需修改其它代碼,只需 重新編譯一次即可;另外,如果你用代碼可重定位方式進(jìn)行軟件開發(fā)時(shí),在寫指令之時(shí)根本 就無法知道自己定義的變量最后會落在哪個(gè)bank 中,想自己設(shè)定具體的bank 都不行。此時(shí), 只有用banksel 宏指令讓編譯器連接器一起在連接定位后再“自動填入”相關(guān)的bank 位設(shè)定 指令。 􀁺 bankisel 和banksel 類似,不過它對付的是用于寄存器相對尋址的STATUS 寄存器中的IRP 位。 它也會用最少的代碼實(shí)現(xiàn)IRP 位的設(shè)定。如果是只有兩個(gè)bank 的芯片,用bankisel 將不產(chǎn) 生任何指令!在代碼可重定位開發(fā)方式下,對可重定位的變量作相對尋址需要設(shè)定IRP 位 時(shí),也只能用bankisel 交由編譯器連接器來替你實(shí)現(xiàn)。 ;芯片選擇PIC16F877A,RAM 共有4 個(gè)bank cblock 0x120 buffer:8 ;從地址0x120 起定義8 字節(jié)的數(shù)據(jù)區(qū) endc bankisel buffer ;用bankisel 自動設(shè)定IRP 位 MOVlw low(buffer) ;取buffer 的地址(只有低8 位) MOVwf FSR ;送給FSR ;編譯后的機(jī)器碼 bsf STATUS, 7 ;真正的設(shè)定IRP 的匯編代碼 MOVlw 0x20 MOVwf FSR 例3-19 􀁺 pagesel pagesel 可以幫助你設(shè)定程序的頁面。使用方式和banksel 相似,只是它改變的是 PCLATH[4:3]兩位。該宏指令也同樣將用最少的代碼實(shí)現(xiàn)程序頁面設(shè)定:程序空間不超過2K 字(只有1 頁)的將不產(chǎn)生任何匯編代碼;程序空間不超過4K 字(最多2 頁)的芯片將只 生成一條設(shè)定PCLATH[3]的匯編代碼;只有超過4K 字(最多4 頁)的芯片才會生成兩條代 碼。同樣,pagesel 在代碼可重定位的開發(fā)模式下也是不可或缺的。 ;芯片選擇PIC16F877A,RAM 共有4 個(gè)頁面 org 0x0100 ;在第0 頁內(nèi) main pagesel sub1 ;用宏指令設(shè)定被調(diào)用子程序的頁面 call sub1 ;隨后調(diào)用該子程序 pagesel $ ;用宏指令設(shè)定當(dāng)前地址的頁面 goto main ;循環(huán) org 0x0800 ;第1 頁起始 sub1 return ;子程序返回 ;編譯后的機(jī)器碼 (main 部分) main bsf PCLATH, 3 ;設(shè)定sub1 所在的頁面 bcf PCLATH, 4 call sub1 bcf PCLATH, 3 ;設(shè)定當(dāng)前指令所在的頁面 bcf PCLATH, 4 goto main 例3-20 􀁺 clrc/setc clrc/setc 針對的是狀態(tài)寄存器STATUS 中的進(jìn)位標(biāo)志位。 clrc 等同于 bcf STATUS, C ;C=0 setc 等同于 bsf STATUS, C ;C=1 􀁺 clrz/setz clrz/setz 針對的是狀態(tài)寄存器STATUS 中的零標(biāo)志位。 clrz 等同于 bcf STATUS, Z ;Z=0 setz 等同于 bsf STATUS, Z ;Z=1 􀁺 clrdc/setdc clrdc/setdc 針對的是狀態(tài)寄存器STATUS 中的半字節(jié)進(jìn)位標(biāo)志位。 clrdc 等同于 bcf STATUS, DC ;DC=0 setdc 等同于 bsf STATUS, DC ;DC=1 􀁺 skpc/skpnc skpc/skpnc 是判狀態(tài)寄存器STATUS 中的進(jìn)位標(biāo)志位,若條件滿足則程序跳過下一條指 令。 skpc 等同于 btfss STATUS, C ;若C=1 則程序跳過下一條指令 skpnc 等同于 btfsc STATUS, C ;若C=0 則程序跳過下一條指令 􀁺 skpz/skpnz skpz/skpnz 是判狀態(tài)寄存器STATUS 中的零標(biāo)志位,若條件滿足則程序跳過下一條指令。 skpz 等同于 btfss STATUS, Z ;若Z=1 則程序跳過下一條指令 skpnz 等同于 btfsc STATUS, Z ;若Z=0 則程序跳過下一條指令 􀁺 skpdc/skpndc skpdc/skpndc 是判狀態(tài)寄存器STATUS 中的半字節(jié)進(jìn)位標(biāo)志位,若條件滿足則程序跳過 下一條指令。 skpdc 等同于 btfss STATUS, DC ;若DC=1 則程序跳過下一條指令 skpndc 等同于 btfsc STATUS, DC ;若DC=0 則程序跳過下一條指令 􀁺 bc/bnc bc/bnc 宏指令的作用有點(diǎn)象51 單片機(jī)的“jc/jnc”指令。它判別狀態(tài)寄存器STATUS 中 的進(jìn)位標(biāo)志位,按進(jìn)位標(biāo)志實(shí)現(xiàn)程序的分支跳轉(zhuǎn)。如例3-21。 MOVlw 0x31 ;W=0x31 addwf sum,f ;sum = sum+W bc Carry1 ;如果發(fā)生進(jìn)位就跳轉(zhuǎn)到Carry1 處執(zhí)行 nop ;如果沒有進(jìn)位則繼續(xù)執(zhí)行bc 的下一條指令 ;... Carry1 nop bc XXX ;如果C=1 就跳轉(zhuǎn)到標(biāo)號XXX,否則程序執(zhí)行bc 的下一條 等同于 btfsc STATUS,C goto XXX bnc YYY ;如果C=0 就跳轉(zhuǎn)到標(biāo)號YYY,否則程序執(zhí)行bnc 的下一條 等同于 btfss STATUS,C goto YYY 例3-21 請不要被bc/bnc 這樣“一條”指令所迷惑,它實(shí)際上是由兩條匯編指令組成,且用到 了“goto”實(shí)現(xiàn)跳轉(zhuǎn),故在用此宏指令前注意頁面的設(shè)定。 􀁺 bz/bnz 同bc/bnc 一樣,只不過判別的是狀態(tài)寄存器STATUS 中的零標(biāo)志位。 MOVlw 0x55 ;W=0x55 xorwf flag,w ;flag = 0x55 ? bz Match ;Z=1, flag=0x55, 跳轉(zhuǎn)到Match 處執(zhí)行 nop ;Z=0,繼續(xù)執(zhí)行bz 的下一條指令 ;... Match nop bz XXX ;如果Z=1 就跳轉(zhuǎn)到標(biāo)號XXX,否則程序執(zhí)行bz 的下一條 等同于 btfsc STATUS,Z goto XXX bnz YYY ;如果Z=0 就跳轉(zhuǎn)到標(biāo)號YYY,否則程序執(zhí)行bnz 的下一條 等同于 btfss STATUS,Z goto YYY 例3-22 􀁺 bdc/bndc 同上,判別的是狀態(tài)寄存器STATUS 中的半字節(jié)進(jìn)位標(biāo)志位。 bdc XXX ;如果DC=1 就跳轉(zhuǎn)到標(biāo)號XXX,否則程序執(zhí)行bdc 的下一條 等同于 btfsc STATUS,DC goto XXX bndc YYY ;如果DC=0 就跳轉(zhuǎn)到標(biāo)號YYY,否則程序執(zhí)行bndc 的下一條 等同于 btfss STATUS,DC goto YYY 例3-23 3.2.5.2 用戶自定義宏 除了MPASM 內(nèi)帶的宏指令外,按實(shí)際開發(fā)的需要和個(gè)人的習(xí)慣,程序員可以自己定義 任意形式的宏指令。大量使用定義合理的宏指令可以使程序的可讀性大大提高,也更容易移 植。 自己定義宏指令時(shí)須遵循一些語法規(guī)則。宏指令的定義由“宏指令名“開始,后跟關(guān)鍵 詞“macro”,其后可以帶若干宏參數(shù),也可以不跟任何宏參數(shù);然后從下一行起開始寫基本 的匯編指令或已被認(rèn)可的其它宏指令(宏嵌套);指令可以是任意多行,最后以關(guān)鍵詞“endm” 結(jié)束整個(gè)宏定義,例如: ;定義宏指令實(shí)現(xiàn)一個(gè)字(兩字節(jié)數(shù))加1 的功能 IncWord macro wordVal ;IncWord 是宏指令名, wordVal 是宏參數(shù) ;下面為宏的實(shí)體(實(shí)際的匯編指令) incf wordVal,f ;對字的低字節(jié)加1,如果結(jié)果為0 則為字節(jié)計(jì)數(shù)溢出 skpnz ;如果沒有溢出(上面指令結(jié)果不為0)就跳過下一條指令 incf wordVal+1,f ;若低字節(jié)加1 后溢出,則字的高字節(jié)加1 endm ;結(jié)束宏指令定義 ;程序中對宏指令的引用 cblock 0x20 counter:2 ;定義一個(gè)兩字節(jié)的字變量 endc Loop IncWord counter ;用宏指令實(shí)現(xiàn)變量counter 每次循環(huán)加1 ;編譯器會把這“一條”宏指令展開成原定義的3 條匯編指令 ;并用實(shí)際的counter 符號替換宏參數(shù)wordVal goto Loop ;跳轉(zhuǎn)重復(fù)循環(huán)。注意一條宏指令展開后可能是多條匯編指令 ;故此處用“$-?”時(shí)要特別小心。 例3-24 使用宏指令時(shí)幾個(gè)問題需要注意。 ㈠ 宏指令不同于子程序調(diào) 用指令。編譯器在編譯你的代碼 時(shí)會用原宏定義中的若干條匯 編指令代替程序中的“一條”宏 指令插入到此宏指令位置處(圖 3-15)。若程序中有很多地方用 了同樣的宏指令,那么相同的匯 編指令集也會被復(fù)制成同樣多 份,它不能節(jié)省代碼長度。而子 程序調(diào)用只有一條指令,若一個(gè) 子程序在程序中被多處調(diào)用,增加的只是調(diào)用指令“call”而子程序只有一個(gè),它可以減少 代碼長度。宏指令最有用的是集成少量且非常相關(guān)的代碼實(shí)現(xiàn)一個(gè)特定任務(wù),例如3-24 中 的字變量加1 這樣的功能。你可以安自己的習(xí)慣和項(xiàng)目的需要設(shè)計(jì)這樣的宏指令,甚至可以 建一個(gè)宏指令庫頭文件,以后程序開發(fā)時(shí)直接用#i nclude 包含進(jìn)你的程序即可使用。 ㈡ 雖然宏指令定義中允許使用語句標(biāo)號以便給“goto”指令引用,但最好不要這樣做。 因?yàn)槿舸撕曛噶顚⒈欢嗵幨褂玫脑,相同的宏定義會被重復(fù)復(fù)制,其中的語句標(biāo)號也會一樣 復(fù)制,這就使得程序有“標(biāo)號重復(fù)定義”的語法錯(cuò)誤而無法成功編譯。所以在宏指令中需要 用goto 指令跳轉(zhuǎn)時(shí)盡量使用“$”配合“+”、“-”運(yùn)算,例如:“goto $-3”、“goto $+2”等 等;或者使用宏參數(shù)給goto 指令一個(gè)特定的語句標(biāo)號。例如3-25 的宏定義: ;定義宏指令實(shí)現(xiàn)寄存器和立即數(shù)比較大小 ;若寄存器值 >=立即數(shù) 則程序跳到某一位置 FL_JGE macro fileReg, litVal, jumpTo ;fileReg 為寄存器, litVal 為立即數(shù), jumpTo 為跳轉(zhuǎn)的語句標(biāo)號 MOVlw litVal & 0xff ;把立即數(shù)送給W (確保0x00~0xff) subwf fileReg, w ;計(jì)算 寄存器-W skpnc ;若C=0 即, 寄存器值<W,程序跳過下一行繼續(xù)運(yùn)行 goto jumpTo ;若 寄存器值>=W,則跳到指定標(biāo)號處繼續(xù)運(yùn)行 源程序編譯后的lst 列表文件(局部) 圖3-15 endm ;結(jié)束宏指令定義 ;程序中對宏指令的引用 val1 equ 0x20 ;定義一個(gè)寄存器變量 ;使用宏指令 FL_JGE val1, .100, Val1_Over ;變量val1 和立即數(shù)100 比較, ;如果val1>=100 則程序跳到Val1_Over 處運(yùn)行 nop ;若val1<.100,則程序執(zhí)行這條語句 ;... ;其它代碼 Val1_Over nop ;若val1>=.100,則程序?qū)⑻竭@里繼續(xù)運(yùn)行 例3-25 ㈢ 程序仿真調(diào)試時(shí)對用戶自定義的宏指令和MPASM 自己提供的內(nèi)部宏指令支持度不 盡相同。在用戶自定義的宏指令處無法設(shè)置斷點(diǎn),但MPASM 自己提供的內(nèi)部宏指令沒有此 限制;在程序單步運(yùn)行時(shí)用戶自定義的宏指令無法“一步”執(zhí)行完畢,調(diào)試器會單步跟蹤進(jìn) 入宏定義體一步一步執(zhí)行,MPASM 內(nèi)部宏指令可以“一步”執(zhí)行完畢。 |
|
|
發(fā)表評論 告訴好友 打印此文 收藏此頁 關(guān)閉窗口 返回頂部 |
|
|
 |
 |
網(wǎng)友評論:(只顯示最新5條。) |
|
|
|
|
|