3.6.1 08汇编源程序格式
把汇编语言写成的源程序“翻译”成机器语言的工具叫汇编程序或编译器(Assembler),以下统一称作编译器。汇编语言源程序可以用通用的文本编辑软件书写编辑,以ASCII码形式存盘。具体的编译器对汇编语言源程序的格式有一定的要求,同时,编译器除了识别单片机的指令系统外,为了能够正确地产生目标代码以及方便汇编语言的编写,编译器还提供了一些在汇编时使用的命令、操作符号,在编写汇编程序时,也必须正确使用它们。由于编译器提供的指令仅是为了更好地做好“翻译”工作,并不产生具体的机器指令,因此这些指令被称为伪指令(Pseudo Instruction)。如,伪指令告诉编译器:从哪里开始编译,到何处结束,汇编后的程序如何放置等相关信息。当然,这些相关信息必须包含在汇编源程序中,否则编译器就难以编译好源程序,难以生成正确的目标代码。
汇编语言源程序以行为单位进行设计,每一行最多可以包含以下四个部分:
标号 操作码 操作数 注释
1.标号(Labels)
对于标号有下列要求及说明:
①如果一个语句有标号,则标号必须从第一列开始书写。
②可以组成标号的字符有:字母A~Z、字母a~z、数字0~9、下划线“_”、美元符号“$”,但开头的第一个符号不能为数字和$。
③08编译器区分标号中字母的大小写,但指令和伪指令不区分大小写。
④标号长度基本上不受限制,但实际使用时通常不要超过20个字符。若希望更多的编译器能够识别,建议标号(或变量名)的长度小于8个字符。
⑤标号后必须带冒号“:”或双冒号“::”,一个冒号表示局部符号,两个冒号表示全局符号。模块外调用的标号需要用全局标号,模块内跳转的标号用局部标号。
⑥一个标号在一个程序中只能定义一次,否则是重复定义,不能通过编译。
⑦一行语句可以只有标号,编译器将把当前程序计数器的值赋给该标号。
2.操作码(Opcodes)
操作码包括指令码以及后面即将介绍的08编译器可以识别的伪指令码。对于有标号的行,必须用至少一个空格或制表符(TAB)将标号与操作码隔开。对于没有标号的行,不能从第一列开始写指令码,应以空格或制表符(TAB)开头。08编译器不区分操作码中字母的大小写。
3.操作数(Operands)
操作数可以是地址、标号或指令码定义的常数,也可以是由伪运算符构成的表达式。若一条指令或伪指令有操作数,则操作数与操作码之间必须用空格隔开书写。操作数多于一个的,操作数之间用逗号“,”分隔。
(1) 常数标识
08编译器识别的常数有十进制(默认,不需要前缀标识)、十六进制(用$或0x前缀标识)、二进制(用%前缀标识)。
(2) “#”表示立即数
一个常数前添加“#”表示一个立即数,不加“#”时,表示一个地址。
特别说明:初学时常常会将立即数前的“#”遗漏,如果该操作数只能是立即数时,编译器会提示错误,如:
BSET 1,PTA //置PTA.1位为高电平(这个语句不对)
编译时会提示“immediate value<0…7> expected”,应该改为:
BSET #1,PTA //置PTA.1位为高电平(这个语句对)
但有的指令操作数可以是立即数,也可以是地址单元,若把MOV #0x50,0x40误写为:MOV 0x50,0x40,编译器当然不知道你有错,但这两句本身含义不同。前者表示将立即数50(相当于十进制数80)值赋给40单元,后者表示将50单元的内容赋给40单元。
(3) 圆点“.”
若圆点“.”单独出现在语句的操作码之后的操作数位置上,则代表当前程序计数器的值被放置在圆点的位置。例如:JMP . 或BRA .指令代表转向本身,相当于永久循环,在调试时希望程序停留在某个地方可以添加这种语句,调试之后应删除。
(4) 星号“*”
有些指令的操作数是8位地址的直接地址寻址方式,内存变量地址空间一定要分配在直接页内(地址空间:$00××~$00FF),并且在内存变量前加星号“*”,否则会出现错误提示:addressing mode error。如:
正确用法:DBNZ *I1,Main1 //I1分配在直接页(这个语句对)
错误用法:DBNZ I1,Main1 //I1分配在直接页(这个语句不对)
(5) 伪运算符
08编译器识别表3-5所示的伪运算符。
表3-5 08编译器识别的伪运算符

