equ
equ 顾名思义是“等于”的意思,其作用和#define伪指令有点类似,也是用一个符号名字替换其它数字变量,但它只能替换立即数。如果要替换一个符号名字,则此符号名必须事先用#define或 equ 伪指令已经定义替换了一个立即数。例如:
#define MyCount 0x70 ;定义 MyCount 符号替换立即数 0x70
w_temp equ 0x20 ;符号名w_temp等于0x20
count1 equ MyCount ;符号名count1等同于MyCount
;如果 MyCount 没有事先定义则会产生一个错误
例 3-06
在绝对定位的编程模式中 equ 被经常用于定义用户自己的变量,即用一个符号名代替一个固定的存储单元地址,上例 3-06 中的 w_temp 定义即属于此类。用 equ 方式定义的符号在汇编后可以生成相关的调试信息,可以通过各种变量观察的方式显示此符号所代表的内存地址处的数据内容,但用#define 方式定义的符号则不能产生调试信息。要注意 equ 伪指令本身并没有限定所定义的一定是一个变量地址,它只是一个简单的符号和数字替换而已,其意义必须和具体的指令结合才能确定,如下例 3-07 中对符号 w_temp 的理解。
w_temp equ 0x20 ;符号名w_temp等于0x20
movlw 0x55 ;W=0x55
movwf w_temp ;把 W的值送给变量w_temp, (0x20单元内容=0x55)
movf w_temp, w ;把w_temp 单元内容送W, (W=0x55)
movwf FSR ;把 W的内容送 FSR, (FSR=0x55)
movlw w_temp ;把w_temp 所代表的立即数即地址值送给W, (W=0x20)
movwf FSR ;让FSR 指针指向w_temp, (FSR=0x20而不是0x55)
例 3-07
cblock / endc
用 equ 伪指令可以给一个符号变量分配一个地址。但在一个程序设计过程中往往需要定义很多变量,你当然可以给每一个变量逐个用 equ 的方法分配一个地址空间。但如果变量很多,这样做就显得非常麻烦,你必须自己安排每个变量的地址,小心不能出现地址重叠;若要在已定义分配好的变量间插入新的变量,那就必须重新逐个安排随后变量的地址等等。cblock/endc 伪指令可以轻松解决有很多变量定义的场合出现的这些问题,我们把它叫作变量块连续定义。具体用法如下:
cblock 伪指令声明变量块的起始地址,endc 伪指令声明变量块定义结束,cblock/endc中间可以插入任意多的变量声明。其地址编排由编译器自动计算:第一个变量地址分配从起始地址开始,然后按所声明变量保留的字节数自动分配后面变量的地址,变量所需保留的字
节数用“:”加后面的数字表示,如果只有一个字节“:1”可以省略不写。以例 3-08来说明:
cblock 0x20 ;变量定义起始地址为0x20
w_temp ;w_temp地址为0x20,占一个字节
status_temp ;status_temp地址为 0x21,占一个字节
buffer:8 ;buffer的起始地址为 0x22,并保留 8个字节单元
var1 ;var1的地址为 0x2a,占一个字节
var2 ;var2的地址为 0x2b,占一个字节
endc ;结束变量连续定义
例 3-08
用 cblock 方式定义的变量和用 equ 方式定义的变量一样在汇编后可以生成相关的调试信息,可以通过各种变量观察的方式显示此符号所代表的内存地址和其中的数据内容,所以实际编程时一般无需关心计算每个变量的具体地址。程序员要注意的用这种方式连续定义很多变量时不要让变量块跨越所处 bank 的边界。你可以在 cblock 中随意插入新定义的变量,或通过改变起始地址的方式使变量块整个挪到其它内存地址处,地址的更新由编译器代劳。
org
org 用以定义程序代码的起始地址,通过此伪指令你可以把程序定位到任何可用的程序空间,它实现的是程序代码绝对定位,如例 3-09:
org 0x0000 ;定义复位入口地址,以下指令从地址0x0000 开始
goto main ;
org 0x0004 ;定义中断入口地址,以下指令从地址0x0004 开始
movwf w_temp ;保存 w
;... ;其它中断服务代码
org 0x0800 ;定义 page1 的起始地址,以下指令代码放在 page1
Sub1 return
例 3-09
只要你认为代码需要确定放在某一特定地址处,在程序的任何地方都可以用 org 伪指令重新定义存放的起始地址,且地址顺序可以任意编排。但要注意的是若干个确定起始地址的代码块不能相互重叠,否则编译器会报错,无法得到正确结果。若用可重定位方式开发指令代码时一般不能用 org 伪指令绝对定位代码。
dt
dt 的作用是定义表格数据。在第一章介绍基本汇编指令时已经提到,PIC 单片机实现表格定义的最基本指令是“retlw xx”,表格中的每一个字节数据都以指令“retlw”的形式出现。若表格较大,就需要很多“retlw”指令,比较麻烦,可读性也差。这时我们可以用此“dt”伪指令替代“retlw”实现很多数据的表格定义。如例 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 伪指令可以让你在源程序中定义片内 EEPROM 的初值。毫无疑问,该条伪指令只适用于那些内含 EEPROM数据存储器的单片机,例如:PIC16F87x、PIC16F62x 等等。在中档PIC 单片机中,除了 PIC16F7x 系列外,其它 Flash 型的单片机都有片上 EEPROM,只是字节数多少的问题。你可以编写代码在程序运行时来设定片内 EEPROM数据区的初值,但此EEPROM 区还可以在芯片编程烧写时通过编程器对其设定初值。对编程器而言 EEPROM 数据区是程序空间的延伸,它有个特别的编程起始地址 0x2100。基于这一前提,我们可以在源程序中利用“org”和“de”伪指令定义片内 EEPROM 数据的初值,这样最后得到的 HEX文件被烧入到单片机内后,EEPROM 区就同时被特定数据所初始化。看例 3-11 的实例
org 0x2100 ;特殊的程序空间起始地址
;编程器能识别此地址作为EEPROM数据区的起始地址
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 所示的定义,芯片完成编程烧入后,其内部 EEPROM 区从 0x00 单元开始被分别初始化成 0x00、0x01、0x02、0x03、0x41、0x42、0x43、0x44。其它未被初始化的 EEPROM单元全部是 0xff。
要注意并不是所有的编程工具都能支持此法定义的 EEPROM 初始值烧入。能直接挂接在 MPLAB 环境下的 Microchip 原厂或兼容的编程工具都可以支持“de”伪指令定义的EEPROM 初值烧入,但其它第三方生产的编程工具就不一定,使用前请咨询编程器的生产厂商。
fill
fill 伪指令可以实现对程序空间连续自动填充某一特定的指令数据,被填充的可以是一个立即数(实际肯定代表某一条指令),也可以是一条形象的汇编指令。基本上在一个设计中都有一些程序空间没有写上具体的指令编码(空白处),在单片机正常运行时这些地方的指令是不会被执行到的。但在有干扰的情况下程序跑飞正好落在这些非法指令处时,就有必要设置软件陷阱捕捉这些非法跳转,让程序恢复正常运行。如果要程序员一个一个地址去分析哪里有空的指令单元然后又用特殊指令一条一条填入,这是根本行不通的。fill 伪指令在这时就派上用场了。
fill 0x0000, 5 ;从当前地址处连续5 个程序字填成0x0000(NOP指令)
fill (goto $), NEXT_BLOCK-$ ;从当前地址开始到标号 NEXT_BLOCK前所有程序空间填上
;goto $ (死循环)指令
org 0x0800
NEXT_BLOCK
例 3-12
请大家特别注意上例 3-12 中第二行 fill 伪指令的用法。在你自己的程序中也可以用同样的方法把所有未用到的程序空间填上“goto $”这样一条死循环的指令。一旦单片机执行过程中非法跳到这些指令处时指令运行就将被“俘获”,停在那里直到看门狗复位,然后程序从头开始。这是软件陷阱的最基本处理方法。若填充指令“goto 0x0000”直接跳转到复位地址处可能会有问题,因为 goto 指令执行时必须和 PCLATH寄存器配合(跨页跳转的问题),若 PCLATH[4:3]不为00就不能跳到复位地址 0x0000 处。在程序跑飞非法跳转到设定的陷阱处时你又怎能保证 PCLATH中的页面设定为正好指向第 0 页?
end
end 伪指令告诉汇编编译器编译工作到此为止,end 后面所有的信息,不管正确与否,一概不管。绝大多数情形下你的程序的最后一行应该是“end”。无论如何,end 必须出现在程序中,不然编译器会报错,无法进行编译工作。