找回密碼
 立即注冊(cè)

QQ登錄

只需一步,快速開(kāi)始

搜索
查看: 8881|回復(fù): 3
收起左側(cè)

PIC單片機(jī)常用偽指令

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

使用道具 舉報(bào)

ID:91152 發(fā)表于 2020-2-24 19:58 | 顯示全部樓層
講解的挺細(xì),例子和圖在哪?
回復(fù)

使用道具 舉報(bào)

ID:91152 發(fā)表于 2020-2-24 19:58 | 顯示全部樓層
講的挺細(xì),挺好,但是例子和圖在哪?
回復(fù)

使用道具 舉報(bào)

ID:232462 發(fā)表于 2024-5-8 14:47 | 顯示全部樓層
fill (goto $), NEXT_BLOCK-$
這句編譯出錯(cuò) 1710053115378.jpg

請(qǐng)問(wèn)高手,如何解決?謝謝!
回復(fù)

使用道具 舉報(bào)

本版積分規(guī)則

手機(jī)版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表