注释即是说明文字,用分号“;”或“//”引导。为了与C语言一致,建议统一使用“//”引导。
3.6.2 08伪操作指令
(1) 变量定义(变量声明)
.blkb n //定义了一个存储区,预留n个字节
.blkw n //定义了一个存储区,预留n个字,每个字占用2字节存储空间
这里的n可以为数字,也可以是已经定义过的符号。一般用于在RAM区用标号定义数据变量或缓冲区。相当于高级语言的变量声明。例如:
I1: .blkb 1 //定义1个字节的存储区(即内存变量I1)
I2: .blkw 1 //定义1个字(2字节)的存储区(即内存变量I2)
在汇编时,经常会使用内存块(缓冲区或数组),例如: 使用语句
LCDBuffer:: .blkb 16 //定义了以LCDBuffer为起始地址的16字节数组
数组范围LCDBuffer~LCDBuffer+15,假如要对LCDBuffer中的12号单元赋值,可以使用如下语句:
STA LCDBuffer+11 //给LCDbuffer的12单元赋值(注意从0开始编号)
(2) 数字常数与字符串常数定义
.byte <expr> [,<expr>]
.word <expr> [,<expr>]
定义程序区中(Flash存储器区)的字节常数(Byte)、字常数(2Byte,双字节常数)。字常数定义程序区放入目标程序的两个连续地址中,高字节在前,低字节在后。该伪指令可以有一个或多个用逗号“,”隔开的操作数,表达式值可以为数字、标号、字符串。
例如:
C1: .byte 0x36 //C1为一个字节的常数C1=0x36
C2: .word 0x3638 //C2为一个字的常数C1=0x3638
C3: .byte ’A’ //一个字节的常数,C3=’A’=0x41(ASCII码)(注意单引号)
C4: .word ”AB” //一个字的常数,C4=”AB”=0x4142(ASCII码)(注意双引号)
利用ASCII与ASCIZ可以定义字符串常数:
.ASCII 字符串 //直接定义ASCII码字符串
.ASCIZ 字符串 //定义ASCII码字符串,但末尾增加0x00
这两个伪指令用于定义字符串,字符串需要一对分界符,在两个分界符之内任意可打印字符都是有效的。C语言中的类型转义字符也是有效的,转义字符都是从反斜线\ 开始:\e ESC、\b 退格、\f 换页、\n 换行、\r 回车、\t TAB。
ASCIZ 定义在字符串的结尾增加了NUL 字符(\0)。例如:
String1:: .ASCII ”123” //String1处开始的数据为:0x31,0x32,0x33
String2:: .ASCIZ ”123” //String2处开始的数据为:0x31,0x32,0x33,0x00
(3) 常数赋值与文本替代符伪指令
<symbol> = <value> //定义一个符号等于常数值。例如:PI=3
.define <symbol> <value> //定义一个文本替代符,汇编时,当程序中出现文本替代符<symbol>时,编译器会用<value>取代。例如:
.define C2 C1 //以后程序中的C2与C1同等看待(注意:C1必须定义过)
这样,程序中“LDHX #C2+6”语句等价于“LDHX #C1+6”。这对于程序的复用时,在编译之前进行一定的统一改变比较方便,而不需要改变程序内容。
(4) 指令存储定位伪指令
.org <value>
定义程序或数据区的起始地址。该伪指令中的value为数字或标号,它告诉编译器,在指令汇编后,其后的指令在存储器中将从地址value开始向地址增大方向存放。对于M68HC08系列单片机来说,一个完整的可以在编译后放入单片机执行的源程序,至少必须有一个ORG语句使程序能够放入Flash区。具体例子参见第四章第一个实例程序。
(5) 文件包含伪指令
.include <filename>
.include是一个附加文件的链接指示命令,利用它,可以把另一个源文件插入当前的源文件一起汇编,成为一个完整的源程序。filename是一个文件名,可以包含文件的绝对路径或相对路径,但建议对于一个工程的相关文件放到同一个文件夹中,所以更多的时候使用相对路径。具体例子参见第四章第一个实例程序。
(6) 宏定义和宏调用伪指令
宏定义伪指令
.macro <macroname>
……
.endmacro
定义一个宏,以.macro <macroname>开始,以.endmacro结束,除了另外一个宏声明外,任意汇编声明可以是宏的组成部分。在宏内部表达式中,@digit(digit 由0~9 的数字代替)是宏被调用时相应的宏参数,定义的宏名称不能与汇编指令及汇编伪指令名称相冲突。初学者一般不使用宏。
宏调用伪指令:<macroname> [<arg0> [,<args>]]
调用宏是在操作码的位置放置宏名,后面跟上相应的参数。汇编器使用组成宏的声明来替换宏,同时用相应的宏参数来扩展@digit。例如:定义一个两个内存单元相乘,并把结果回存到内存单元的宏MUL2。
.macro MUL2
LDA @0
LDX @1
MUL
STA @0
STX @1
.endmacro
调用这个宏MUL2方法如下:
MUL2 $40,$41
这个宏等同于
LDA $40
LDX $41
MUL
STA $40
STX $41
(7) 定义存储区域伪指令
.area <name> [(attributes)] //定义代码或数据装入的内存区域
连接器将所有使用同一名称的区域集合连至一起并且根据它们的属性进行连接或覆盖。具体用法参见本书各个实例源程序,只要参照使用即可,这里不再进一步说明。
练习题
【基础题】
1.指出下列指令中的源操作数和目的操作数的寻址方式。
(1) MOV #$80 , $80 (2) MOV $80,$A0
(3) MOV $80 , X+ (4) MOV X+ , $80
(5) LDA $80 , X
2.说明跳转指令BRA和JMP的差别。
3.哪些指令影响堆栈指针?如何影响?
4.说明芯片初始化时堆栈指针的初始化方法。
5.写出主要汇编伪指令?
6.比较HC08 CPU、S08 CPU、RS08 CPU的主要差异。
【综合题】
7.编写汇编子程序实现双字节数据加法和减法运算。
8.编写汇编子程序实现双字节BCD码数据加法和减法运算。
9.编写一段延时1000个指令周期的延时子程序。


